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.*; 030 import org.jetbrains.kotlin.codegen.state.GenerationState; 031 import org.jetbrains.kotlin.codegen.state.JetTypeMapper; 032 import org.jetbrains.kotlin.descriptors.*; 033 import org.jetbrains.kotlin.fileClasses.FileClassesPackage; 034 import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider; 035 import org.jetbrains.kotlin.load.java.JvmAbi; 036 import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder; 037 import org.jetbrains.kotlin.load.kotlin.PackageClassUtils; 038 import org.jetbrains.kotlin.name.ClassId; 039 import org.jetbrains.kotlin.name.FqName; 040 import org.jetbrains.kotlin.name.Name; 041 import org.jetbrains.kotlin.psi.JetFile; 042 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; 043 import org.jetbrains.kotlin.resolve.DescriptorUtils; 044 import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage; 045 import org.jetbrains.kotlin.resolve.jvm.AsmTypes; 046 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 047 import org.jetbrains.kotlin.serialization.ProtoBuf; 048 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor; 049 import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf; 050 import org.jetbrains.kotlin.types.expressions.OperatorConventions; 051 import org.jetbrains.org.objectweb.asm.*; 052 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; 053 import org.jetbrains.org.objectweb.asm.tree.*; 054 import org.jetbrains.org.objectweb.asm.util.Textifier; 055 import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor; 056 057 import java.io.IOException; 058 import java.io.PrintWriter; 059 import java.io.StringWriter; 060 import java.util.ListIterator; 061 062 import static kotlin.KotlinPackage.substringAfterLast; 063 import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName; 064 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isTrait; 065 066 public class InlineCodegenUtil { 067 public static final boolean GENERATE_SMAP = true; 068 public static final int API = Opcodes.ASM5; 069 070 public static final String CAPTURED_FIELD_PREFIX = "$"; 071 public static final String THIS$0 = "this$0"; 072 public static final String RECEIVER$0 = "receiver$0"; 073 public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$"; 074 public static final String FIRST_FUN_LABEL = "$$$$$ROOT$$$$$"; 075 public static final String NUMBERED_FUNCTION_PREFIX = "kotlin/jvm/functions/Function"; 076 public static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"; 077 public static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall"; 078 public static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall"; 079 public static final String INLINE_MARKER_FINALLY_START = "finallyStart"; 080 public static final String INLINE_MARKER_FINALLY_END = "finallyEnd"; 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( 207 @NotNull CodegenContext codegenContext, 208 @NotNull JetTypeMapper typeMapper, 209 @NotNull JvmFileClassesProvider fileClassesManager 210 ) { 211 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper, fileClassesManager); 212 } 213 214 private static String getInlineName( 215 @NotNull CodegenContext codegenContext, 216 @NotNull DeclarationDescriptor currentDescriptor, 217 @NotNull JetTypeMapper typeMapper, 218 @NotNull JvmFileClassesProvider fileClassesProvider 219 ) { 220 if (currentDescriptor instanceof PackageFragmentDescriptor) { 221 PsiFile file = getContainingFile(codegenContext); 222 223 Type implementationOwnerType; 224 if (file == null) { 225 implementationOwnerType = CodegenContextUtil.getImplementationOwnerClassType(codegenContext); 226 } else { 227 implementationOwnerType = FileClassesPackage.getFileClassType(fileClassesProvider, (JetFile) file); 228 } 229 230 if (implementationOwnerType == null) { 231 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 232 //noinspection ConstantConditions 233 throw new RuntimeException("Couldn't find declaration for " + 234 contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() + 235 "; context: " + codegenContext); 236 } 237 238 return implementationOwnerType.getInternalName(); 239 } 240 else if (currentDescriptor instanceof ClassifierDescriptor) { 241 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor); 242 return type.getInternalName(); 243 } else if (currentDescriptor instanceof FunctionDescriptor) { 244 ClassDescriptor descriptor = 245 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_CALLABLE, (FunctionDescriptor) currentDescriptor); 246 if (descriptor != null) { 247 Type type = typeMapper.mapType(descriptor); 248 return type.getInternalName(); 249 } 250 } 251 252 //TODO: add suffix for special case 253 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString(); 254 255 //noinspection ConstantConditions 256 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper, fileClassesProvider) + "$" + suffix; 257 } 258 259 public static boolean isInvokeOnLambda(@NotNull String owner, @NotNull String name) { 260 return OperatorConventions.INVOKE.asString().equals(name) && 261 owner.startsWith(NUMBERED_FUNCTION_PREFIX) && 262 isInteger(owner.substring(NUMBERED_FUNCTION_PREFIX.length())); 263 } 264 265 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) { 266 return "<init>".equals(methodName) && isAnonymousClass(internalName); 267 } 268 269 public static boolean isAnonymousSingletonLoad(@NotNull String internalName, @NotNull String fieldName) { 270 return JvmAbi.INSTANCE_FIELD.equals(fieldName) && isAnonymousClass(internalName); 271 } 272 273 public static boolean isAnonymousClass(String internalName) { 274 String shortName = getLastNamePart(internalName); 275 int index = shortName.lastIndexOf("$"); 276 277 if (index < 0) { 278 return false; 279 } 280 281 String suffix = shortName.substring(index + 1); 282 return isInteger(suffix); 283 } 284 285 @NotNull 286 private static String getLastNamePart(@NotNull String internalName) { 287 int index = internalName.lastIndexOf("/"); 288 return index < 0 ? internalName : internalName.substring(index + 1); 289 } 290 291 @Nullable 292 public static PsiFile getContainingFile(CodegenContext codegenContext) { 293 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 294 PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor); 295 if (psiElement != null) { 296 return psiElement.getContainingFile(); 297 } 298 return null; 299 } 300 301 @NotNull 302 public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) { 303 return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode); 304 } 305 306 private static boolean isInteger(@NotNull String string) { 307 if (string.isEmpty()) { 308 return false; 309 } 310 311 for (int i = 0; i < string.length(); i++) { 312 if (!Character.isDigit(string.charAt(i))) { 313 return false; 314 } 315 } 316 317 return true; 318 } 319 320 public static boolean isCapturedFieldName(@NotNull String fieldName) { 321 // TODO: improve this heuristic 322 return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) || 323 THIS$0.equals(fieldName) || 324 RECEIVER$0.equals(fieldName); 325 } 326 327 public static boolean isReturnOpcode(int opcode) { 328 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN; 329 } 330 331 //marked return could be either non-local or local in case of labeled lambda self-returns 332 public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) { 333 if (!isReturnOpcode(returnIns.getOpcode())) { 334 return false; 335 } 336 AbstractInsnNode globalFlag = returnIns.getPrevious(); 337 return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner); 338 } 339 340 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) { 341 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false); 342 } 343 344 public static Type getReturnType(int opcode) { 345 switch (opcode) { 346 case Opcodes.RETURN: return Type.VOID_TYPE; 347 case Opcodes.IRETURN: return Type.INT_TYPE; 348 case Opcodes.DRETURN: return Type.DOUBLE_TYPE; 349 case Opcodes.FRETURN: return Type.FLOAT_TYPE; 350 case Opcodes.LRETURN: return Type.LONG_TYPE; 351 default: return AsmTypes.OBJECT_TYPE; 352 } 353 } 354 355 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) { 356 InsnList instructions = to.instructions; 357 ListIterator<AbstractInsnNode> iterator = from.instructions.iterator(); 358 while (iterator.hasNext()) { 359 AbstractInsnNode next = iterator.next(); 360 instructions.insertBefore(beforeNode, next); 361 } 362 } 363 364 365 public static MethodNode createEmptyMethodNode() { 366 return new MethodNode(API, 0, "fake", "()V", null, null); 367 } 368 369 @NotNull 370 public static LabelNode firstLabelInChain(@NotNull LabelNode node) { 371 LabelNode curNode = node; 372 while (curNode.getPrevious() instanceof LabelNode) { 373 curNode = (LabelNode) curNode.getPrevious(); 374 } 375 return curNode; 376 } 377 378 @NotNull 379 public static String getNodeText(@Nullable MethodNode node) { 380 return getNodeText(node, new Textifier()); 381 } 382 383 @NotNull 384 public static String getNodeText(@Nullable MethodNode node, @NotNull Textifier textifier) { 385 if (node == null) { 386 return "Not generated"; 387 } 388 node.accept(new TraceMethodVisitor(textifier)); 389 StringWriter sw = new StringWriter(); 390 textifier.print(new PrintWriter(sw)); 391 sw.flush(); 392 return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString(); 393 } 394 395 @NotNull 396 /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) { 397 //try to find just compiled classes then in dependencies 398 try { 399 OutputFile outputFile = state.getFactory().get(internalName + ".class"); 400 if (outputFile != null) { 401 return new ClassReader(outputFile.asByteArray()); 402 } else { 403 VirtualFile file = findVirtualFile(state.getProject(), internalName); 404 if (file == null) { 405 throw new RuntimeException("Couldn't find virtual file for " + internalName); 406 } 407 return new ClassReader(file.contentsToByteArray()); 408 } 409 } 410 catch (IOException e) { 411 throw new RuntimeException(e); 412 } 413 } 414 415 public static void generateFinallyMarker(@NotNull InstructionAdapter v, int depth, boolean start) { 416 v.iconst(depth); 417 v.invokestatic(INLINE_MARKER_CLASS_NAME, start ? INLINE_MARKER_FINALLY_START : INLINE_MARKER_FINALLY_END, "(I)V", false); 418 } 419 420 public static boolean isFinallyEnd(@NotNull AbstractInsnNode node) { 421 return isFinallyMarker(node, INLINE_MARKER_FINALLY_END); 422 } 423 424 public static boolean isFinallyStart(@NotNull AbstractInsnNode node) { 425 return isFinallyMarker(node, INLINE_MARKER_FINALLY_START); 426 } 427 428 public static boolean isFinallyMarker(@Nullable AbstractInsnNode node) { 429 return isFinallyMarker(node, INLINE_MARKER_FINALLY_END) || isFinallyMarker(node, INLINE_MARKER_FINALLY_START); 430 } 431 432 public static boolean isFinallyMarker(@Nullable AbstractInsnNode node, String name) { 433 if (!(node instanceof MethodInsnNode)) return false; 434 MethodInsnNode method = (MethodInsnNode) node; 435 return INLINE_MARKER_CLASS_NAME.equals(method.owner) && name.equals(method.name); 436 } 437 438 public static boolean isFinallyMarkerRequired(@NotNull MethodContext context) { 439 return context.isInlineFunction() || context.isInliningLambda(); 440 } 441 442 public static int getConstant(AbstractInsnNode ins) { 443 int opcode = ins.getOpcode(); 444 Integer value; 445 if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) { 446 value = opcode - Opcodes.ICONST_0; 447 } 448 else if (opcode == Opcodes.BIPUSH || opcode == Opcodes.SIPUSH) { 449 IntInsnNode index = (IntInsnNode) ins; 450 value = index.operand; 451 } 452 else { 453 LdcInsnNode index = (LdcInsnNode) ins; 454 value = (Integer) index.cst; 455 } 456 return value; 457 } 458 459 public static class LabelTextifier extends Textifier { 460 461 public LabelTextifier() { 462 super(API); 463 } 464 465 @Nullable 466 @TestOnly 467 @SuppressWarnings("UnusedDeclaration") 468 public String getLabelNameIfExists(@NotNull Label l) { 469 return labelNames == null ? null : labelNames.get(l); 470 } 471 } 472 473 public static void addInlineMarker( 474 @NotNull InstructionAdapter v, 475 boolean isStartNotEnd 476 ) { 477 v.visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME, 478 (isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME), 479 "()V", false); 480 } 481 482 public static boolean isInlineMarker(AbstractInsnNode insn) { 483 return isInlineMarker(insn, null); 484 } 485 486 public static boolean isInlineMarker(AbstractInsnNode insn, String name) { 487 if (insn instanceof MethodInsnNode) { 488 MethodInsnNode methodInsnNode = (MethodInsnNode) insn; 489 return insn.getOpcode() == Opcodes.INVOKESTATIC && 490 methodInsnNode.owner.equals(INLINE_MARKER_CLASS_NAME) && 491 (name != null ? methodInsnNode.name.equals(name) 492 : methodInsnNode.name.equals(INLINE_MARKER_BEFORE_METHOD_NAME) || 493 methodInsnNode.name.equals(INLINE_MARKER_AFTER_METHOD_NAME)); 494 } 495 else { 496 return false; 497 } 498 } 499 500 public static boolean isBeforeInlineMarker(AbstractInsnNode insn) { 501 return isInlineMarker(insn, INLINE_MARKER_BEFORE_METHOD_NAME); 502 } 503 504 public static boolean isAfterInlineMarker(AbstractInsnNode insn) { 505 return isInlineMarker(insn, INLINE_MARKER_AFTER_METHOD_NAME); 506 } 507 508 public static int getLoadStoreArgSize(int opcode) { 509 return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1; 510 } 511 512 public static boolean isStoreInstruction(int opcode) { 513 return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE; 514 } 515 }