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 kotlin.StringsKt; 024 import org.jetbrains.annotations.NotNull; 025 import org.jetbrains.annotations.Nullable; 026 import org.jetbrains.annotations.TestOnly; 027 import org.jetbrains.kotlin.backend.common.output.OutputFile; 028 import org.jetbrains.kotlin.codegen.MemberCodegen; 029 import org.jetbrains.kotlin.codegen.binding.CodegenBinding; 030 import org.jetbrains.kotlin.codegen.context.CodegenContext; 031 import org.jetbrains.kotlin.codegen.context.CodegenContextUtil; 032 import org.jetbrains.kotlin.codegen.context.MethodContext; 033 import org.jetbrains.kotlin.codegen.optimization.common.UtilKt; 034 import org.jetbrains.kotlin.codegen.state.GenerationState; 035 import org.jetbrains.kotlin.codegen.state.JetTypeMapper; 036 import org.jetbrains.kotlin.descriptors.*; 037 import org.jetbrains.kotlin.fileClasses.FileClasses; 038 import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider; 039 import org.jetbrains.kotlin.load.java.JvmAbi; 040 import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder; 041 import org.jetbrains.kotlin.name.ClassId; 042 import org.jetbrains.kotlin.name.FqName; 043 import org.jetbrains.kotlin.name.Name; 044 import org.jetbrains.kotlin.psi.KtFile; 045 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; 046 import org.jetbrains.kotlin.resolve.jvm.AsmTypes; 047 import org.jetbrains.kotlin.resolve.jvm.JvmClassName; 048 import org.jetbrains.kotlin.util.OperatorNameConventions; 049 import org.jetbrains.org.objectweb.asm.*; 050 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; 051 import org.jetbrains.org.objectweb.asm.tree.*; 052 import org.jetbrains.org.objectweb.asm.util.Textifier; 053 import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor; 054 055 import java.io.IOException; 056 import java.io.PrintWriter; 057 import java.io.StringWriter; 058 import java.util.ListIterator; 059 060 public class InlineCodegenUtil { 061 public static final boolean GENERATE_SMAP = true; 062 public static final int API = Opcodes.ASM5; 063 064 public static final String CAPTURED_FIELD_PREFIX = "$"; 065 public static final String THIS$0 = "this$0"; 066 public static final String THIS = "this"; 067 public static final String RECEIVER$0 = "receiver$0"; 068 public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$"; 069 public static final String FIRST_FUN_LABEL = "$$$$$ROOT$$$$$"; 070 public static final String NUMBERED_FUNCTION_PREFIX = "kotlin/jvm/functions/Function"; 071 public static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"; 072 public static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall"; 073 public static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall"; 074 public static final String INLINE_MARKER_FINALLY_START = "finallyStart"; 075 public static final String INLINE_MARKER_FINALLY_END = "finallyEnd"; 076 public static final String INLINE_TRANSFORMATION_SUFFIX = "$inlined"; 077 public static final String INLINE_FUN_THIS_0_SUFFIX = "$inline_fun"; 078 079 @Nullable 080 public static SMAPAndMethodNode getMethodNode( 081 byte[] classData, 082 final String methodName, 083 final String methodDescriptor, 084 ClassId classId 085 ) throws ClassNotFoundException, IOException { 086 ClassReader cr = new ClassReader(classData); 087 final MethodNode[] node = new MethodNode[1]; 088 final String[] debugInfo = new String[2]; 089 final int[] lines = new int[2]; 090 lines[0] = Integer.MAX_VALUE; 091 lines[1] = Integer.MIN_VALUE; 092 cr.accept(new ClassVisitor(API) { 093 094 @Override 095 public void visitSource(String source, String debug) { 096 super.visitSource(source, debug); 097 debugInfo[0] = source; 098 debugInfo[1] = debug; 099 } 100 101 @Override 102 public MethodVisitor visitMethod( 103 int access, 104 @NotNull String name, 105 @NotNull String desc, 106 String signature, 107 String[] exceptions 108 ) { 109 if (methodName.equals(name) && methodDescriptor.equals(desc)) { 110 node[0] = new MethodNode(API, access, name, desc, signature, exceptions) { 111 @Override 112 public void visitLineNumber(int line, @NotNull Label start) { 113 super.visitLineNumber(line, start); 114 lines[0] = Math.min(lines[0], line); 115 lines[1] = Math.max(lines[1], line); 116 } 117 }; 118 return node[0]; 119 } 120 return null; 121 } 122 }, ClassReader.SKIP_FRAMES | (GENERATE_SMAP ? 0 : ClassReader.SKIP_DEBUG)); 123 124 SMAP smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classId.asString(), lines[0], lines[1]); 125 return new SMAPAndMethodNode(node[0], smap); 126 } 127 128 public static void initDefaultSourceMappingIfNeeded(@NotNull CodegenContext context, @NotNull MemberCodegen codegen, @NotNull GenerationState state) { 129 if (state.isInlineEnabled()) { 130 CodegenContext<?> parentContext = context.getParentContext(); 131 while (parentContext != null) { 132 if (parentContext instanceof MethodContext) { 133 if (((MethodContext) parentContext).isInlineFunction()) { 134 //just init default one to one mapping 135 codegen.getOrCreateSourceMapper(); 136 break; 137 } 138 } 139 parentContext = parentContext.getParentContext(); 140 } 141 } 142 } 143 144 @NotNull 145 public static VirtualFile getVirtualFileForCallable(@NotNull ClassId containerClassId, @NotNull GenerationState state) { 146 JvmVirtualFileFinder fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(state.getProject()); 147 VirtualFile file = fileFinder.findVirtualFileWithHeader(containerClassId); 148 if (file == null) { 149 throw new IllegalStateException("Couldn't find declaration file for " + containerClassId); 150 } 151 return file; 152 } 153 154 @Nullable 155 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalClassName) { 156 FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName(); 157 String classNameWithDollars = StringsKt.substringAfterLast(internalClassName, "/", internalClassName); 158 JvmVirtualFileFinder fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project); 159 //TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file 160 // we construct valid.package.name/RelativeClassNameAsSingleName that should work in compiler, but fails for inner classes in IDE 161 return fileFinder.findVirtualFileWithHeader(new ClassId(packageFqName, Name.identifier(classNameWithDollars))); 162 } 163 164 public static String getInlineName( 165 @NotNull CodegenContext codegenContext, 166 @NotNull JetTypeMapper typeMapper, 167 @NotNull JvmFileClassesProvider fileClassesManager 168 ) { 169 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper, fileClassesManager); 170 } 171 172 private static String getInlineName( 173 @NotNull CodegenContext codegenContext, 174 @NotNull DeclarationDescriptor currentDescriptor, 175 @NotNull JetTypeMapper typeMapper, 176 @NotNull JvmFileClassesProvider fileClassesProvider 177 ) { 178 if (currentDescriptor instanceof PackageFragmentDescriptor) { 179 PsiFile file = getContainingFile(codegenContext); 180 181 Type implementationOwnerType; 182 if (file == null) { 183 implementationOwnerType = CodegenContextUtil.getImplementationOwnerClassType(codegenContext); 184 } else { 185 implementationOwnerType = FileClasses.getFileClassType(fileClassesProvider, (KtFile) file); 186 } 187 188 if (implementationOwnerType == null) { 189 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 190 //noinspection ConstantConditions 191 throw new RuntimeException("Couldn't find declaration for " + 192 contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() + 193 "; context: " + codegenContext); 194 } 195 196 return implementationOwnerType.getInternalName(); 197 } 198 else if (currentDescriptor instanceof ClassifierDescriptor) { 199 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor); 200 return type.getInternalName(); 201 } else if (currentDescriptor instanceof FunctionDescriptor) { 202 ClassDescriptor descriptor = 203 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_CALLABLE, (FunctionDescriptor) currentDescriptor); 204 if (descriptor != null) { 205 Type type = typeMapper.mapType(descriptor); 206 return type.getInternalName(); 207 } 208 } 209 210 //TODO: add suffix for special case 211 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString(); 212 213 //noinspection ConstantConditions 214 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper, fileClassesProvider) + "$" + suffix; 215 } 216 217 public static boolean isInvokeOnLambda(@NotNull String owner, @NotNull String name) { 218 return OperatorNameConventions.INVOKE.asString().equals(name) && 219 owner.startsWith(NUMBERED_FUNCTION_PREFIX) && 220 isInteger(owner.substring(NUMBERED_FUNCTION_PREFIX.length())); 221 } 222 223 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) { 224 return "<init>".equals(methodName) && isAnonymousClass(internalName); 225 } 226 227 public static boolean isAnonymousSingletonLoad(@NotNull String internalName, @NotNull String fieldName) { 228 return JvmAbi.INSTANCE_FIELD.equals(fieldName) && isAnonymousClass(internalName); 229 } 230 231 public static boolean isAnonymousClass(String internalName) { 232 String shortName = getLastNamePart(internalName); 233 int index = shortName.lastIndexOf("$"); 234 235 if (index < 0) { 236 return false; 237 } 238 239 String suffix = shortName.substring(index + 1); 240 return isInteger(suffix); 241 } 242 243 @NotNull 244 private static String getLastNamePart(@NotNull String internalName) { 245 int index = internalName.lastIndexOf("/"); 246 return index < 0 ? internalName : internalName.substring(index + 1); 247 } 248 249 @Nullable 250 public static PsiFile getContainingFile(CodegenContext codegenContext) { 251 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 252 PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor); 253 if (psiElement != null) { 254 return psiElement.getContainingFile(); 255 } 256 return null; 257 } 258 259 @NotNull 260 public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) { 261 return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode); 262 } 263 264 private static boolean isInteger(@NotNull String string) { 265 if (string.isEmpty()) { 266 return false; 267 } 268 269 for (int i = 0; i < string.length(); i++) { 270 if (!Character.isDigit(string.charAt(i))) { 271 return false; 272 } 273 } 274 275 return true; 276 } 277 278 public static boolean isCapturedFieldName(@NotNull String fieldName) { 279 // TODO: improve this heuristic 280 return fieldName.startsWith(CAPTURED_FIELD_PREFIX) || 281 THIS$0.equals(fieldName) || 282 RECEIVER$0.equals(fieldName); 283 } 284 285 public static boolean isReturnOpcode(int opcode) { 286 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN; 287 } 288 289 //marked return could be either non-local or local in case of labeled lambda self-returns 290 public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) { 291 if (!isReturnOpcode(returnIns.getOpcode())) { 292 return false; 293 } 294 AbstractInsnNode globalFlag = returnIns.getPrevious(); 295 return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner); 296 } 297 298 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) { 299 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false); 300 } 301 302 public static Type getReturnType(int opcode) { 303 switch (opcode) { 304 case Opcodes.RETURN: return Type.VOID_TYPE; 305 case Opcodes.IRETURN: return Type.INT_TYPE; 306 case Opcodes.DRETURN: return Type.DOUBLE_TYPE; 307 case Opcodes.FRETURN: return Type.FLOAT_TYPE; 308 case Opcodes.LRETURN: return Type.LONG_TYPE; 309 default: return AsmTypes.OBJECT_TYPE; 310 } 311 } 312 313 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) { 314 InsnList instructions = to.instructions; 315 ListIterator<AbstractInsnNode> iterator = from.instructions.iterator(); 316 while (iterator.hasNext()) { 317 AbstractInsnNode next = iterator.next(); 318 instructions.insertBefore(beforeNode, next); 319 } 320 } 321 322 323 public static MethodNode createEmptyMethodNode() { 324 return new MethodNode(API, 0, "fake", "()V", null, null); 325 } 326 327 @NotNull 328 public static LabelNode firstLabelInChain(@NotNull LabelNode node) { 329 LabelNode curNode = node; 330 while (curNode.getPrevious() instanceof LabelNode) { 331 curNode = (LabelNode) curNode.getPrevious(); 332 } 333 return curNode; 334 } 335 336 @NotNull 337 public static String getNodeText(@Nullable MethodNode node) { 338 return getNodeText(node, new Textifier()); 339 } 340 341 @NotNull 342 public static String getNodeText(@Nullable MethodNode node, @NotNull Textifier textifier) { 343 if (node == null) { 344 return "Not generated"; 345 } 346 node.accept(new TraceMethodVisitor(textifier)); 347 StringWriter sw = new StringWriter(); 348 textifier.print(new PrintWriter(sw)); 349 sw.flush(); 350 return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString(); 351 } 352 353 @NotNull 354 /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) { 355 //try to find just compiled classes then in dependencies 356 try { 357 OutputFile outputFile = state.getFactory().get(internalName + ".class"); 358 if (outputFile != null) { 359 return new ClassReader(outputFile.asByteArray()); 360 } else { 361 VirtualFile file = findVirtualFile(state.getProject(), internalName); 362 if (file == null) { 363 throw new RuntimeException("Couldn't find virtual file for " + internalName); 364 } 365 return new ClassReader(file.contentsToByteArray()); 366 } 367 } 368 catch (IOException e) { 369 throw new RuntimeException(e); 370 } 371 } 372 373 public static void generateFinallyMarker(@NotNull InstructionAdapter v, int depth, boolean start) { 374 v.iconst(depth); 375 v.invokestatic(INLINE_MARKER_CLASS_NAME, start ? INLINE_MARKER_FINALLY_START : INLINE_MARKER_FINALLY_END, "(I)V", false); 376 } 377 378 public static boolean isFinallyEnd(@NotNull AbstractInsnNode node) { 379 return isFinallyMarker(node, INLINE_MARKER_FINALLY_END); 380 } 381 382 public static boolean isFinallyStart(@NotNull AbstractInsnNode node) { 383 return isFinallyMarker(node, INLINE_MARKER_FINALLY_START); 384 } 385 386 public static boolean isFinallyMarker(@Nullable AbstractInsnNode node) { 387 return isFinallyMarker(node, INLINE_MARKER_FINALLY_END) || isFinallyMarker(node, INLINE_MARKER_FINALLY_START); 388 } 389 390 public static boolean isFinallyMarker(@Nullable AbstractInsnNode node, String name) { 391 if (!(node instanceof MethodInsnNode)) return false; 392 MethodInsnNode method = (MethodInsnNode) node; 393 return INLINE_MARKER_CLASS_NAME.equals(method.owner) && name.equals(method.name); 394 } 395 396 public static boolean isFinallyMarkerRequired(@NotNull MethodContext context) { 397 return context.isInlineFunction() || context.isInliningLambda(); 398 } 399 400 public static int getConstant(AbstractInsnNode ins) { 401 int opcode = ins.getOpcode(); 402 Integer value; 403 if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) { 404 value = opcode - Opcodes.ICONST_0; 405 } 406 else if (opcode == Opcodes.BIPUSH || opcode == Opcodes.SIPUSH) { 407 IntInsnNode index = (IntInsnNode) ins; 408 value = index.operand; 409 } 410 else { 411 LdcInsnNode index = (LdcInsnNode) ins; 412 value = (Integer) index.cst; 413 } 414 return value; 415 } 416 417 public static class LabelTextifier extends Textifier { 418 419 public LabelTextifier() { 420 super(API); 421 } 422 423 @Nullable 424 @TestOnly 425 @SuppressWarnings("UnusedDeclaration") 426 public String getLabelNameIfExists(@NotNull Label l) { 427 return labelNames == null ? null : labelNames.get(l); 428 } 429 } 430 431 public static void addInlineMarker( 432 @NotNull InstructionAdapter v, 433 boolean isStartNotEnd 434 ) { 435 v.visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME, 436 (isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME), 437 "()V", false); 438 } 439 440 public static boolean isInlineMarker(AbstractInsnNode insn) { 441 return isInlineMarker(insn, null); 442 } 443 444 public static boolean isInlineMarker(AbstractInsnNode insn, String name) { 445 if (insn instanceof MethodInsnNode) { 446 MethodInsnNode methodInsnNode = (MethodInsnNode) insn; 447 return insn.getOpcode() == Opcodes.INVOKESTATIC && 448 methodInsnNode.owner.equals(INLINE_MARKER_CLASS_NAME) && 449 (name != null ? methodInsnNode.name.equals(name) 450 : methodInsnNode.name.equals(INLINE_MARKER_BEFORE_METHOD_NAME) || 451 methodInsnNode.name.equals(INLINE_MARKER_AFTER_METHOD_NAME)); 452 } 453 else { 454 return false; 455 } 456 } 457 458 public static boolean isBeforeInlineMarker(AbstractInsnNode insn) { 459 return isInlineMarker(insn, INLINE_MARKER_BEFORE_METHOD_NAME); 460 } 461 462 public static boolean isAfterInlineMarker(AbstractInsnNode insn) { 463 return isInlineMarker(insn, INLINE_MARKER_AFTER_METHOD_NAME); 464 } 465 466 public static int getLoadStoreArgSize(int opcode) { 467 return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1; 468 } 469 470 public static boolean isStoreInstruction(int opcode) { 471 return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE; 472 } 473 474 public static int calcMarkerShift(Parameters parameters, MethodNode node) { 475 int markerShiftTemp = getIndexAfterLastMarker(node); 476 return markerShiftTemp - parameters.getRealArgsSizeOnStack() + parameters.getArgsSizeOnStack(); 477 } 478 479 protected static int getIndexAfterLastMarker(MethodNode node) { 480 int markerShiftTemp = -1; 481 for (LocalVariableNode variable : node.localVariables) { 482 if (isFakeLocalVariableForInline(variable.name)) { 483 markerShiftTemp = Math.max(markerShiftTemp, variable.index + 1); 484 } 485 } 486 return markerShiftTemp; 487 } 488 489 public static boolean isFakeLocalVariableForInline(@NotNull String name) { 490 return name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) || name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT); 491 } 492 493 public static boolean isThis0(String name) { 494 return THIS$0.equals(name); 495 } 496 497 @Nullable 498 public static AbstractInsnNode getPrevMeaningful(@NotNull AbstractInsnNode node) { 499 AbstractInsnNode result = node.getPrevious(); 500 while (result != null && !UtilKt.isMeaningful(result)) { 501 result = result.getPrevious(); 502 } 503 return result; 504 } 505 506 public static void removeInterval(@NotNull MethodNode node, @NotNull AbstractInsnNode startInc, @NotNull AbstractInsnNode endInc) { 507 while (startInc != endInc) { 508 AbstractInsnNode next = startInc.getNext(); 509 node.instructions.remove(startInc); 510 startInc = next; 511 } 512 node.instructions.remove(startInc); 513 } 514 }