Cache read classes and method nodes for inline

It decrease GENERATE phase nearly for 10%
This commit is contained in:
Denis Zharkov
2016-03-14 16:07:52 +03:00
parent 26081bf817
commit 1d0c37ff20
5 changed files with 190 additions and 69 deletions
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2016 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.util.containers.SLRUMap
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.org.objectweb.asm.commons.Method
data class MethodId(val containingFqName: FqName, val method: Method)
class InlineCache {
val classBytes: SLRUMap<ClassId, ByteArray> = SLRUMap(30, 20)
val methodNodeById: SLRUMap<MethodId, SMAPAndMethodNode> = SLRUMap(60, 50)
}
inline fun <K, V> SLRUMap<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
val value = get(key)
return if (value == null) {
val answer = defaultValue()
put(key, answer)
answer
} else {
value
}
}
@@ -19,6 +19,8 @@ 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 kotlin.jvm.functions.Function0;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.CodegenUtil;
@@ -64,6 +66,7 @@ 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.API;
import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.addInlineMarker;
import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.getConstant;
import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isFunctionLiteral;
@@ -184,88 +187,161 @@ public class InlineCodegen extends CallGenerator {
@NotNull
static SMAPAndMethodNode createMethodNode(
@NotNull FunctionDescriptor functionDescriptor,
@NotNull final FunctionDescriptor functionDescriptor,
@NotNull JvmMethodSignature jvmSignature,
@NotNull ExpressionCodegen codegen,
@NotNull CodegenContext context,
boolean callDefault,
@NotNull GenerationState state) throws IOException {
@NotNull final GenerationState state
) {
KotlinTypeMapper typeMapper = state.getTypeMapper();
Method asmMethod = callDefault
final Method asmMethod = callDefault
? typeMapper.mapDefaultMethod(functionDescriptor, context.getContextKind())
: jvmSignature.getAsmMethod();
MethodId methodId = new MethodId(DescriptorUtils.getFqNameSafe(functionDescriptor.getContainingDeclaration()), asmMethod);
if (!isBuiltInArrayIntrinsic(functionDescriptor) && !(functionDescriptor instanceof DeserializedSimpleFunctionDescriptor)) {
return doCreateMethodNodeFromSource(functionDescriptor, jvmSignature, codegen, context, callDefault, state, asmMethod);
}
SMAPAndMethodNode resultInCache =
InlineCacheKt
.getOrPut(state.getInlineCache().getMethodNodeById(), methodId, new Function0<SMAPAndMethodNode>() {
@Override
public SMAPAndMethodNode invoke() {
return doCreateMethodNodeFromCompiled(functionDescriptor,
state, asmMethod);
}
});
return resultInCache.copyWithNewNode(cloneMethodNode(resultInCache.getNode()));
}
@NotNull
private static MethodNode cloneMethodNode(@NotNull MethodNode methodNode) {
methodNode.instructions.resetLabels();
MethodNode result = new MethodNode(
API, methodNode.access, methodNode.name, methodNode.desc, methodNode.signature,
methodNode.exceptions.toArray(ArrayUtil.EMPTY_STRING_ARRAY));
methodNode.accept(result);
return result;
}
@NotNull
private static SMAPAndMethodNode doCreateMethodNodeFromCompiled(
@NotNull FunctionDescriptor functionDescriptor,
@NotNull final GenerationState state,
@NotNull Method asmMethod
) {
KotlinTypeMapper typeMapper = state.getTypeMapper();
SMAPAndMethodNode nodeAndSMAP;
if (isBuiltInArrayIntrinsic(functionDescriptor)) {
ClassId classId = IntrinsicArrayConstructorsKt.getClassId();
byte[] bytes = InlineCacheKt.getOrPut(state.getInlineCache().getClassBytes(), classId, new Function0<byte[]>() {
@Override
public byte[] invoke() {
return IntrinsicArrayConstructorsKt.getBytecode();
}
});
nodeAndSMAP = InlineCodegenUtil.getMethodNode(
IntrinsicArrayConstructorsKt.getBytecode(),
bytes,
asmMethod.getName(),
asmMethod.getDescriptor(),
IntrinsicArrayConstructorsKt.getClassId()
classId
);
if (nodeAndSMAP == null) {
throw new IllegalStateException("Couldn't obtain array constructor body for " + descriptorName(functionDescriptor));
}
return nodeAndSMAP;
}
else if (functionDescriptor instanceof DeserializedSimpleFunctionDescriptor) {
KotlinTypeMapper.ContainingClassesInfo containingClasses = typeMapper.getContainingClassesForDeserializedCallable(
(DeserializedSimpleFunctionDescriptor) functionDescriptor);
ClassId containerId = containingClasses.getImplClassId();
VirtualFile file = InlineCodegenUtil.findVirtualFile(state, containerId);
if (file == null) {
throw new IllegalStateException("Couldn't find declaration file for " + containerId);
assert functionDescriptor instanceof DeserializedSimpleFunctionDescriptor;
KotlinTypeMapper.ContainingClassesInfo containingClasses = typeMapper.getContainingClassesForDeserializedCallable(
(DeserializedSimpleFunctionDescriptor) functionDescriptor);
final ClassId containerId = containingClasses.getImplClassId();
byte[] bytes = InlineCacheKt.getOrPut(state.getInlineCache().getClassBytes(), containerId, new Function0<byte[]>() {
@Override
public byte[] invoke() {
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);
}
}
});
nodeAndSMAP = InlineCodegenUtil.getMethodNode(
file.contentsToByteArray(), asmMethod.getName(), asmMethod.getDescriptor(), containerId
nodeAndSMAP = InlineCodegenUtil.getMethodNode(
bytes, asmMethod.getName(), asmMethod.getDescriptor(), containerId
);
if (nodeAndSMAP == null) {
throw new IllegalStateException("Couldn't obtain compiled function body for " + descriptorName(functionDescriptor));
}
return nodeAndSMAP;
}
@NotNull
private static SMAPAndMethodNode doCreateMethodNodeFromSource(
@NotNull FunctionDescriptor functionDescriptor,
@NotNull JvmMethodSignature jvmSignature,
@NotNull ExpressionCodegen codegen,
@NotNull CodegenContext context,
boolean callDefault,
@NotNull GenerationState state,
@NotNull Method asmMethod
) {
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor);
if (!(element instanceof KtNamedFunction)) {
throw new IllegalStateException("Couldn't find declaration for function " + descriptorName(functionDescriptor));
}
KtNamedFunction inliningFunction = (KtNamedFunction) element;
MethodNode node = new MethodNode(InlineCodegenUtil.API,
getMethodAsmFlags(functionDescriptor, context.getContextKind()) | (callDefault ? Opcodes.ACC_STATIC : 0),
asmMethod.getName(),
asmMethod.getDescriptor(),
null,
null);
//for maxLocals calculation
MethodVisitor maxCalcAdapter = InlineCodegenUtil.wrapWithMaxLocalCalc(node);
MethodContext methodContext = context.getParentContext().intoFunction(functionDescriptor);
SMAP smap;
if (callDefault) {
Type implementationOwner = state.getTypeMapper().mapImplementationOwner(functionDescriptor);
FakeMemberCodegen parentCodegen = new FakeMemberCodegen(codegen.getParentCodegen(), inliningFunction,
(FieldOwnerContext) methodContext.getParentContext(),
implementationOwner.getInternalName());
FunctionCodegen.generateDefaultImplBody(
methodContext, functionDescriptor, maxCalcAdapter, DefaultParameterValueLoader.DEFAULT,
inliningFunction, parentCodegen, asmMethod
);
if (nodeAndSMAP == null) {
throw new IllegalStateException("Couldn't obtain compiled function body for " + descriptorName(functionDescriptor));
}
smap = createSMAPWithDefaultMapping(inliningFunction, parentCodegen.getOrCreateSourceMapper().getResultMappings());
}
else {
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor);
if (!(element instanceof KtNamedFunction)) {
throw new IllegalStateException("Couldn't find declaration for function " + descriptorName(functionDescriptor));
}
KtNamedFunction inliningFunction = (KtNamedFunction) element;
MethodNode node = new MethodNode(InlineCodegenUtil.API,
getMethodAsmFlags(functionDescriptor, context.getContextKind()) | (callDefault ? Opcodes.ACC_STATIC : 0),
asmMethod.getName(),
asmMethod.getDescriptor(),
null,
null);
//for maxLocals calculation
MethodVisitor maxCalcAdapter = InlineCodegenUtil.wrapWithMaxLocalCalc(node);
MethodContext methodContext = context.getParentContext().intoFunction(functionDescriptor);
SMAP smap;
if (callDefault) {
Type implementationOwner = typeMapper.mapImplementationOwner(functionDescriptor);
FakeMemberCodegen parentCodegen = new FakeMemberCodegen(codegen.getParentCodegen(), inliningFunction,
(FieldOwnerContext) methodContext.getParentContext(),
implementationOwner.getInternalName());
FunctionCodegen.generateDefaultImplBody(
methodContext, functionDescriptor, maxCalcAdapter, DefaultParameterValueLoader.DEFAULT,
inliningFunction, parentCodegen, asmMethod
);
smap = createSMAPWithDefaultMapping(inliningFunction, parentCodegen.getOrCreateSourceMapper().getResultMappings());
}
else {
smap = generateMethodBody(maxCalcAdapter, functionDescriptor, methodContext, inliningFunction, jvmSignature, false, codegen, state);
}
nodeAndSMAP = new SMAPAndMethodNode(node, smap);
maxCalcAdapter.visitMaxs(-1, -1);
maxCalcAdapter.visitEnd();
smap = generateMethodBody(maxCalcAdapter, functionDescriptor, methodContext, inliningFunction, jvmSignature, false, codegen, state);
}
return nodeAndSMAP;
maxCalcAdapter.visitMaxs(-1, -1);
maxCalcAdapter.visitEnd();
return new SMAPAndMethodNode(node, smap);
}
private static boolean isBuiltInArrayIntrinsic(@NotNull FunctionDescriptor functionDescriptor) {
@@ -86,7 +86,7 @@ public class InlineCodegenUtil {
final String methodName,
final String methodDescriptor,
ClassId classId
) throws IOException {
) {
ClassReader cr = new ClassReader(classData);
final MethodNode[] node = new MethodNode[1];
final String[] debugInfo = new String[2];
@@ -23,19 +23,22 @@ import java.util.*
//TODO comment
class SMAPAndMethodNode(val node: MethodNode, val classSMAP: SMAP) {
private val lineNumbers =
InsnSequence(node.instructions.first, null).filterIsInstance<LineNumberNode>().map { node ->
val index = classSMAP.intervals.binarySearch(RangeMapping(node.line, node.line, 1), Comparator {
value, key ->
if (key.dest in value) 0 else RangeMapping.Comparator.compare(value, key)
})
if (index < 0) {
error("Unmapped label in inlined function $node ${node.line}")
}
LabelAndMapping(node, classSMAP.intervals[index])
}.toList()
val ranges = createLineNumberSequence(node, classSMAP).map { it.mapper }.distinct().toList()
val ranges = lineNumbers.asSequence().map { it.mapper }.distinct().toList()
fun copyWithNewNode(newMethodNode: MethodNode) = SMAPAndMethodNode(newMethodNode, classSMAP)
}
private fun createLineNumberSequence(node: MethodNode, classSMAP: SMAP): Sequence<LabelAndMapping> {
return InsnSequence(node.instructions.first, null).filterIsInstance<LineNumberNode>().map { node ->
val index = classSMAP.intervals.binarySearch(RangeMapping(node.line, node.line, 1), Comparator {
value, key ->
if (key.dest in value) 0 else RangeMapping.Comparator.compare(value, key)
})
if (index < 0) {
error("Unmapped label in inlined function $node ${node.line}")
}
LabelAndMapping(node, classSMAP.intervals[index])
}
}
class LabelAndMapping(val lineNumberNode: LineNumberNode, val mapper: RangeMapping)
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.context.RootContext
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.codegen.inline.InlineCache
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.codegen.optimization.OptimizationClassBuilderFactory
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
@@ -89,6 +90,7 @@ class GenerationState @JvmOverloads constructor(
}
val fileClassesProvider: CodegenFileClassesProvider = CodegenFileClassesProvider()
val inlineCache: InlineCache = InlineCache()
private fun getIncrementalCacheForThisTarget() =
if (incrementalCompilationComponents != null && targetId != null)