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