KT-11499 Normalize stacks on return from an inline function when the function is inlined at call site.
This commit is contained in:
@@ -528,7 +528,7 @@ public class FunctionCodegen {
|
||||
catch (Throwable t) {
|
||||
String bytecode = renderByteCodeIfAvailable(mv);
|
||||
throw new CompilationException(
|
||||
"wrong code generated" +
|
||||
"wrong code generated\n" +
|
||||
(description != null ? " for " + description : "") +
|
||||
t.getClass().getName() +
|
||||
" " +
|
||||
|
||||
@@ -314,11 +314,21 @@ public class InlineCodegenUtil {
|
||||
|
||||
//marked return could be either non-local or local in case of labeled lambda self-returns
|
||||
public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) {
|
||||
if (!isReturnOpcode(returnIns.getOpcode())) {
|
||||
return false;
|
||||
return getMarkedReturnLabelOrNull(returnIns) != null;
|
||||
}
|
||||
|
||||
public static @Nullable String getMarkedReturnLabelOrNull(@NotNull AbstractInsnNode returnInsn) {
|
||||
if (!isReturnOpcode(returnInsn.getOpcode())) {
|
||||
return null;
|
||||
}
|
||||
AbstractInsnNode globalFlag = returnIns.getPrevious();
|
||||
return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner);
|
||||
AbstractInsnNode previous = returnInsn.getPrevious();
|
||||
if (previous instanceof MethodInsnNode) {
|
||||
MethodInsnNode marker = (MethodInsnNode) previous;
|
||||
if (NON_LOCAL_RETURN.equals(marker.owner)) {
|
||||
return marker.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.codegen.StackValue;
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
|
||||
import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer;
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
||||
import org.jetbrains.kotlin.utils.SmartList;
|
||||
import org.jetbrains.kotlin.utils.SmartSet;
|
||||
import org.jetbrains.org.objectweb.asm.Label;
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
||||
@@ -35,6 +36,7 @@ import org.jetbrains.org.objectweb.asm.commons.Method;
|
||||
import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter;
|
||||
import org.jetbrains.org.objectweb.asm.tree.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.*;
|
||||
import org.jetbrains.org.objectweb.asm.util.Printer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -121,7 +123,7 @@ public class MethodInliner {
|
||||
int finallyDeepShift
|
||||
) {
|
||||
//analyze body
|
||||
MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node, finallyDeepShift);
|
||||
MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node, labelOwner, finallyDeepShift);
|
||||
|
||||
//substitute returns with "goto end" instruction to keep non local returns in lambdas
|
||||
Label end = new Label();
|
||||
@@ -398,44 +400,12 @@ public class MethodInliner {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) {
|
||||
protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, int finallyDeepShift) {
|
||||
node = prepareNode(node, finallyDeepShift);
|
||||
|
||||
try {
|
||||
new MandatoryMethodTransformer().transform("fake", node);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw wrapException(e, node, "couldn't inline method call");
|
||||
}
|
||||
Frame<SourceValue>[] sources = analyzeMethodNodeBeforeInline(node);
|
||||
LocalReturnsNormalizer localReturnsNormalizer = LocalReturnsNormalizer.createFor(node, labelOwner, sources);
|
||||
|
||||
Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected Frame<SourceValue> newFrame(
|
||||
int nLocals, int nStack
|
||||
) {
|
||||
return new Frame<SourceValue>(nLocals, nStack) {
|
||||
@Override
|
||||
public void execute(
|
||||
@NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter
|
||||
) throws AnalyzerException {
|
||||
if (insn.getOpcode() == Opcodes.RETURN) {
|
||||
//there is exception on void non local return in frame
|
||||
return;
|
||||
}
|
||||
super.execute(insn, interpreter);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Frame<SourceValue>[] sources;
|
||||
try {
|
||||
sources = analyzer.analyze("fake", node);
|
||||
}
|
||||
catch (AnalyzerException e) {
|
||||
throw wrapException(e, node, "couldn't inline method call");
|
||||
}
|
||||
Set<AbstractInsnNode> toDelete = SmartSet.create();
|
||||
InsnList instructions = node.instructions;
|
||||
AbstractInsnNode cur = instructions.getFirst();
|
||||
@@ -547,9 +517,44 @@ public class MethodInliner {
|
||||
}
|
||||
}
|
||||
|
||||
localReturnsNormalizer.transform(node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private Frame<SourceValue>[] analyzeMethodNodeBeforeInline(@NotNull MethodNode node) {
|
||||
try {
|
||||
new MandatoryMethodTransformer().transform("fake", node);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw wrapException(e, node, "couldn't inline method call");
|
||||
}
|
||||
|
||||
Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected Frame<SourceValue> newFrame(int nLocals, int nStack) {
|
||||
return new Frame<SourceValue>(nLocals, nStack) {
|
||||
@Override
|
||||
public void execute(@NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter) throws AnalyzerException {
|
||||
// This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else.
|
||||
if (insn.getOpcode() == Opcodes.RETURN) return;
|
||||
super.execute(insn, interpreter);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Frame<SourceValue>[] sources;
|
||||
try {
|
||||
sources = analyzer.analyze("fake", node);
|
||||
}
|
||||
catch (AnalyzerException e) {
|
||||
throw wrapException(e, node, "couldn't inline method call");
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
private static boolean isEmptyTryInterval(@NotNull TryCatchBlockNode tryCatchBlockNode) {
|
||||
LabelNode start = tryCatchBlockNode.start;
|
||||
AbstractInsnNode end = tryCatchBlockNode.end;
|
||||
@@ -758,20 +763,14 @@ public class MethodInliner {
|
||||
AbstractInsnNode insnNode = instructions.getFirst();
|
||||
while (insnNode != null) {
|
||||
if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
|
||||
AbstractInsnNode previous = insnNode.getPrevious();
|
||||
MethodInsnNode flagNode;
|
||||
boolean isLocalReturn = true;
|
||||
String labelName = null;
|
||||
if (previous != null && previous instanceof MethodInsnNode && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) {
|
||||
flagNode = (MethodInsnNode) previous;
|
||||
labelName = flagNode.name;
|
||||
}
|
||||
String labelName = InlineCodegenUtil.getMarkedReturnLabelOrNull(insnNode);
|
||||
|
||||
if (labelName != null) {
|
||||
isLocalReturn = labelOwner.isMyLabel(labelName);
|
||||
//remove global return flag
|
||||
if (isLocalReturn) {
|
||||
instructions.remove(previous);
|
||||
instructions.remove(insnNode.getPrevious());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -783,7 +782,7 @@ public class MethodInliner {
|
||||
insnNode = jumpInsnNode;
|
||||
}
|
||||
|
||||
//genetate finally block before nonLocalReturn flag/return/goto
|
||||
//generate finally block before nonLocalReturn flag/return/goto
|
||||
LabelNode label = new LabelNode();
|
||||
instructions.insert(insnNode, label);
|
||||
result.add(new PointForExternalFinallyBlocks(getInstructionToInsertFinallyBefore(insnNode, isLocalReturn),
|
||||
@@ -795,6 +794,119 @@ public class MethodInliner {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class LocalReturnsNormalizer {
|
||||
private static class LocalReturn {
|
||||
private final AbstractInsnNode returnInsn;
|
||||
private final AbstractInsnNode insertBeforeInsn;
|
||||
private final Frame<SourceValue> frame;
|
||||
|
||||
public LocalReturn(
|
||||
@NotNull AbstractInsnNode returnInsn,
|
||||
@NotNull AbstractInsnNode insertBeforeInsn,
|
||||
@NotNull Frame<SourceValue> frame
|
||||
) {
|
||||
this.returnInsn = returnInsn;
|
||||
this.insertBeforeInsn = insertBeforeInsn;
|
||||
this.frame = frame;
|
||||
}
|
||||
|
||||
public void transform(@NotNull InsnList insnList, int returnVariableIndex) {
|
||||
boolean isReturnWithValue = returnInsn.getOpcode() != Opcodes.RETURN;
|
||||
|
||||
int expectedStackSize = isReturnWithValue ? 1 : 0;
|
||||
int actualStackSize = frame.getStackSize();
|
||||
if (expectedStackSize == actualStackSize) return;
|
||||
|
||||
int stackSize = actualStackSize;
|
||||
if (isReturnWithValue) {
|
||||
int storeOpcode = Opcodes.ISTORE + returnInsn.getOpcode() - Opcodes.IRETURN;
|
||||
insnList.insertBefore(insertBeforeInsn, new VarInsnNode(storeOpcode, returnVariableIndex));
|
||||
stackSize--;
|
||||
}
|
||||
|
||||
while (stackSize > 0) {
|
||||
int stackElementSize = frame.getStack(stackSize - 1).getSize();
|
||||
int popOpcode = stackElementSize == 1 ? Opcodes.POP : Opcodes.POP2;
|
||||
insnList.insertBefore(insertBeforeInsn, new InsnNode(popOpcode));
|
||||
stackSize--;
|
||||
}
|
||||
|
||||
if (isReturnWithValue) {
|
||||
int loadOpcode = Opcodes.ILOAD + returnInsn.getOpcode() - Opcodes.IRETURN;
|
||||
insnList.insertBefore(insertBeforeInsn, new VarInsnNode(loadOpcode, returnVariableIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final List<LocalReturn> localReturns = new SmartList<LocalReturn>();
|
||||
|
||||
private boolean needsReturnVariable = false;
|
||||
private int returnOpcode = -1;
|
||||
|
||||
private void addLocalReturnToTransform(
|
||||
@NotNull AbstractInsnNode returnInsn,
|
||||
@NotNull AbstractInsnNode insertBeforeInsn,
|
||||
@NotNull Frame<SourceValue> sourceValueFrame
|
||||
) {
|
||||
assert InlineCodegenUtil.isReturnOpcode(returnInsn.getOpcode()) : "return instruction expected";
|
||||
assert returnOpcode < 0 || returnOpcode == returnInsn.getOpcode() :
|
||||
"Return op should be " + Printer.OPCODES[returnOpcode] + ", got " + Printer.OPCODES[returnInsn.getOpcode()];
|
||||
returnOpcode = returnInsn.getOpcode();
|
||||
|
||||
localReturns.add(new LocalReturn(returnInsn, insertBeforeInsn, sourceValueFrame));
|
||||
|
||||
if (returnInsn.getOpcode() != Opcodes.RETURN && sourceValueFrame.getStackSize() > 1) {
|
||||
needsReturnVariable = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void transform(MethodNode methodNode) {
|
||||
int returnVariableIndex = -1;
|
||||
if (needsReturnVariable) {
|
||||
returnVariableIndex = methodNode.maxLocals;
|
||||
methodNode.maxLocals++;
|
||||
}
|
||||
|
||||
for (LocalReturn localReturn : localReturns) {
|
||||
localReturn.transform(methodNode.instructions, returnVariableIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public static LocalReturnsNormalizer createFor(
|
||||
@NotNull MethodNode methodNode,
|
||||
@NotNull LabelOwner owner,
|
||||
@NotNull Frame<SourceValue>[] frames
|
||||
) {
|
||||
LocalReturnsNormalizer result = new LocalReturnsNormalizer();
|
||||
|
||||
AbstractInsnNode[] instructions = methodNode.instructions.toArray();
|
||||
|
||||
for (int i = 0; i < instructions.length; ++i) {
|
||||
Frame<SourceValue> frame = frames[i];
|
||||
// Don't care about dead code, it will be eliminated
|
||||
if (frame == null) continue;
|
||||
|
||||
AbstractInsnNode insnNode = instructions[i];
|
||||
if (!InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) continue;
|
||||
|
||||
AbstractInsnNode insertBeforeInsn = insnNode;
|
||||
|
||||
// TODO extract isLocalReturn / isNonLocalReturn, see processReturns
|
||||
String labelName = getMarkedReturnLabelOrNull(insnNode);
|
||||
if (labelName != null) {
|
||||
if (!owner.isMyLabel(labelName)) continue;
|
||||
insertBeforeInsn = insnNode.getPrevious();
|
||||
}
|
||||
|
||||
result.addLocalReturnToTransform(insnNode, insertBeforeInsn, frame);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@NotNull
|
||||
private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal) {
|
||||
return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious();
|
||||
|
||||
+35
-37
@@ -16,9 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.optimization.boxing
|
||||
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||
import org.jetbrains.kotlin.codegen.optimization.removeNodeGetNext
|
||||
import org.jetbrains.kotlin.codegen.optimization.replaceNodeGetNext
|
||||
@@ -39,55 +37,55 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() {
|
||||
private class Transformer(val methodNode: MethodNode) {
|
||||
private val insnList = methodNode.instructions
|
||||
|
||||
private val frames: Array<Frame<SourceValue>?> = Analyzer<SourceValue>(SourceInterpreter()).analyze("fake", methodNode)
|
||||
private val insns = insnList.toArray()
|
||||
|
||||
private val dontTouchInsns = hashSetOf<AbstractInsnNode>()
|
||||
private val transformations = hashMapOf<AbstractInsnNode, () -> Unit>()
|
||||
private val removableNops = hashSetOf<InsnNode>()
|
||||
|
||||
private val frames: Array<out Frame<SourceValue>?> = analyzeMethodBody()
|
||||
|
||||
fun transform() {
|
||||
computeDontTouchInsns()
|
||||
computeTransformations()
|
||||
transformations.values.forEach { it() }
|
||||
postprocessNops()
|
||||
}
|
||||
|
||||
private fun computeDontTouchInsns() {
|
||||
for (i in 0..insns.lastIndex) {
|
||||
val frame = frames[i] ?: continue
|
||||
val insn = insns[i]
|
||||
private fun analyzeMethodBody(): Array<out Frame<SourceValue>?> =
|
||||
Analyzer<SourceValue>(object : SourceInterpreter() {
|
||||
override fun naryOperation(insn: AbstractInsnNode, values: MutableList<out SourceValue>): SourceValue {
|
||||
for (value in values) {
|
||||
dontTouchInsns.addAll(value.insns)
|
||||
}
|
||||
return super.naryOperation(insn, values)
|
||||
}
|
||||
|
||||
when (insn.opcode) {
|
||||
Opcodes.DUP ->
|
||||
dontTouchWordsOnTop(i, frame, 1)
|
||||
Opcodes.DUP_X1 ->
|
||||
dontTouchWordsOnTop(i, frame, 2)
|
||||
Opcodes.DUP_X2 ->
|
||||
dontTouchWordsOnTop(i, frame, 3)
|
||||
Opcodes.DUP2 ->
|
||||
dontTouchWordsOnTop(i, frame, 2)
|
||||
Opcodes.DUP2_X1 ->
|
||||
dontTouchWordsOnTop(i, frame, 3)
|
||||
Opcodes.DUP2_X2 ->
|
||||
dontTouchWordsOnTop(i, frame, 4)
|
||||
Opcodes.SWAP ->
|
||||
dontTouchWordsOnTop(i, frame, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun copyOperation(insn: AbstractInsnNode, value: SourceValue): SourceValue {
|
||||
dontTouchInsns.addAll(value.insns)
|
||||
return super.copyOperation(insn, value)
|
||||
}
|
||||
|
||||
override fun unaryOperation(insn: AbstractInsnNode, value: SourceValue): SourceValue {
|
||||
if (insn.opcode != Opcodes.CHECKCAST) {
|
||||
dontTouchInsns.addAll(value.insns)
|
||||
}
|
||||
return super.unaryOperation(insn, value)
|
||||
}
|
||||
|
||||
override fun binaryOperation(insn: AbstractInsnNode, value1: SourceValue, value2: SourceValue): SourceValue {
|
||||
dontTouchInsns.addAll(value1.insns)
|
||||
dontTouchInsns.addAll(value2.insns)
|
||||
return super.binaryOperation(insn, value1, value2)
|
||||
}
|
||||
|
||||
override fun ternaryOperation(insn: AbstractInsnNode, value1: SourceValue, value2: SourceValue, value3: SourceValue): SourceValue {
|
||||
dontTouchInsns.addAll(value1.insns)
|
||||
dontTouchInsns.addAll(value2.insns)
|
||||
dontTouchInsns.addAll(value3.insns)
|
||||
return super.ternaryOperation(insn, value1, value2, value3)
|
||||
}
|
||||
}).analyze("fake", methodNode)
|
||||
|
||||
private fun dontTouchWordsOnTop(at: Int, frame: Frame<SourceValue>, expectedWords: Int) {
|
||||
var words = 0
|
||||
var offset = 0
|
||||
while (words < expectedWords) {
|
||||
val value = frame.peek(offset) ?: throwIllegalStackInsn(at)
|
||||
offset++
|
||||
words += value.size
|
||||
dontTouchInsns.addAll(value.insns)
|
||||
}
|
||||
if (words != expectedWords) throwIllegalStackInsn(at)
|
||||
}
|
||||
|
||||
private fun computeTransformations() {
|
||||
transformations.clear()
|
||||
|
||||
Reference in New Issue
Block a user