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