Fixes for call site finally generation before non-local returns

This commit is contained in:
Michael Bogdanov
2014-10-28 14:16:47 +03:00
parent dd8c3f0e49
commit d0f6f03380
12 changed files with 625 additions and 233 deletions
@@ -0,0 +1,143 @@
/*
* Copyright 2010-2014 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.codegen.inline
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import org.jetbrains.org.objectweb.asm.tree.LabelNode
import com.google.common.collect.LinkedListMultimap
import org.jetbrains.org.objectweb.asm.Label
import java.util.ArrayList
import com.intellij.util.containers.Stack
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import com.google.common.collect.Lists
import org.jetbrains.org.objectweb.asm.tree.TryCatchBlockNode
import java.util.Comparator
import java.util.Collections
public abstract class CoveringTryCatchNodeProcessor<T: IntervalWithHandler>() {
private val tryBlockStarts = LinkedListMultimap.create<LabelNode, T>()
private val tryBlockEnds = LinkedListMultimap.create<LabelNode, T>()
public val allTryCatchNodes: ArrayList<T> = arrayListOf()
private val currentCoveringBlocks: Stack<T> = Stack()
public val coveringFromInnermost: List<T>
get() = currentCoveringBlocks.reverse()
fun addNewTryCatchNode(newInfo: T) {
tryBlockStarts.put(newInfo.startLabel, newInfo)
tryBlockEnds.put(newInfo.endLabel, newInfo)
allTryCatchNodes.add(newInfo)
}
fun remapStartLabel(oldStart: LabelNode, remapped: T) {
tryBlockStarts.remove(oldStart, remapped)
tryBlockStarts.put(remapped.startLabel, remapped)
}
public fun getStartNodes(label: LabelNode): List<T> {
return tryBlockStarts.get(label)
}
public fun getEndNodes(label: LabelNode): List<T> {
return tryBlockEnds.get(label)
}
//Keep information about try blocks that cover current instruction -
// pushing and popping it to stack entering and exiting tryCatchBlock start and end labels
public open fun updateCoveringTryBlocks(curIns: AbstractInsnNode, directOrder: Boolean) {
if (curIns !is LabelNode) return
val infosToClose = if (!directOrder) getStartNodes(curIns) else getEndNodes(curIns)
for (startNode in infosToClose) {
val pop = currentCoveringBlocks.pop()
//Temporary disabled cause during patched structure of exceptions changed
// if (startNode != pop) {
// throw RuntimeException("Wrong try-catch structure " + startNode + " " + pop + " " + infosToClose.size())
// };
}
//Reversing list order cause we should pop external block before internal one
// (originally internal blocks goes before external one, such invariant preserved via sortTryCatchBlocks method)
val infoToOpen = if (!directOrder) getEndNodes(curIns) else getStartNodes(curIns)
for (info in infoToOpen.reverse()) {
currentCoveringBlocks.add(info)
}
}
public abstract fun instructionIndex(inst: AbstractInsnNode): Int
private fun isEmptyInterval(node: T): Boolean {
val start = node.startLabel
var end: AbstractInsnNode = node.endLabel
while (end != start && end is LabelNode) {
end = end.getPrevious()
}
return start == end;
}
public fun getNonEmptyNodes(): List<T> {
return allTryCatchNodes.filterNot { isEmptyInterval(it) }
}
public fun sortTryCatchBlocks() {
val comp = Comparator {(t1: T, t2: T): Int ->
var result = instructionIndex(t1.handler) - instructionIndex(t2.handler)
if (result == 0) {
result = instructionIndex(t1.startLabel) - instructionIndex(t2.startLabel)
if (result == 0) {
assert(false, "Error: support multicatch finallies!")
result = instructionIndex(t1.endLabel) - instructionIndex(t2.endLabel)
}
}
result
}
Collections.sort<T>(allTryCatchNodes, comp)
}
}
public class DefaultProcessor(val node: MethodNode) : CoveringTryCatchNodeProcessor<TryCatchBlockNodeWrapper>() {
{
node.tryCatchBlocks.forEach { addNode(it) }
}
fun addNode(node: TryCatchBlockNode) {
addNewTryCatchNode(TryCatchBlockNodeWrapper(node))
}
override fun instructionIndex(inst: AbstractInsnNode): Int {
return node.instructions.indexOf(inst)
}
}
public class TryCatchBlockNodeWrapper(val node: TryCatchBlockNode) : IntervalWithHandler {
override val startLabel: LabelNode
get() = node.start
override val endLabel: LabelNode
get() = node.end
override val handler: LabelNode
get() = node.handler
override val type: String?
get() = node.type
}
@@ -18,6 +18,7 @@ package org.jetbrains.jet.codegen.inline;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.codegen.*;
@@ -41,17 +42,18 @@ import org.jetbrains.jet.lang.resolve.java.jvmSignature.JvmMethodSignature;
import org.jetbrains.jet.lang.types.lang.InlineStrategy;
import org.jetbrains.jet.lang.types.lang.InlineUtil;
import org.jetbrains.jet.renderer.DescriptorRenderer;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.Method;
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
import org.jetbrains.org.objectweb.asm.tree.LabelNode;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import org.jetbrains.org.objectweb.asm.tree.TryCatchBlockNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.*;
import static org.jetbrains.jet.codegen.AsmUtil.getMethodAsmFlags;
import static org.jetbrains.jet.codegen.AsmUtil.isPrimitive;
@@ -239,7 +241,7 @@ public class InlineCodegen implements CallGenerator {
}
}
};
List<MethodInliner.ExternalFinallyBlockInfo> infos = MethodInliner.processReturns(adapter, labelOwner, true, null);
List<MethodInliner.PointForExternalFinallyBlocks> infos = MethodInliner.processReturns(adapter, labelOwner, true, null);
generateAndInsertFinallyBlocks(adapter, infos);
adapter.accept(new InliningInstructionAdapter(codegen.v));
@@ -481,19 +483,62 @@ public class InlineCodegen implements CallGenerator {
}
public void generateAndInsertFinallyBlocks(MethodNode intoNode, List<MethodInliner.ExternalFinallyBlockInfo> insertPoints) {
public void generateAndInsertFinallyBlocks(MethodNode intoNode, List<MethodInliner.PointForExternalFinallyBlocks> insertPoints) {
if (!codegen.hasFinallyBlocks()) return;
for (MethodInliner.ExternalFinallyBlockInfo insertPoint : insertPoints) {
MethodNode finallyNode = InlineCodegenUtil.createEmptyMethodNode();
ExpressionCodegen finallyCodegen =
new ExpressionCodegen(finallyNode, codegen.getFrameMap(), codegen.getReturnType(),
codegen.getContext(), codegen.getState(), codegen.getParentCodegen());
finallyCodegen.addBlockStackElementsForNonLocalReturns(codegen.getBlockStackElements());
Map<AbstractInsnNode, MethodInliner.PointForExternalFinallyBlocks> extensionPoints =
new HashMap<AbstractInsnNode, MethodInliner.PointForExternalFinallyBlocks>();
for (MethodInliner.PointForExternalFinallyBlocks insertPoint : insertPoints) {
extensionPoints.put(insertPoint.beforeIns, insertPoint);
}
finallyCodegen.generateFinallyBlocksIfNeeded(insertPoint.returnType);
DefaultProcessor processor = new DefaultProcessor(intoNode);
InlineCodegenUtil.insertNodeBefore(finallyNode, intoNode, insertPoint.beforeIns);
AbstractInsnNode curInstr = intoNode.instructions.getFirst();
while (curInstr != null) {
processor.updateCoveringTryBlocks(curInstr, true);
MethodInliner.PointForExternalFinallyBlocks extension = extensionPoints.get(curInstr);
if (extension != null) {
Label start = new Label();
Label end = new Label();
MethodNode finallyNode = InlineCodegenUtil.createEmptyMethodNode();
finallyNode.visitLabel(start);
ExpressionCodegen finallyCodegen =
new ExpressionCodegen(finallyNode, codegen.getFrameMap(), codegen.getReturnType(),
codegen.getContext(), codegen.getState(), codegen.getParentCodegen());
finallyCodegen.addBlockStackElementsForNonLocalReturns(codegen.getBlockStackElements());
finallyCodegen.generateFinallyBlocksIfNeeded(extension.returnType);
finallyNode.visitLabel(end);
//Exception table for external try/catch/finally blocks will be generated in original codegen after exiting this method
InlineCodegenUtil.insertNodeBefore(finallyNode, intoNode, curInstr);
List<TryCatchBlockNodeWrapper> blocks = processor.getCoveringFromInnermost();
ListIterator<TryCatchBlockNodeWrapper> iterator = blocks.listIterator(blocks.size());
while (iterator.hasPrevious()) {
TryCatchBlockNodeWrapper previous = iterator.previous();
LabelNode oldStart = previous.getStartLabel();
TryCatchBlockNode node = previous.getNode();
node.start = (LabelNode) end.info;
processor.remapStartLabel(oldStart, previous);
TryCatchBlockNode additionalNode = new TryCatchBlockNode(oldStart, (LabelNode) start.info, node.handler, node.type);
processor.addNode(additionalNode);
}
}
curInstr = curInstr.getNext();
}
processor.sortTryCatchBlocks();
Iterable<TryCatchBlockNodeWrapper> nodes = processor.getNonEmptyNodes();
intoNode.tryCatchBlocks.clear();
for (TryCatchBlockNodeWrapper node : nodes) {
intoNode.tryCatchBlocks.add(node.getNode());
}
}
@@ -17,14 +17,14 @@
package org.jetbrains.jet.codegen.inline;
import com.google.common.base.Objects;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.jet.codegen.AsmUtil;
import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.tree.*;
import org.jetbrains.org.objectweb.asm.util.Textifier;
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
@@ -35,7 +35,7 @@ import java.util.*;
import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.*;
public class InternalFinallyBlockInliner {
public class InternalFinallyBlockInliner extends CoveringTryCatchNodeProcessor<TryCatchBlockNodeInfo> {
private static class FinallyBlockInfo {
@@ -76,22 +76,18 @@ public class InternalFinallyBlockInliner {
@NotNull
private final MethodNode inlineFun;
private final List<TryCatchBlockNodeInfo> inlineFunTryBlockInfo;
private final ListMultimap<LabelNode, TryCatchBlockNodeInfo> tryBlockStarts = LinkedListMultimap.create();
private final ListMultimap<LabelNode, TryCatchBlockNodeInfo> tryBlockEnds = LinkedListMultimap.create();
//lambdaTryCatchBlockNodes is number of TryCatchBlockNodes that was inlined with lambdas into function
//due to code generation specific they placed before function TryCatchBlockNodes
private InternalFinallyBlockInliner(@NotNull MethodNode inlineFun, List<TryCatchBlockNodeInfo> inlineFunTryBlockInfo) {
this.inlineFun = inlineFun;
this.inlineFunTryBlockInfo = inlineFunTryBlockInfo;
for (TryCatchBlockNodeInfo block : inlineFunTryBlockInfo) {
addNewTryCatchNode(block);
}
}
private int initAndGetVarIndexForNonLocalReturnValue() {
//sortTryCatchBlocks();/*TODO maybe remove*/
mapLabelsToTryCatchBlocks();
MaxLocalsCalculator tempCalcNode = new MaxLocalsCalculator(
InlineCodegenUtil.API,
@@ -104,7 +100,6 @@ public class InternalFinallyBlockInliner {
private void processInlineFunFinallyBlocks() {
int nextTempNonLocalVarIndex = initAndGetVarIndexForNonLocalReturnValue();
Stack<TryCatchBlockNodeInfo> coveringTryCatchBlocks = new Stack<TryCatchBlockNodeInfo>();
InsnList instructions = inlineFun.instructions;
//As we do finally block code search after non-local return instruction
@@ -112,13 +107,19 @@ public class InternalFinallyBlockInliner {
// So we do instruction processing in reverse order!
AbstractInsnNode curIns = instructions.getLast();
while (curIns != null) {
updateCoveringTryBlocks(coveringTryCatchBlocks, curIns);
updateCoveringTryBlocks(curIns);
//At this point only global return is possible, local one already substituted with: goto endLabel
if (!InlineCodegenUtil.isReturnOpcode(curIns.getOpcode()) ||
!InlineCodegenUtil.isMarkedReturn(curIns) ||
coveringTryCatchBlocks.isEmpty() ||
coveringTryCatchBlocks.get(0).getOnlyCopyNotProcess()) {
!InlineCodegenUtil.isMarkedReturn(curIns)) {
curIns = curIns.getPrevious();
continue;
}
List<TryCatchBlockNodeInfo> currentCoveringNodes = getCoveringFromInnermost();
checkCoveringBlocksInvariant(currentCoveringNodes);
if (currentCoveringNodes.isEmpty() ||
currentCoveringNodes.get(currentCoveringNodes.size() - 1).getOnlyCopyNotProcess()) {
curIns = curIns.getPrevious();
continue;
}
@@ -132,25 +133,22 @@ public class InternalFinallyBlockInliner {
// Each group that corresponds to try/*catches*/finally contains tryCatch block with default handler.
// For each such group we should insert corresponding finally before non-local return.
// So we split all try blocks on current instructions to groups and process them independently
List<TryBlockCluster<TryCatchBlockNodeInfo>> clusters = InlinePackage.doClustering(coveringTryCatchBlocks);
ListIterator<TryBlockCluster<TryCatchBlockNodeInfo>> tryCatchBlockIterator = clusters.listIterator(clusters.size());
List<TryBlockCluster<TryCatchBlockNodeInfo>> clustersFromInnermost = InlinePackage.doClustering(currentCoveringNodes);
Iterator<TryBlockCluster<TryCatchBlockNodeInfo>> tryCatchBlockIterator = clustersFromInnermost.iterator();
checkClusterInvariant(clusters);
checkClusterInvariant(clustersFromInnermost);
//Reverse visiting cause innermost tryCatchBlocks in the end
List<TryCatchBlockNodeInfo> patched = new ArrayList<TryCatchBlockNodeInfo>();
while (tryCatchBlockIterator.hasPrevious()) {
TryBlockCluster clusterToFindFinally = tryCatchBlockIterator.previous();
List<TryCatchBlockNodeInfo> additionalNodesToSplit = new ArrayList<TryCatchBlockNodeInfo>();
while (tryCatchBlockIterator.hasNext()) {
TryBlockCluster clusterToFindFinally = tryCatchBlockIterator.next();
List<TryCatchBlockNodeInfo> clusterBlocks = clusterToFindFinally.getBlocks();
TryCatchBlockNodeInfo originalTryCatchBlock = clusterBlocks.get(0);
TryCatchBlockNodeInfo nodeWithDefaultHandlerIfExists = clusterBlocks.get(clusterBlocks.size() - 1);
FinallyBlockInfo finallyInfo = findFinallyBlockBody(originalTryCatchBlock, inlineFunTryBlockInfo);
FinallyBlockInfo finallyInfo = findFinallyBlockBody(nodeWithDefaultHandlerIfExists, getAllTryCatchNodes());
if (finallyInfo == null) continue;
if (originalTryCatchBlock.getOnlyCopyNotProcess()) {
patched.addAll(clusterBlocks);
if (nodeWithDefaultHandlerIfExists.getOnlyCopyNotProcess()) {
additionalNodesToSplit.addAll(clusterBlocks);
continue;
}
@@ -158,54 +156,36 @@ public class InternalFinallyBlockInliner {
List<TryCatchBlockNodePosition> tryCatchBlockInlinedInFinally = findTryCatchBlocksInlinedInFinally(finallyInfo);
//Keep some information about label nodes, we need it to understand whether it's jump inside finally block or outside
// in first case we do call VISIT on instruction otherwise recreating jump instruction (see below)
Set<LabelNode> labelsInsideFinally = rememberOriginalLabelNodes(finallyInfo);
//Creating temp node for finally block copy with some additional instruction
MethodNode finallyBlockCopy = createEmptyMethodNode();
Label newFinallyStart = new Label();
Label newFinallyEnd = new Label();
Label insertedBlockEnd = new Label();
if (nonLocalReturnType != Type.VOID_TYPE && !finallyInfo.isEmpty()) {
boolean generateAloadAstore = nonLocalReturnType != Type.VOID_TYPE && !finallyInfo.isEmpty();
if (generateAloadAstore) {
finallyBlockCopy.visitVarInsn(nonLocalReturnType.getOpcode(Opcodes.ISTORE), nextTempNonLocalVarIndex);
}
finallyBlockCopy.visitLabel(newFinallyStart);
//Keep some information about label nodes, we need it to understand whether it's jump inside finally block or outside
// in first case we do call VISIT on instruction otherwise recreating jump instruction (see below)
Set<LabelNode> labelsInsideFinally = rememberOriginalLabelNodes(finallyInfo);
//Writing finally block body to temporary node
AbstractInsnNode currentIns = finallyInfo.startIns;
while (currentIns != finallyInfo.endInsExclusive) {
//This condition allows another model for non-local returns processing
if (false && InlineCodegenUtil.isReturnOpcode(currentIns.getOpcode()) && !InlineCodegenUtil.isMarkedReturn(currentIns)) {
//substitute all local returns in finally finallyInfo with non-local one lambdaFinallyBlocks try finallyInfo
//TODO same for jumps
Type localReturnType = InlineCodegenUtil.getReturnType(currentIns.getOpcode());
substituteReturnValueInFinally(nextTempNonLocalVarIndex, nonLocalReturnType, finallyBlockCopy,
localReturnType, true);
boolean isInsOrJumpInsideFinally =
!(currentIns instanceof JumpInsnNode) ||
labelsInsideFinally.contains(((JumpInsnNode) currentIns).label);
instrInsertFinallyBefore.accept(finallyBlockCopy);
curIns.accept(finallyBlockCopy);
}
else {
boolean isInsOrJumpInsideFinally =
!(currentIns instanceof JumpInsnNode) ||
labelsInsideFinally.contains(((JumpInsnNode) currentIns).label);
if (isInsOrJumpInsideFinally) {
currentIns.accept(finallyBlockCopy); //VISIT
}
else {
//keep original jump: add currentIns clone
finallyBlockCopy.instructions.add(new JumpInsnNode(currentIns.getOpcode(), ((JumpInsnNode) currentIns).label));
}
}
copyInstruction(nextTempNonLocalVarIndex, curIns, instrInsertFinallyBefore, nonLocalReturnType, finallyBlockCopy,
currentIns, isInsOrJumpInsideFinally);
currentIns = currentIns.getNext();
}
finallyBlockCopy.visitLabel(newFinallyEnd);
if (nonLocalReturnType != Type.VOID_TYPE && !finallyInfo.isEmpty()) {
if (generateAloadAstore) {
finallyBlockCopy.visitVarInsn(nonLocalReturnType.getOpcode(Opcodes.ILOAD), nextTempNonLocalVarIndex);
nextTempNonLocalVarIndex += nonLocalReturnType.getSize(); //TODO: do more wise indexing
}
@@ -216,27 +196,59 @@ public class InternalFinallyBlockInliner {
InlineCodegenUtil.insertNodeBefore(finallyBlockCopy, inlineFun, instrInsertFinallyBefore);
updateExceptionTable(clusterBlocks, newFinallyStart, newFinallyEnd,
tryCatchBlockInlinedInFinally, labelsInsideFinally, (LabelNode) insertedBlockEnd.info, patched);
}
tryCatchBlockInlinedInFinally, labelsInsideFinally, (LabelNode) insertedBlockEnd.info, additionalNodesToSplit);
}
//skip just inserted finally
curIns = curIns.getPrevious();
while (curIns != null && curIns != nextPrev) {
updateCoveringTryBlocks(coveringTryCatchBlocks, curIns);
updateCoveringTryBlocks(curIns);
curIns = curIns.getPrevious();
}
//curIns = nextPrev;
}
substitureTryBlockNodes();
substituteTryBlockNodes();
}
private void checkCoveringBlocksInvariant(Stack<TryCatchBlockNodeInfo> coveringTryCatchBlocks) {
private static void copyInstruction(
int nextTempNonLocalVarIndex,
@NotNull AbstractInsnNode curIns,
@NotNull AbstractInsnNode instrInsertFinallyBefore,
@NotNull Type nonLocalReturnType,
@NotNull MethodNode finallyBlockCopy,
@NotNull AbstractInsnNode currentIns,
boolean isInsOrJumpInsideFinally
) {
//This condition allows another model for non-local returns processing
if (false) {
boolean isReturnForSubstitution =
InlineCodegenUtil.isReturnOpcode(currentIns.getOpcode()) && !InlineCodegenUtil.isMarkedReturn(currentIns);
if (!isInsOrJumpInsideFinally || isReturnForSubstitution) {
//substitute all local returns and jumps outside finally with non-local return
Type localReturnType = InlineCodegenUtil.getReturnType(currentIns.getOpcode());
substituteReturnValueInFinally(nextTempNonLocalVarIndex, nonLocalReturnType, finallyBlockCopy,
localReturnType, isReturnForSubstitution);
instrInsertFinallyBefore.accept(finallyBlockCopy);
curIns.accept(finallyBlockCopy);
} else {
currentIns.accept(finallyBlockCopy); //VISIT
}
}
else {
if (isInsOrJumpInsideFinally) {
currentIns.accept(finallyBlockCopy); //VISIT
}
else {
//keep original jump: add currentIns clone
finallyBlockCopy.instructions.add(new JumpInsnNode(currentIns.getOpcode(), ((JumpInsnNode) currentIns).label));
}
}
}
private static void checkCoveringBlocksInvariant(List<TryCatchBlockNodeInfo> coveringTryCatchBlocks) {
boolean isWasOnlyLocal = false;
for (TryCatchBlockNodeInfo info : coveringTryCatchBlocks) {
for (TryCatchBlockNodeInfo info : Lists.reverse(coveringTryCatchBlocks)) {
assert !isWasOnlyLocal || info.getOnlyCopyNotProcess();
if (info.getOnlyCopyNotProcess()) {
isWasOnlyLocal = true;
@@ -244,10 +256,10 @@ public class InternalFinallyBlockInliner {
}
}
private void checkClusterInvariant(List<TryBlockCluster<TryCatchBlockNodeInfo>> clusters) {
private static void checkClusterInvariant(List<TryBlockCluster<TryCatchBlockNodeInfo>> clusters) {
boolean isWasOnlyLocal;
isWasOnlyLocal = false;
for (TryBlockCluster<TryCatchBlockNodeInfo> cluster : clusters) {
for (TryBlockCluster<TryCatchBlockNodeInfo> cluster : Lists.reverse(clusters)) {
TryCatchBlockNodeInfo info = cluster.getBlocks().get(0);
assert !isWasOnlyLocal || info.getOnlyCopyNotProcess();
if (info.getOnlyCopyNotProcess()) {
@@ -267,7 +279,6 @@ public class InternalFinallyBlockInliner {
return labelsInsideFinally;
}
@Nullable
private void updateExceptionTable(
@NotNull List<TryCatchBlockNodeInfo> updatingClusterBlocks,
@NotNull Label newFinallyStart,
@@ -303,9 +314,7 @@ public class InternalFinallyBlockInliner {
assert inlineFun.instructions.indexOf(additionalTryCatchBlock.start) <= inlineFun.instructions.indexOf(additionalTryCatchBlock.end);
TryCatchBlockNodeInfo newInfo = new TryCatchBlockNodeInfo(additionalTryCatchBlock, true);
tryBlockStarts.put(newInfo.getStartLabel(), newInfo);
tryBlockEnds.put(newInfo.getEndLabel(), newInfo);
inlineFunTryBlockInfo.add(newInfo);
addNewTryCatchNode(newInfo);
}
}
else if (clusterPosition == TryCatchPosition.END) {
@@ -350,7 +359,7 @@ public class InternalFinallyBlockInliner {
}
assert handler2Cluster.isEmpty() : "Unmatched clusters " + handler2Cluster.size();
List<TryCatchBlockNodeInfo > toProcess = new ArrayList<TryCatchBlockNodeInfo >();
List<TryCatchBlockNodeInfo > toProcess = new ArrayList<TryCatchBlockNodeInfo>();
toProcess.addAll(patched);
toProcess.addAll(updatingClusterBlocks);
patched.clear();
@@ -360,33 +369,25 @@ public class InternalFinallyBlockInliner {
//update exception mapping
LabelNode oldStartNode = block.getNode().start;
block.getNode().start = (LabelNode) newFinallyEnd.info;
tryBlockStarts.remove(oldStartNode, block);
tryBlockStarts.put(block.getStartLabel(), block);
remapStartLabel(oldStartNode, block);
//if (!block.getOnlyCopyNotProcess()) {
patched.add(block);
//}
patched.add(block);
TryCatchBlockNode additionalTryCatchBlock =
new TryCatchBlockNode(oldStartNode, (LabelNode) newFinallyStart.info, block.getNode().handler, block.getNode().type);
TryCatchBlockNodeInfo newInfo = new TryCatchBlockNodeInfo(additionalTryCatchBlock, block.getOnlyCopyNotProcess());
tryBlockStarts.put(additionalTryCatchBlock.start, newInfo);
tryBlockEnds.put(additionalTryCatchBlock.end, newInfo);
inlineFunTryBlockInfo.add(newInfo);
addNewTryCatchNode(newInfo);
//TODO add assert
}
sortTryCatchBlocks(inlineFunTryBlockInfo);
sortTryCatchBlocks();
}
private void patchTryBlocks(@NotNull LabelNode newStartLabelNode, @NotNull TryCatchBlockNodeInfo endNode) {
LabelNode oldStart = endNode.getStartLabel();
endNode.getNode().start = newStartLabelNode;
tryBlockStarts.remove(oldStart, endNode);
tryBlockStarts.put(endNode.getNode().start, endNode);
remapStartLabel(oldStart, endNode);
TryCatchBlockNode endTryBlock = endNode.getNode();
TryCatchBlockNode additionalTryCatchBlock =
@@ -396,10 +397,7 @@ public class InternalFinallyBlockInliner {
endTryBlock.type);
TryCatchBlockNodeInfo newInfo = new TryCatchBlockNodeInfo(additionalTryCatchBlock, endNode.getOnlyCopyNotProcess());
tryBlockStarts.put(newInfo.getStartLabel(), newInfo);
tryBlockEnds.put(newInfo.getEndLabel(), newInfo);
inlineFunTryBlockInfo.add(newInfo);
addNewTryCatchNode(newInfo);
}
private static LabelNode getNewOrOldLabel(LabelNode oldHandler, @NotNull Set<LabelNode> labelsInsideFinally) {
@@ -412,23 +410,9 @@ public class InternalFinallyBlockInliner {
//Keep information about try blocks that cover current instruction -
// pushing and popping it to stack entering and exiting tryCatchBlock start and end labels
private void updateCoveringTryBlocks(Stack<TryCatchBlockNodeInfo> coveringTryBlocks, AbstractInsnNode curIns) {
if (!(curIns instanceof LabelNode)) return;
List<TryCatchBlockNodeInfo> infos = tryBlockStarts.get((LabelNode) curIns);
for (TryCatchBlockNodeInfo startNode : infos) {
TryCatchBlockNodeInfo pop = coveringTryBlocks.pop();
//Temporary disabled cause during patched structure of exceptions changed
//assert startNode == pop : "Wrong try-catch structure " + startNode + " " + pop + " " + infos.size();
}
//Reversing list order cause we should pop external block before internal one
// (originally internal blocks goes before external one, such invariant preserved via sortTryCatchBlocks method)
for (TryCatchBlockNodeInfo info : Lists.reverse(tryBlockEnds.get((LabelNode) curIns))) {
coveringTryBlocks.add(info);
}
checkCoveringBlocksInvariant(coveringTryBlocks);
public void updateCoveringTryBlocks(@NotNull AbstractInsnNode curIns) {
super.updateCoveringTryBlocks(curIns, false);
checkCoveringBlocksInvariant(getCoveringFromInnermost());
}
private static boolean hasFinallyBlocks(List<TryCatchBlockNodeInfo> inlineFunTryBlockInfo) {
@@ -440,13 +424,6 @@ public class InternalFinallyBlockInliner {
return false;
}
private void mapLabelsToTryCatchBlocks() {
for (TryCatchBlockNodeInfo block : inlineFunTryBlockInfo) {
tryBlockStarts.put(block.getNode().start, block);
tryBlockEnds.put(block.getNode().end, block);
}
}
//As described above all tryCatch group that have finally block also should contains tryCatchBlockNode with default handler.
//So we assume that instructions between end of tryCatchBlock and start of next tryCatchBlock with same default handler is required finally body.
//There is at least two tryCatchBlockNodes in list cause there is always tryCatchBlockNode on first instruction of default handler:
@@ -499,10 +476,7 @@ public class InternalFinallyBlockInliner {
FinallyBlockInfo finallyInfo = new FinallyBlockInfo(startFinallyChain.getNext(), endFinallyChainExclusive);
if (inlineFun.instructions.indexOf(finallyInfo.startIns) > inlineFun.instructions.indexOf(finallyInfo.endInsExclusive)) {
AbstractInsnNode startNode = finallyInfo.endInsExclusive;
AbstractInsnNode stopNode = finallyInfo.startIns;
writeNodes(startNode, stopNode);
throw new AssertionError();
throw new AssertionError("Inconsistent finally: block end occurs before start " + traceInterval(finallyInfo.endInsExclusive, finallyInfo.startIns));
}
return finallyInfo;
@@ -553,18 +527,15 @@ public class InternalFinallyBlockInliner {
if (!(curInstr instanceof LabelNode)) continue;
LabelNode curLabel = (LabelNode) curInstr;
List<TryCatchBlockNodeInfo> startedTryBlocks = tryBlockStarts.get(curLabel);
if (startedTryBlocks != null) {
for (TryCatchBlockNodeInfo block : startedTryBlocks) {
assert !processedBlocks.containsKey(block) : "Try catch block already processed before start label!!! " + block;
TryCatchBlockNodePosition info = new TryCatchBlockNodePosition(block, TryCatchPosition.START);
processedBlocks.put(block, info);
result.add(info);
}
List<TryCatchBlockNodeInfo> startedTryBlocks = getStartNodes(curLabel);
for (TryCatchBlockNodeInfo block : startedTryBlocks) {
assert !processedBlocks.containsKey(block) : "Try catch block already processed before start label!!! " + block;
TryCatchBlockNodePosition info = new TryCatchBlockNodePosition(block, TryCatchPosition.START);
processedBlocks.put(block, info);
result.add(info);
}
List<TryCatchBlockNodeInfo> endedTryBlocks = tryBlockEnds.get(curLabel);
if (endedTryBlocks == null) continue;
List<TryCatchBlockNodeInfo> endedTryBlocks = getEndNodes(curLabel);
for (TryCatchBlockNodeInfo block : endedTryBlocks) {
TryCatchBlockNodePosition info = processedBlocks.get(block);
@@ -582,19 +553,6 @@ public class InternalFinallyBlockInliner {
return result;
}
private void writeNodes(AbstractInsnNode startNode, AbstractInsnNode stopNode) {
Textifier p = new Textifier();
TraceMethodVisitor visitor = new TraceMethodVisitor(p);
while (startNode != stopNode) {
startNode.accept(visitor);
startNode = startNode.getNext();
}
startNode.accept(visitor);
StringWriter out = new StringWriter();
p.print(new PrintWriter(out));
System.out.println(out.toString());
}
private static void substituteReturnValueInFinally(
int nonLocalVarIndex,
@NotNull Type nonLocalReturnType,
@@ -619,59 +577,38 @@ public class InternalFinallyBlockInliner {
return result;
}
private void sortTryCatchBlocks(@NotNull List<TryCatchBlockNodeInfo> inlineFunTryBlockInfo) {
Comparator<TryCatchBlockNodeInfo> comp = new Comparator<TryCatchBlockNodeInfo>() {
@Override
public int compare(@NotNull TryCatchBlockNodeInfo t1, @NotNull TryCatchBlockNodeInfo t2) {
int result = inlineFun.instructions.indexOf(t1.getNode().handler) - inlineFun.instructions.indexOf(t2.getNode().handler);
if (result == 0) {
result = inlineFun.instructions.indexOf(t1.getNode().start) - inlineFun.instructions.indexOf(t2.getNode().start);
if (result == 0) {
assert false : "Error: support multicatch finallies!";
result = inlineFun.instructions.indexOf(t1.getNode().end) - inlineFun.instructions.indexOf(t2.getNode().end);
}
}
return result;
}
};
Collections.sort(inlineFunTryBlockInfo, comp);
@Override
public int instructionIndex(@NotNull AbstractInsnNode inst) {
return inlineFun.instructions.indexOf(inst);
}
for (TryCatchBlockNodeInfo info : inlineFunTryBlockInfo) {
TryCatchBlockNode node = info.getNode();
assertNotEmptyTryNode(node);
private void substituteTryBlockNodes() {
inlineFun.tryCatchBlocks.clear();
for (TryCatchBlockNodeInfo info : getNonEmptyNodes()) {
inlineFun.tryCatchBlocks.add(info.getNode());
}
}
private void assertNotEmptyTryNode(TryCatchBlockNode node) {
LabelNode start = node.start;
AbstractInsnNode end = node.end;
while (end != start && end instanceof LabelNode) {
end = end.getPrevious();
private static String traceInterval(AbstractInsnNode startNode, AbstractInsnNode stopNode) {
Textifier p = new Textifier();
TraceMethodVisitor visitor = new TraceMethodVisitor(p);
while (startNode != stopNode) {
startNode.accept(visitor);
startNode = startNode.getNext();
}
assert start != end;
startNode.accept(visitor);
StringWriter out = new StringWriter();
p.print(new PrintWriter(out));
return out.toString();
}
private static int counter = 0;
@SuppressWarnings({"UnusedDeclaration", "UseOfSystemOutOrSystemErr"})
@TestOnly
private void flushCurrentState(@NotNull AbstractInsnNode curNonLocal) {
substitureTryBlockNodes();
System.out.println( );
System.out.println();
System.out.println("Iteration: " + counter++);
substituteTryBlockNodes();
System.out.println("Will process instruction at : " + inlineFun.instructions.indexOf(curNonLocal) + " " + curNonLocal.toString());
String text = getNodeText(inlineFun);
System.out.println(text);
}
private void substitureTryBlockNodes() {
inlineFun.tryCatchBlocks.clear();
for (TryCatchBlockNodeInfo info : inlineFunTryBlockInfo) {
inlineFun.tryCatchBlocks.add(info.getNode());
}
}
private int indexOf(AbstractInsnNode node) {
return inlineFun.instructions.indexOf(node) + 1;
}
}
@@ -586,11 +586,11 @@ public class MethodInliner {
@NotNull
//process local and global returns (local substituted with goto end-label global kept unchanged)
public static List<ExternalFinallyBlockInfo> processReturns(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, Label endLabel) {
public static List<PointForExternalFinallyBlocks> processReturns(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, boolean remapReturn, Label endLabel) {
if (!remapReturn) {
return Collections.emptyList();
}
List<ExternalFinallyBlockInfo> result = new ArrayList<ExternalFinallyBlockInfo>();
List<PointForExternalFinallyBlocks> result = new ArrayList<PointForExternalFinallyBlocks>();
InsnList instructions = node.instructions;
AbstractInsnNode insnNode = instructions.getFirst();
while (insnNode != null) {
@@ -621,7 +621,7 @@ public class MethodInliner {
}
//genetate finally block before nonLocalReturn flag/return/goto
result.add(new ExternalFinallyBlockInfo(isLocalReturn ? insnNode : insnNode.getPrevious(), getReturnType(insnNode.getOpcode())
result.add(new PointForExternalFinallyBlocks(isLocalReturn ? insnNode : insnNode.getPrevious(), getReturnType(insnNode.getOpcode())
));
}
insnNode = insnNode.getNext();
@@ -629,13 +629,14 @@ public class MethodInliner {
return result;
}
public static class ExternalFinallyBlockInfo {
//Place to insert finally blocks from try blocks that wraps inline fun call
public static class PointForExternalFinallyBlocks {
final AbstractInsnNode beforeIns;
final Type returnType;
public ExternalFinallyBlockInfo(AbstractInsnNode beforeIns, Type returnType) {
public PointForExternalFinallyBlocks(AbstractInsnNode beforeIns, Type returnType) {
this.beforeIns = beforeIns;
this.returnType = returnType;
}
@@ -43,7 +43,7 @@ class TryCatchBlockNodeInfo(val node: TryCatchBlockNode, val onlyCopyNotProcess:
override val endLabel: LabelNode
get() = node.end
override val handler: LabelNode
get() = node.handler!!
get() = node.handler
override val type: String?
get() = node.type
}
@@ -0,0 +1,101 @@
import test.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
fun test1(): Holder {
val h = Holder("")
try {
val internalResult = doCall(h) {
h += "in lambda body"
return h
}
}
finally {
h += "in call site finally"
}
h += "local"
return h
}
fun test1Lambda(): Holder {
val h = Holder("")
val internalResult = doCall(h) {
try {
h += "in lambda body"
return h
}
finally {
h += "in lambda finally"
}
}
h += "local"
return h
}
fun test2(h: Holder): Holder {
try {
val internalResult = doCallWithException(h) {
h += "in lambda body"
return h
}
}
finally {
h += "in call site finally"
}
h += "local"
return h
}
fun test2Lambda(h: Holder): Holder {
val internalResult = doCallWithException(h) {
try {
h += "in lambda body"
return h
}
finally {
h += "in lambda finally"
}
}
h += "local"
return h
}
fun box(): String {
val test = test1()
if (test.value != "in lambda body -> inline fun finally -> in call site finally") return "fail 1: $test"
val testLambda = test1Lambda()
if (testLambda.value != "in lambda body -> in lambda finally -> inline fun finally") return "fail 1 lambda: $testLambda"
var h = Holder()
assertError(2, h, "in lambda body -> inline fun finally -> in call site finally") {
test2(h)
}
h = Holder()
assertError(22, h, "in lambda body -> in lambda finally -> inline fun finally") {
test2Lambda(h)
}
return "OK"
}
public fun assertError(index: Int, h: Holder, expected: String, l: (h: Holder) -> Holder) {
try {
l(h)
fail("fail $index: no error")
}
catch (e: Exception) {
assertEquals(expected, h.value, "failed on $index")
}
}
@@ -0,0 +1,33 @@
package test
public class Holder(var value: String = "") {
public fun plusAssign(s: String?) {
if (value.length != 0) {
value += " -> "
}
value += s
}
override fun toString(): String {
return value
}
}
public inline fun <R> doCall(h: Holder, block: ()-> R) : R {
try {
return block()
} finally {
h += "inline fun finally"
}
}
public inline fun <R> doCallWithException(h: Holder, block: ()-> R) : R {
try {
return block()
} finally {
h += "inline fun finally"
throw RuntimeException("fail");
}
}
@@ -0,0 +1,101 @@
import test.*
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.fail
fun test1(): Holder {
val h = Holder("")
try {
doCall(h) {
h += "in lambda body"
return h
}
}
finally {
h += "in call site finally"
}
h += "local"
return h
}
fun test1Lambda(): Holder {
val h = Holder("")
doCall(h) {
try {
h += "in lambda body"
return h
}
finally {
h += "in lambda finally"
}
}
h += "local"
return h
}
fun test2(h: Holder): Holder {
try {
doCallWithException(h) {
h += "in lambda body"
return h
}
}
finally {
h += "in call site finally"
}
h += "local"
return h
}
fun test2Lambda(h: Holder): Holder {
doCallWithException(h) {
try {
h += "in lambda body"
return h
}
finally {
h += "in lambda finally"
}
}
h += "local"
return h
}
fun box(): String {
val test = test1()
if (test.value != "in lambda body -> inline fun finally -> in call site finally") return "fail 1: $test"
val testLambda = test1Lambda()
if (testLambda.value != "in lambda body -> in lambda finally -> inline fun finally") return "fail 1 lambda: $testLambda"
var h = Holder()
assertError(2, h, "in lambda body -> inline fun finally -> in call site finally") {
test2(h)
}
h = Holder()
assertError(22, h, "in lambda body -> in lambda finally -> inline fun finally") {
test2Lambda(h)
}
return "OK"
}
public fun assertError(index: Int, h: Holder, expected: String, l: (h: Holder) -> Holder) {
try {
l(h)
fail("fail $index: no error")
}
catch (e: Exception) {
assertEquals(expected, h.value, "failed on $index")
}
}
@@ -0,0 +1,33 @@
package test
public class Holder(var value: String = "") {
public fun plusAssign(s: String?) {
if (value.length != 0) {
value += " -> "
}
value += s
}
override fun toString(): String {
return value
}
}
public inline fun <R> doCall(h: Holder, block: ()-> R) {
try {
block()
} finally {
h += "inline fun finally"
}
}
public inline fun <R> doCallWithException(h: Holder, block: ()-> R) {
try {
block()
} finally {
h += "inline fun finally"
throw RuntimeException("fail");
}
}
@@ -25,31 +25,5 @@ fun box() : String {
return "fail"
}
// maybe we shouldinline fun test(s: ()->Int): Int {
var i = 0;
try {
i = s()
i = i + 10
} finally {
return i
}
}
fun box() : String {
var p: Int = 1
test {
try {
p = 1
return "OK" //finally from inline fun doen't split this try
} catch(e: Exception) {
p = -1;
p
} finally {
p++
}
}
return "fail"
}
check test data
// maybe we should check test data
// 13 TRYCATCHBLOCK
@@ -422,6 +422,18 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxCodegenT
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/tryFinally/callSite/callSite.1.kt");
doTestMultiFileWithInlineCheck(fileName);
}
@TestMetadata("exceptionTableSplit.1.kt")
public void testExceptionTableSplit() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/tryFinally/callSite/exceptionTableSplit.1.kt");
doTestMultiFileWithInlineCheck(fileName);
}
@TestMetadata("exceptionTableSplitNoReturn.1.kt")
public void testExceptionTableSplitNoReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/tryFinally/callSite/exceptionTableSplitNoReturn.1.kt");
doTestMultiFileWithInlineCheck(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/tryFinally/declSite")
@@ -422,6 +422,18 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/tryFinally/callSite/callSite.1.kt");
doBoxTestWithInlineCheck(fileName);
}
@TestMetadata("exceptionTableSplit.1.kt")
public void testExceptionTableSplit() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/tryFinally/callSite/exceptionTableSplit.1.kt");
doBoxTestWithInlineCheck(fileName);
}
@TestMetadata("exceptionTableSplitNoReturn.1.kt")
public void testExceptionTableSplitNoReturn() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/tryFinally/callSite/exceptionTableSplitNoReturn.1.kt");
doBoxTestWithInlineCheck(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxInline/nonLocalReturns/tryFinally/declSite")