001 /* 002 * Copyright 2010-2015 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.kotlin.codegen.inline; 018 019 import com.intellij.openapi.project.Project; 020 import com.intellij.openapi.vfs.VirtualFile; 021 import com.intellij.psi.PsiElement; 022 import com.intellij.psi.PsiFile; 023 import org.jetbrains.annotations.NotNull; 024 import org.jetbrains.annotations.Nullable; 025 import org.jetbrains.annotations.TestOnly; 026 import org.jetbrains.kotlin.backend.common.output.OutputFile; 027 import org.jetbrains.kotlin.codegen.MemberCodegen; 028 import org.jetbrains.kotlin.codegen.binding.CodegenBinding; 029 import org.jetbrains.kotlin.codegen.context.CodegenContext; 030 import org.jetbrains.kotlin.codegen.context.MethodContext; 031 import org.jetbrains.kotlin.codegen.context.PackageContext; 032 import org.jetbrains.kotlin.codegen.state.GenerationState; 033 import org.jetbrains.kotlin.codegen.state.JetTypeMapper; 034 import org.jetbrains.kotlin.descriptors.*; 035 import org.jetbrains.kotlin.load.java.JvmAbi; 036 import org.jetbrains.kotlin.load.kotlin.PackageClassUtils; 037 import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils; 038 import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder; 039 import org.jetbrains.kotlin.name.ClassId; 040 import org.jetbrains.kotlin.name.FqName; 041 import org.jetbrains.kotlin.name.Name; 042 import org.jetbrains.kotlin.psi.JetFile; 043 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; 044 import org.jetbrains.kotlin.resolve.DescriptorUtils; 045 import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage; 046 import org.jetbrains.kotlin.resolve.jvm.AsmTypes; 047 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 048 import org.jetbrains.kotlin.serialization.ProtoBuf; 049 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor; 050 import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf; 051 import org.jetbrains.kotlin.types.expressions.OperatorConventions; 052 import org.jetbrains.org.objectweb.asm.*; 053 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; 054 import org.jetbrains.org.objectweb.asm.tree.*; 055 import org.jetbrains.org.objectweb.asm.util.Textifier; 056 import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor; 057 058 import java.io.IOException; 059 import java.io.PrintWriter; 060 import java.io.StringWriter; 061 import java.util.ListIterator; 062 063 import static kotlin.KotlinPackage.substringAfterLast; 064 import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName; 065 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isTrait; 066 067 public class InlineCodegenUtil { 068 public static final boolean GENERATE_SMAP = true; 069 public static final int API = Opcodes.ASM5; 070 071 public static final String CAPTURED_FIELD_PREFIX = "$"; 072 public static final String THIS$0 = "this$0"; 073 public static final String RECEIVER$0 = "receiver$0"; 074 public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$"; 075 public static final String FIRST_FUN_LABEL = "$$$$$ROOT$$$$$"; 076 public static final String NUMBERED_FUNCTION_PREFIX = "kotlin/jvm/functions/Function"; 077 public static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"; 078 public static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall"; 079 public static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall"; 080 public static final String INLINE_MARKER_GOTO_TRY_CATCH_BLOCK_END = "goToTryCatchBlockEnd"; 081 082 @Nullable 083 public static SMAPAndMethodNode getMethodNode( 084 byte[] classData, 085 final String methodName, 086 final String methodDescriptor, 087 ClassId classId 088 ) throws ClassNotFoundException, IOException { 089 ClassReader cr = new ClassReader(classData); 090 final MethodNode[] node = new MethodNode[1]; 091 final String[] debugInfo = new String[2]; 092 final int[] lines = new int[2]; 093 lines[0] = Integer.MAX_VALUE; 094 lines[1] = Integer.MIN_VALUE; 095 cr.accept(new ClassVisitor(API) { 096 097 @Override 098 public void visitSource(String source, String debug) { 099 super.visitSource(source, debug); 100 debugInfo[0] = source; 101 debugInfo[1] = debug; 102 } 103 104 @Override 105 public MethodVisitor visitMethod( 106 int access, 107 @NotNull String name, 108 @NotNull String desc, 109 String signature, 110 String[] exceptions 111 ) { 112 if (methodName.equals(name) && methodDescriptor.equals(desc)) { 113 node[0] = new MethodNode(API, access, name, desc, signature, exceptions) { 114 @Override 115 public void visitLineNumber(int line, @NotNull Label start) { 116 super.visitLineNumber(line, start); 117 lines[0] = Math.min(lines[0], line); 118 lines[1] = Math.max(lines[1], line); 119 } 120 }; 121 return node[0]; 122 } 123 return null; 124 } 125 }, ClassReader.SKIP_FRAMES | (GENERATE_SMAP ? 0 : ClassReader.SKIP_DEBUG)); 126 127 SMAP smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classId.toString(), lines[0], lines[1]); 128 return new SMAPAndMethodNode(node[0], smap); 129 } 130 131 public static void initDefaultSourceMappingIfNeeded(@NotNull CodegenContext context, @NotNull MemberCodegen codegen, @NotNull GenerationState state) { 132 if (state.isInlineEnabled()) { 133 CodegenContext<?> parentContext = context.getParentContext(); 134 while (parentContext != null) { 135 if (parentContext instanceof MethodContext) { 136 if (((MethodContext) parentContext).isInlineFunction()) { 137 //just init default one to one mapping 138 codegen.getOrCreateSourceMapper(); 139 break; 140 } 141 } 142 parentContext = parentContext.getParentContext(); 143 } 144 } 145 } 146 147 @NotNull 148 public static VirtualFile getVirtualFileForCallable(@NotNull ClassId containerClassId, @NotNull GenerationState state) { 149 JvmVirtualFileFinder fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(state.getProject()); 150 VirtualFile file = fileFinder.findVirtualFileWithHeader(containerClassId); 151 if (file == null) { 152 throw new IllegalStateException("Couldn't find declaration file for " + containerClassId); 153 } 154 return file; 155 } 156 157 public static ClassId getContainerClassIdForInlineCallable(DeserializedSimpleFunctionDescriptor deserializedDescriptor) { 158 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration(); 159 ClassId containerClassId; 160 if (parentDeclaration instanceof PackageFragmentDescriptor) { 161 ProtoBuf.Callable proto = deserializedDescriptor.getProto(); 162 if (!proto.hasExtension(JvmProtoBuf.implClassName)) { 163 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor); 164 } 165 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JvmProtoBuf.implClassName)); 166 containerClassId = new ClassId(((PackageFragmentDescriptor) parentDeclaration).getFqName(), name); 167 } else { 168 containerClassId = getContainerClassId(deserializedDescriptor); 169 } 170 if (containerClassId == null) { 171 throw new IllegalStateException("Couldn't find container FQName for " + deserializedDescriptor.getName()); 172 } 173 return containerClassId; 174 } 175 176 @Nullable 177 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalClassName) { 178 FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName(); 179 String classNameWithDollars = substringAfterLast(internalClassName, "/", internalClassName); 180 JvmVirtualFileFinder fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project); 181 //TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file 182 // we construct valid.package.name/RelativeClassNameAsSingleName that should work in compiler, but fails for inner classes in IDE 183 return fileFinder.findVirtualFileWithHeader(new ClassId(packageFqName, Name.identifier(classNameWithDollars))); 184 } 185 186 //TODO: navigate to inner classes 187 @Nullable 188 public static ClassId getContainerClassId(@NotNull DeclarationDescriptor referencedDescriptor) { 189 ClassOrPackageFragmentDescriptor 190 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false); 191 if (containerDescriptor instanceof PackageFragmentDescriptor) { 192 return PackageClassUtils.getPackageClassId(getFqName(containerDescriptor).toSafe()); 193 } 194 if (containerDescriptor instanceof ClassDescriptor) { 195 ClassId classId = DescriptorUtilPackage.getClassId((ClassDescriptor) containerDescriptor); 196 if (isTrait(containerDescriptor)) { 197 FqName relativeClassName = classId.getRelativeClassName(); 198 //TODO test nested trait fun inlining 199 classId = new ClassId(classId.getPackageFqName(), Name.identifier(relativeClassName.shortName().asString() + JvmAbi.TRAIT_IMPL_SUFFIX)); 200 } 201 return classId; 202 } 203 return null; 204 } 205 206 public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) { 207 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper); 208 } 209 210 private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) { 211 if (currentDescriptor instanceof PackageFragmentDescriptor) { 212 PsiFile file = getContainingFile(codegenContext); 213 214 Type packagePartType; 215 if (file == null) { 216 //in case package fragment clinit 217 assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext; 218 packagePartType = ((PackageContext) codegenContext).getPackagePartType(); 219 } else { 220 packagePartType = PackagePartClassUtils.getPackagePartType((JetFile) file); 221 } 222 223 if (packagePartType == null) { 224 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 225 //noinspection ConstantConditions 226 throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() ); 227 } 228 229 return packagePartType.getInternalName(); 230 } 231 else if (currentDescriptor instanceof ClassifierDescriptor) { 232 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor); 233 return type.getInternalName(); 234 } else if (currentDescriptor instanceof FunctionDescriptor) { 235 ClassDescriptor descriptor = 236 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor); 237 if (descriptor != null) { 238 Type type = typeMapper.mapType(descriptor); 239 return type.getInternalName(); 240 } 241 } 242 243 //TODO: add suffix for special case 244 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString(); 245 246 //noinspection ConstantConditions 247 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix; 248 } 249 250 public static boolean isInvokeOnLambda(@NotNull String owner, @NotNull String name) { 251 return OperatorConventions.INVOKE.asString().equals(name) && 252 owner.startsWith(NUMBERED_FUNCTION_PREFIX) && 253 isInteger(owner.substring(NUMBERED_FUNCTION_PREFIX.length())); 254 } 255 256 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) { 257 return "<init>".equals(methodName) && isAnonymousClass(internalName); 258 } 259 260 public static boolean isAnonymousSingletonLoad(@NotNull String internalName, @NotNull String fieldName) { 261 return JvmAbi.INSTANCE_FIELD.equals(fieldName) && isAnonymousClass(internalName); 262 } 263 264 public static boolean isAnonymousClass(String internalName) { 265 String shortName = getLastNamePart(internalName); 266 int index = shortName.lastIndexOf("$"); 267 268 if (index < 0) { 269 return false; 270 } 271 272 String suffix = shortName.substring(index + 1); 273 return isInteger(suffix); 274 } 275 276 @NotNull 277 private static String getLastNamePart(@NotNull String internalName) { 278 int index = internalName.lastIndexOf("/"); 279 return index < 0 ? internalName : internalName.substring(index + 1); 280 } 281 282 @Nullable 283 public static PsiFile getContainingFile(CodegenContext codegenContext) { 284 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 285 PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor); 286 if (psiElement != null) { 287 return psiElement.getContainingFile(); 288 } 289 return null; 290 } 291 292 @NotNull 293 public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) { 294 return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode); 295 } 296 297 private static boolean isInteger(@NotNull String string) { 298 if (string.isEmpty()) { 299 return false; 300 } 301 302 for (int i = 0; i < string.length(); i++) { 303 if (!Character.isDigit(string.charAt(i))) { 304 return false; 305 } 306 } 307 308 return true; 309 } 310 311 public static boolean isCapturedFieldName(@NotNull String fieldName) { 312 // TODO: improve this heuristic 313 return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) || 314 THIS$0.equals(fieldName) || 315 RECEIVER$0.equals(fieldName); 316 } 317 318 public static boolean isReturnOpcode(int opcode) { 319 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN; 320 } 321 322 //marked return could be either non-local or local in case of labeled lambda self-returns 323 public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) { 324 assert isReturnOpcode(returnIns.getOpcode()) : "Should be called on return instruction, but " + returnIns; 325 AbstractInsnNode globalFlag = returnIns.getPrevious(); 326 return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner); 327 } 328 329 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) { 330 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false); 331 } 332 333 public static Type getReturnType(int opcode) { 334 switch (opcode) { 335 case Opcodes.RETURN: return Type.VOID_TYPE; 336 case Opcodes.IRETURN: return Type.INT_TYPE; 337 case Opcodes.DRETURN: return Type.DOUBLE_TYPE; 338 case Opcodes.FRETURN: return Type.FLOAT_TYPE; 339 case Opcodes.LRETURN: return Type.LONG_TYPE; 340 default: return AsmTypes.OBJECT_TYPE; 341 } 342 } 343 344 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) { 345 InsnList instructions = to.instructions; 346 ListIterator<AbstractInsnNode> iterator = from.instructions.iterator(); 347 while (iterator.hasNext()) { 348 AbstractInsnNode next = iterator.next(); 349 instructions.insertBefore(beforeNode, next); 350 } 351 } 352 353 354 public static MethodNode createEmptyMethodNode() { 355 return new MethodNode(API, 0, "fake", "()V", null, null); 356 } 357 358 static boolean isLineNumberOrLabel(@Nullable AbstractInsnNode node) { 359 return node instanceof LineNumberNode || node instanceof LabelNode; 360 } 361 362 @NotNull 363 public static LabelNode firstLabelInChain(@NotNull LabelNode node) { 364 LabelNode curNode = node; 365 while (curNode.getPrevious() instanceof LabelNode) { 366 curNode = (LabelNode) curNode.getPrevious(); 367 } 368 return curNode; 369 } 370 371 @NotNull 372 public static String getNodeText(@Nullable MethodNode node) { 373 return getNodeText(node, new Textifier()); 374 } 375 376 @NotNull 377 public static String getNodeText(@Nullable MethodNode node, @NotNull Textifier textifier) { 378 if (node == null) { 379 return "Not generated"; 380 } 381 node.accept(new TraceMethodVisitor(textifier)); 382 StringWriter sw = new StringWriter(); 383 textifier.print(new PrintWriter(sw)); 384 sw.flush(); 385 return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString(); 386 } 387 388 @NotNull 389 /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) { 390 //try to find just compiled classes then in dependencies 391 try { 392 OutputFile outputFile = state.getFactory().get(internalName + ".class"); 393 if (outputFile != null) { 394 return new ClassReader(outputFile.asByteArray()); 395 } else { 396 VirtualFile file = findVirtualFile(state.getProject(), internalName); 397 if (file == null) { 398 throw new RuntimeException("Couldn't find virtual file for " + internalName); 399 } 400 return new ClassReader(file.contentsToByteArray()); 401 } 402 } 403 catch (IOException e) { 404 throw new RuntimeException(e); 405 } 406 } 407 408 public static void generateGoToTryCatchBlockEndMarker(@NotNull InstructionAdapter v) { 409 v.invokestatic(INLINE_MARKER_CLASS_NAME, INLINE_MARKER_GOTO_TRY_CATCH_BLOCK_END, "()V", false); 410 } 411 412 public static boolean isGoToTryCatchBlockEnd(@NotNull AbstractInsnNode node) { 413 if (!(node.getPrevious() instanceof MethodInsnNode)) return false; 414 MethodInsnNode previous = (MethodInsnNode) node.getPrevious(); 415 return node.getOpcode() == Opcodes.GOTO && 416 INLINE_MARKER_CLASS_NAME.equals(previous.owner) && 417 INLINE_MARKER_GOTO_TRY_CATCH_BLOCK_END.equals(previous.name); 418 } 419 420 public static class LabelTextifier extends Textifier { 421 422 public LabelTextifier() { 423 super(API); 424 } 425 426 @Nullable 427 @TestOnly 428 @SuppressWarnings("UnusedDeclaration") 429 public String getLabelNameIfExists(@NotNull Label l) { 430 return labelNames == null ? null : labelNames.get(l); 431 } 432 } 433 434 public static void addInlineMarker( 435 @NotNull InstructionAdapter v, 436 boolean isStartNotEnd 437 ) { 438 v.visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME, 439 (isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME), 440 "()V", false); 441 } 442 443 public static int getLoadStoreArgSize(int opcode) { 444 return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1; 445 } 446 }