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