From 1d0c37ff20f0cafa7292ca2d1f092005fc3f932b Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Mon, 14 Mar 2016 16:07:52 +0300 Subject: [PATCH] Cache read classes and method nodes for inline It decrease GENERATE phase nearly for 10% --- .../kotlin/codegen/inline/InlineCache.kt | 40 ++++ .../kotlin/codegen/inline/InlineCodegen.java | 188 ++++++++++++------ .../codegen/inline/InlineCodegenUtil.java | 2 +- .../codegen/inline/SMAPAndMethodNode.kt | 27 +-- .../kotlin/codegen/state/GenerationState.kt | 2 + 5 files changed, 190 insertions(+), 69 deletions(-) create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCache.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCache.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCache.kt new file mode 100644 index 00000000000..92d669d44b2 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCache.kt @@ -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 = SLRUMap(30, 20) + val methodNodeById: SLRUMap = SLRUMap(60, 50) +} + +inline fun SLRUMap.getOrPut(key: K, defaultValue: () -> V): V { + val value = get(key) + return if (value == null) { + val answer = defaultValue() + put(key, answer) + answer + } else { + value + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java index eb96aa489f3..bc1eb0dfb0a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java @@ -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() { + @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() { + @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() { + @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) { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java index e393e9958b7..39480c85689 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java @@ -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]; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPAndMethodNode.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPAndMethodNode.kt index 4a911b60a5f..cb0183e2f72 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPAndMethodNode.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPAndMethodNode.kt @@ -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().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 { + return InsnSequence(node.instructions.first, null).filterIsInstance().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) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index e8413e1dedd..0c38b1373f9 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -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)