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 {
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();
}
}
@@ -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.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);
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) {
@@ -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 {
public final int index;
private final int index;
public final LambdaInfo lambdaInfo;
@@ -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<CapturedParamInfo> markRecaptured(List<CapturedParamInfo> originalCaptured, LambdaInfo lambda) {
return originalCaptured;
public AbstractInsnNode transformIfNeeded(
@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 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<CapturedParamInfo> 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();
}
}
@@ -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<CapturedParamInfo> 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;
}
}
@@ -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<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline();
AbstractInsnNode cur = constructor.instructions.getFirst();
cur = cur.getNext(); //skip super call
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) {
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<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>();
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);
@@ -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<InvokeCall> invokeCalls = new ArrayList<InvokeCall>();
@@ -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<InvokeCall> currentInvokes = new LinkedList<InvokeCall>(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 : "<init> call not corresponds to new call" + owner + " " + name;
if (invocation.shouldRegenerate()) {
//put additional captured parameters on stack
List<CapturedParamInfo> recaptured = invocation.getAllRecapturedParameters();
List<CapturedParamInfo> 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<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();
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<AbstractInsnNode> 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<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) {
AbstractInsnNode prev = cur.getPrevious();
while (prev.getType() == AbstractInsnNode.LABEL || prev.getType() == AbstractInsnNode.LINE) {
@@ -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) {
@@ -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<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
public void addCapturedFields(LambdaInfo lambdaInfo, ParametersBuilder builder) {
if (canProcess(lambdaInfo.getLambdaClassType().getInternalName())) {
List<CapturedParamInfo> 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<CapturedParamInfo> captured) {
if (isRecapturedLambdaType(fieldInsnNode.owner)) {
public CapturedParamInfo findField(@NotNull FieldInsnNode fieldInsnNode, @NotNull Collection<CapturedParamInfo> 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;
}
}
@@ -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) {
@@ -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 {
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"
}
@@ -4,7 +4,7 @@ fun concat(suffix: String, l: (s: String) -> Unit) {
l(suffix)
}
fun <T> mfun(arg: T, f: (T) -> Unit) {
fun <T> noInlineFun(arg: T, f: (T) -> Unit) {
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) {
result = use<Long>{
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()
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"
}
@@ -25,3 +25,6 @@ public fun <R> useNoInline(block: ()-> R) : R {
public fun Input.copyTo(output: Output, size: Int): Long {
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");
}
@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");
@@ -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");