Support repeated 'invoke' calls on coroutines defined within inline lambdas

#KT-12782 Fixed
This commit is contained in:
Denis Zharkov
2016-06-30 14:24:24 +03:00
parent ae46c726fc
commit 1c3db309e5
7 changed files with 375 additions and 6 deletions
@@ -67,6 +67,14 @@ public class AnonymousObjectTransformer extends ObjectTransformer<AnonymousObjec
public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
InlineCodegenUtil.assertVersionNotGreaterThanGeneratedOne(version, name, inliningContext.state);
classBuilder.defineClass(null, version, access, name, signature, superName, interfaces);
if (interfaces != null) {
for (String anInterface : interfaces) {
if("kotlin/coroutines/Continuation".equals(anInterface)) {
inliningContext.setContinuation(true);
break;
}
}
}
}
@Override
@@ -139,6 +147,8 @@ public class AnonymousObjectTransformer extends ObjectTransformer<AnonymousObjec
transformationInfo, parentRemapper);
List<MethodVisitor> deferringMethods = new ArrayList<MethodVisitor>();
generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, parentRemapper, additionalFakeParams);
for (MethodNode next : methodsToTransform) {
MethodVisitor deferringVisitor = newMethod(classBuilder, next);
InlineResult funResult =
@@ -159,8 +169,6 @@ public class AnonymousObjectTransformer extends ObjectTransformer<AnonymousObjec
method.visitEnd();
}
generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, parentRemapper, additionalFakeParams);
SourceMapper.Companion.flushToClassBuilder(sourceMapper, classBuilder);
ClassVisitor visitor = classBuilder.getVisitor();
@@ -362,7 +370,7 @@ public class AnonymousObjectTransformer extends ObjectTransformer<AnonymousObjec
@NotNull MethodNode constructor,
@NotNull ParametersBuilder capturedParamBuilder,
@NotNull ParametersBuilder constructorParamBuilder,
@NotNull final AnonymousObjectTransformationInfo transformationInfo,
@NotNull AnonymousObjectTransformationInfo transformationInfo,
@NotNull FieldRemapper parentFieldRemapper
) {
Set<LambdaInfo> capturedLambdas = new LinkedHashSet<LambdaInfo>(); //captured var of inlined parameter
@@ -35,6 +35,10 @@ public class InliningContext {
public final ReifiedTypeInliner reifiedTypeInliner;
public final boolean isInliningLambda;
public final boolean classRegeneration;
public final Map<String, AnonymousObjectTransformationInfo> internalNameToAnonymousObjectTransformationInfo =
new HashMap<String, AnonymousObjectTransformationInfo>();
private boolean isContinuation;
public InliningContext(
@Nullable InliningContext parent,
@@ -117,4 +121,21 @@ public class InliningContext {
assert parent != null : "At least root context should return proper value";
return parent.getCallSiteInfo();
}
@Nullable
public AnonymousObjectTransformationInfo findAnonymousObjectTransformationInfo(@NotNull String internalName) {
if (getRoot().internalNameToAnonymousObjectTransformationInfo.containsKey(internalName)) {
return getRoot().internalNameToAnonymousObjectTransformationInfo.get(internalName);
}
return null;
}
public boolean isContinuation() {
return isContinuation;
}
public void setContinuation(boolean continuation) {
isContinuation = continuation;
}
}
@@ -260,16 +260,27 @@ public class MethodInliner {
assert transformationInfo instanceof AnonymousObjectTransformationInfo :
"<init> call doesn't correspond to object transformation info: " +
owner + "." + name + ", info " + transformationInfo;
if (transformationInfo.shouldRegenerate(isSameModule)) {
InliningContext parent = inliningContext.getParent();
boolean shouldRegenerate = transformationInfo.shouldRegenerate(isSameModule);
if (shouldRegenerate || (parent != null && parent.isContinuation())) {
assert shouldRegenerate || inlineCallSiteInfo.getOwnerClassName().equals(transformationInfo.getOldClassName())
: "Only coroutines can call their own constructors";
//put additional captured parameters on stack
AnonymousObjectTransformationInfo info = (AnonymousObjectTransformationInfo) transformationInfo;
AnonymousObjectTransformationInfo oldInfo = inliningContext.findAnonymousObjectTransformationInfo(owner);
if (oldInfo != null) {
info = oldInfo;
}
for (CapturedParamDesc capturedParamDesc : info.getAllRecapturedParameters()) {
visitFieldInsn(
Opcodes.GETSTATIC, capturedParamDesc.getContainingLambdaName(),
"$$$" + capturedParamDesc.getFieldName(), capturedParamDesc.getType().getDescriptor()
);
}
super.visitMethodInsn(opcode, transformationInfo.getNewClassName(), name, info.getNewConstructorDescriptor(), itf);
super.visitMethodInsn(opcode, info.getNewClassName(), name, info.getNewConstructorDescriptor(), itf);
//TODO: add new inner class also for other contexts
if (inliningContext.getParent() instanceof RegeneratedClassContext) {
@@ -565,7 +576,9 @@ public class MethodInliner {
@NotNull Map<Integer, LambdaInfo> lambdaMapping,
boolean needReification
) {
return new AnonymousObjectTransformationInfo(
boolean memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType) == null;
AnonymousObjectTransformationInfo info = new AnonymousObjectTransformationInfo(
anonymousType, needReification, lambdaMapping,
inliningContext.classRegeneration,
isAlreadyRegenerated(anonymousType),
@@ -573,6 +586,11 @@ public class MethodInliner {
false,
inliningContext.nameGenerator
);
if (memoizeAnonymousObject) {
inliningContext.getRoot().internalNameToAnonymousObjectTransformationInfo.put(anonymousType, info);
}
return info;
}
private boolean isAlreadyRegenerated(@NotNull String owner) {
@@ -0,0 +1,88 @@
class Controller {
var lastSuspension: Continuation<String>? = null
var result = "fail"
suspend fun suspendHere(x: Continuation<String>) {
lastSuspension = x
}
fun hasNext() = lastSuspension != null
fun next() {
val x = lastSuspension!!
lastSuspension = null
x.resume("56")
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
val controller1 = Controller()
val controller2 = Controller()
c(controller1).resume(Unit)
c(controller2).resume(Unit)
runControllers(controller1, controller2)
}
fun builder2(coroutine c: Controller.(Long, String) -> Continuation<Unit>) {
val controller1 = Controller()
val controller2 = Controller()
c(controller1, 1234567890123456789L, "Q").resume(Unit)
c(controller2, 1234567890123456789L, "Q").resume(Unit)
runControllers(controller1, controller2)
}
private fun runControllers(controller1: Controller, controller2: Controller) {
while (controller1.hasNext()) {
if (!controller2.hasNext()) throw RuntimeException("fail 1")
if (controller1.lastSuspension === controller2.lastSuspension) throw RuntimeException("equal references")
controller1.next()
controller2.next()
}
if (controller2.hasNext()) throw RuntimeException("fail 2")
if (controller1.result != "OK") throw RuntimeException("fail 3")
if (controller2.result != "OK") throw RuntimeException("fail 4")
}
inline fun run(b: () -> Unit) {
b()
}
fun box(): String {
// with capture and params
var x = "O"
var y = "K"
// inlined
run {
// no suspension
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
result = x + y
}
// 1 suspension
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
if (suspendHere() != "56") return@builder2
result = x + y
}
// 2 suspensions
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
if (suspendHere() != "56") return@builder2
suspendHere()
result = x + y
}
}
return "OK"
}
@@ -0,0 +1,109 @@
class Controller {
var lastSuspension: Continuation<String>? = null
var result = "fail"
suspend fun suspendHere(x: Continuation<String>) {
lastSuspension = x
}
fun hasNext() = lastSuspension != null
fun next() {
val x = lastSuspension!!
lastSuspension = null
x.resume("56")
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
val controller1 = Controller()
val controller2 = Controller()
c(controller1).resume(Unit)
c(controller2).resume(Unit)
runControllers(controller1, controller2)
}
fun builder2(coroutine c: Controller.(Long, String) -> Continuation<Unit>) {
val controller1 = Controller()
val controller2 = Controller()
c(controller1, 1234567890123456789L, "Q").resume(Unit)
c(controller2, 1234567890123456789L, "Q").resume(Unit)
runControllers(controller1, controller2)
}
private fun runControllers(controller1: Controller, controller2: Controller) {
while (controller1.hasNext()) {
if (!controller2.hasNext()) throw RuntimeException("fail 1")
if (controller1.lastSuspension === controller2.lastSuspension) throw RuntimeException("equal references")
controller1.next()
controller2.next()
}
if (controller2.hasNext()) throw RuntimeException("fail 2")
if (controller1.result != "OK") throw RuntimeException("fail 3")
if (controller2.result != "OK") throw RuntimeException("fail 4")
}
inline fun run(b: () -> Unit) {
b()
}
fun box(): String {
// with capture and params
var x = "O"
// inlined
run {
var y = "K"
{
// no suspension
builder {
result = "OK"
}
// 1 suspension
builder {
if (suspendHere() != "56") return@builder
result = "OK"
}
// 2 suspensions
builder {
if (suspendHere() != "56") return@builder
suspendHere()
result = "OK"
}
// no suspension
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
result = x + y
}
// 1 suspension
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
if (suspendHere() != "56") return@builder2
result = x + y
}
// 2 suspensions
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
if (suspendHere() != "56") return@builder2
suspendHere()
result = x + y
}
}()
}
return "OK"
}
@@ -0,0 +1,107 @@
class Controller {
var lastSuspension: Continuation<String>? = null
var result = "fail"
suspend fun suspendHere(x: Continuation<String>) {
lastSuspension = x
}
fun hasNext() = lastSuspension != null
fun next() {
val x = lastSuspension!!
lastSuspension = null
x.resume("56")
}
}
fun builder(coroutine c: Controller.() -> Continuation<Unit>) {
val controller1 = Controller()
val controller2 = Controller()
c(controller1).resume(Unit)
c(controller2).resume(Unit)
runControllers(controller1, controller2)
}
fun builder2(coroutine c: Controller.(Long, String) -> Continuation<Unit>) {
val controller1 = Controller()
val controller2 = Controller()
c(controller1, 1234567890123456789L, "Q").resume(Unit)
c(controller2, 1234567890123456789L, "Q").resume(Unit)
runControllers(controller1, controller2)
}
private fun runControllers(controller1: Controller, controller2: Controller) {
while (controller1.hasNext()) {
if (!controller2.hasNext()) throw RuntimeException("fail 1")
if (controller1.lastSuspension === controller2.lastSuspension) throw RuntimeException("equal references")
controller1.next()
controller2.next()
}
if (controller2.hasNext()) throw RuntimeException("fail 2")
if (controller1.result != "OK") throw RuntimeException("fail 3")
if (controller2.result != "OK") throw RuntimeException("fail 4")
}
inline fun run(b: () -> Unit) {
b()
}
fun box(): String {
var x = "O"
{
var y = "K"
// inlined
run {
// no suspension
builder {
result = "OK"
}
// 1 suspension
builder {
if (suspendHere() != "56") return@builder
result = "OK"
}
// 2 suspensions
builder {
if (suspendHere() != "56") return@builder
suspendHere()
result = "OK"
}
// no suspension
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
result = x + y
}
// 1 suspension
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
if (suspendHere() != "56") return@builder2
result = x + y
}
// 2 suspensions
builder2 { a, b ->
if (a != 1234567890123456789L || b != "Q" ) return@builder2
if (suspendHere() != "56") return@builder2
suspendHere()
result = x + y
}
}
} ()
return "OK"
}
@@ -4231,6 +4231,24 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("multipleInvokeCallsInsideInlineLambda1.kt")
public void testMultipleInvokeCallsInsideInlineLambda1() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda1.kt");
doTest(fileName);
}
@TestMetadata("multipleInvokeCallsInsideInlineLambda2.kt")
public void testMultipleInvokeCallsInsideInlineLambda2() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda2.kt");
doTest(fileName);
}
@TestMetadata("multipleInvokeCallsInsideInlineLambda3.kt")
public void testMultipleInvokeCallsInsideInlineLambda3() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/multipleInvokeCallsInsideInlineLambda3.kt");
doTest(fileName);
}
@TestMetadata("nestedTryCatch.kt")
public void testNestedTryCatch() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/nestedTryCatch.kt");