From 2dcc0bce46ed77fce39cdac14bd1273a44a39005 Mon Sep 17 00:00:00 2001 From: Mikhael Bogdanov Date: Mon, 10 Mar 2014 14:24:31 +0400 Subject: [PATCH] Inline refactoring: more templates for captured fields Support of complex lambda inlining cases --- .../jet/codegen/inline/CapturedParamDesc.java | 59 ++++++ .../jet/codegen/inline/CapturedParamInfo.java | 42 ++-- .../codegen/inline/CapturedParamOwner.java | 24 +++ .../jet/codegen/inline/InlineCodegen.java | 6 +- .../codegen/inline/InlinedLambdaRemapper.java | 62 ++++++ .../jet/codegen/inline/InvokeCall.java | 2 +- .../codegen/inline/LambdaFieldRemapper.java | 88 ++++---- .../jet/codegen/inline/LambdaInfo.java | 16 +- .../jet/codegen/inline/LambdaTransformer.java | 38 ++-- .../jet/codegen/inline/MethodInliner.java | 198 ++++++++++-------- .../jet/codegen/inline/ParametersBuilder.java | 27 ++- .../RegeneratedLambdaFieldRemapper.java | 134 ++++++------ .../jet/codegen/inline/RemapVisitor.java | 31 ++- .../boxInline/buildersAndLambdaCapturing/1.kt | 177 ++++++++++++++++ .../boxInline/buildersAndLambdaCapturing/2.kt | 108 ++++++++++ .../boxInline/lambdaInLambdaNoInline/1.kt | 40 +++- .../boxInline/lambdaInLambdaNoInline/2.kt | 2 +- .../1.kt | 72 +++++++ .../2.kt | 5 + compiler/testData/codegen/boxInline/with/1.kt | 20 +- compiler/testData/codegen/boxInline/with/2.kt | 3 + .../BlackBoxInlineCodegenTestGenerated.java | 10 + ...otlinAgainstInlineKotlinTestGenerated.java | 10 + 23 files changed, 906 insertions(+), 268 deletions(-) create mode 100644 compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamDesc.java create mode 100644 compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamOwner.java create mode 100644 compiler/backend/src/org/jetbrains/jet/codegen/inline/InlinedLambdaRemapper.java create mode 100644 compiler/testData/codegen/boxInline/buildersAndLambdaCapturing/1.kt create mode 100644 compiler/testData/codegen/boxInline/buildersAndLambdaCapturing/2.kt create mode 100644 compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline/1.kt create mode 100644 compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline/2.kt diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamDesc.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamDesc.java new file mode 100644 index 00000000000..15a6c86c846 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamDesc.java @@ -0,0 +1,59 @@ +/* + * 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.annotations.NotNull; +import org.jetbrains.asm4.Type; +import org.jetbrains.jet.codegen.context.EnclosedValueDescriptor; + +public class CapturedParamDesc { + + private final CapturedParamOwner containingLambda; + + private final String fieldName; + + private final Type type; + + public CapturedParamDesc(@NotNull CapturedParamOwner containingLambda, @NotNull EnclosedValueDescriptor descriptor) { + this.containingLambda = containingLambda; + this.type = descriptor.getType(); + this.fieldName = descriptor.getFieldName(); + } + + public CapturedParamDesc(@NotNull CapturedParamOwner containingLambda, @NotNull String fieldName, @NotNull Type type) { + this.containingLambda = containingLambda; + this.fieldName = fieldName; + this.type = type; + } + + public CapturedParamOwner getContainingLambda() { + return containingLambda; + } + + public String getFieldName() { + return fieldName; + } + + public Type getType() { + return type; + } + + + public static CapturedParamDesc createDesc(CapturedParamOwner containingLambdaInfo, String fieldName, Type type) { + return new CapturedParamDesc(containingLambdaInfo, fieldName, type); + } +} diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamInfo.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamInfo.java index fb36534adb9..e64ffe57271 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamInfo.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamInfo.java @@ -23,26 +23,29 @@ import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants; public class CapturedParamInfo extends ParameterInfo { - private final String fieldName; + public static final CapturedParamInfo STUB = new CapturedParamInfo(CapturedParamDesc.createDesc(new CapturedParamOwner() { + @Override + public Type getType() { + return Type.getObjectType("STUB"); + } + }, "STUB", Type.getObjectType("STUB")), true, -1, -1); + + public final CapturedParamDesc desc; private int shift = 0; - private LambdaInfo recapturedFrom; - - public static final CapturedParamInfo STUB = new CapturedParamInfo("", AsmTypeConstants.OBJECT_TYPE, true, -1, -1); - - public CapturedParamInfo(@NotNull String fieldName, @NotNull Type type, boolean skipped, int index, int remapIndex) { - super(type, skipped, index, remapIndex); - this.fieldName = fieldName; + public CapturedParamInfo(@NotNull CapturedParamDesc desc, boolean skipped, int index, int remapIndex) { + super(desc.getType(), skipped, index, remapIndex); + this.desc = desc; } - public CapturedParamInfo(@NotNull String fieldName, @NotNull Type type, boolean skipped, int index, StackValue remapIndex) { - super(type, skipped, index, remapIndex); - this.fieldName = fieldName; + public CapturedParamInfo(@NotNull CapturedParamDesc desc, boolean skipped, int index, StackValue remapIndex) { + super(desc.getType(), skipped, index, remapIndex); + this.desc = desc; } public String getFieldName() { - return fieldName; + return desc.getFieldName(); } @Override @@ -54,22 +57,17 @@ public class CapturedParamInfo extends ParameterInfo { this.shift = shift; } - public LambdaInfo getRecapturedFrom() { - return recapturedFrom; - } - - public void setRecapturedFrom(LambdaInfo recapturedFrom) { - this.recapturedFrom = recapturedFrom; - } - public CapturedParamInfo newIndex(int newIndex) { return clone(newIndex, getRemapValue()); } public CapturedParamInfo clone(int newIndex, StackValue newRamapIndex) { - CapturedParamInfo capturedParamInfo = new CapturedParamInfo(fieldName, type, isSkipped, newIndex, newRamapIndex); + CapturedParamInfo capturedParamInfo = new CapturedParamInfo(desc, isSkipped, newIndex, newRamapIndex); capturedParamInfo.setLambda(lambda); - capturedParamInfo.setRecapturedFrom(recapturedFrom); return capturedParamInfo; } + + public String getContainingLambdaName() { + return desc.getContainingLambda().getType().getInternalName(); + } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamOwner.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamOwner.java new file mode 100644 index 00000000000..bec5136eeb4 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/CapturedParamOwner.java @@ -0,0 +1,24 @@ +/* + * 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.asm4.Type; + +public interface CapturedParamOwner { + + Type getType(); +} diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlineCodegen.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlineCodegen.java index 24e2647d4a1..1d4fc21f218 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlineCodegen.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlineCodegen.java @@ -213,11 +213,11 @@ public class InlineCodegen implements ParentCodegenAware, CallGenerator { codegen.getInlineNameGenerator().subGenerator(functionDescriptor.getName().asString()), codegen.getContext(), call, Collections.emptyMap(), false, false); - MethodInliner inliner = new MethodInliner(node, parameters, info, null, new LambdaFieldRemapper(null, null, parameters), isSameModule); //with captured + MethodInliner inliner = new MethodInliner(node, parameters, info, new LambdaFieldRemapper(null, null, parameters), isSameModule, "InlineCodegenRoot " + call.getCallElement()); //with captured VarRemapper.ParamRemapper remapper = new VarRemapper.ParamRemapper(parameters, initialFrameSize); - return inliner.doInline(codegen.v, remapper); + return inliner.doInline(codegen.v, remapper, new LambdaFieldRemapper(null, null, parameters)); } private void generateClosuresBodies() { @@ -451,7 +451,7 @@ public class InlineCodegen implements ParentCodegenAware, CallGenerator { StringWriter sw = new StringWriter(); p.print(new PrintWriter(sw)); sw.flush(); - return node.name + ": \n " + sw.getBuffer().toString(); + return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString(); } private static String descriptorName(DeclarationDescriptor descriptor) { diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlinedLambdaRemapper.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlinedLambdaRemapper.java new file mode 100644 index 00000000000..54dbec68898 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InlinedLambdaRemapper.java @@ -0,0 +1,62 @@ +/* + * 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.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.asm4.tree.FieldInsnNode; + +import java.util.Collection; + +public class InlinedLambdaRemapper extends LambdaFieldRemapper { + + public InlinedLambdaRemapper( + @NotNull String lambdaInternalName, + @NotNull LambdaFieldRemapper parent, + @NotNull Parameters methodParams + ) { + super(lambdaInternalName, parent, methodParams); + } + + + @Override + public void addCapturedFields( + LambdaInfo lambdaInfo, ParametersBuilder builder + ) { + parent.addCapturedFields(lambdaInfo, builder); + } + + + @Override + @Nullable + public CapturedParamInfo findField( + @NotNull FieldInsnNode fieldInsnNode, + @NotNull Collection captured + ) { + return parent.findField(fieldInsnNode, captured); + } + + @Override + public LambdaFieldRemapper getParent() { + return parent.getParent(); + } + + @Override + public boolean isRoot() { + return parent.isRoot(); + } +} diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InvokeCall.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InvokeCall.java index 76e0760a8c8..1eb0d3f9ae9 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/InvokeCall.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/InvokeCall.java @@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable; class InvokeCall { - public final int index; + private final int index; public final LambdaInfo lambdaInfo; diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaFieldRemapper.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaFieldRemapper.java index 2fc0329dc23..9c69db1a338 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaFieldRemapper.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaFieldRemapper.java @@ -22,16 +22,14 @@ import org.jetbrains.asm4.Opcodes; import org.jetbrains.asm4.tree.AbstractInsnNode; import org.jetbrains.asm4.tree.FieldInsnNode; import org.jetbrains.asm4.tree.MethodNode; -import org.jetbrains.asm4.tree.VarInsnNode; +import org.jetbrains.jet.codegen.StackValue; import java.util.Collection; import java.util.List; -import static org.jetbrains.jet.codegen.inline.MethodInliner.getPreviousNoLabelNoLine; - public class LambdaFieldRemapper { - private String lambdaInternalName; + private final String lambdaInternalName; protected LambdaFieldRemapper parent; @@ -43,42 +41,58 @@ public class LambdaFieldRemapper { params = methodParams; } - public AbstractInsnNode doTransform(MethodNode node, FieldInsnNode fieldInsnNode, CapturedParamInfo capturedField) { - AbstractInsnNode loadThis = getPreviousThis(fieldInsnNode); - - int opcode = fieldInsnNode.getOpcode() == Opcodes.GETFIELD ? capturedField.getType().getOpcode(Opcodes.ILOAD) : capturedField.getType().getOpcode(Opcodes.ISTORE); - VarInsnNode newInstruction = new VarInsnNode(opcode, capturedField.getIndex()); - - node.instructions.remove(loadThis); //remove aload this - node.instructions.insertBefore(fieldInsnNode, newInstruction); - node.instructions.remove(fieldInsnNode); //remove aload field - - return newInstruction; + public void addCapturedFields(LambdaInfo lambdaInfo, ParametersBuilder builder) { + for (CapturedParamInfo info : lambdaInfo.getCapturedVars()) { + builder.addCapturedParam(info, info); + } } - protected static AbstractInsnNode getPreviousThis(FieldInsnNode fieldInsnNode) { - AbstractInsnNode loadThis = getPreviousNoLabelNoLine(fieldInsnNode); - - assert loadThis.getType() == AbstractInsnNode.VAR_INSN || loadThis.getType() == AbstractInsnNode.FIELD_INSN : - "Field access instruction should go after load this but goes after " + loadThis; - assert loadThis.getOpcode() == Opcodes.ALOAD || loadThis.getOpcode() == Opcodes.GETSTATIC : - "This should be loaded by ALOAD or GETSTATIC but " + loadThis.getOpcode(); - return loadThis; + public boolean canProcess(@NotNull String fieldOwner) { + return fieldOwner.equals(getLambdaInternalName()); } - public List markRecaptured(List originalCaptured, LambdaInfo lambda) { - return originalCaptured; + public AbstractInsnNode transformIfNeeded( + @NotNull List capturedFieldAccess, + int currentInstruction, + @NotNull MethodNode node + ) { + if (capturedFieldAccess.size() == 1) { + //just aload + return null; + } + + AbstractInsnNode transformed = null; + boolean checkParent = !isRoot() && currentInstruction < capturedFieldAccess.size() - 1; + if (checkParent) { + transformed = parent.transformIfNeeded(capturedFieldAccess, currentInstruction + 1, node); + } + + if (transformed == null) { + //if parent couldn't transform + FieldInsnNode insnNode = (FieldInsnNode) capturedFieldAccess.get(currentInstruction); + if (canProcess(insnNode.owner)) { + insnNode.name = "$$$" + insnNode.name; + insnNode.setOpcode(Opcodes.GETSTATIC); + + for (int i = 0; i < currentInstruction; i++) { + AbstractInsnNode toRemove = capturedFieldAccess.get(i); + node.instructions.remove(toRemove); + } + transformed = capturedFieldAccess.get(capturedFieldAccess.size() - 1); + } + } + + return transformed; } - - public boolean canProcess(@NotNull String owner, @NotNull String currentLambdaType) { - return owner.equals(currentLambdaType); + public CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode) { + return findField(fieldInsnNode, params.getCaptured()); } @Nullable public CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode, @NotNull Collection captured) { for (CapturedParamInfo valueDescriptor : captured) { - if (valueDescriptor.getFieldName().equals(fieldInsnNode.name)) { + if (valueDescriptor.getFieldName().equals(fieldInsnNode.name) && fieldInsnNode.owner.equals(valueDescriptor.getContainingLambdaName())) { return valueDescriptor; } } @@ -88,6 +102,7 @@ public class LambdaFieldRemapper { public LambdaFieldRemapper getParent() { return parent; } + public String getLambdaInternalName() { return lambdaInternalName; } @@ -96,16 +111,9 @@ public class LambdaFieldRemapper { return parent == null; } - public boolean shouldPatch(@NotNull FieldInsnNode node) { - return !isRoot() && parent.shouldPatch(node); - } - - @NotNull - public AbstractInsnNode patch(@NotNull FieldInsnNode field, @NotNull MethodNode node) { - //parent is inlined so we need patch instruction chain - if (!isRoot()){ - return parent.patch(field, node); - } - throw new IllegalStateException("Should be invoked"); + @Nullable + public StackValue getFieldForInline(@NotNull FieldInsnNode node, @Nullable StackValue prefix) { + CapturedParamInfo field = MethodInliner.findCapturedField(node, this); + return field.getRemapValue(); } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaInfo.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaInfo.java index 5d42649aaa1..f8d00cc72be 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaInfo.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaInfo.java @@ -37,7 +37,7 @@ import static org.jetbrains.jet.codegen.binding.CodegenBinding.CLOSURE; import static org.jetbrains.jet.codegen.binding.CodegenBinding.anonymousClassForFunction; import static org.jetbrains.jet.codegen.binding.CodegenBinding.asmTypeForAnonymousClass; -public class LambdaInfo { +public class LambdaInfo implements CapturedParamOwner { public final JetFunctionLiteralExpression expression; @@ -123,8 +123,8 @@ public class LambdaInfo { } @NotNull - public static CapturedParamInfo getCapturedParamInfo(@NotNull EnclosedValueDescriptor descriptor, int index) { - return new CapturedParamInfo(descriptor.getFieldName(), descriptor.getType(), false, index, -1); + public CapturedParamInfo getCapturedParamInfo(@NotNull EnclosedValueDescriptor descriptor, int index) { + return new CapturedParamInfo(CapturedParamDesc.createDesc(this, descriptor.getFieldName(), descriptor.getType()), false, index, -1); } public void setParamOffset(int paramOffset) { @@ -149,11 +149,8 @@ public class LambdaInfo { builder.addNextParameter(type, false, null); } + remapper.addCapturedFields(this, builder); - List infos = remapper.markRecaptured(getCapturedVars(), this); - for (CapturedParamInfo info : infos) { - builder.addCapturedParam(info.getFieldName(), info.getType(), info.isSkipped, info); - } return builder.buildParameters(); } @@ -164,4 +161,9 @@ public class LambdaInfo { } return size; } + + @Override + public Type getType() { + return closureClassType; + } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaTransformer.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaTransformer.java index 29312c2c787..28c646edc7f 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaTransformer.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaTransformer.java @@ -27,10 +27,7 @@ import org.jetbrains.asm4.tree.FieldInsnNode; import org.jetbrains.asm4.tree.MethodNode; import org.jetbrains.asm4.tree.VarInsnNode; import org.jetbrains.jet.OutputFile; -import org.jetbrains.jet.codegen.AsmUtil; -import org.jetbrains.jet.codegen.ClassBuilder; -import org.jetbrains.jet.codegen.ClosureCodegen; -import org.jetbrains.jet.codegen.FieldInfo; +import org.jetbrains.jet.codegen.*; import org.jetbrains.jet.codegen.state.GenerationState; import org.jetbrains.jet.codegen.state.JetTypeMapper; @@ -128,11 +125,14 @@ public class LambdaTransformer { Parameters parameters = getLambdaParameters(builder, invocation); MethodVisitor invokeVisitor = newMethod(classBuilder, invoke); - RegeneratedLambdaFieldRemapper - remapper = new RegeneratedLambdaFieldRemapper(oldLambdaType.getInternalName(), newLambdaType.getInternalName(), parameters, invocation.getCapturedLambdasToInline(), - parentRemapper); - MethodInliner inliner = new MethodInliner(invoke, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")), oldLambdaType, - remapper, isSameModule); + + RegeneratedLambdaFieldRemapper remapper = + new RegeneratedLambdaFieldRemapper(oldLambdaType.getInternalName(), newLambdaType.getInternalName(), + parameters, invocation.getCapturedLambdasToInline(), + parentRemapper); + + MethodInliner inliner = new MethodInliner(invoke, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")), + remapper, isSameModule, "Transformer for " + invocation.getOwnerInternalName()); InlineResult result = inliner.doInline(invokeVisitor, new VarRemapper.ParamRemapper(parameters, 0), remapper, false); invokeVisitor.visitMaxs(-1, -1); @@ -198,16 +198,23 @@ public class LambdaTransformer { ); } - private static void extractParametersMapping(MethodNode constructor, ParametersBuilder builder, ConstructorInvocation invocation) { + private void extractParametersMapping(MethodNode constructor, ParametersBuilder builder, final ConstructorInvocation invocation) { Map indexToLambda = invocation.getLambdasToInline(); AbstractInsnNode cur = constructor.instructions.getFirst(); cur = cur.getNext(); //skip super call List capturedLambdas = new ArrayList(); //captured var of inlined parameter + CapturedParamOwner owner = new CapturedParamOwner() { + @Override + public Type getType() { + return Type.getObjectType(invocation.getOwnerInternalName()); + } + }; + while (cur != null) { if (cur.getType() == AbstractInsnNode.FIELD_INSN) { FieldInsnNode fieldNode = (FieldInsnNode) cur; - CapturedParamInfo info = builder.addCapturedParam(fieldNode.name, Type.getType(fieldNode.desc), false, null); + CapturedParamInfo info = builder.addCapturedParam(fieldNode.name, Type.getType(fieldNode.desc), false, null, owner); assert fieldNode.getPrevious() instanceof VarInsnNode : "Previous instruction should be VarInsnNode but was " + fieldNode.getPrevious(); VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious(); @@ -227,8 +234,13 @@ public class LambdaTransformer { List allRecapturedParameters = new ArrayList(); for (LambdaInfo info : capturedLambdas) { for (CapturedParamInfo var : info.getCapturedVars()) { - CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(getNewFieldName(var.getFieldName()), var.getType(), true, var); - recapturedParamInfo.setRecapturedFrom(info); + CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(getNewFieldName(var.getFieldName()), var.getType(), var.isSkipped, var, info); + StackValue composed = StackValue.composed(StackValue.local(0, oldLambdaType), + StackValue.field(var.getType(), + oldLambdaType, /*TODO owner type*/ + getNewFieldName(var.getFieldName()), false) + ); + recapturedParamInfo.setRemapValue(composed); allRecapturedParameters.add(var); } capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info); diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/MethodInliner.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/MethodInliner.java index 95ea4a090fc..00639cd75d5 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/MethodInliner.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/MethodInliner.java @@ -3,7 +3,6 @@ package org.jetbrains.jet.codegen.inline; import com.google.common.collect.Lists; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.asm4.Label; import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.Opcodes; @@ -13,7 +12,6 @@ import org.jetbrains.asm4.commons.Method; import org.jetbrains.asm4.commons.RemappingMethodAdapter; import org.jetbrains.asm4.tree.*; import org.jetbrains.asm4.tree.analysis.*; -import org.jetbrains.jet.codegen.AsmUtil; import org.jetbrains.jet.codegen.ClosureCodegen; import org.jetbrains.jet.codegen.StackValue; import org.jetbrains.jet.codegen.state.JetTypeMapper; @@ -21,7 +19,8 @@ import org.jetbrains.jet.utils.UtilsPackage; import java.util.*; -import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.*; +import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.isInvokeOnLambda; +import static org.jetbrains.jet.codegen.inline.InlineCodegenUtil.isLambdaConstructorCall; public class MethodInliner { @@ -31,13 +30,12 @@ public class MethodInliner { private final InliningContext inliningContext; - @Nullable - private final Type lambdaType; - - private final LambdaFieldRemapper lambdaFieldRemapper; + private final LambdaFieldRemapper nodeRemapper; private final boolean isSameModule; + private final String errorPrefix; + private final JetTypeMapper typeMapper; private final List invokeCalls = new ArrayList(); @@ -60,23 +58,23 @@ public class MethodInliner { @NotNull MethodNode node, @NotNull Parameters parameters, @NotNull InliningContext parent, - @Nullable Type lambdaType, - LambdaFieldRemapper lambdaFieldRemapper, - boolean isSameModule + @NotNull LambdaFieldRemapper nodeRemapper, + boolean isSameModule, + @NotNull String errorPrefix ) { this.node = node; this.parameters = parameters; this.inliningContext = parent; - this.lambdaType = lambdaType; - this.lambdaFieldRemapper = lambdaFieldRemapper; + this.nodeRemapper = nodeRemapper; this.isSameModule = isSameModule; + this.errorPrefix = errorPrefix; this.typeMapper = parent.state.getTypeMapper(); this.result = InlineResult.create(); } - public InlineResult doInline(MethodVisitor adapter, VarRemapper.ParamRemapper remapper) { - return doInline(adapter, remapper, lambdaFieldRemapper, true); + public InlineResult doInline(MethodVisitor adapter, VarRemapper.ParamRemapper remapper, LambdaFieldRemapper fieldRemapper) { + return doInline(adapter, remapper, fieldRemapper, true); } public InlineResult doInline( @@ -85,27 +83,30 @@ public class MethodInliner { LambdaFieldRemapper capturedRemapper, boolean remapReturn ) { //analyze body - MethodNode transformedNode = node; - try { - transformedNode = markPlacesForInlineAndRemoveInlinable(transformedNode); - } - catch (AnalyzerException e) { - throw UtilsPackage.rethrow(e); - } + MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node); transformedNode = doInline(transformedNode, capturedRemapper); removeClosureAssertions(transformedNode); transformedNode.instructions.resetLabels(); Label end = new Label(); - RemapVisitor visitor = new RemapVisitor(adapter, end, remapper, remapReturn); - transformedNode.accept(visitor); + RemapVisitor visitor = new RemapVisitor(adapter, end, remapper, remapReturn, nodeRemapper); + try { + transformedNode.accept(visitor); + } catch (Exception e) { + String text = InlineCodegen.getNodeText(transformedNode); + + throw new RuntimeException(errorPrefix + ": couldn't inline method call '" + + transformedNode + + "\ncause: " + + text, e); + } visitor.visitLabel(end); return result; } - private MethodNode doInline(MethodNode node, final LambdaFieldRemapper capturedRemapper) { + private MethodNode doInline(MethodNode node, final LambdaFieldRemapper capturedRemapper) { final Deque currentInvokes = new LinkedList(invokeCalls); @@ -164,15 +165,20 @@ public class MethodInliner { Parameters lambdaParameters = info.addAllParameters(capturedRemapper); + InlinedLambdaRemapper newCapturedRemapper = + new InlinedLambdaRemapper(info.getLambdaClassType().getInternalName(), capturedRemapper, lambdaParameters); + + LambdaFieldRemapper lambdaFieldRemapper = + new LambdaFieldRemapper(info.getLambdaClassType().getInternalName(), capturedRemapper, lambdaParameters); + setInlining(true); MethodInliner inliner = new MethodInliner(info.getNode(), lambdaParameters, inliningContext.subInlineLambda(info), - info.getLambdaClassType(), - capturedRemapper, true /*cause all calls in same module as lambda*/ - ); + lambdaFieldRemapper, true /*cause all calls in same module as lambda*/, + "Lambda inlining " + info.getLambdaClassType().getInternalName()); VarRemapper.ParamRemapper remapper = new VarRemapper.ParamRemapper(lambdaParameters, valueParamShift); - InlineResult lambdaResult = inliner.doInline(this.mv, remapper);//TODO add skipped this and receiver + InlineResult lambdaResult = inliner.doInline(this.mv, remapper, newCapturedRemapper);//TODO add skipped this and receiver result.addAllClassesToRemove(lambdaResult); //return value boxing/unboxing @@ -185,22 +191,8 @@ public class MethodInliner { assert invocation != null : " call not corresponds to new call" + owner + " " + name; if (invocation.shouldRegenerate()) { //put additional captured parameters on stack - List recaptured = invocation.getAllRecapturedParameters(); - List contextCaptured = MethodInliner.this.parameters.getCaptured(); - for (CapturedParamInfo capturedParamInfo : recaptured) { - CapturedParamInfo result = null; - for (CapturedParamInfo info : contextCaptured) { - //TODO more sophisticated check - if (info.getFieldName().equals(capturedParamInfo.getFieldName())) { - result = info; - } - } - if (result == null) { - throw new UnsupportedOperationException( - "Unsupported operation: could not transform non-inline lambda inside inlined one: " + - owner + "." + name); - } - super.visitVarInsn(capturedParamInfo.getType().getOpcode(Opcodes.ILOAD), result.getIndex()); + for (CapturedParamInfo capturedParamInfo : invocation.getAllRecapturedParameters()) { + visitFieldInsn(Opcodes.GETSTATIC, capturedParamInfo.getContainingLambdaName(), "$$$" + capturedParamInfo.getFieldName(), capturedParamInfo.getType().getDescriptor()); } super.visitMethodInsn(opcode, invocation.getNewLambdaType().getInternalName(), name, invocation.getNewConstructorDescriptor()); invocation = null; @@ -212,6 +204,7 @@ public class MethodInliner { super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc); } } + }; node.accept(inliner); @@ -219,8 +212,18 @@ public class MethodInliner { return resultNode; } - public void merge() { + public CapturedParamInfo findCapturedField(FieldInsnNode node) { + return findCapturedField(node, nodeRemapper); + } + @NotNull + public static CapturedParamInfo findCapturedField(FieldInsnNode node, LambdaFieldRemapper fieldRemapper) { + FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc); + CapturedParamInfo field = fieldRemapper.findField(fin); + if (field == null) { + throw new IllegalStateException("Couldn't find captured field " + node.owner + "." + node.name + " in " + fieldRemapper.getLambdaInternalName()); + } + return field; } @NotNull @@ -272,11 +275,21 @@ public class MethodInliner { } @NotNull - protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node) throws AnalyzerException { + protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node) { node = prepareNode(node); Analyzer analyzer = new Analyzer(new SourceInterpreter()); - Frame[] sources = analyzer.analyze("fake", node); + Frame[] sources; + try { + sources = analyzer.analyze("fake", node); + } catch (AnalyzerException e) { + String text = InlineCodegen.getNodeText(node); + + throw new RuntimeException("Couldn't inline method call '" + + node + + "\ncause: " + + text, e); + } AbstractInsnNode cur = node.instructions.getFirst(); int index = 0; @@ -301,15 +314,11 @@ public class MethodInliner { if (sourceValue.insns.size() == 1) { AbstractInsnNode insnNode = sourceValue.insns.iterator().next(); - if (insnNode.getType() == AbstractInsnNode.VAR_INSN) { - assert insnNode.getOpcode() == Opcodes.ALOAD : insnNode.toString(); - varIndex = ((VarInsnNode) insnNode).var; - lambdaInfo = getLambda(varIndex); - if (lambdaInfo != null) { - //remove inlinable access - node.instructions.remove(insnNode); - } + lambdaInfo = getLambdaIfExists(insnNode); + if (lambdaInfo != null) { + //remove inlinable access + node.instructions.remove(insnNode); } } @@ -323,13 +332,10 @@ public class MethodInliner { SourceValue sourceValue = frame.getStack(paramStart + i); if (sourceValue.insns.size() == 1) { AbstractInsnNode insnNode = sourceValue.insns.iterator().next(); - if (insnNode.getOpcode() == Opcodes.ALOAD) { - int varIndex = ((VarInsnNode) insnNode).var; - LambdaInfo lambdaInfo = getLambda(varIndex); - if (lambdaInfo != null) { - lambdaMapping.put(i, lambdaInfo); - node.instructions.remove(insnNode); - } + LambdaInfo lambdaInfo = getLambdaIfExists(insnNode); + if (lambdaInfo != null) { + lambdaMapping.put(i, lambdaInfo); + node.instructions.remove(insnNode); } } } @@ -366,11 +372,21 @@ public class MethodInliner { return node; } - @Nullable - public LambdaInfo getLambda(int index) { - if (index < parameters.totalSize()) { - return parameters.get(index).getLambda(); + public LambdaInfo getLambdaIfExists(AbstractInsnNode insnNode) { + if (insnNode.getOpcode() == Opcodes.ALOAD) { + int varIndex = ((VarInsnNode) insnNode).var; + if (varIndex < parameters.totalSize()) { + return parameters.get(varIndex).getLambda(); + } + } else if (insnNode instanceof FieldInsnNode) { + FieldInsnNode fieldInsnNode = (FieldInsnNode) insnNode; + if (fieldInsnNode.name.startsWith("$$$")) { + CapturedParamInfo field = findCapturedField(fieldInsnNode); + return field.getLambda(); + + } } + return null; } @@ -398,43 +414,43 @@ public class MethodInliner { } private void transformCaptured(@NotNull MethodNode node) { - if (lambdaType == null) { + if (nodeRemapper.isRoot()) { return; } - //remove all this and shift all variables to captured ones size + //encode all captured variable chains - ALOAD 0 ALOAD this$0 GETFIELD $captured - to GETFIELD $$$$captured AbstractInsnNode cur = node.instructions.getFirst(); while (cur != null) { - if (cur.getType() == AbstractInsnNode.FIELD_INSN) { - FieldInsnNode fieldInsnNode = (FieldInsnNode) cur; - //TODO check closure - if (lambdaFieldRemapper.canProcess(fieldInsnNode.owner, lambdaType.getInternalName())) { - CapturedParamInfo result = this.lambdaFieldRemapper.findField(fieldInsnNode, parameters.getCaptured()); - - if (result == null) { - throw new UnsupportedOperationException("Coudn't find field " + - fieldInsnNode.owner + - "." + - fieldInsnNode.name + - " (" + - fieldInsnNode.desc + - ") in captured vars of " + lambdaType); + if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) { + if (((VarInsnNode) cur).var == 0) { + List accessChain = getCapturedFieldAccessChain((VarInsnNode) cur); + AbstractInsnNode insnNode = nodeRemapper.transformIfNeeded(accessChain, 1, node); + if (insnNode != null) { + cur = insnNode; } - - if (result.isSkipped()) { - //lambda class transformation: skip captured this - } else { - cur = this.lambdaFieldRemapper.doTransform(node, fieldInsnNode, result); - } - } - else if (lambdaFieldRemapper.shouldPatch(fieldInsnNode)) { - cur = lambdaFieldRemapper.patch(fieldInsnNode, node); } } cur = cur.getNext(); } } + @NotNull + public static List getCapturedFieldAccessChain(@NotNull VarInsnNode node) { + List fieldAccessChain = new ArrayList(); + fieldAccessChain.add(node); + AbstractInsnNode next = node.getNext(); + while (next != null && next instanceof FieldInsnNode) { + fieldAccessChain.add(next); + if("this$0".equals(((FieldInsnNode) next).name)) { + next = next.getNext(); + } else { + break; + } + } + + return fieldAccessChain; + } + public static AbstractInsnNode getPreviousNoLabelNoLine(AbstractInsnNode cur) { AbstractInsnNode prev = cur.getPrevious(); while (prev.getType() == AbstractInsnNode.LABEL || prev.getType() == AbstractInsnNode.LINE) { diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/ParametersBuilder.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/ParametersBuilder.java index 89d40be7106..633eb3a1bdb 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/ParametersBuilder.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/ParametersBuilder.java @@ -16,6 +16,7 @@ package org.jetbrains.jet.codegen.inline; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.asm4.Type; @@ -46,9 +47,29 @@ public class ParametersBuilder { return this; } - public CapturedParamInfo addCapturedParam(String fieldName, Type type, boolean skipped, @Nullable ParameterInfo original) { - return addCapturedParameter(new CapturedParamInfo(fieldName, type, skipped, nextCaptured, - original != null ? original.getIndex() : -1)); + public CapturedParamInfo addCapturedParam( + @NotNull CapturedParamInfo param, + @Nullable CapturedParamInfo originalField + ) { + CapturedParamInfo info = new CapturedParamInfo(param.desc, param.isSkipped, nextCaptured, originalField != null ? originalField.getIndex() : -1); + info.setLambda(param.getLambda()); + return addCapturedParameter(info); + } + + public CapturedParamInfo addCapturedParam( + String fieldName, + Type type, + boolean skipped, + @Nullable ParameterInfo original, + CapturedParamOwner containingLambda + ) { + CapturedParamInfo info = + new CapturedParamInfo(CapturedParamDesc.createDesc(containingLambda, fieldName, type), skipped, nextCaptured, + original != null ? original.getIndex() : -1); + if (original != null) { + info.setLambda(original.getLambda()); + } + return addCapturedParameter(info); } private void addParameter(ParameterInfo info) { diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/RegeneratedLambdaFieldRemapper.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/RegeneratedLambdaFieldRemapper.java index 1431c55cab3..fa800bc95c5 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/RegeneratedLambdaFieldRemapper.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/RegeneratedLambdaFieldRemapper.java @@ -20,9 +20,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.asm4.Opcodes; import org.jetbrains.asm4.Type; -import org.jetbrains.asm4.tree.AbstractInsnNode; import org.jetbrains.asm4.tree.FieldInsnNode; -import org.jetbrains.asm4.tree.MethodNode; +import org.jetbrains.jet.codegen.StackValue; import java.util.Collection; import java.util.List; @@ -53,52 +52,35 @@ public class RegeneratedLambdaFieldRemapper extends LambdaFieldRemapper { } @Override - public AbstractInsnNode doTransform( - MethodNode node, FieldInsnNode fieldInsnNode, CapturedParamInfo capturedField - ) { - boolean isRecaptured = isRecapturedLambdaType(fieldInsnNode.owner); - - if (!isRecaptured && capturedField.getLambda() != null) { - //strict inlining - return super.doTransform(node, fieldInsnNode, capturedField); - } - - AbstractInsnNode loadThis = getPreviousThis(fieldInsnNode); - - int opcode = Opcodes.GETSTATIC; - - String descriptor = Type.getObjectType(newOwnerType).getDescriptor(); - - //HACK: it would be reverted again to ALOAD 0 later - FieldInsnNode thisStub = new FieldInsnNode(opcode, newOwnerType, "$$$this", descriptor); - - node.instructions.insertBefore(loadThis, thisStub); - node.instructions.remove(loadThis); - - fieldInsnNode.owner = newOwnerType; - fieldInsnNode.name = isRecaptured || capturedField.getRecapturedFrom() != null ? LambdaTransformer.getNewFieldName(capturedField.getFieldName()) : capturedField.getFieldName(); - - return fieldInsnNode; - } - - @Override - public List markRecaptured(List originalCaptured, LambdaInfo lambda) { - List captured = parameters.getCaptured(); - for (CapturedParamInfo originalField : originalCaptured) { - for (CapturedParamInfo capturedParamInfo : captured) { - if (capturedParamInfo.getRecapturedFrom() == lambda) { - if (capturedParamInfo.getFieldName().equals(LambdaTransformer.getNewFieldName(originalField.getFieldName()))) { - originalField.setRecapturedFrom(lambda);//just mark recaptured + public void addCapturedFields(LambdaInfo lambdaInfo, ParametersBuilder builder) { + if (canProcess(lambdaInfo.getLambdaClassType().getInternalName())) { + List captured = parameters.getCaptured(); + for (CapturedParamInfo originalField : lambdaInfo.getCapturedVars()) { + CapturedParamInfo foundField = null; + for (CapturedParamInfo capturedParamInfo : captured) { + if (capturedParamInfo.getContainingLambdaName().equals(originalField.getContainingLambdaName())) { + if (capturedParamInfo.getFieldName().equals(LambdaTransformer.getNewFieldName(originalField.getFieldName()))) { + foundField = originalField; + break; + } } } + + if (foundField == null) { + throw new IllegalStateException("Captured parameter should exists in outer context: " + originalField.getFieldName()); + } + + CapturedParamInfo info = builder.addCapturedParam(foundField, foundField); } + } else { + //in case when inlining lambda into another one inside inline function + parent.addCapturedFields(lambdaInfo, builder); } - return originalCaptured; } @Override - public boolean canProcess(String owner, String currentLambdaType) { - return super.canProcess(owner, currentLambdaType) || isRecapturedLambdaType(owner); + public boolean canProcess(@NotNull String fieldOwner) { + return super.canProcess(fieldOwner) || isRecapturedLambdaType(fieldOwner); } private boolean isRecapturedLambdaType(String owner) { @@ -107,8 +89,11 @@ public class RegeneratedLambdaFieldRemapper extends LambdaFieldRemapper { @Nullable @Override - public CapturedParamInfo findField(FieldInsnNode fieldInsnNode, Collection captured) { - if (isRecapturedLambdaType(fieldInsnNode.owner)) { + public CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode, @NotNull Collection captured) { + boolean searchInParent = !canProcess(fieldInsnNode.owner); + if (searchInParent) { + return parent.findField(fieldInsnNode); + } else if (isRecapturedLambdaType(fieldInsnNode.owner)) { LambdaInfo info = recapturedLambdas.get(fieldInsnNode.owner); return super.findField(fieldInsnNode, info.getCapturedVars()); } @@ -117,39 +102,42 @@ public class RegeneratedLambdaFieldRemapper extends LambdaFieldRemapper { } } - @Override - public boolean shouldPatch(@NotNull FieldInsnNode node) { - //parent is inlined so we need patch instruction chain - return shouldPatchByMe(node) || parent.shouldPatch(node); + @Nullable + public CapturedParamInfo findFieldInMyCaptured(@NotNull FieldInsnNode fieldInsnNode) { + if (isRecapturedLambdaType(fieldInsnNode.owner)) { + LambdaInfo info = recapturedLambdas.get(fieldInsnNode.owner); + return super.findField(fieldInsnNode, info.getCapturedVars()); + } + else { + return super.findField(fieldInsnNode, parameters.getCaptured()); + } } - private boolean shouldPatchByMe(@NotNull FieldInsnNode node) { - //parent is inlined so we need patch instruction chain - //aloading inlined this - return parent.isRoot() && node.owner.equals(getLambdaInternalName()) && node.name.equals("this$0"); - } - - @NotNull + @Nullable @Override - public AbstractInsnNode patch(@NotNull FieldInsnNode fieldInsnNode, @NotNull MethodNode node) { - if (!shouldPatchByMe(fieldInsnNode)) { - return parent.patch(fieldInsnNode, node); - } - //parent is inlined so we need patch instruction chain - AbstractInsnNode previous = fieldInsnNode.getPrevious(); - AbstractInsnNode nextInstruction = fieldInsnNode.getNext(); - if (!(nextInstruction instanceof FieldInsnNode)) { - throw new IllegalStateException( - "Instruction after inlined one should be field access: " + nextInstruction); - } - if (!(previous instanceof FieldInsnNode)) { - throw new IllegalStateException("Instruction before inlined one should be field access: " + previous); - } - FieldInsnNode next = (FieldInsnNode) nextInstruction; - node.instructions.remove(next.getPrevious()); - next.owner = Type.getType(((FieldInsnNode) previous).desc).getInternalName(); - next.name = LambdaTransformer.getNewFieldName(next.name); + public StackValue getFieldForInline(@NotNull FieldInsnNode node, @Nullable StackValue prefix) { + FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc); + CapturedParamInfo field = findFieldInMyCaptured(fin); - return next; + boolean searchInParent = false; + if (field == null) { + field = findFieldInMyCaptured(new FieldInsnNode(Opcodes.GETSTATIC, oldOwnerType, "this$0", Type.getObjectType(parent.getLambdaInternalName()).getDescriptor())); + searchInParent = true; + if (field == null) { + throw new IllegalStateException("Could find captured this " + getLambdaInternalName()); + } + } + + String newName = field.getContainingLambdaName().equals(getLambdaInternalName()) + ? field.getFieldName() + : LambdaTransformer.getNewFieldName(field.getFieldName()); + StackValue result = + StackValue.composed(prefix == null ? StackValue.local(0, Type.getObjectType(getLambdaInternalName())) : prefix, + StackValue.field(field.getType(), + Type.getObjectType(newOwnerType), /*TODO owner type*/ + newName, false) + ); + + return searchInParent ? parent.getFieldForInline(node, result) : result; } } diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/RemapVisitor.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/RemapVisitor.java index 0c48282f006..8b354a0b9b7 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/RemapVisitor.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/RemapVisitor.java @@ -21,6 +21,8 @@ import org.jetbrains.asm4.Label; import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.Opcodes; import org.jetbrains.asm4.commons.InstructionAdapter; +import org.jetbrains.asm4.tree.FieldInsnNode; +import org.jetbrains.jet.codegen.StackValue; public class RemapVisitor extends InstructionAdapter { @@ -29,12 +31,14 @@ public class RemapVisitor extends InstructionAdapter { private final VarRemapper remapper; private final boolean remapReturn; + private LambdaFieldRemapper nodeRemapper; - protected RemapVisitor(MethodVisitor mv, Label end, VarRemapper.ParamRemapper remapper, boolean remapReturn) { + protected RemapVisitor(MethodVisitor mv, Label end, VarRemapper.ParamRemapper remapper, boolean remapReturn, LambdaFieldRemapper nodeRemapper) { super(InlineCodegenUtil.API, mv); this.end = end; this.remapper = remapper; this.remapReturn = remapReturn; + this.nodeRemapper = nodeRemapper; } @Override @@ -57,6 +61,22 @@ public class RemapVisitor extends InstructionAdapter { remapper.visitVarInsn(opcode, var, new InstructionAdapter(mv)); } + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (name.startsWith("$$$")) { + if (nodeRemapper instanceof RegeneratedLambdaFieldRemapper || nodeRemapper.isRoot()) { + FieldInsnNode fin = new FieldInsnNode(opcode, owner, name, desc); + StackValue inline = nodeRemapper.getFieldForInline(fin, null); + inline.put(inline.type, this); + } else { + super.visitFieldInsn(opcode, owner, name, desc); + } + } + else { + super.visitFieldInsn(opcode, owner, name, desc); + } + } + @Override public void visitLocalVariable( String name, String desc, String signature, Label start, Label end, int index @@ -84,15 +104,6 @@ public class RemapVisitor extends InstructionAdapter { return null; } - @Override - public void visitFieldInsn(int opcode, String owner, String name, String desc) { - if (name.equals("$$$this")) { - super.visitVarInsn(Opcodes.ALOAD, 0); - } else { - super.visitFieldInsn(opcode, owner, name, desc); - } - } - //TODO not skip for lambdas @Override public void visitLineNumber(int line, Label start) { diff --git a/compiler/testData/codegen/boxInline/buildersAndLambdaCapturing/1.kt b/compiler/testData/codegen/boxInline/buildersAndLambdaCapturing/1.kt new file mode 100644 index 00000000000..0c4eb3d1ba0 --- /dev/null +++ b/compiler/testData/codegen/boxInline/buildersAndLambdaCapturing/1.kt @@ -0,0 +1,177 @@ +import builders.* + +inline fun testAllInline(f: () -> String) : String { + val args = array("1", "2", "3") + val result = + html { + val htmlVal = 0 + head { + title { +"XML encoding with Kotlin" } + } + body { + var bodyVar = 1 + h1 { +"XML encoding with Kotlin" } + p { +"this format can be used as an alternative markup to XML" } + + // an element with attributes and text content + a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } + + // mixed content + p { + +"This is some" + b { +"mixed" } + +"text. For more see the" + a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } + +"project" + } + p { +"some text" } + + // content generated from command-line arguments + p { + +"Command line arguments were:" + ul { + for (arg in args) + li { +arg; +"$htmlVal"; +"$bodyVar"; +"${f()}" } + } + } + } + } + + return result.toString()!! +} + +inline fun testHtmlNoInline(f: () -> String) : String { + val args = array("1", "2", "3") + val result = + htmlNoInline() { + val htmlVal = 0 + head { + title { +"XML encoding with Kotlin" } + } + body { + var bodyVar = 1 + h1 { +"XML encoding with Kotlin" } + p { +"this format can be used as an alternative markup to XML" } + + // an element with attributes and text content + a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } + + // mixed content + p { + +"This is some" + b { +"mixed" } + +"text. For more see the" + a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } + +"project" + } + p { +"some text" } + + // content generated from command-line arguments + p { + +"Command line arguments were:" + ul { + for (arg in args) + li { +arg; +"$htmlVal"; +"$bodyVar"; +"${f()}" } + } + } + } + } + + return result.toString()!! +} + +inline fun testBodyNoInline(f: () -> String) : String { + val args = array("1", "2", "3") + val result = + html { + val htmlVal = 0 + head { + title { +"XML encoding with Kotlin" } + } + bodyNoInline { + var bodyVar = 1 + h1 { +"XML encoding with Kotlin" } + p { +"this format can be used as an alternative markup to XML" } + + // an element with attributes and text content + a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } + + // mixed content + p { + +"This is some" + b { +"mixed" } + +"text. For more see the" + a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } + +"project" + } + p { +"some text" } + + // content generated from command-line arguments + p { + +"Command line arguments were:" + ul { + for (arg in args) + li { +arg; +"$htmlVal"; +"$bodyVar"; +"${f()}" } + } + } + } + } + + return result.toString()!! +} + +inline fun testBodyHtmlNoInline(f: () -> String) : String { + val args = array("1", "2", "3") + val result = + htmlNoInline { + val htmlVal = 0 + head { + title { +"XML encoding with Kotlin" } + } + bodyNoInline { + var bodyVar = 1 + h1 { +"XML encoding with Kotlin" } + p { +"this format can be used as an alternative markup to XML" } + + // an element with attributes and text content + a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } + + // mixed content + p { + +"This is some" + b { +"mixed" } + +"text. For more see the" + a(href = "http://jetbrains.com/kotlin") { +"Kotlin" } + +"project" + } + p { +"some text" } + + // content generated from command-line arguments + p { + +"Command line arguments were:" + ul { + for (arg in args) + li { +arg; +"$htmlVal"; +"$bodyVar"; +"${f()}" } + } + } + } + } + + return result.toString()!! +} + +fun box(): String { + var expected = testAllInline({"x"}); + print(expected + " " + testHtmlNoInline({"x"})) + + if (expected != testHtmlNoInline({"x"})) return "fail 1: ${testHtmlNoInline({"x"})}\nbut expected\n${expected} " + if (expected != testBodyNoInline({"x"})) return "fail 2: ${testBodyNoInline({"x"})}\nbut expected\n${expected} " + if (expected != testBodyHtmlNoInline({"x"})) return "fail 3: ${testBodyHtmlNoInline({"x"})}\nbut expected\n${expected} " + + var captured = "x" + if (expected != testHtmlNoInline({captured})) return "fail 4: ${testHtmlNoInline({captured})}\nbut expected\n${expected} " + if (expected != testBodyNoInline({captured})) return "fail 5: ${testBodyNoInline({captured})}\nbut expected\n${expected} " + if (expected != testBodyHtmlNoInline({captured})) return "fail 6: ${testBodyHtmlNoInline({captured})}\nbut expected\n${expected} " + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/buildersAndLambdaCapturing/2.kt b/compiler/testData/codegen/boxInline/buildersAndLambdaCapturing/2.kt new file mode 100644 index 00000000000..f2c29052b97 --- /dev/null +++ b/compiler/testData/codegen/boxInline/buildersAndLambdaCapturing/2.kt @@ -0,0 +1,108 @@ +package builders + +import java.util.ArrayList +import java.util.HashMap + +trait Element { + fun render(builder: StringBuilder, indent: String) + + override fun toString(): String { + val builder = StringBuilder() + render(builder, "") + return builder.toString() + } +} + +class TextElement(val text: String) : Element { + override fun render(builder: StringBuilder, indent: String) { + builder.append("$indent$text\n") + } +} + +abstract class Tag(val name: String) : Element { + val children = ArrayList() + val attributes = HashMap() + + inline protected fun initTag(tag: T, init: T.() -> Unit): T { + tag.init() + children.add(tag) + return tag + } + + override fun render(builder: StringBuilder, indent: String) { + builder.append("$indent<$name${renderAttributes()}>\n") + for (c in children) { + c.render(builder, indent + " ") + } + builder.append("$indent\n") + } + + private fun renderAttributes(): String? { + val builder = StringBuilder() + for (a in attributes.keySet()) { + builder.append(" $a=\"${attributes[a]}\"") + } + return builder.toString() + } +} + +abstract class TagWithText(name: String) : Tag(name) { + fun String.plus() { + children.add(TextElement(this)) + } +} + +class HTML() : TagWithText("html") { + inline fun head(init: Head.() -> Unit) = initTag(Head(), init) + + inline fun body(init: Body.() -> Unit) = initTag(Body(), init) + + fun bodyNoInline(init: Body.() -> Unit) = initTag(Body(), init) +} + +class Head() : TagWithText("head") { + inline fun title(init: Title.() -> Unit) = initTag(Title(), init) +} + +class Title() : TagWithText("title") + +abstract class BodyTag(name: String) : TagWithText(name) { + inline fun b(init: B.() -> Unit) = initTag(B(), init) + inline fun p(init: P.() -> Unit) = initTag(P(), init) + inline fun pNoInline(init: P.() -> Unit) = initTag(P(), init) + inline fun h1(init: H1.() -> Unit) = initTag(H1(), init) + inline fun ul(init: UL.() -> Unit) = initTag(UL(), init) + inline fun a(href: String, init: A.() -> Unit) { + val a = initTag(A(), init) + a.href = href + } +} + +class Body() : BodyTag("body") +class UL() : BodyTag("ul") { + inline fun li(init: LI.() -> Unit) = initTag(LI(), init) +} + +class B() : BodyTag("b") +class LI() : BodyTag("li") +class P() : BodyTag("p") +class H1() : BodyTag("h1") +class A() : BodyTag("a") { + public var href: String + get() = attributes["href"]!! + set(value) { + attributes["href"] = value + } +} + +inline fun html(init: HTML.() -> Unit): HTML { + val html = HTML() + html.init() + return html +} + +fun htmlNoInline(init: HTML.() -> Unit): HTML { + val html = HTML() + html.init() + return html +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/lambdaInLambdaNoInline/1.kt b/compiler/testData/codegen/boxInline/lambdaInLambdaNoInline/1.kt index b1a1bb5e49d..5b87bb844e2 100644 --- a/compiler/testData/codegen/boxInline/lambdaInLambdaNoInline/1.kt +++ b/compiler/testData/codegen/boxInline/lambdaInLambdaNoInline/1.kt @@ -2,7 +2,7 @@ import test.* fun test1(param: String): String { var result = "fail1" - mfun(param) { a -> + noInlineFun(param) { a -> concat("start") { result = doSmth(a).toString() } @@ -11,9 +11,43 @@ fun test1(param: String): String { return result } +fun test11(param: String): String { + var result = "fail1" + noInlineFun("stub") { a -> + concat("start") { + result = doSmth(param).toString() + } + } + + return result +} + +inline fun test2(param: () -> String): String { + var result = "fail1" + noInlineFun("stub") { a -> + concat(param()) { + result = doSmth(param()).toString() + } + } + + return result +} + +inline fun test22(param: () -> String): String { + var result = "fail1" + {{result = param()}()}() + + return result +} + + fun box(): String { - if (test1("start") != "start") return "fail1" - if (test1("nostart") != "nostart") return "fail2" + if (test1("start") != "start") return "fail1: ${test1("start")}" + if (test1("nostart") != "nostart") return "fail2: ${test1("nostart")}" + if (test11("start") != "start") return "fail3: ${test11("start")}" + + if (test2({"start"}) != "start") return "fail4: ${test2({"start"})}" + if (test22({"start"}) != "start") return "fail5: ${test22({"start"})}" return "OK" } \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/lambdaInLambdaNoInline/2.kt b/compiler/testData/codegen/boxInline/lambdaInLambdaNoInline/2.kt index 6c89799fa66..b539a8231a1 100644 --- a/compiler/testData/codegen/boxInline/lambdaInLambdaNoInline/2.kt +++ b/compiler/testData/codegen/boxInline/lambdaInLambdaNoInline/2.kt @@ -4,7 +4,7 @@ fun concat(suffix: String, l: (s: String) -> Unit) { l(suffix) } -fun mfun(arg: T, f: (T) -> Unit) { +fun noInlineFun(arg: T, f: (T) -> Unit) { f(arg) } diff --git a/compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline/1.kt b/compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline/1.kt new file mode 100644 index 00000000000..2c24fa025ff --- /dev/null +++ b/compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline/1.kt @@ -0,0 +1,72 @@ +import test.* + +inline fun test1(param: () -> String): String { + var result = "fail" + inlineFun("1") { c -> + { + inlineFun("2") { a -> + { + { + result = param() + c + a + }() + }() + } + }() + } + + return result +} + + +inline fun test2(param: () -> String): String { + var result = "fail" + inlineFun("2") { a -> + { + { + result = param() + a + }() + }() + } + + return result +} + +inline fun test3(param: () -> String): String { + var result = "fail" + inlineFun("2") { d -> + inlineFun("1") { c -> + { + inlineFun("2") { a -> + { + { + result = param() + c + a + }() + }() + } + }() + } + } + + return result +} + + +fun box(): String { + if (test1({"start"}) != "start12") return "fail1: ${test1({"start"})}" + if (test2({"start"}) != "start2") return "fail2: ${test2({"start"})}" + if (test3({"start"}) != "start12") return "fail3: ${test3({"start"})}" + + var captured1 = "sta"; + val captured2 = "rt"; + if (test1({captured1 + captured2}) != "start12") return "fail4: ${test1({captured1 + captured2})}" + if (test2({captured1 + captured2}) != "start2") return "fail5: ${test2({captured1 + captured2})}" + if (test3({captured1 + captured2}) != "start12") return "fail6: ${test3({captured1 + captured2})}" + + return { + if (test1 { captured1 + captured2 } != "start12") "fail7: ${test1 { captured1 + captured2 }}" + else if (test2 { captured1 + captured2 } != "start2") "fail8: ${test2 { captured1 + captured2 }}" + else if (test3 { captured1 + captured2 } != "start12") "fail9: ${test3 { captured1 + captured2 }}" + else "OK" + } () + +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline/2.kt b/compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline/2.kt new file mode 100644 index 00000000000..dd9a104eeb4 --- /dev/null +++ b/compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline/2.kt @@ -0,0 +1,5 @@ +package test + +inline fun inlineFun(arg: T, f: (T) -> Unit) { + f(arg) +} diff --git a/compiler/testData/codegen/boxInline/with/1.kt b/compiler/testData/codegen/boxInline/with/1.kt index 31d1c8f9aa3..888e80b9fe1 100644 --- a/compiler/testData/codegen/boxInline/with/1.kt +++ b/compiler/testData/codegen/boxInline/with/1.kt @@ -6,7 +6,22 @@ fun Data.test1(d: Data) : Long { with(input) { result = use{ val output = Output(d) - useNoInline{ + use{ + data() + copyTo(output, 10) + } + } + } + return result +} + +fun Data.test2(d: Data) : Long { + val input = Input(this) + var result = 10.toLong() + with2(input) { + result = use{ + val output = Output(d) + useNoInline{ data() copyTo(output, 10) } @@ -21,5 +36,8 @@ fun box(): String { val result = Data().test1(Data()) if (result != 100.toLong()) return "test1: ${result}" + val result2 = Data().test2(Data()) + if (result2 != 100.toLong()) return "test2: ${result2}" + return "OK" } diff --git a/compiler/testData/codegen/boxInline/with/2.kt b/compiler/testData/codegen/boxInline/with/2.kt index 8b4d1d51dfe..f0746c03958 100644 --- a/compiler/testData/codegen/boxInline/with/2.kt +++ b/compiler/testData/codegen/boxInline/with/2.kt @@ -25,3 +25,6 @@ public fun useNoInline(block: ()-> R) : R { public fun Input.copyTo(output: Output, size: Int): Long { return output.doOutput(this.data()).toLong() } + + +public inline fun with2(receiver : T, body : T.() -> Unit) : Unit = {receiver.body()}() diff --git a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxInlineCodegenTestGenerated.java index bef611583ce..47d3bebffe8 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxInlineCodegenTestGenerated.java @@ -41,6 +41,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxCodegenT doTestMultiFile("compiler/testData/codegen/boxInline/builders"); } + @TestMetadata("buildersAndLambdaCapturing") + public void testBuildersAndLambdaCapturing() throws Exception { + doTestMultiFile("compiler/testData/codegen/boxInline/buildersAndLambdaCapturing"); + } + @TestMetadata("captureInlinable") public void testCaptureInlinable() throws Exception { doTestMultiFile("compiler/testData/codegen/boxInline/captureInlinable"); @@ -141,6 +146,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxCodegenT doTestMultiFile("compiler/testData/codegen/boxInline/noInlineLambdaChain"); } + @TestMetadata("noInlineLambdaChainWithCapturedInline") + public void testNoInlineLambdaChainWithCapturedInline() throws Exception { + doTestMultiFile("compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline"); + } + @TestMetadata("noInlineLambdaX2") public void testNoInlineLambdaX2() throws Exception { doTestMultiFile("compiler/testData/codegen/boxInline/noInlineLambdaX2"); diff --git a/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileKotlinAgainstInlineKotlinTestGenerated.java index 6057a880014..d0ba6aaba6b 100644 --- a/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -41,6 +41,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi doBoxTest("compiler/testData/codegen/boxInline/builders"); } + @TestMetadata("buildersAndLambdaCapturing") + public void testBuildersAndLambdaCapturing() throws Exception { + doBoxTest("compiler/testData/codegen/boxInline/buildersAndLambdaCapturing"); + } + @TestMetadata("captureInlinable") public void testCaptureInlinable() throws Exception { doBoxTest("compiler/testData/codegen/boxInline/captureInlinable"); @@ -141,6 +146,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi doBoxTest("compiler/testData/codegen/boxInline/noInlineLambdaChain"); } + @TestMetadata("noInlineLambdaChainWithCapturedInline") + public void testNoInlineLambdaChainWithCapturedInline() throws Exception { + doBoxTest("compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline"); + } + @TestMetadata("noInlineLambdaX2") public void testNoInlineLambdaX2() throws Exception { doBoxTest("compiler/testData/codegen/boxInline/noInlineLambdaX2");