Add default lambda inlining prototype

This commit is contained in:
Mikhael Bogdanov
2017-05-08 15:28:03 +02:00
parent b7af4ac882
commit 97bcf9f538
23 changed files with 477 additions and 3 deletions
@@ -28,6 +28,7 @@ enum class ValueKind {
DEFAULT_MASK,
METHOD_HANDLE_IN_DEFAULT,
CAPTURED,
DEFAULT_LAMBDA_CAPTURED_PARAMETER
}
abstract class CallGenerator {
@@ -417,6 +417,11 @@ public class InlineCodegen extends CallGenerator {
node, maskStartIndex, maskValues, methodHandleInDefaultMethodIndex,
DefaultMethodUtilKt.extractDefaultLambdaOffsetAndDescriptor(jvmSignature, functionDescriptor)
);
for (DefaultLambda lambda : defaultLambdas) {
invocationParamBuilder.buildParameters().getParameterByDeclarationSlot(lambda.getOffset()).setLambda(lambda);
LambdaInfo prev = expressionMap.put(lambda.getOffset(), lambda);
assert prev == null : "Lambda with offset " + lambda.getOffset() + " already exists: " + prev;
}
}
ReifiedTypeParametersUsages reificationResult = reifiedTypeInliner.reifyInstructions(node);
generateClosuresBodies();
@@ -668,7 +673,11 @@ public class InlineCodegen extends CallGenerator {
info = invocationParamBuilder.addNextValueParameter(type, false, remappedValue, parameterIndex);
}
recordParameterValueInLocalVal(false, isDefaultParameter, info);
recordParameterValueInLocalVal(
false,
isDefaultParameter || kind == ValueKind.DEFAULT_LAMBDA_CAPTURED_PARAMETER,
info
);
}
}
@@ -826,12 +835,34 @@ public class InlineCodegen extends CallGenerator {
if (next instanceof ExpressionLambda) {
codegen.pushClosureOnStack(((ExpressionLambda) next).getClassDescriptor(), true, this, functionReferenceReceiver);
}
else if (next instanceof DefaultLambda) {
rememberCapturedForDefaultLambda((DefaultLambda) next);
}
else {
//TODO
throw new RuntimeException("Unknown lambda: " + next);
}
activeLambda = null;
}
private void rememberCapturedForDefaultLambda(@NotNull DefaultLambda defaultLambda) {
List<CapturedParamDesc> vars = defaultLambda.getCapturedVars();
int paramIndex = 0;
for (CapturedParamDesc captured : vars) {
putArgumentOrCapturedToLocalVal(
captured.getType(),
//HACK: actually parameter would be placed on stack in default function
// also see ValueKind.DEFAULT_LAMBDA_CAPTURED_PARAMETER check
StackValue.onStack(captured.getType()),
paramIndex,
paramIndex,
ValueKind.DEFAULT_LAMBDA_CAPTURED_PARAMETER
);
paramIndex++;
defaultLambda.getParameterOffsetsInDefault().add(invocationParamBuilder.getNextParameterOffset());
}
}
@NotNull
public static CodegenContext getContext(
@NotNull DeclarationDescriptor descriptor, @NotNull GenerationState state, @Nullable KtFile sourceFile
@@ -84,6 +84,8 @@ class DefaultLambda(
val offset: Int
) : LambdaInfo(parameterDescriptor.isCrossinline, false) {
val parameterOffsetsInDefault: MutableList<Int> = arrayListOf()
override lateinit var invokeMethod: Method
private set
@@ -76,6 +76,7 @@ public class LocalVarRemapper {
}
else {
//captured params are not used directly in this inlined method, they are used in closure
//except captured ones for default lambdas, they are generated in default body
remappedIndex = actualParamsSize - params.getArgsSizeOnStack() + index;
}
@@ -332,6 +332,22 @@ class MethodInliner(
}
}
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
if (InlineCodegenUtil.DEFAULT_LAMBDA_FAKE_CALL == owner) {
val index = name.substringAfter(InlineCodegenUtil.DEFAULT_LAMBDA_FAKE_CALL).toInt()
val lambda = getLambdaIfExists(index) as DefaultLambda
lambda.parameterOffsetsInDefault.zip(lambda.capturedVars).asReversed().forEach {
(_, captured) ->
super.visitFieldInsn(
Opcodes.PUTSTATIC, captured.containingLambdaName, "$$$" + captured.fieldName, captured.type.descriptor
)
}
}
else {
super.visitMethodInsn(opcode, owner, name, desc, itf)
}
}
override fun visitLocalVariable(
name: String, desc: String, signature: String?, start: Label, end: Label, index: Int
) {
@@ -20,6 +20,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.codegen.StackValue;
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.commons.InstructionAdapter;
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode;
@@ -62,7 +63,12 @@ public class RemapVisitor extends MethodBodyVisitor {
FieldInsnNode fin = new FieldInsnNode(opcode, owner, name, desc);
StackValue inline = nodeRemapper.getFieldForInline(fin, null);
assert inline != null : "Captured field should have not null stackValue " + fin;
inline.put(inline.type, this);
if (Opcodes.PUTSTATIC == opcode) {
inline.store(StackValue.onStack(inline.type), this);
}
else {
inline.put(inline.type, this);
}
return;
}
super.visitFieldInsn(opcode, owner, name, desc);
@@ -0,0 +1,16 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(crossinline inlineLambda: () -> String = { "OK" }, noinline noInlineLambda: () -> String = { inlineLambda() }): String {
return noInlineLambda()
}
// FILE: 2.kt
//NO_CHECK_LAMBDA_INLINING
import test.*
fun box(): String {
return inlineFun()
}
@@ -0,0 +1,18 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
fun ok() = "OK"
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(lambda: () -> String = ::ok): String {
return lambda()
}
// FILE: 2.kt
import test.*
fun box(): String {
return inlineFun()
}
@@ -0,0 +1,23 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
fun ok() = "OK"
class A(val value: String) {
fun ok() = value
}
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(a: A, lambda: (A) -> String = A::ok): String {
return lambda(a)
}
// FILE: 2.kt
import test.*
fun box(): String {
return inlineFun(A("OK"))
}
@@ -0,0 +1,19 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
class A(val value: String) {
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(lambda: () -> String = { value }): String {
return lambda()
}
}
// FILE: 2.kt
import test.*
fun box(): String {
return A("OK").inlineFun()
}
@@ -0,0 +1,29 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
//problem in test framework
inline fun inlineFunStub(){}
interface A {
val value: String
fun test() = inlineFun()
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
private inline fun inlineFun(lambda: () -> String = { value }): String {
return lambda()
}
}
// FILE: 2.kt
import test.*
class B : A {
override val value: String = "OK"
}
fun box(): String {
return B().test()
}
@@ -0,0 +1,20 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
//WITH_RUNTIME
package test
object X {
@JvmStatic
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(capturedParam: String, lambda: () -> String = { capturedParam }): String {
return lambda()
}
}
// FILE: 2.kt
import test.*
fun box(): String {
return X.inlineFun("OK")
}
@@ -0,0 +1,16 @@
// FILE: 1.kt
package test
inline fun inlineFun(capturedParam: String, noinline lambda: () -> String = { capturedParam }): String {
return call(lambda)
}
fun call(lambda: () -> String ) = lambda()
// FILE: 2.kt
//NO_CHECK_LAMBDA_INLINING
import test.*
fun box(): String {
return inlineFun("OK")
}
@@ -0,0 +1,16 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(crossinline inlineLambda: () -> String, noinline noInlineLambda: () -> String = { inlineLambda() }): String {
return noInlineLambda()
}
// FILE: 2.kt
//NO_CHECK_LAMBDA_INLINING
import test.*
fun box(): String {
return inlineFun ({ "OK" })
}
@@ -0,0 +1,16 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(capturedParam: String, lambda: () -> String = { capturedParam }): String {
return lambda()
}
// FILE: 2.kt
import test.*
fun box(): String {
return inlineFun("OK")
}
@@ -0,0 +1,16 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(capturedParam: String, lambda: () -> Any = { capturedParam as Any }): Any {
return lambda()
}
// FILE: 2.kt
import test.*
fun box(): String {
return inlineFun("OK") as String
}
@@ -0,0 +1,16 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(lambda: () -> Any = { "OK" as Any }): Any {
return lambda()
}
// FILE: 2.kt
import test.*
fun box(): String {
return inlineFun() as String
}
@@ -0,0 +1,20 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
open class A(val value: String)
class B(value: String): A(value)
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun <T : A> inlineFun(capturedParam: T, lambda: () -> T = { capturedParam }): T {
return lambda()
}
// FILE: 2.kt
import test.*
fun box(): String {
return inlineFun(B("O")).value + inlineFun(A("K")).value
}
@@ -0,0 +1,16 @@
// FILE: 1.kt
// SKIP_INLINE_CHECK_IN: inlineFun$default
package test
@Suppress("NOT_YET_SUPPORTED_IN_INLINE")
inline fun inlineFun(lambda: () -> String = { "OK" }): String {
return lambda()
}
// FILE: 2.kt
import test.*
fun box(): String {
return inlineFun()
}
@@ -941,6 +941,93 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
doTest(fileName);
}
@TestMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LambdaInlining extends AbstractBlackBoxInlineCodegenTest {
public void testAllFilesPresentInLambdaInlining() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("defaultLambdaInNoInline.kt")
public void testDefaultLambdaInNoInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt");
doTest(fileName);
}
@TestMetadata("functionReference.kt")
public void testFunctionReference() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/functionReference.kt");
doTest(fileName);
}
@TestMetadata("functionReferenceFromClass.kt")
public void testFunctionReferenceFromClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/functionReferenceFromClass.kt");
doTest(fileName);
}
@TestMetadata("instanceCapuredInClass.kt")
public void testInstanceCapuredInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/instanceCapuredInClass.kt");
doTest(fileName);
}
@TestMetadata("instanceCapuredInInterface.kt")
public void testInstanceCapuredInInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/instanceCapuredInInterface.kt");
doTest(fileName);
}
@TestMetadata("jvmStaticDefault.kt")
public void testJvmStaticDefault() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/jvmStaticDefault.kt");
doTest(fileName);
}
@TestMetadata("noInline.kt")
public void testNoInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/noInline.kt");
doTest(fileName);
}
@TestMetadata("nonDefaultInlineInNoInline.kt")
public void testNonDefaultInlineInNoInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/nonDefaultInlineInNoInline.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simple.kt");
doTest(fileName);
}
@TestMetadata("simpleErased.kt")
public void testSimpleErased() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simpleErased.kt");
doTest(fileName);
}
@TestMetadata("simpleErasedStaticInstance.kt")
public void testSimpleErasedStaticInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simpleErasedStaticInstance.kt");
doTest(fileName);
}
@TestMetadata("simpleGeneric.kt")
public void testSimpleGeneric() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simpleGeneric.kt");
doTest(fileName);
}
@TestMetadata("simpleStaticInstance.kt")
public void testSimpleStaticInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simpleStaticInstance.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxInline/defaultValues/maskElimination")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -941,6 +941,93 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
doTest(fileName);
}
@TestMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class LambdaInlining extends AbstractCompileKotlinAgainstInlineKotlinTest {
public void testAllFilesPresentInLambdaInlining() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("defaultLambdaInNoInline.kt")
public void testDefaultLambdaInNoInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/defaultLambdaInNoInline.kt");
doTest(fileName);
}
@TestMetadata("functionReference.kt")
public void testFunctionReference() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/functionReference.kt");
doTest(fileName);
}
@TestMetadata("functionReferenceFromClass.kt")
public void testFunctionReferenceFromClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/functionReferenceFromClass.kt");
doTest(fileName);
}
@TestMetadata("instanceCapuredInClass.kt")
public void testInstanceCapuredInClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/instanceCapuredInClass.kt");
doTest(fileName);
}
@TestMetadata("instanceCapuredInInterface.kt")
public void testInstanceCapuredInInterface() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/instanceCapuredInInterface.kt");
doTest(fileName);
}
@TestMetadata("jvmStaticDefault.kt")
public void testJvmStaticDefault() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/jvmStaticDefault.kt");
doTest(fileName);
}
@TestMetadata("noInline.kt")
public void testNoInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/noInline.kt");
doTest(fileName);
}
@TestMetadata("nonDefaultInlineInNoInline.kt")
public void testNonDefaultInlineInNoInline() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/nonDefaultInlineInNoInline.kt");
doTest(fileName);
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simple.kt");
doTest(fileName);
}
@TestMetadata("simpleErased.kt")
public void testSimpleErased() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simpleErased.kt");
doTest(fileName);
}
@TestMetadata("simpleErasedStaticInstance.kt")
public void testSimpleErasedStaticInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simpleErasedStaticInstance.kt");
doTest(fileName);
}
@TestMetadata("simpleGeneric.kt")
public void testSimpleGeneric() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simpleGeneric.kt");
doTest(fileName);
}
@TestMetadata("simpleStaticInstance.kt")
public void testSimpleStaticInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/simpleStaticInstance.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxInline/defaultValues/maskElimination")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -59,4 +59,5 @@ public class OutputPrefixPostfixTestGenerated extends AbstractOutputPrefixPostfi
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/outputPrefixPostfix/simpleWithPrefixAndPostfix.kt");
doTest(fileName);
}
}
@@ -53,4 +53,5 @@ public class SourceMapGenerationSmokeTestGenerated extends AbstractSourceMapGene
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/sourcemap/methodCallInMethod.kt");
doTest(fileName);
}
}