Detecting tail calls through CFA

This commit is contained in:
Andrey Breslav
2013-12-06 00:00:01 +04:00
parent 9f319e8b24
commit 97319808b6
16 changed files with 483 additions and 7 deletions
@@ -24,19 +24,23 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.cfg.pseudocode.*;
import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.*;
import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.Edges;
import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.InstructionAnalyzeStrategy;
import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.InstructionDataAnalyzeStrategy;
import org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableInitState;
import org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableUseState;
import org.jetbrains.jet.lang.cfg.pseudocode.*;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory;
import org.jetbrains.jet.lang.diagnostics.Diagnostic;
import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory;
import org.jetbrains.jet.lang.diagnostics.Errors;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
import org.jetbrains.jet.lang.resolve.calls.tail.TailRecursionKind;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.lexer.JetTokens;
@@ -50,6 +54,10 @@ import static org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableUseStat
import static org.jetbrains.jet.lang.diagnostics.Errors.*;
import static org.jetbrains.jet.lang.resolve.BindingContext.CAPTURED_IN_CLOSURE;
import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE;
import static org.jetbrains.jet.lang.resolve.BindingContext.*;
import static org.jetbrains.jet.lang.resolve.calls.tail.TailRecursionKind.IN_TRY;
import static org.jetbrains.jet.lang.resolve.calls.tail.TailRecursionKind.MIGHT_BE;
import static org.jetbrains.jet.lang.resolve.calls.tail.TailRecursionKind.NON_TAIL;
import static org.jetbrains.jet.lang.types.TypeUtils.noExpectedType;
public class JetFlowInformationProvider {
@@ -64,7 +72,7 @@ public class JetFlowInformationProvider {
@NotNull BindingTrace trace,
@NotNull Pseudocode pseudocode
) {
subroutine = declaration;
this.subroutine = declaration;
this.trace = trace;
this.pseudocode = pseudocode;
}
@@ -94,7 +102,7 @@ public class JetFlowInformationProvider {
}
checkDefiniteReturn(expectedReturnType);
checkDefiniteReturnInLocalFunctions();
checkLocalFunctions();
if (isLocalObject) return;
@@ -106,6 +114,8 @@ public class JetFlowInformationProvider {
markUnusedVariables();
markUnusedLiteralsInBlock();
markTailCalls();
}
private void collectReturnExpressions(@NotNull final Collection<JetElement> returnedExpressions) {
@@ -168,7 +178,7 @@ public class JetFlowInformationProvider {
}
}
private void checkDefiniteReturnInLocalFunctions() {
private void checkLocalFunctions() {
for (LocalFunctionDeclarationInstruction localDeclarationInstruction : pseudocode.getLocalDeclarations()) {
JetElement element = localDeclarationInstruction.getElement();
if (element instanceof JetNamedFunction) {
@@ -178,7 +188,9 @@ public class JetFlowInformationProvider {
JetFlowInformationProvider providerForLocalDeclaration =
new JetFlowInformationProvider(localFunction, trace, localDeclarationInstruction.getBody());
providerForLocalDeclaration.checkDefiniteReturn(expectedType != null ? expectedType : NO_EXPECTED_TYPE);
providerForLocalDeclaration.markTailCalls();
}
}
}
@@ -610,6 +622,117 @@ public class JetFlowInformationProvider {
}
////////////////////////////////////////////////////////////////////////////////
// Tail calls
public void markTailCalls() {
final DeclarationDescriptor subroutineDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, subroutine);
if (!(subroutineDescriptor instanceof FunctionDescriptor)) return;
if (!KotlinBuiltIns.getInstance().isTailRecursive(subroutineDescriptor)) return;
// finally blocks are copied which leads to multiple diagnostics reported on one instruction
class KindAndCall {
TailRecursionKind kind;
ResolvedCall<?> call;
KindAndCall(TailRecursionKind kind, ResolvedCall<?> call) {
this.kind = kind;
this.call = call;
}
}
final Map<JetElement, KindAndCall> calls = new HashMap<JetElement, KindAndCall>();
PseudocodeTraverser.traverse(
pseudocode,
FORWARD,
new InstructionAnalyzeStrategy() {
@Override
public void execute(@NotNull Instruction instruction) {
if (!(instruction instanceof CallInstruction)) return;
CallInstruction callInstruction = (CallInstruction) instruction;
ResolvedCall<?> resolvedCall = trace.get(RESOLVED_CALL, callInstruction.getElement());
if (resolvedCall == null) return;
// is this a recursive call?
if (!resolvedCall.getResultingDescriptor().getOriginal().equals(subroutineDescriptor)) return;
JetElement element = callInstruction.getElement();
//noinspection unchecked
JetExpression parent = PsiTreeUtil.getParentOfType(
element,
JetTryExpression.class, JetFunction.class, JetClassInitializer.class
);
if (parent instanceof JetTryExpression) {
// We do not support tail calls Collections.singletonMap() try-catch-finally, for simplicity of the mental model
// very few cases there would be real tail-calls, and it's often not so easy for the user to see why
calls.put(element, new KindAndCall(IN_TRY, resolvedCall));
return;
}
boolean isTail = PseudocodeTraverser.traverseFollowingInstructions(
callInstruction,
new HashSet<Instruction>(),
FORWARD,
new TailRecursionDetector(subroutine, callInstruction)
);
TailRecursionKind kind = isTail ? MIGHT_BE : NON_TAIL;
KindAndCall kindAndCall = calls.get(element);
calls.put(element,
new KindAndCall(
combineKinds(kind, kindAndCall == null ? null : kindAndCall.kind),
resolvedCall
)
);
}
}
);
for (Map.Entry<JetElement, KindAndCall> entry : calls.entrySet()) {
JetElement element = entry.getKey();
KindAndCall kindAndCall = entry.getValue();
switch (kindAndCall.kind) {
case MIGHT_BE:
case IN_RETURN:
trace.record(TAIL_RECURSION_CALL, kindAndCall.call, TailRecursionKind.IN_RETURN);
trace.record(BindingContext.HAS_TAIL_CALLS, (FunctionDescriptor) subroutineDescriptor);
break;
case IN_TRY:
trace.report(Errors.TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED.on(element));
break;
case NON_TAIL:
trace.report(Errors.NON_TAIL_RECURSIVE_CALL.on(element));
break;
}
}
}
private static TailRecursionKind combineKinds(TailRecursionKind kind, @Nullable TailRecursionKind existingKind) {
TailRecursionKind resultingKind;
if (existingKind == null || existingKind == kind) {
resultingKind = kind;
}
else {
if (check(kind, existingKind, IN_TRY, MIGHT_BE)) {
resultingKind = IN_TRY;
}
else if (check(kind, existingKind, IN_TRY, NON_TAIL)) {
resultingKind = IN_TRY;
}
else {
// MIGHT_BE, NON_TAIL
resultingKind = NON_TAIL;
}
}
return resultingKind;
}
private static boolean check(Object a, Object b, Object x, Object y) {
return (a == x && b == y) || (a == y && b == x);
}
////////////////////////////////////////////////////////////////////////////////
// Utility classes and methods
/**
@@ -0,0 +1,66 @@
/*
* Copyright 2010-2013 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.jet.lang.cfg;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.cfg.pseudocode.*;
import org.jetbrains.jet.lang.psi.JetElement;
public class TailRecursionDetector extends InstructionVisitorWithResult<Boolean> implements PseudocodeTraverser.InstructionHandler {
private final JetElement subroutine;
private final Instruction start;
public TailRecursionDetector(@NotNull JetElement subroutine, @NotNull Instruction start) {
this.subroutine = subroutine;
this.start = start;
}
@Override
public boolean handle(@NotNull Instruction instruction) {
return instruction == start || instruction.accept(this);
}
@Override
public Boolean visitInstruction(Instruction instruction) {
return false;
}
@Override
public Boolean visitSubroutineExit(SubroutineExitInstruction instruction) {
return !instruction.isError() && instruction.getSubroutine() == subroutine;
}
@Override
public Boolean visitSubroutineSink(SubroutineSinkInstruction instruction) {
return instruction.getSubroutine() == subroutine;
}
@Override
public Boolean visitJump(AbstractJumpInstruction instruction) {
return true;
}
@Override
public Boolean visitThrowExceptionInstruction(ThrowExceptionInstruction instruction) {
return false;
}
@Override
public Boolean visitMarkInstruction(MarkInstruction instruction) {
return true;
}
}