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 }