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