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 }