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