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