/*
* Copyright 2010-2015 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.kotlin.codegen.inline;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.CodegenUtil;
import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment;
import org.jetbrains.kotlin.codegen.*;
import org.jetbrains.kotlin.codegen.context.*;
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructorsKt;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.incremental.KotlinLookupLocation;
import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.ImportedFromObjectCallableDescriptor;
import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature;
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.expressions.DoubleColonLHS;
import org.jetbrains.kotlin.types.expressions.LabelResolver;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.Method;
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
import org.jetbrains.org.objectweb.asm.tree.InsnList;
import org.jetbrains.org.objectweb.asm.tree.LabelNode;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import java.io.IOException;
import java.util.*;
import static org.jetbrains.kotlin.codegen.AsmUtil.getMethodAsmFlags;
import static org.jetbrains.kotlin.codegen.AsmUtil.isPrimitive;
import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.*;
import static org.jetbrains.kotlin.descriptors.annotations.AnnotationUtilKt.isInlineOnly;
import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isFunctionLiteral;
public class InlineCodegen extends CallGenerator {
private final GenerationState state;
private final KotlinTypeMapper typeMapper;
private final FunctionDescriptor functionDescriptor;
private final JvmMethodSignature jvmSignature;
private final KtElement callElement;
private final MethodContext context;
private final ExpressionCodegen codegen;
private final boolean asFunctionInline;
private final int initialFrameSize;
private final boolean isSameModule;
private final ParametersBuilder invocationParamBuilder = ParametersBuilder.newBuilder();
private final Map<Integer, LambdaInfo> expressionMap = new LinkedHashMap<>();
private final ReifiedTypeInliner reifiedTypeInliner;
@Nullable
private final TypeParameterMappings typeParameterMappings;
private LambdaInfo activeLambda;
private final SourceMapper sourceMapper;
private Runnable delayedHiddenWriting;
private List<Integer> maskValues = new ArrayList<>();
private int maskStartIndex = -1;
private int methodHandleInDefaultMethodIndex = -1;
public InlineCodegen(
@NotNull ExpressionCodegen codegen,
@NotNull GenerationState state,
@NotNull FunctionDescriptor function,
@NotNull KtElement callElement,
@Nullable TypeParameterMappings typeParameterMappings
) {
assert InlineUtil.isInline(function) || InlineUtil.isArrayConstructorWithLambda(function) :
"InlineCodegen can inline only inline functions and array constructors: " + function;
this.state = state;
this.typeMapper = state.getTypeMapper();
this.codegen = codegen;
this.callElement = callElement;
this.functionDescriptor =
InlineUtil.isArrayConstructorWithLambda(function)
? FictitiousArrayConstructor.create((ConstructorDescriptor) function)
: function.getOriginal();
this.typeParameterMappings = typeParameterMappings;
reifiedTypeInliner = new ReifiedTypeInliner(typeParameterMappings);
initialFrameSize = codegen.getFrameMap().getCurrentSize();
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor);
context = (MethodContext) getContext(functionDescriptor, state, element != null ? (KtFile) element.getContainingFile() : null);
jvmSignature = typeMapper.mapSignatureWithGeneric(functionDescriptor, context.getContextKind());
// TODO: implement AS_FUNCTION inline strategy
this.asFunctionInline = false;
isSameModule = JvmCodegenUtil.isCallInsideSameModuleAsDeclared(functionDescriptor, codegen.getContext(), state.getOutDirectory());
sourceMapper = codegen.getParentCodegen().getOrCreateSourceMapper();
if (!(functionDescriptor instanceof FictitiousArrayConstructor)) {
reportIncrementalInfo(functionDescriptor, codegen.getContext().getFunctionDescriptor().getOriginal(), jvmSignature, state);
String functionOrAccessorName = typeMapper.mapAsmMethod(function).getName();
//track changes for property accessor and @JvmName inline functions/property accessors
if(!functionOrAccessorName.equals(functionDescriptor.getName().asString())) {
MemberScope scope = getMemberScope(functionDescriptor);
if (scope != null) {
//Fake lookup to track track changes for property accessors and @JvmName functions/property accessors
scope.getContributedFunctions(Name.identifier(functionOrAccessorName), new KotlinLookupLocation(callElement));
}
}
}
}
@Nullable
private static MemberScope getMemberScope(@NotNull FunctionDescriptor functionOrAccessor) {
CallableMemberDescriptor callableMemberDescriptor = JvmCodegenUtil.getDirectMember(functionOrAccessor);
DeclarationDescriptor classOrPackageFragment = callableMemberDescriptor.getContainingDeclaration();
if (classOrPackageFragment instanceof ClassDescriptor) {
return ((ClassDescriptor) classOrPackageFragment).getUnsubstitutedMemberScope();
}
else if (classOrPackageFragment instanceof PackageFragmentDescriptor) {
return ((PackageFragmentDescriptor) classOrPackageFragment).getMemberScope();
}
return null;
}
@Override
public void genCallInner(
@NotNull Callable callableMethod,
@Nullable ResolvedCall<?> resolvedCall,
boolean callDefault,
@NotNull ExpressionCodegen codegen
) {
if (!state.getInlineCycleReporter().enterIntoInlining(resolvedCall)) {
generateStub(resolvedCall, codegen);
return;
}
SMAPAndMethodNode nodeAndSmap = null;
try {
nodeAndSmap = createMethodNode(functionDescriptor, jvmSignature, codegen, context, callDefault, resolvedCall);
endCall(inlineCall(nodeAndSmap, callDefault));
}
catch (CompilationException e) {
throw e;
}
catch (InlineException e) {
throw throwCompilationException(nodeAndSmap, e, false);
}
catch (Exception e) {
throw throwCompilationException(nodeAndSmap, e, true);
}
finally {
state.getInlineCycleReporter().exitFromInliningOf(resolvedCall);
}
}
@NotNull
private CompilationException throwCompilationException(
@Nullable SMAPAndMethodNode nodeAndSmap, @NotNull Exception e, boolean generateNodeText
) {
CallableMemberDescriptor contextDescriptor = codegen.getContext().getContextDescriptor();
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor);
MethodNode node = nodeAndSmap != null ? nodeAndSmap.getNode() : null;
throw new CompilationException(
"Couldn't inline method call '" + functionDescriptor.getName() + "' into\n" +
DescriptorRenderer.DEBUG_TEXT.render(contextDescriptor) + "\n" +
(element != null ? element.getText() : "<no source>") +
(generateNodeText ? ("\nCause: " + InlineCodegenUtil.getNodeText(node)) : ""),
e, callElement
);
}
private void generateStub(@Nullable ResolvedCall<?> resolvedCall, @NotNull ExpressionCodegen codegen) {
leaveTemps();
assert resolvedCall != null;
String message = "Call is part of inline cycle: " + resolvedCall.getCall().getCallElement().getText();
AsmUtil.genThrow(codegen.v, "java/lang/UnsupportedOperationException", message);
}
private void endCall(@NotNull InlineResult result) {
leaveTemps();
codegen.propagateChildReifiedTypeParametersUsages(result.getReifiedTypeParametersUsages());
state.getFactory().removeClasses(result.calcClassesToRemove());
codegen.markLineNumberAfterInlineIfNeeded();
}
@NotNull
static SMAPAndMethodNode createMethodNode(
@NotNull FunctionDescriptor functionDescriptor,
@NotNull JvmMethodSignature jvmSignature,
@NotNull ExpressionCodegen codegen,
@NotNull CodegenContext context,
boolean callDefault,
@Nullable ResolvedCall<?> resolvedCall
) {
if (InlineCodegenUtil.isSpecialEnumMethod(functionDescriptor)) {
assert resolvedCall != null : "Resolved call for " + functionDescriptor + " should be not null";
Map<TypeParameterDescriptor, KotlinType> arguments = resolvedCall.getTypeArguments();
assert arguments.size() == 1 : "Resolved call for " + functionDescriptor + " should have 1 type argument";
MethodNode node =
InlineCodegenUtil.createSpecialEnumMethodBody(
codegen,
functionDescriptor.getName().asString(),
arguments.keySet().iterator().next().getDefaultType(),
codegen.getState().getTypeMapper()
);
return new SMAPAndMethodNode(node, SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1));
}
else if (CoroutineCodegenUtilKt.isBuiltInSuspendCoroutineOrReturnInJvm(functionDescriptor)) {
return new SMAPAndMethodNode(
CoroutineCodegenUtilKt.createMethodNodeForSuspendCoroutineOrReturn(
functionDescriptor, codegen.getState().getTypeMapper()
),
SMAPParser.parseOrCreateDefault(null, null, "fake", -1, -1)
);
}
GenerationState state = codegen.getState();
Method asmMethod =
callDefault
? state.getTypeMapper().mapDefaultMethod(functionDescriptor, context.getContextKind())
: jvmSignature.getAsmMethod();
MethodId methodId = new MethodId(DescriptorUtils.getFqNameSafe(functionDescriptor.getContainingDeclaration()), asmMethod);
CallableMemberDescriptor directMember = getDirectMemberAndCallableFromObject(functionDescriptor);
if (!isBuiltInArrayIntrinsic(functionDescriptor) && !(directMember instanceof DeserializedCallableMemberDescriptor)) {
return doCreateMethodNodeFromSource(functionDescriptor, jvmSignature, codegen, context, callDefault, state, asmMethod);
}
SMAPAndMethodNode resultInCache = InlineCacheKt.getOrPut(
state.getInlineCache().getMethodNodeById(), methodId, () -> {
SMAPAndMethodNode result = doCreateMethodNodeFromCompiled(directMember, state, asmMethod);
if (result == null) {
throw new IllegalStateException("Couldn't obtain compiled function body for " + functionDescriptor);
}
return result;
}
);
return resultInCache.copyWithNewNode(cloneMethodNode(resultInCache.getNode()));
}
@NotNull
private static CallableMemberDescriptor getDirectMemberAndCallableFromObject(@NotNull FunctionDescriptor functionDescriptor) {
CallableMemberDescriptor directMember = JvmCodegenUtil.getDirectMember(functionDescriptor);
if (directMember instanceof ImportedFromObjectCallableDescriptor) {
return ((ImportedFromObjectCallableDescriptor) directMember).getCallableFromObject();
}
return directMember;
}
@NotNull
private static MethodNode cloneMethodNode(@NotNull MethodNode methodNode) {
methodNode.instructions.resetLabels();
MethodNode result = new MethodNode(
API, methodNode.access, methodNode.name, methodNode.desc, methodNode.signature,
ArrayUtil.toStringArray(methodNode.exceptions)
);
methodNode.accept(result);
return result;
}
@Nullable
private static SMAPAndMethodNode doCreateMethodNodeFromCompiled(
@NotNull CallableMemberDescriptor callableDescriptor,
@NotNull GenerationState state,
@NotNull Method asmMethod
) {
if (isBuiltInArrayIntrinsic(callableDescriptor)) {
ClassId classId = IntrinsicArrayConstructorsKt.getClassId();
byte[] bytes =
InlineCacheKt.getOrPut(state.getInlineCache().getClassBytes(), classId, IntrinsicArrayConstructorsKt::getBytecode);
return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), classId.asString());
}
assert callableDescriptor instanceof DeserializedCallableMemberDescriptor : "Not a deserialized function or proper: " + callableDescriptor;
KotlinTypeMapper.ContainingClassesInfo containingClasses =
state.getTypeMapper().getContainingClassesForDeserializedCallable((DeserializedCallableMemberDescriptor) callableDescriptor);
ClassId containerId = containingClasses.getImplClassId();
byte[] bytes = InlineCacheKt.getOrPut(state.getInlineCache().getClassBytes(), containerId, () -> {
VirtualFile file = InlineCodegenUtil.findVirtualFile(state, containerId);
if (file == null) {
throw new IllegalStateException("Couldn't find declaration file for " + containerId);
}
try {
return file.contentsToByteArray();
}
catch (IOException e) {
throw new RuntimeException(e);
}
});
return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), containerId.asString());
}
@NotNull
private static SMAPAndMethodNode doCreateMethodNodeFromSource(
@NotNull FunctionDescriptor callableDescriptor,
@NotNull JvmMethodSignature jvmSignature,
@NotNull ExpressionCodegen codegen,
@NotNull CodegenContext context,
boolean callDefault,
@NotNull GenerationState state,
@NotNull Method asmMethod
) {
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(callableDescriptor);
if (!(element instanceof KtNamedFunction || element instanceof KtPropertyAccessor)) {
throw new IllegalStateException("Couldn't find declaration for function " + callableDescriptor);
}
KtDeclarationWithBody inliningFunction = (KtDeclarationWithBody) element;
MethodNode node = new MethodNode(
InlineCodegenUtil.API,
getMethodAsmFlags(callableDescriptor, context.getContextKind(), state) | (callDefault ? Opcodes.ACC_STATIC : 0),
asmMethod.getName(),
asmMethod.getDescriptor(),
null, null
);
//for maxLocals calculation
MethodVisitor maxCalcAdapter = InlineCodegenUtil.wrapWithMaxLocalCalc(node);
CodegenContext parentContext = context.getParentContext();
assert parentContext != null : "Context has no parent: " + context;
MethodContext methodContext = parentContext.intoFunction(callableDescriptor);
SMAP smap;
if (callDefault) {
Type implementationOwner = state.getTypeMapper().mapImplementationOwner(callableDescriptor);
FakeMemberCodegen parentCodegen = new FakeMemberCodegen(
codegen.getParentCodegen(), inliningFunction, (FieldOwnerContext) methodContext.getParentContext(),
implementationOwner.getInternalName()
);
if (!(element instanceof KtNamedFunction)) {
throw new IllegalStateException("Property accessors with default parameters not supported " + callableDescriptor);
}
FunctionCodegen.generateDefaultImplBody(
methodContext, callableDescriptor, maxCalcAdapter, DefaultParameterValueLoader.DEFAULT,
(KtNamedFunction) inliningFunction, parentCodegen, asmMethod
);
smap = createSMAPWithDefaultMapping(inliningFunction, parentCodegen.getOrCreateSourceMapper().getResultMappings());
}
else {
smap = generateMethodBody(maxCalcAdapter, callableDescriptor, methodContext, inliningFunction, jvmSignature, codegen,
null);
}
maxCalcAdapter.visitMaxs(-1, -1);
maxCalcAdapter.visitEnd();
return new SMAPAndMethodNode(node, smap);
}
private static boolean isBuiltInArrayIntrinsic(@NotNull CallableMemberDescriptor callableDescriptor) {
if (callableDescriptor instanceof FictitiousArrayConstructor) return true;
String name = callableDescriptor.getName().asString();
return (name.equals("arrayOf") || name.equals("emptyArray")) &&
callableDescriptor.getContainingDeclaration() instanceof BuiltInsPackageFragment;
}
@NotNull
private InlineResult inlineCall(@NotNull SMAPAndMethodNode nodeAndSmap, boolean callDefault) {
assert delayedHiddenWriting == null : "'putHiddenParamsIntoLocals' should be called after 'processAndPutHiddenParameters(true)'";
DefaultSourceMapper defaultSourceMapper = codegen.getParentCodegen().getOrCreateSourceMapper();
defaultSourceMapper.setCallSiteMarker(new CallSiteMarker(codegen.getLastLineNumber()));
MethodNode node = nodeAndSmap.getNode();
if (callDefault) {
List<DefaultLambda> defaultLambdas = DefaultMethodUtilKt.expandMaskConditionsAndUpdateVariableNodes(
node, maskStartIndex, maskValues, methodHandleInDefaultMethodIndex,
DefaultMethodUtilKt.extractDefaultLambdaOffsetAndDescriptor(jvmSignature, functionDescriptor)
);
for (DefaultLambda lambda : defaultLambdas) {
invocationParamBuilder.buildParameters().getParameterByDeclarationSlot(lambda.getOffset()).setLambda(lambda);
LambdaInfo prev = expressionMap.put(lambda.getOffset(), lambda);
assert prev == null : "Lambda with offset " + lambda.getOffset() + " already exists: " + prev;
}
}
ReifiedTypeParametersUsages reificationResult = reifiedTypeInliner.reifyInstructions(node);
generateClosuresBodies();
//through generation captured parameters will be added to invocationParamBuilder
putClosureParametersOnStack();
addInlineMarker(codegen.v, true);
Parameters parameters = invocationParamBuilder.buildParameters();
InliningContext info = new RootInliningContext(
expressionMap, state, codegen.getInlineNameGenerator().subGenerator(jvmSignature.getAsmMethod().getName()),
callElement, getInlineCallSiteInfo(), reifiedTypeInliner, typeParameterMappings
);
MethodInliner inliner = new MethodInliner(
node, parameters, info, new FieldRemapper(null, null, parameters), isSameModule,
"Method inlining " + callElement.getText(),
createNestedSourceMapper(nodeAndSmap, sourceMapper), info.getCallSiteInfo(),
isInlineOnly(functionDescriptor) ? new InlineOnlySmapSkipper(codegen) : null
); //with captured
LocalVarRemapper remapper = new LocalVarRemapper(parameters, initialFrameSize);
MethodNode adapter = InlineCodegenUtil.createEmptyMethodNode();
//hack to keep linenumber info, otherwise jdi will skip begin of linenumber chain
adapter.visitInsn(Opcodes.NOP);
InlineResult result = inliner.doInline(adapter, remapper, true, LabelOwner.SKIP_ALL);
result.getReifiedTypeParametersUsages().mergeAll(reificationResult);
CallableMemberDescriptor descriptor = getLabelOwnerDescriptor(codegen.getContext());
Set<String> labels = getDeclarationLabels(DescriptorToSourceUtils.descriptorToDeclaration(descriptor), descriptor);
List<MethodInliner.PointForExternalFinallyBlocks> infos = MethodInliner.processReturns(adapter, labels::contains, true, null);
generateAndInsertFinallyBlocks(
adapter, infos, ((StackValue.Local) remapper.remap(parameters.getArgsSizeOnStack() + 1).value).index
);
removeStaticInitializationTrigger(adapter);
if (!InlineCodegenUtil.isFinallyMarkerRequired(codegen.getContext())) {
InlineCodegenUtil.removeFinallyMarkers(adapter);
}
adapter.accept(new MethodBodyVisitor(codegen.v));
addInlineMarker(codegen.v, false);
defaultSourceMapper.setCallSiteMarker(null);
return result;
}
@NotNull
private static CallableMemberDescriptor getLabelOwnerDescriptor(@NotNull MethodContext context) {
if (context.getParentContext() instanceof ClosureContext &&
((ClosureContext) context.getParentContext()).getOriginalSuspendLambdaDescriptor() != null) {
//noinspection ConstantConditions
return ((ClosureContext) context.getParentContext()).getOriginalSuspendLambdaDescriptor();
}
return context.getContextDescriptor();
}
private static void removeStaticInitializationTrigger(@NotNull MethodNode methodNode) {
InsnList insnList = methodNode.instructions;
AbstractInsnNode insn = insnList.getFirst();
while (insn != null) {
if (MultifileClassPartCodegen.isStaticInitTrigger(insn)) {
AbstractInsnNode clinitTriggerCall = insn;
insn = insn.getNext();
insnList.remove(clinitTriggerCall);
}
else {
insn = insn.getNext();
}
}
}
@NotNull
private InlineCallSiteInfo getInlineCallSiteInfo() {
MethodContext context = codegen.getContext();
MemberCodegen<?> parentCodegen = codegen.getParentCodegen();
while (context instanceof InlineLambdaContext) {
CodegenContext closureContext = context.getParentContext();
assert closureContext instanceof ClosureContext : "Parent context of inline lambda should be closure context";
assert closureContext.getParentContext() instanceof MethodContext : "Closure context should appear in method context";
context = (MethodContext) closureContext.getParentContext();
assert parentCodegen instanceof FakeMemberCodegen : "Parent codegen of inlined lambda should be FakeMemberCodegen";
parentCodegen = ((FakeMemberCodegen) parentCodegen).delegate;
}
JvmMethodSignature signature = typeMapper.mapSignatureSkipGeneric(context.getFunctionDescriptor(), context.getContextKind());
return new InlineCallSiteInfo(
parentCodegen.getClassName(), signature.getAsmMethod().getName(), signature.getAsmMethod().getDescriptor()
);
}
private void generateClosuresBodies() {
for (LambdaInfo info : expressionMap.values()) {
info.generateLambdaBody(codegen);
}
}
@NotNull
public static SMAP generateMethodBody(
@NotNull MethodVisitor adapter,
@NotNull FunctionDescriptor descriptor,
@NotNull MethodContext context,
@NotNull KtExpression expression,
@NotNull JvmMethodSignature jvmMethodSignature,
@NotNull ExpressionCodegen codegen,
@Nullable ExpressionLambda lambdaInfo
) {
boolean isLambda = lambdaInfo != null;
GenerationState state = codegen.getState();
// Wrapping for preventing marking actual parent codegen as containing reified markers
FakeMemberCodegen parentCodegen = new FakeMemberCodegen(
codegen.getParentCodegen(), expression, (FieldOwnerContext) context.getParentContext(),
isLambda ? codegen.getParentCodegen().getClassName()
: state.getTypeMapper().mapImplementationOwner(descriptor).getInternalName()
);
FunctionGenerationStrategy strategy;
if (expression instanceof KtCallableReferenceExpression) {
KtCallableReferenceExpression callableReferenceExpression = (KtCallableReferenceExpression) expression;
KtExpression receiverExpression = callableReferenceExpression.getReceiverExpression();
Type receiverType =
receiverExpression != null && codegen.getBindingContext().getType(receiverExpression) != null
? codegen.getState().getTypeMapper().mapType(codegen.getBindingContext().getType(receiverExpression))
: null;
if (isLambda && lambdaInfo.isPropertyReference()) {
Type asmType = state.getTypeMapper().mapClass(lambdaInfo.getClassDescriptor());
PropertyReferenceInfo info = lambdaInfo.getPropertyReferenceInfo();
strategy = new PropertyReferenceCodegen.PropertyReferenceGenerationStrategy(
true, info.getGetFunction(), info.getTarget(), asmType, receiverType,
lambdaInfo.getFunctionWithBodyOrCallableReference(), state, true);
}
else {
strategy = new FunctionReferenceGenerationStrategy(
state,
descriptor,
CallUtilKt
.getResolvedCallWithAssert(callableReferenceExpression.getCallableReference(), codegen.getBindingContext()),
receiverType,
null,
true
);
}
}
else if (expression instanceof KtFunctionLiteral) {
strategy = new ClosureGenerationStrategy(state, (KtDeclarationWithBody) expression);
}
else {
strategy = new FunctionGenerationStrategy.FunctionDefault(state, (KtDeclarationWithBody) expression);
}
FunctionCodegen.generateMethodBody(adapter, descriptor, context, jvmMethodSignature, strategy, parentCodegen);
if (isLambda) {
codegen.propagateChildReifiedTypeParametersUsages(parentCodegen.getReifiedTypeParametersUsages());
}
return createSMAPWithDefaultMapping(expression, parentCodegen.getOrCreateSourceMapper().getResultMappings());
}
private static SMAP createSMAPWithDefaultMapping(
@NotNull KtExpression declaration,
@NotNull List<FileMapping> mappings
) {
PsiFile containingFile = declaration.getContainingFile();
Integer lineNumbers = CodegenUtil.getLineNumberForElement(containingFile, true);
assert lineNumbers != null : "Couldn't extract line count in " + containingFile;
return new SMAP(mappings);
}
private static class FakeMemberCodegen extends MemberCodegen {
private final MemberCodegen delegate;
private final String className;
@SuppressWarnings("unchecked")
public FakeMemberCodegen(
@NotNull MemberCodegen wrapped,
@NotNull KtElement declaration,
@NotNull FieldOwnerContext codegenContext,
@NotNull String className
) {
super(wrapped, declaration, codegenContext);
this.delegate = wrapped;
this.className = className;
}
@Override
protected void generateDeclaration() {
throw new IllegalStateException();
}
@Override
protected void generateBody() {
throw new IllegalStateException();
}
@Override
protected void generateKotlinMetadataAnnotation() {
throw new IllegalStateException();
}
@NotNull
@Override
public NameGenerator getInlineNameGenerator() {
return delegate.getInlineNameGenerator();
}
@NotNull
@Override
//TODO: obtain name from context
public String getClassName() {
return className;
}
}
private void putArgumentOrCapturedToLocalVal(
@NotNull Type type,
@NotNull StackValue stackValue,
int capturedParamIndex,
int parameterIndex,
@NotNull ValueKind kind
) {
boolean isDefaultParameter = kind == ValueKind.DEFAULT_PARAMETER;
if (!isDefaultParameter && shouldPutGeneralValue(type, stackValue)) {
stackValue.put(type, codegen.v);
}
if (!asFunctionInline && Type.VOID_TYPE != type) {
//TODO remap only inlinable closure => otherwise we could get a lot of problem
boolean couldBeRemapped = !shouldPutGeneralValue(type, stackValue) && kind != ValueKind.DEFAULT_PARAMETER;
StackValue remappedValue = couldBeRemapped ? stackValue : null;
ParameterInfo info;
if (capturedParamIndex >= 0) {
CapturedParamDesc capturedParamInfoInLambda = activeLambda.getCapturedVars().get(capturedParamIndex);
info = invocationParamBuilder.addCapturedParam(capturedParamInfoInLambda, capturedParamInfoInLambda.getFieldName(), false);
info.setRemapValue(remappedValue);
}
else {
info = invocationParamBuilder.addNextValueParameter(type, false, remappedValue, parameterIndex);
}
recordParameterValueInLocalVal(
false,
isDefaultParameter || kind == ValueKind.DEFAULT_LAMBDA_CAPTURED_PARAMETER,
info
);
}
}
/*descriptor is null for captured vars*/
private static boolean shouldPutGeneralValue(@NotNull Type type, @NotNull StackValue stackValue) {
//remap only inline functions (and maybe non primitives)
//TODO - clean asserion and remapping logic
if (isPrimitive(type) != isPrimitive(stackValue.type)) {
//don't remap boxing/unboxing primitives - lost identity and perfomance
return true;
}
if (stackValue instanceof StackValue.Local) {
return false;
}
StackValue field = stackValue;
if (stackValue instanceof StackValue.FieldForSharedVar) {
field = ((StackValue.FieldForSharedVar) stackValue).receiver;
}
//check that value corresponds to captured inlining parameter
if (field instanceof StackValue.Field) {
DeclarationDescriptor varDescriptor = ((StackValue.Field) field).descriptor;
//check that variable is inline function parameter
return !(varDescriptor instanceof ParameterDescriptor &&
InlineUtil.isInlineLambdaParameter((ParameterDescriptor) varDescriptor) &&
InlineUtil.isInline(varDescriptor.getContainingDeclaration()));
}
return true;
}
private Runnable recordParameterValueInLocalVal(boolean delayedWritingToLocals, boolean skipStore, @NotNull ParameterInfo... infos) {
int[] index = new int[infos.length];
for (int i = 0; i < infos.length; i++) {
ParameterInfo info = infos[i];
if (!info.isSkippedOrRemapped()) {
index[i] = codegen.getFrameMap().enterTemp(info.getType());
}
else {
index[i] = -1;
}
}
Runnable runnable = () -> {
for (int i = infos.length - 1; i >= 0; i--) {
ParameterInfo info = infos[i];
if (!info.isSkippedOrRemapped()) {
Type type = info.type;
StackValue.Local local = StackValue.local(index[i], type);
if (!skipStore) {
local.store(StackValue.onStack(type), codegen.v);
}
if (info instanceof CapturedParamInfo) {
info.setRemapValue(local);
((CapturedParamInfo) info).setSynthetic(true);
}
}
}
};
if (delayedWritingToLocals) return runnable;
runnable.run();
return null;
}
@Override
public void processAndPutHiddenParameters(boolean justProcess) {
if ((getMethodAsmFlags(functionDescriptor, context.getContextKind(), state) & Opcodes.ACC_STATIC) == 0) {
invocationParamBuilder.addNextParameter(AsmTypes.OBJECT_TYPE, false);
}
for (JvmMethodParameterSignature param : jvmSignature.getValueParameters()) {
if (param.getKind() == JvmMethodParameterKind.VALUE) {
break;
}
invocationParamBuilder.addNextParameter(param.getAsmType(), false);
}
invocationParamBuilder.markValueParametersStart();
List<ParameterInfo> hiddenParameters = invocationParamBuilder.buildParameters().getParameters();
delayedHiddenWriting = recordParameterValueInLocalVal(justProcess, false, hiddenParameters.toArray(new ParameterInfo[hiddenParameters.size()]));
}
private void leaveTemps() {
List<ParameterInfo> infos = invocationParamBuilder.listAllParams();
for (ListIterator<? extends ParameterInfo> iterator = infos.listIterator(infos.size()); iterator.hasPrevious(); ) {
ParameterInfo param = iterator.previous();
if (!param.isSkippedOrRemapped() || CapturedParamInfo.isSynthetic(param)) {
codegen.getFrameMap().leaveTemp(param.type);
}
}
}
/*lambda or callable reference*/
private static boolean isInliningParameter(@NotNull KtExpression expression, @NotNull ValueParameterDescriptor valueParameterDescriptor) {
//TODO deparenthisise typed
KtExpression deparenthesized = KtPsiUtil.deparenthesize(expression);
return InlineUtil.isInlineLambdaParameter(valueParameterDescriptor) &&
isInlinableParameterExpression(deparenthesized);
}
private static boolean isInlinableParameterExpression(@Nullable KtExpression deparenthesized) {
return deparenthesized instanceof KtLambdaExpression ||
deparenthesized instanceof KtNamedFunction ||
deparenthesized instanceof KtCallableReferenceExpression;
}
private LambdaInfo rememberClosure(@NotNull KtExpression expression, @NotNull Type type, @NotNull ValueParameterDescriptor parameter) {
KtExpression lambda = KtPsiUtil.deparenthesize(expression);
assert isInlinableParameterExpression(lambda) : "Couldn't find inline expression in " + expression.getText();
LambdaInfo info =
new ExpressionLambda(lambda, typeMapper, parameter.isCrossinline(), getBoundCallableReferenceReceiver(expression) != null);
ParameterInfo closureInfo = invocationParamBuilder.addNextValueParameter(type, true, null, parameter.getIndex());
closureInfo.setLambda(info);
expressionMap.put(closureInfo.getIndex(), info);
return info;
}
@NotNull
public static Set<String> getDeclarationLabels(@Nullable PsiElement lambdaOrFun, @NotNull DeclarationDescriptor descriptor) {
Set<String> result = new HashSet<>();
if (lambdaOrFun != null) {
Name label = LabelResolver.INSTANCE.getLabelNameIfAny(lambdaOrFun);
if (label != null) {
result.add(label.asString());
}
}
if (!isFunctionLiteral(descriptor)) {
if (!descriptor.getName().isSpecial()) {
result.add(descriptor.getName().asString());
}
result.add(InlineCodegenUtil.FIRST_FUN_LABEL);
}
return result;
}
private void putClosureParametersOnStack() {
for (LambdaInfo next : expressionMap.values()) {
//closure parameters for bounded callable references are generated inplace
if (next.isBoundCallableReference) continue;
putClosureParametersOnStack(next, null);
}
}
private void putClosureParametersOnStack(@NotNull LambdaInfo next, @Nullable StackValue functionReferenceReceiver) {
activeLambda = next;
if (next instanceof ExpressionLambda) {
codegen.pushClosureOnStack(((ExpressionLambda) next).getClassDescriptor(), true, this, functionReferenceReceiver);
}
else if (next instanceof DefaultLambda) {
rememberCapturedForDefaultLambda((DefaultLambda) next);
}
else {
throw new RuntimeException("Unknown lambda: " + next);
}
activeLambda = null;
}
private void rememberCapturedForDefaultLambda(@NotNull DefaultLambda defaultLambda) {
List<CapturedParamDesc> vars = defaultLambda.getCapturedVars();
int paramIndex = 0;
for (CapturedParamDesc captured : vars) {
putArgumentOrCapturedToLocalVal(
captured.getType(),
//HACK: actually parameter would be placed on stack in default function
// also see ValueKind.DEFAULT_LAMBDA_CAPTURED_PARAMETER check
StackValue.onStack(captured.getType()),
paramIndex,
paramIndex,
ValueKind.DEFAULT_LAMBDA_CAPTURED_PARAMETER
);
paramIndex++;
defaultLambda.getParameterOffsetsInDefault().add(invocationParamBuilder.getNextParameterOffset());
}
}
@NotNull
public static CodegenContext getContext(
@NotNull DeclarationDescriptor descriptor, @NotNull GenerationState state, @Nullable KtFile sourceFile
) {
if (descriptor instanceof PackageFragmentDescriptor) {
return new PackageContext((PackageFragmentDescriptor) descriptor, state.getRootContext(), null, sourceFile);
}
DeclarationDescriptor container = descriptor.getContainingDeclaration();
assert container != null : "No container for descriptor: " + descriptor;
CodegenContext parent = getContext(container, state, sourceFile);
if (descriptor instanceof ScriptDescriptor) {
List<ScriptDescriptor> earlierScripts = state.getReplSpecific().getEarlierScriptsForReplInterpreter();
return parent.intoScript(
(ScriptDescriptor) descriptor,
earlierScripts == null ? Collections.emptyList() : earlierScripts,
(ClassDescriptor) descriptor, state.getTypeMapper()
);
}
else if (descriptor instanceof ClassDescriptor) {
OwnerKind kind = DescriptorUtils.isInterface(descriptor) ? OwnerKind.DEFAULT_IMPLS : OwnerKind.IMPLEMENTATION;
return parent.intoClass((ClassDescriptor) descriptor, kind, state);
}
else if (descriptor instanceof FunctionDescriptor) {
return parent.intoFunction((FunctionDescriptor) descriptor);
}
throw new IllegalStateException("Couldn't build context for " + descriptor);
}
@Override
public void genValueAndPut(
@NotNull ValueParameterDescriptor valueParameterDescriptor,
@NotNull KtExpression argumentExpression,
@NotNull Type parameterType,
int parameterIndex
) {
if (isInliningParameter(argumentExpression, valueParameterDescriptor)) {
LambdaInfo lambdaInfo = rememberClosure(argumentExpression, parameterType, valueParameterDescriptor);
KtExpression receiver = getBoundCallableReferenceReceiver(argumentExpression);
if (receiver != null) {
putClosureParametersOnStack(lambdaInfo, codegen.gen(receiver));
}
}
else {
StackValue value = codegen.gen(argumentExpression);
putValueIfNeeded(parameterType, value, ValueKind.GENERAL, valueParameterDescriptor.getIndex());
}
}
private KtExpression getBoundCallableReferenceReceiver(
@NotNull KtExpression argumentExpression
) {
KtExpression deparenthesized = KtPsiUtil.deparenthesize(argumentExpression);
if (deparenthesized instanceof KtCallableReferenceExpression) {
KtExpression receiverExpression = ((KtCallableReferenceExpression) deparenthesized).getReceiverExpression();
if (receiverExpression != null) {
DoubleColonLHS lhs = state.getBindingContext().get(BindingContext.DOUBLE_COLON_LHS, receiverExpression);
if (lhs instanceof DoubleColonLHS.Expression) return receiverExpression;
}
}
return null;
}
@Override
public void putValueIfNeeded(@NotNull Type parameterType, @NotNull StackValue value, @NotNull ValueKind kind, int parameterIndex) {
if (processDefaultMaskOrMethodHandler(value, kind)) return;
assert maskValues.isEmpty() : "Additional default call arguments should be last ones, but " + value;
putArgumentOrCapturedToLocalVal(parameterType, value, -1, parameterIndex, kind);
}
private boolean processDefaultMaskOrMethodHandler(@NotNull StackValue value, @NotNull ValueKind kind) {
if (kind != ValueKind.DEFAULT_MASK && kind != ValueKind.METHOD_HANDLE_IN_DEFAULT) {
return false;
}
assert value instanceof StackValue.Constant : "Additional default method argument should be constant, but " + value;
Object constantValue = ((StackValue.Constant) value).value;
if (kind == ValueKind.DEFAULT_MASK) {
assert constantValue instanceof Integer : "Mask should be of Integer type, but " + constantValue;
maskValues.add((Integer) constantValue);
if (maskStartIndex == -1) {
maskStartIndex = invocationParamBuilder.getNextParameterOffset();
}
}
else {
assert constantValue == null : "Additional method handle for default argument should be null, but " + constantValue;
methodHandleInDefaultMethodIndex = maskStartIndex + maskValues.size();
}
return true;
}
@Override
public void putCapturedValueOnStack(@NotNull StackValue stackValue, @NotNull Type valueType, int paramIndex) {
putArgumentOrCapturedToLocalVal(stackValue.type, stackValue, paramIndex, paramIndex, ValueKind.CAPTURED);
}
private void generateAndInsertFinallyBlocks(
@NotNull MethodNode intoNode,
@NotNull List<MethodInliner.PointForExternalFinallyBlocks> insertPoints,
int offsetForFinallyLocalVar
) {
if (!codegen.hasFinallyBlocks()) return;
Map<AbstractInsnNode, MethodInliner.PointForExternalFinallyBlocks> extensionPoints = new HashMap<>();
for (MethodInliner.PointForExternalFinallyBlocks insertPoint : insertPoints) {
extensionPoints.put(insertPoint.beforeIns, insertPoint);
}
DefaultProcessor processor = new DefaultProcessor(intoNode, offsetForFinallyLocalVar);
int curFinallyDepth = 0;
AbstractInsnNode curInstr = intoNode.instructions.getFirst();
while (curInstr != null) {
processor.processInstruction(curInstr, true);
if (InlineCodegenUtil.isFinallyStart(curInstr)) {
//TODO depth index calc could be more precise
curFinallyDepth = getConstant(curInstr.getPrevious());
}
MethodInliner.PointForExternalFinallyBlocks extension = extensionPoints.get(curInstr);
if (extension != null) {
Label start = new Label();
MethodNode finallyNode = InlineCodegenUtil.createEmptyMethodNode();
finallyNode.visitLabel(start);
ExpressionCodegen finallyCodegen =
new ExpressionCodegen(finallyNode, codegen.getFrameMap(), codegen.getReturnType(),
codegen.getContext(), codegen.getState(), codegen.getParentCodegen());
finallyCodegen.addBlockStackElementsForNonLocalReturns(codegen.getBlockStackElements(), curFinallyDepth);
FrameMap frameMap = finallyCodegen.getFrameMap();
FrameMap.Mark mark = frameMap.mark();
int marker = -1;
Set<LocalVarNodeWrapper> intervals = processor.getLocalVarsMetaInfo().getCurrentIntervals();
for (LocalVarNodeWrapper interval : intervals) {
marker = Math.max(interval.getNode().index + 1, marker);
}
while (frameMap.getCurrentSize() < Math.max(processor.getNextFreeLocalIndex(), offsetForFinallyLocalVar + marker)) {
frameMap.enterTemp(Type.INT_TYPE);
}
finallyCodegen.generateFinallyBlocksIfNeeded(extension.returnType, extension.finallyIntervalEnd.getLabel());
//Exception table for external try/catch/finally blocks will be generated in original codegen after exiting this method
InlineCodegenUtil.insertNodeBefore(finallyNode, intoNode, curInstr);
SimpleInterval splitBy = new SimpleInterval((LabelNode) start.info, extension.finallyIntervalEnd);
processor.getTryBlocksMetaInfo().splitCurrentIntervals(splitBy, true);
//processor.getLocalVarsMetaInfo().splitAndRemoveIntervalsFromCurrents(splitBy);
mark.dropTo();
}
curInstr = curInstr.getNext();
}
processor.substituteTryBlockNodes(intoNode);
//processor.substituteLocalVarTable(intoNode);
}
@NotNull
public static SourceMapper createNestedSourceMapper(@NotNull SMAPAndMethodNode nodeAndSmap, @NotNull SourceMapper parent) {
return new NestedSourceMapper(parent, nodeAndSmap.getSortedRanges(), nodeAndSmap.getClassSMAP().getSourceInfo());
}
static void reportIncrementalInfo(
@NotNull FunctionDescriptor sourceDescriptor,
@NotNull FunctionDescriptor targetDescriptor,
@NotNull JvmMethodSignature jvmSignature,
@NotNull GenerationState state
) {
IncrementalCache incrementalCache = state.getIncrementalCacheForThisTarget();
if (incrementalCache == null) return;
String classFilePath = InlineCodegenUtilsKt.getClassFilePath(sourceDescriptor, state.getTypeMapper(), incrementalCache);
String sourceFilePath = InlineCodegenUtilsKt.getSourceFilePath(targetDescriptor);
Method method = jvmSignature.getAsmMethod();
incrementalCache.registerInline(classFilePath, method.getName() + method.getDescriptor(), sourceFilePath);
}
@Override
public void reorderArgumentsIfNeeded(
@NotNull List<ArgumentAndDeclIndex> actualArgsWithDeclIndex, @NotNull List<? extends Type> valueParameterTypes
) {
}
@Override
public void putHiddenParamsIntoLocals() {
assert delayedHiddenWriting != null : "processAndPutHiddenParameters(true) should be called before putHiddenParamsIntoLocals";
delayedHiddenWriting.run();
delayedHiddenWriting = null;
}
}