Inline refactoring: more templates for captured fields

Support of complex lambda inlining cases
This commit is contained in:
Mikhael Bogdanov
2014-03-10 14:24:31 +04:00
parent 4a8bcc614a
commit 2dcc0bce46
23 changed files with 906 additions and 268 deletions
@@ -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);
}
}
@@ -23,26 +23,29 @@ import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
public class CapturedParamInfo extends ParameterInfo { 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 int shift = 0;
private LambdaInfo recapturedFrom; public CapturedParamInfo(@NotNull CapturedParamDesc desc, boolean skipped, int index, int remapIndex) {
super(desc.getType(), skipped, index, remapIndex);
public static final CapturedParamInfo STUB = new CapturedParamInfo("", AsmTypeConstants.OBJECT_TYPE, true, -1, -1); this.desc = desc;
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 String fieldName, @NotNull Type type, boolean skipped, int index, StackValue remapIndex) { public CapturedParamInfo(@NotNull CapturedParamDesc desc, boolean skipped, int index, StackValue remapIndex) {
super(type, skipped, index, remapIndex); super(desc.getType(), skipped, index, remapIndex);
this.fieldName = fieldName; this.desc = desc;
} }
public String getFieldName() { public String getFieldName() {
return fieldName; return desc.getFieldName();
} }
@Override @Override
@@ -54,22 +57,17 @@ public class CapturedParamInfo extends ParameterInfo {
this.shift = shift; this.shift = shift;
} }
public LambdaInfo getRecapturedFrom() {
return recapturedFrom;
}
public void setRecapturedFrom(LambdaInfo recapturedFrom) {
this.recapturedFrom = recapturedFrom;
}
public CapturedParamInfo newIndex(int newIndex) { public CapturedParamInfo newIndex(int newIndex) {
return clone(newIndex, getRemapValue()); return clone(newIndex, getRemapValue());
} }
public CapturedParamInfo clone(int newIndex, StackValue newRamapIndex) { 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.setLambda(lambda);
capturedParamInfo.setRecapturedFrom(recapturedFrom);
return capturedParamInfo; return capturedParamInfo;
} }
public String getContainingLambdaName() {
return desc.getContainingLambda().getType().getInternalName();
}
} }
@@ -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();
}
@@ -213,11 +213,11 @@ public class InlineCodegen implements ParentCodegenAware, CallGenerator {
codegen.getInlineNameGenerator().subGenerator(functionDescriptor.getName().asString()), codegen.getInlineNameGenerator().subGenerator(functionDescriptor.getName().asString()),
codegen.getContext(), call, Collections.<String, String>emptyMap(), false, false); codegen.getContext(), call, Collections.<String, String>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); 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() { private void generateClosuresBodies() {
@@ -451,7 +451,7 @@ public class InlineCodegen implements ParentCodegenAware, CallGenerator {
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
p.print(new PrintWriter(sw)); p.print(new PrintWriter(sw));
sw.flush(); sw.flush();
return node.name + ": \n " + sw.getBuffer().toString(); return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString();
} }
private static String descriptorName(DeclarationDescriptor descriptor) { private static String descriptorName(DeclarationDescriptor descriptor) {
@@ -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<CapturedParamInfo> captured
) {
return parent.findField(fieldInsnNode, captured);
}
@Override
public LambdaFieldRemapper getParent() {
return parent.getParent();
}
@Override
public boolean isRoot() {
return parent.isRoot();
}
}
@@ -20,7 +20,7 @@ import org.jetbrains.annotations.Nullable;
class InvokeCall { class InvokeCall {
public final int index; private final int index;
public final LambdaInfo lambdaInfo; public final LambdaInfo lambdaInfo;
@@ -22,16 +22,14 @@ import org.jetbrains.asm4.Opcodes;
import org.jetbrains.asm4.tree.AbstractInsnNode; import org.jetbrains.asm4.tree.AbstractInsnNode;
import org.jetbrains.asm4.tree.FieldInsnNode; import org.jetbrains.asm4.tree.FieldInsnNode;
import org.jetbrains.asm4.tree.MethodNode; 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.Collection;
import java.util.List; import java.util.List;
import static org.jetbrains.jet.codegen.inline.MethodInliner.getPreviousNoLabelNoLine;
public class LambdaFieldRemapper { public class LambdaFieldRemapper {
private String lambdaInternalName; private final String lambdaInternalName;
protected LambdaFieldRemapper parent; protected LambdaFieldRemapper parent;
@@ -43,42 +41,58 @@ public class LambdaFieldRemapper {
params = methodParams; params = methodParams;
} }
public AbstractInsnNode doTransform(MethodNode node, FieldInsnNode fieldInsnNode, CapturedParamInfo capturedField) { public void addCapturedFields(LambdaInfo lambdaInfo, ParametersBuilder builder) {
AbstractInsnNode loadThis = getPreviousThis(fieldInsnNode); for (CapturedParamInfo info : lambdaInfo.getCapturedVars()) {
builder.addCapturedParam(info, info);
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;
} }
protected static AbstractInsnNode getPreviousThis(FieldInsnNode fieldInsnNode) { public boolean canProcess(@NotNull String fieldOwner) {
AbstractInsnNode loadThis = getPreviousNoLabelNoLine(fieldInsnNode); return fieldOwner.equals(getLambdaInternalName());
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 List<CapturedParamInfo> markRecaptured(List<CapturedParamInfo> originalCaptured, LambdaInfo lambda) { public AbstractInsnNode transformIfNeeded(
return originalCaptured; @NotNull List<AbstractInsnNode> 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 CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode) {
public boolean canProcess(@NotNull String owner, @NotNull String currentLambdaType) { return findField(fieldInsnNode, params.getCaptured());
return owner.equals(currentLambdaType);
} }
@Nullable @Nullable
public CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode, @NotNull Collection<CapturedParamInfo> captured) { public CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode, @NotNull Collection<CapturedParamInfo> captured) {
for (CapturedParamInfo valueDescriptor : captured) { for (CapturedParamInfo valueDescriptor : captured) {
if (valueDescriptor.getFieldName().equals(fieldInsnNode.name)) { if (valueDescriptor.getFieldName().equals(fieldInsnNode.name) && fieldInsnNode.owner.equals(valueDescriptor.getContainingLambdaName())) {
return valueDescriptor; return valueDescriptor;
} }
} }
@@ -88,6 +102,7 @@ public class LambdaFieldRemapper {
public LambdaFieldRemapper getParent() { public LambdaFieldRemapper getParent() {
return parent; return parent;
} }
public String getLambdaInternalName() { public String getLambdaInternalName() {
return lambdaInternalName; return lambdaInternalName;
} }
@@ -96,16 +111,9 @@ public class LambdaFieldRemapper {
return parent == null; return parent == null;
} }
public boolean shouldPatch(@NotNull FieldInsnNode node) { @Nullable
return !isRoot() && parent.shouldPatch(node); public StackValue getFieldForInline(@NotNull FieldInsnNode node, @Nullable StackValue prefix) {
} CapturedParamInfo field = MethodInliner.findCapturedField(node, this);
return field.getRemapValue();
@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");
} }
} }
@@ -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.anonymousClassForFunction;
import static org.jetbrains.jet.codegen.binding.CodegenBinding.asmTypeForAnonymousClass; import static org.jetbrains.jet.codegen.binding.CodegenBinding.asmTypeForAnonymousClass;
public class LambdaInfo { public class LambdaInfo implements CapturedParamOwner {
public final JetFunctionLiteralExpression expression; public final JetFunctionLiteralExpression expression;
@@ -123,8 +123,8 @@ public class LambdaInfo {
} }
@NotNull @NotNull
public static CapturedParamInfo getCapturedParamInfo(@NotNull EnclosedValueDescriptor descriptor, int index) { public CapturedParamInfo getCapturedParamInfo(@NotNull EnclosedValueDescriptor descriptor, int index) {
return new CapturedParamInfo(descriptor.getFieldName(), descriptor.getType(), false, index, -1); return new CapturedParamInfo(CapturedParamDesc.createDesc(this, descriptor.getFieldName(), descriptor.getType()), false, index, -1);
} }
public void setParamOffset(int paramOffset) { public void setParamOffset(int paramOffset) {
@@ -149,11 +149,8 @@ public class LambdaInfo {
builder.addNextParameter(type, false, null); builder.addNextParameter(type, false, null);
} }
remapper.addCapturedFields(this, builder);
List<CapturedParamInfo> infos = remapper.markRecaptured(getCapturedVars(), this);
for (CapturedParamInfo info : infos) {
builder.addCapturedParam(info.getFieldName(), info.getType(), info.isSkipped, info);
}
return builder.buildParameters(); return builder.buildParameters();
} }
@@ -164,4 +161,9 @@ public class LambdaInfo {
} }
return size; return size;
} }
@Override
public Type getType() {
return closureClassType;
}
} }
@@ -27,10 +27,7 @@ import org.jetbrains.asm4.tree.FieldInsnNode;
import org.jetbrains.asm4.tree.MethodNode; import org.jetbrains.asm4.tree.MethodNode;
import org.jetbrains.asm4.tree.VarInsnNode; import org.jetbrains.asm4.tree.VarInsnNode;
import org.jetbrains.jet.OutputFile; import org.jetbrains.jet.OutputFile;
import org.jetbrains.jet.codegen.AsmUtil; import org.jetbrains.jet.codegen.*;
import org.jetbrains.jet.codegen.ClassBuilder;
import org.jetbrains.jet.codegen.ClosureCodegen;
import org.jetbrains.jet.codegen.FieldInfo;
import org.jetbrains.jet.codegen.state.GenerationState; import org.jetbrains.jet.codegen.state.GenerationState;
import org.jetbrains.jet.codegen.state.JetTypeMapper; import org.jetbrains.jet.codegen.state.JetTypeMapper;
@@ -128,11 +125,14 @@ public class LambdaTransformer {
Parameters parameters = getLambdaParameters(builder, invocation); Parameters parameters = getLambdaParameters(builder, invocation);
MethodVisitor invokeVisitor = newMethod(classBuilder, invoke); MethodVisitor invokeVisitor = newMethod(classBuilder, invoke);
RegeneratedLambdaFieldRemapper
remapper = new RegeneratedLambdaFieldRemapper(oldLambdaType.getInternalName(), newLambdaType.getInternalName(), parameters, invocation.getCapturedLambdasToInline(), RegeneratedLambdaFieldRemapper remapper =
parentRemapper); new RegeneratedLambdaFieldRemapper(oldLambdaType.getInternalName(), newLambdaType.getInternalName(),
MethodInliner inliner = new MethodInliner(invoke, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")), oldLambdaType, parameters, invocation.getCapturedLambdasToInline(),
remapper, isSameModule); 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); InlineResult result = inliner.doInline(invokeVisitor, new VarRemapper.ParamRemapper(parameters, 0), remapper, false);
invokeVisitor.visitMaxs(-1, -1); 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<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline(); Map<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline();
AbstractInsnNode cur = constructor.instructions.getFirst(); AbstractInsnNode cur = constructor.instructions.getFirst();
cur = cur.getNext(); //skip super call cur = cur.getNext(); //skip super call
List<LambdaInfo> capturedLambdas = new ArrayList<LambdaInfo>(); //captured var of inlined parameter List<LambdaInfo> capturedLambdas = new ArrayList<LambdaInfo>(); //captured var of inlined parameter
CapturedParamOwner owner = new CapturedParamOwner() {
@Override
public Type getType() {
return Type.getObjectType(invocation.getOwnerInternalName());
}
};
while (cur != null) { while (cur != null) {
if (cur.getType() == AbstractInsnNode.FIELD_INSN) { if (cur.getType() == AbstractInsnNode.FIELD_INSN) {
FieldInsnNode fieldNode = (FieldInsnNode) cur; 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(); assert fieldNode.getPrevious() instanceof VarInsnNode : "Previous instruction should be VarInsnNode but was " + fieldNode.getPrevious();
VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious(); VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
@@ -227,8 +234,13 @@ public class LambdaTransformer {
List<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>(); List<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>();
for (LambdaInfo info : capturedLambdas) { for (LambdaInfo info : capturedLambdas) {
for (CapturedParamInfo var : info.getCapturedVars()) { for (CapturedParamInfo var : info.getCapturedVars()) {
CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(getNewFieldName(var.getFieldName()), var.getType(), true, var); CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(getNewFieldName(var.getFieldName()), var.getType(), var.isSkipped, var, info);
recapturedParamInfo.setRecapturedFrom(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); allRecapturedParameters.add(var);
} }
capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info); capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
@@ -3,7 +3,6 @@ package org.jetbrains.jet.codegen.inline;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.Label; import org.jetbrains.asm4.Label;
import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.MethodVisitor;
import org.jetbrains.asm4.Opcodes; 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.commons.RemappingMethodAdapter;
import org.jetbrains.asm4.tree.*; import org.jetbrains.asm4.tree.*;
import org.jetbrains.asm4.tree.analysis.*; import org.jetbrains.asm4.tree.analysis.*;
import org.jetbrains.jet.codegen.AsmUtil;
import org.jetbrains.jet.codegen.ClosureCodegen; import org.jetbrains.jet.codegen.ClosureCodegen;
import org.jetbrains.jet.codegen.StackValue; import org.jetbrains.jet.codegen.StackValue;
import org.jetbrains.jet.codegen.state.JetTypeMapper; import org.jetbrains.jet.codegen.state.JetTypeMapper;
@@ -21,7 +19,8 @@ import org.jetbrains.jet.utils.UtilsPackage;
import java.util.*; 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 { public class MethodInliner {
@@ -31,13 +30,12 @@ public class MethodInliner {
private final InliningContext inliningContext; private final InliningContext inliningContext;
@Nullable private final LambdaFieldRemapper nodeRemapper;
private final Type lambdaType;
private final LambdaFieldRemapper lambdaFieldRemapper;
private final boolean isSameModule; private final boolean isSameModule;
private final String errorPrefix;
private final JetTypeMapper typeMapper; private final JetTypeMapper typeMapper;
private final List<InvokeCall> invokeCalls = new ArrayList<InvokeCall>(); private final List<InvokeCall> invokeCalls = new ArrayList<InvokeCall>();
@@ -60,23 +58,23 @@ public class MethodInliner {
@NotNull MethodNode node, @NotNull MethodNode node,
@NotNull Parameters parameters, @NotNull Parameters parameters,
@NotNull InliningContext parent, @NotNull InliningContext parent,
@Nullable Type lambdaType, @NotNull LambdaFieldRemapper nodeRemapper,
LambdaFieldRemapper lambdaFieldRemapper, boolean isSameModule,
boolean isSameModule @NotNull String errorPrefix
) { ) {
this.node = node; this.node = node;
this.parameters = parameters; this.parameters = parameters;
this.inliningContext = parent; this.inliningContext = parent;
this.lambdaType = lambdaType; this.nodeRemapper = nodeRemapper;
this.lambdaFieldRemapper = lambdaFieldRemapper;
this.isSameModule = isSameModule; this.isSameModule = isSameModule;
this.errorPrefix = errorPrefix;
this.typeMapper = parent.state.getTypeMapper(); this.typeMapper = parent.state.getTypeMapper();
this.result = InlineResult.create(); this.result = InlineResult.create();
} }
public InlineResult doInline(MethodVisitor adapter, VarRemapper.ParamRemapper remapper) { public InlineResult doInline(MethodVisitor adapter, VarRemapper.ParamRemapper remapper, LambdaFieldRemapper fieldRemapper) {
return doInline(adapter, remapper, lambdaFieldRemapper, true); return doInline(adapter, remapper, fieldRemapper, true);
} }
public InlineResult doInline( public InlineResult doInline(
@@ -85,27 +83,30 @@ public class MethodInliner {
LambdaFieldRemapper capturedRemapper, boolean remapReturn LambdaFieldRemapper capturedRemapper, boolean remapReturn
) { ) {
//analyze body //analyze body
MethodNode transformedNode = node; MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node);
try {
transformedNode = markPlacesForInlineAndRemoveInlinable(transformedNode);
}
catch (AnalyzerException e) {
throw UtilsPackage.rethrow(e);
}
transformedNode = doInline(transformedNode, capturedRemapper); transformedNode = doInline(transformedNode, capturedRemapper);
removeClosureAssertions(transformedNode); removeClosureAssertions(transformedNode);
transformedNode.instructions.resetLabels(); transformedNode.instructions.resetLabels();
Label end = new Label(); Label end = new Label();
RemapVisitor visitor = new RemapVisitor(adapter, end, remapper, remapReturn); RemapVisitor visitor = new RemapVisitor(adapter, end, remapper, remapReturn, nodeRemapper);
transformedNode.accept(visitor); 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); visitor.visitLabel(end);
return result; return result;
} }
private MethodNode doInline(MethodNode node, final LambdaFieldRemapper capturedRemapper) { private MethodNode doInline(MethodNode node, final LambdaFieldRemapper capturedRemapper) {
final Deque<InvokeCall> currentInvokes = new LinkedList<InvokeCall>(invokeCalls); final Deque<InvokeCall> currentInvokes = new LinkedList<InvokeCall>(invokeCalls);
@@ -164,15 +165,20 @@ public class MethodInliner {
Parameters lambdaParameters = info.addAllParameters(capturedRemapper); 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); setInlining(true);
MethodInliner inliner = new MethodInliner(info.getNode(), lambdaParameters, MethodInliner inliner = new MethodInliner(info.getNode(), lambdaParameters,
inliningContext.subInlineLambda(info), inliningContext.subInlineLambda(info),
info.getLambdaClassType(), lambdaFieldRemapper, true /*cause all calls in same module as lambda*/,
capturedRemapper, true /*cause all calls in same module as lambda*/ "Lambda inlining " + info.getLambdaClassType().getInternalName());
);
VarRemapper.ParamRemapper remapper = new VarRemapper.ParamRemapper(lambdaParameters, valueParamShift); 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); result.addAllClassesToRemove(lambdaResult);
//return value boxing/unboxing //return value boxing/unboxing
@@ -185,22 +191,8 @@ public class MethodInliner {
assert invocation != null : "<init> call not corresponds to new call" + owner + " " + name; assert invocation != null : "<init> call not corresponds to new call" + owner + " " + name;
if (invocation.shouldRegenerate()) { if (invocation.shouldRegenerate()) {
//put additional captured parameters on stack //put additional captured parameters on stack
List<CapturedParamInfo> recaptured = invocation.getAllRecapturedParameters(); for (CapturedParamInfo capturedParamInfo : invocation.getAllRecapturedParameters()) {
List<CapturedParamInfo> contextCaptured = MethodInliner.this.parameters.getCaptured(); visitFieldInsn(Opcodes.GETSTATIC, capturedParamInfo.getContainingLambdaName(), "$$$" + capturedParamInfo.getFieldName(), capturedParamInfo.getType().getDescriptor());
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());
} }
super.visitMethodInsn(opcode, invocation.getNewLambdaType().getInternalName(), name, invocation.getNewConstructorDescriptor()); super.visitMethodInsn(opcode, invocation.getNewLambdaType().getInternalName(), name, invocation.getNewConstructorDescriptor());
invocation = null; invocation = null;
@@ -212,6 +204,7 @@ public class MethodInliner {
super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc); super.visitMethodInsn(opcode, changeOwnerForExternalPackage(owner, opcode), name, desc);
} }
} }
}; };
node.accept(inliner); node.accept(inliner);
@@ -219,8 +212,18 @@ public class MethodInliner {
return resultNode; 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 @NotNull
@@ -272,11 +275,21 @@ public class MethodInliner {
} }
@NotNull @NotNull
protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node) throws AnalyzerException { protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node) {
node = prepareNode(node); node = prepareNode(node);
Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()); Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter());
Frame<SourceValue>[] sources = analyzer.analyze("fake", node); Frame<SourceValue>[] 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(); AbstractInsnNode cur = node.instructions.getFirst();
int index = 0; int index = 0;
@@ -301,15 +314,11 @@ public class MethodInliner {
if (sourceValue.insns.size() == 1) { if (sourceValue.insns.size() == 1) {
AbstractInsnNode insnNode = sourceValue.insns.iterator().next(); 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) { lambdaInfo = getLambdaIfExists(insnNode);
//remove inlinable access if (lambdaInfo != null) {
node.instructions.remove(insnNode); //remove inlinable access
} node.instructions.remove(insnNode);
} }
} }
@@ -323,13 +332,10 @@ public class MethodInliner {
SourceValue sourceValue = frame.getStack(paramStart + i); SourceValue sourceValue = frame.getStack(paramStart + i);
if (sourceValue.insns.size() == 1) { if (sourceValue.insns.size() == 1) {
AbstractInsnNode insnNode = sourceValue.insns.iterator().next(); AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
if (insnNode.getOpcode() == Opcodes.ALOAD) { LambdaInfo lambdaInfo = getLambdaIfExists(insnNode);
int varIndex = ((VarInsnNode) insnNode).var; if (lambdaInfo != null) {
LambdaInfo lambdaInfo = getLambda(varIndex); lambdaMapping.put(i, lambdaInfo);
if (lambdaInfo != null) { node.instructions.remove(insnNode);
lambdaMapping.put(i, lambdaInfo);
node.instructions.remove(insnNode);
}
} }
} }
} }
@@ -366,11 +372,21 @@ public class MethodInliner {
return node; return node;
} }
@Nullable public LambdaInfo getLambdaIfExists(AbstractInsnNode insnNode) {
public LambdaInfo getLambda(int index) { if (insnNode.getOpcode() == Opcodes.ALOAD) {
if (index < parameters.totalSize()) { int varIndex = ((VarInsnNode) insnNode).var;
return parameters.get(index).getLambda(); 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; return null;
} }
@@ -398,43 +414,43 @@ public class MethodInliner {
} }
private void transformCaptured(@NotNull MethodNode node) { private void transformCaptured(@NotNull MethodNode node) {
if (lambdaType == null) { if (nodeRemapper.isRoot()) {
return; 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(); AbstractInsnNode cur = node.instructions.getFirst();
while (cur != null) { while (cur != null) {
if (cur.getType() == AbstractInsnNode.FIELD_INSN) { if (cur instanceof VarInsnNode && cur.getOpcode() == Opcodes.ALOAD) {
FieldInsnNode fieldInsnNode = (FieldInsnNode) cur; if (((VarInsnNode) cur).var == 0) {
//TODO check closure List<AbstractInsnNode> accessChain = getCapturedFieldAccessChain((VarInsnNode) cur);
if (lambdaFieldRemapper.canProcess(fieldInsnNode.owner, lambdaType.getInternalName())) { AbstractInsnNode insnNode = nodeRemapper.transformIfNeeded(accessChain, 1, node);
CapturedParamInfo result = this.lambdaFieldRemapper.findField(fieldInsnNode, parameters.getCaptured()); if (insnNode != null) {
cur = insnNode;
if (result == null) {
throw new UnsupportedOperationException("Coudn't find field " +
fieldInsnNode.owner +
"." +
fieldInsnNode.name +
" (" +
fieldInsnNode.desc +
") in captured vars of " + lambdaType);
} }
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(); cur = cur.getNext();
} }
} }
@NotNull
public static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode node) {
List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>();
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) { public static AbstractInsnNode getPreviousNoLabelNoLine(AbstractInsnNode cur) {
AbstractInsnNode prev = cur.getPrevious(); AbstractInsnNode prev = cur.getPrevious();
while (prev.getType() == AbstractInsnNode.LABEL || prev.getType() == AbstractInsnNode.LINE) { while (prev.getType() == AbstractInsnNode.LABEL || prev.getType() == AbstractInsnNode.LINE) {
@@ -16,6 +16,7 @@
package org.jetbrains.jet.codegen.inline; package org.jetbrains.jet.codegen.inline;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.Type; import org.jetbrains.asm4.Type;
@@ -46,9 +47,29 @@ public class ParametersBuilder {
return this; return this;
} }
public CapturedParamInfo addCapturedParam(String fieldName, Type type, boolean skipped, @Nullable ParameterInfo original) { public CapturedParamInfo addCapturedParam(
return addCapturedParameter(new CapturedParamInfo(fieldName, type, skipped, nextCaptured, @NotNull CapturedParamInfo param,
original != null ? original.getIndex() : -1)); @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) { private void addParameter(ParameterInfo info) {
@@ -20,9 +20,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.asm4.Opcodes; import org.jetbrains.asm4.Opcodes;
import org.jetbrains.asm4.Type; import org.jetbrains.asm4.Type;
import org.jetbrains.asm4.tree.AbstractInsnNode;
import org.jetbrains.asm4.tree.FieldInsnNode; 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.Collection;
import java.util.List; import java.util.List;
@@ -53,52 +52,35 @@ public class RegeneratedLambdaFieldRemapper extends LambdaFieldRemapper {
} }
@Override @Override
public AbstractInsnNode doTransform( public void addCapturedFields(LambdaInfo lambdaInfo, ParametersBuilder builder) {
MethodNode node, FieldInsnNode fieldInsnNode, CapturedParamInfo capturedField if (canProcess(lambdaInfo.getLambdaClassType().getInternalName())) {
) { List<CapturedParamInfo> captured = parameters.getCaptured();
boolean isRecaptured = isRecapturedLambdaType(fieldInsnNode.owner); for (CapturedParamInfo originalField : lambdaInfo.getCapturedVars()) {
CapturedParamInfo foundField = null;
if (!isRecaptured && capturedField.getLambda() != null) { for (CapturedParamInfo capturedParamInfo : captured) {
//strict inlining if (capturedParamInfo.getContainingLambdaName().equals(originalField.getContainingLambdaName())) {
return super.doTransform(node, fieldInsnNode, capturedField); if (capturedParamInfo.getFieldName().equals(LambdaTransformer.getNewFieldName(originalField.getFieldName()))) {
} foundField = originalField;
break;
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<CapturedParamInfo> markRecaptured(List<CapturedParamInfo> originalCaptured, LambdaInfo lambda) {
List<CapturedParamInfo> 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
} }
} }
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 @Override
public boolean canProcess(String owner, String currentLambdaType) { public boolean canProcess(@NotNull String fieldOwner) {
return super.canProcess(owner, currentLambdaType) || isRecapturedLambdaType(owner); return super.canProcess(fieldOwner) || isRecapturedLambdaType(fieldOwner);
} }
private boolean isRecapturedLambdaType(String owner) { private boolean isRecapturedLambdaType(String owner) {
@@ -107,8 +89,11 @@ public class RegeneratedLambdaFieldRemapper extends LambdaFieldRemapper {
@Nullable @Nullable
@Override @Override
public CapturedParamInfo findField(FieldInsnNode fieldInsnNode, Collection<CapturedParamInfo> captured) { public CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode, @NotNull Collection<CapturedParamInfo> captured) {
if (isRecapturedLambdaType(fieldInsnNode.owner)) { boolean searchInParent = !canProcess(fieldInsnNode.owner);
if (searchInParent) {
return parent.findField(fieldInsnNode);
} else if (isRecapturedLambdaType(fieldInsnNode.owner)) {
LambdaInfo info = recapturedLambdas.get(fieldInsnNode.owner); LambdaInfo info = recapturedLambdas.get(fieldInsnNode.owner);
return super.findField(fieldInsnNode, info.getCapturedVars()); return super.findField(fieldInsnNode, info.getCapturedVars());
} }
@@ -117,39 +102,42 @@ public class RegeneratedLambdaFieldRemapper extends LambdaFieldRemapper {
} }
} }
@Override @Nullable
public boolean shouldPatch(@NotNull FieldInsnNode node) { public CapturedParamInfo findFieldInMyCaptured(@NotNull FieldInsnNode fieldInsnNode) {
//parent is inlined so we need patch instruction chain if (isRecapturedLambdaType(fieldInsnNode.owner)) {
return shouldPatchByMe(node) || parent.shouldPatch(node); 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) { @Nullable
//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
@Override @Override
public AbstractInsnNode patch(@NotNull FieldInsnNode fieldInsnNode, @NotNull MethodNode node) { public StackValue getFieldForInline(@NotNull FieldInsnNode node, @Nullable StackValue prefix) {
if (!shouldPatchByMe(fieldInsnNode)) { FieldInsnNode fin = new FieldInsnNode(node.getOpcode(), node.owner, node.name.substring(3), node.desc);
return parent.patch(fieldInsnNode, node); CapturedParamInfo field = findFieldInMyCaptured(fin);
}
//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);
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;
} }
} }
@@ -21,6 +21,8 @@ import org.jetbrains.asm4.Label;
import org.jetbrains.asm4.MethodVisitor; import org.jetbrains.asm4.MethodVisitor;
import org.jetbrains.asm4.Opcodes; import org.jetbrains.asm4.Opcodes;
import org.jetbrains.asm4.commons.InstructionAdapter; import org.jetbrains.asm4.commons.InstructionAdapter;
import org.jetbrains.asm4.tree.FieldInsnNode;
import org.jetbrains.jet.codegen.StackValue;
public class RemapVisitor extends InstructionAdapter { public class RemapVisitor extends InstructionAdapter {
@@ -29,12 +31,14 @@ public class RemapVisitor extends InstructionAdapter {
private final VarRemapper remapper; private final VarRemapper remapper;
private final boolean remapReturn; 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); super(InlineCodegenUtil.API, mv);
this.end = end; this.end = end;
this.remapper = remapper; this.remapper = remapper;
this.remapReturn = remapReturn; this.remapReturn = remapReturn;
this.nodeRemapper = nodeRemapper;
} }
@Override @Override
@@ -57,6 +61,22 @@ public class RemapVisitor extends InstructionAdapter {
remapper.visitVarInsn(opcode, var, new InstructionAdapter(mv)); 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 @Override
public void visitLocalVariable( public void visitLocalVariable(
String name, String desc, String signature, Label start, Label end, int index String name, String desc, String signature, Label start, Label end, int index
@@ -84,15 +104,6 @@ public class RemapVisitor extends InstructionAdapter {
return null; 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 //TODO not skip for lambdas
@Override @Override
public void visitLineNumber(int line, Label start) { public void visitLineNumber(int line, Label start) {
@@ -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"
}
@@ -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<Element>()
val attributes = HashMap<String, String>()
inline protected fun initTag<T : Element>(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</$name>\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
}
@@ -2,7 +2,7 @@ import test.*
fun test1(param: String): String { fun test1(param: String): String {
var result = "fail1" var result = "fail1"
mfun(param) { a -> noInlineFun(param) { a ->
concat("start") { concat("start") {
result = doSmth(a).toString() result = doSmth(a).toString()
} }
@@ -11,9 +11,43 @@ fun test1(param: String): String {
return result 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 { fun box(): String {
if (test1("start") != "start") return "fail1" if (test1("start") != "start") return "fail1: ${test1("start")}"
if (test1("nostart") != "nostart") return "fail2" 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" return "OK"
} }
@@ -4,7 +4,7 @@ fun concat(suffix: String, l: (s: String) -> Unit) {
l(suffix) l(suffix)
} }
fun <T> mfun(arg: T, f: (T) -> Unit) { fun <T> noInlineFun(arg: T, f: (T) -> Unit) {
f(arg) f(arg)
} }
@@ -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"
} ()
}
@@ -0,0 +1,5 @@
package test
inline fun <T> inlineFun(arg: T, f: (T) -> Unit) {
f(arg)
}
+19 -1
View File
@@ -6,7 +6,22 @@ fun Data.test1(d: Data) : Long {
with(input) { with(input) {
result = use<Long>{ result = use<Long>{
val output = Output(d) val output = Output(d)
useNoInline<Long>{ use<Long>{
data()
copyTo(output, 10)
}
}
}
return result
}
fun Data.test2(d: Data) : Long {
val input = Input(this)
var result = 10.toLong()
with2(input) {
result = use<Long>{
val output = Output(d)
useNoInline<Long>{
data() data()
copyTo(output, 10) copyTo(output, 10)
} }
@@ -21,5 +36,8 @@ fun box(): String {
val result = Data().test1(Data()) val result = Data().test1(Data())
if (result != 100.toLong()) return "test1: ${result}" if (result != 100.toLong()) return "test1: ${result}"
val result2 = Data().test2(Data())
if (result2 != 100.toLong()) return "test2: ${result2}"
return "OK" return "OK"
} }
@@ -25,3 +25,6 @@ public fun <R> useNoInline(block: ()-> R) : R {
public fun Input.copyTo(output: Output, size: Int): Long { public fun Input.copyTo(output: Output, size: Int): Long {
return output.doOutput(this.data()).toLong() return output.doOutput(this.data()).toLong()
} }
public inline fun with2<T>(receiver : T, body : T.() -> Unit) : Unit = {receiver.body()}()
@@ -41,6 +41,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxCodegenT
doTestMultiFile("compiler/testData/codegen/boxInline/builders"); doTestMultiFile("compiler/testData/codegen/boxInline/builders");
} }
@TestMetadata("buildersAndLambdaCapturing")
public void testBuildersAndLambdaCapturing() throws Exception {
doTestMultiFile("compiler/testData/codegen/boxInline/buildersAndLambdaCapturing");
}
@TestMetadata("captureInlinable") @TestMetadata("captureInlinable")
public void testCaptureInlinable() throws Exception { public void testCaptureInlinable() throws Exception {
doTestMultiFile("compiler/testData/codegen/boxInline/captureInlinable"); doTestMultiFile("compiler/testData/codegen/boxInline/captureInlinable");
@@ -141,6 +146,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxCodegenT
doTestMultiFile("compiler/testData/codegen/boxInline/noInlineLambdaChain"); doTestMultiFile("compiler/testData/codegen/boxInline/noInlineLambdaChain");
} }
@TestMetadata("noInlineLambdaChainWithCapturedInline")
public void testNoInlineLambdaChainWithCapturedInline() throws Exception {
doTestMultiFile("compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline");
}
@TestMetadata("noInlineLambdaX2") @TestMetadata("noInlineLambdaX2")
public void testNoInlineLambdaX2() throws Exception { public void testNoInlineLambdaX2() throws Exception {
doTestMultiFile("compiler/testData/codegen/boxInline/noInlineLambdaX2"); doTestMultiFile("compiler/testData/codegen/boxInline/noInlineLambdaX2");
@@ -41,6 +41,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
doBoxTest("compiler/testData/codegen/boxInline/builders"); doBoxTest("compiler/testData/codegen/boxInline/builders");
} }
@TestMetadata("buildersAndLambdaCapturing")
public void testBuildersAndLambdaCapturing() throws Exception {
doBoxTest("compiler/testData/codegen/boxInline/buildersAndLambdaCapturing");
}
@TestMetadata("captureInlinable") @TestMetadata("captureInlinable")
public void testCaptureInlinable() throws Exception { public void testCaptureInlinable() throws Exception {
doBoxTest("compiler/testData/codegen/boxInline/captureInlinable"); doBoxTest("compiler/testData/codegen/boxInline/captureInlinable");
@@ -141,6 +146,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
doBoxTest("compiler/testData/codegen/boxInline/noInlineLambdaChain"); doBoxTest("compiler/testData/codegen/boxInline/noInlineLambdaChain");
} }
@TestMetadata("noInlineLambdaChainWithCapturedInline")
public void testNoInlineLambdaChainWithCapturedInline() throws Exception {
doBoxTest("compiler/testData/codegen/boxInline/noInlineLambdaChainWithCapturedInline");
}
@TestMetadata("noInlineLambdaX2") @TestMetadata("noInlineLambdaX2")
public void testNoInlineLambdaX2() throws Exception { public void testNoInlineLambdaX2() throws Exception {
doBoxTest("compiler/testData/codegen/boxInline/noInlineLambdaX2"); doBoxTest("compiler/testData/codegen/boxInline/noInlineLambdaX2");