001 /* 002 * Copyright 2010-2013 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.jet.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.jet.codegen.binding.CodegenBinding; 027 import org.jetbrains.jet.codegen.context.CodegenContext; 028 import org.jetbrains.jet.codegen.context.PackageContext; 029 import org.jetbrains.jet.codegen.state.GenerationState; 030 import org.jetbrains.jet.codegen.state.JetTypeMapper; 031 import org.jetbrains.jet.descriptors.serialization.JavaProtoBuf; 032 import org.jetbrains.jet.descriptors.serialization.ProtoBuf; 033 import org.jetbrains.jet.descriptors.serialization.descriptors.DeserializedSimpleFunctionDescriptor; 034 import org.jetbrains.jet.lang.descriptors.*; 035 import org.jetbrains.jet.lang.psi.JetFile; 036 import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils; 037 import org.jetbrains.jet.lang.resolve.DescriptorUtils; 038 import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants; 039 import org.jetbrains.jet.lang.resolve.java.JvmAbi; 040 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 041 import org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils; 042 import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils; 043 import org.jetbrains.jet.lang.resolve.kotlin.VirtualFileFinder; 044 import org.jetbrains.jet.lang.resolve.name.FqName; 045 import org.jetbrains.jet.lang.resolve.name.Name; 046 import org.jetbrains.org.objectweb.asm.*; 047 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; 048 import org.jetbrains.org.objectweb.asm.tree.*; 049 import org.jetbrains.org.objectweb.asm.util.Textifier; 050 import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor; 051 052 import java.io.IOException; 053 import java.io.InputStream; 054 import java.io.PrintWriter; 055 import java.io.StringWriter; 056 import java.util.Arrays; 057 import java.util.ListIterator; 058 059 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName; 060 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait; 061 062 public class InlineCodegenUtil { 063 public static final int API = Opcodes.ASM5; 064 public static final String INVOKE = "invoke"; 065 066 public static final String CAPTURED_FIELD_PREFIX = "$"; 067 068 public static final String THIS$0 = "this$0"; 069 070 public static final String RECEIVER$0 = "receiver$0"; 071 072 public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$"; 073 074 public static final String ROOT_LABEL = "$$$$$ROOT$$$$$"; 075 public static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"; 076 public static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall"; 077 public static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall"; 078 079 @Nullable 080 public static MethodNode getMethodNode( 081 byte[] classData, 082 final String methodName, 083 final String methodDescriptor 084 ) throws ClassNotFoundException, IOException { 085 ClassReader cr = new ClassReader(classData); 086 final MethodNode[] methodNode = new MethodNode[1]; 087 cr.accept(new ClassVisitor(API) { 088 089 @Override 090 public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) { 091 if (methodName.equals(name) && methodDescriptor.equals(desc)) { 092 return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions); 093 } 094 return null; 095 } 096 }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); 097 098 return methodNode[0]; 099 } 100 101 102 @NotNull 103 public static VirtualFile getVirtualFileForCallable(@NotNull DeserializedSimpleFunctionDescriptor deserializedDescriptor, @NotNull GenerationState state) { 104 VirtualFile file; 105 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration(); 106 if (parentDeclaration instanceof PackageFragmentDescriptor) { 107 ProtoBuf.Callable proto = deserializedDescriptor.getProto(); 108 if (!proto.hasExtension(JavaProtoBuf.implClassName)) { 109 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor); 110 } 111 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JavaProtoBuf.implClassName)); 112 FqName packagePartFqName = 113 PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) parentDeclaration).getFqName()).parent().child( 114 name); 115 file = findVirtualFileWithHeader(state.getProject(), packagePartFqName); 116 } else { 117 file = findVirtualFileContainingDescriptor(state.getProject(), deserializedDescriptor); 118 } 119 120 if (file == null) { 121 throw new IllegalStateException("Couldn't find declaration file for " + deserializedDescriptor.getName()); 122 } 123 124 return file; 125 } 126 127 @Nullable 128 public static VirtualFile findVirtualFileWithHeader(@NotNull Project project, @NotNull FqName containerFqName) { 129 VirtualFileFinder fileFinder = VirtualFileFinder.SERVICE.getInstance(project); 130 return fileFinder.findVirtualFileWithHeader(containerFqName); 131 } 132 133 @Nullable 134 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) { 135 VirtualFileFinder fileFinder = VirtualFileFinder.SERVICE.getInstance(project); 136 return fileFinder.findVirtualFile(internalName); 137 } 138 139 //TODO: navigate to inner classes 140 @Nullable 141 public static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) { 142 ClassOrPackageFragmentDescriptor 143 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false); 144 if (containerDescriptor instanceof PackageFragmentDescriptor) { 145 return PackageClassUtils.getPackageClassFqName(getFqName(containerDescriptor).toSafe()); 146 } 147 if (containerDescriptor instanceof ClassDescriptor) { 148 FqName fqName = DeserializedResolverUtils.kotlinFqNameToJavaFqName(getFqName(containerDescriptor)); 149 if (isTrait(containerDescriptor)) { 150 return fqName.parent().child(Name.identifier(fqName.shortName() + JvmAbi.TRAIT_IMPL_SUFFIX)); 151 } 152 return fqName; 153 } 154 return null; 155 } 156 157 public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) { 158 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper); 159 } 160 161 private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) { 162 if (currentDescriptor instanceof PackageFragmentDescriptor) { 163 PsiFile file = getContainingFile(codegenContext); 164 165 Type packagePartType; 166 if (file == null) { 167 //in case package fragment clinit 168 assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext; 169 packagePartType = ((PackageContext) codegenContext).getPackagePartType(); 170 } else { 171 packagePartType = PackagePartClassUtils.getPackagePartType((JetFile) file); 172 } 173 174 if (packagePartType == null) { 175 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 176 //noinspection ConstantConditions 177 throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() ); 178 } 179 180 return packagePartType.getInternalName(); 181 } 182 else if (currentDescriptor instanceof ClassifierDescriptor) { 183 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor); 184 return type.getInternalName(); 185 } else if (currentDescriptor instanceof FunctionDescriptor) { 186 ClassDescriptor descriptor = 187 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor); 188 if (descriptor != null) { 189 Type type = typeMapper.mapType(descriptor); 190 return type.getInternalName(); 191 } 192 } 193 194 //TODO: add suffix for special case 195 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString(); 196 197 //noinspection ConstantConditions 198 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix; 199 } 200 201 @Nullable 202 private static VirtualFile findVirtualFileContainingDescriptor( 203 @NotNull Project project, 204 @NotNull DeclarationDescriptor referencedDescriptor 205 ) { 206 FqName containerFqName = getContainerFqName(referencedDescriptor); 207 if (containerFqName == null) { 208 return null; 209 } 210 return findVirtualFileWithHeader(project, containerFqName); 211 } 212 213 214 public static boolean isInvokeOnLambda(String owner, String name) { 215 if (!INVOKE.equals(name)) { 216 return false; 217 } 218 219 for (String prefix : Arrays.asList("kotlin/Function", "kotlin/ExtensionFunction")) { 220 if (owner.startsWith(prefix)) { 221 String suffix = owner.substring(prefix.length()); 222 if (isInteger(suffix)) { 223 return true; 224 } 225 } 226 } 227 return false; 228 } 229 230 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) { 231 return "<init>".equals(methodName) && isAnonymousClass(internalName); 232 } 233 234 public static boolean isAnonymousClass(String internalName) { 235 String shortName = getLastNamePart(internalName); 236 int index = shortName.lastIndexOf("$"); 237 238 if (index < 0) { 239 return false; 240 } 241 242 String suffix = shortName.substring(index + 1); 243 return isInteger(suffix); 244 } 245 246 @NotNull 247 private static String getLastNamePart(@NotNull String internalName) { 248 int index = internalName.lastIndexOf("/"); 249 return index < 0 ? internalName : internalName.substring(index + 1); 250 } 251 252 @Nullable 253 public static PsiFile getContainingFile(CodegenContext codegenContext) { 254 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 255 PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor); 256 if (psiElement != null) { 257 return psiElement.getContainingFile(); 258 } 259 return null; 260 } 261 262 @NotNull 263 public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) { 264 return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode); 265 } 266 267 private static boolean isInteger(@NotNull String string) { 268 if (string.isEmpty()) { 269 return false; 270 } 271 272 for (int i = 0; i < string.length(); i++) { 273 if (!Character.isDigit(string.charAt(i))) { 274 return false; 275 } 276 } 277 278 return true; 279 } 280 281 public static boolean isCapturedFieldName(@NotNull String fieldName) { 282 // TODO: improve this heuristic 283 return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) || 284 THIS$0.equals(fieldName) || 285 RECEIVER$0.equals(fieldName); 286 } 287 288 public static boolean isReturnOpcode(int opcode) { 289 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN; 290 } 291 292 //marked return could be either non-local or local in case of labeled lambda self-returns 293 public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) { 294 assert isReturnOpcode(returnIns.getOpcode()) : "Should be called on return instruction, but " + returnIns; 295 AbstractInsnNode globalFlag = returnIns.getPrevious(); 296 return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner); 297 } 298 299 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) { 300 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false); 301 } 302 303 public static Type getReturnType(int opcode) { 304 switch (opcode) { 305 case Opcodes.RETURN: return Type.VOID_TYPE; 306 case Opcodes.IRETURN: return Type.INT_TYPE; 307 case Opcodes.DRETURN: return Type.DOUBLE_TYPE; 308 case Opcodes.FRETURN: return Type.FLOAT_TYPE; 309 case Opcodes.LRETURN: return Type.LONG_TYPE; 310 default: return AsmTypeConstants.OBJECT_TYPE; 311 } 312 } 313 314 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) { 315 InsnList instructions = to.instructions; 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 324 public static MethodNode createEmptyMethodNode() { 325 return new MethodNode(API, 0, "fake", "()V", null, null); 326 } 327 328 private static boolean isLastGoto(@NotNull AbstractInsnNode insnNode, @NotNull AbstractInsnNode stopAt) { 329 if (insnNode.getOpcode() == Opcodes.GOTO) { 330 insnNode = insnNode.getNext(); 331 while (insnNode != stopAt && isLineNumberOrLabel(insnNode)) { 332 insnNode = insnNode.getNext(); 333 } 334 return stopAt == insnNode; 335 } 336 return false; 337 } 338 339 static boolean isLineNumberOrLabel(@Nullable AbstractInsnNode node) { 340 return node instanceof LineNumberNode || node instanceof LabelNode; 341 } 342 343 344 @NotNull 345 public static LabelNode firstLabelInChain(@NotNull LabelNode node) { 346 LabelNode curNode = node; 347 while (curNode.getPrevious() instanceof LabelNode) { 348 curNode = (LabelNode) curNode.getPrevious(); 349 } 350 return curNode; 351 } 352 353 @NotNull 354 public static String getNodeText(@Nullable MethodNode node) { 355 return getNodeText(node, new Textifier()); 356 } 357 358 @NotNull 359 public static String getNodeText(@Nullable MethodNode node, @NotNull Textifier textifier) { 360 if (node == null) { 361 return "Not generated"; 362 } 363 node.accept(new TraceMethodVisitor(textifier)); 364 StringWriter sw = new StringWriter(); 365 textifier.print(new PrintWriter(sw)); 366 sw.flush(); 367 return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString(); 368 } 369 370 public static class LabelTextifier extends Textifier { 371 372 public LabelTextifier() { 373 super(API); 374 } 375 376 @Nullable 377 @TestOnly 378 @SuppressWarnings("UnusedDeclaration") 379 public String getLabelNameIfExists(@NotNull Label l) { 380 return labelNames == null ? null : labelNames.get(l); 381 } 382 } 383 384 public static void addInlineMarker( 385 @NotNull InstructionAdapter v, 386 boolean isStartNotEnd 387 ) { 388 v.visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME, 389 (isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME), 390 "()V", false); 391 } 392 }