All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil Maven / Gradle / Ivy

There is a newer version: 2.1.0-Beta1
Show newest version
/*
 * Copyright 2010-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.kotlin.codegen.inline;

import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.codegen.MemberCodegen;
import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
import org.jetbrains.kotlin.codegen.context.CodegenContext;
import org.jetbrains.kotlin.codegen.context.CodegenContextUtil;
import org.jetbrains.kotlin.codegen.context.InlineLambdaContext;
import org.jetbrains.kotlin.codegen.context.MethodContext;
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructorsKt;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
import org.jetbrains.kotlin.codegen.when.WhenByEnumsMapping;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.fileClasses.FileClasses;
import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider;
import org.jetbrains.kotlin.load.java.JvmAbi;
import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
import org.jetbrains.kotlin.util.OperatorNameConventions;
import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
import org.jetbrains.org.objectweb.asm.tree.*;
import org.jetbrains.org.objectweb.asm.util.Textifier;
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ListIterator;

public class InlineCodegenUtil {
    public static final boolean GENERATE_SMAP = true;
    public static final int API = Opcodes.ASM5;

    private static final String CAPTURED_FIELD_PREFIX = "$";
    private static final String NON_CAPTURED_FIELD_PREFIX = "$$";
    public static final String THIS$0 = "this$0";
    public static final String THIS = "this";
    private static final String RECEIVER$0 = "receiver$0";
    private static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$";
    public static final String FIRST_FUN_LABEL = "$$$$$ROOT$$$$$";
    public static final String NUMBERED_FUNCTION_PREFIX = "kotlin/jvm/functions/Function";
    private static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker";
    private static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall";
    private static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall";
    private static final String INLINE_MARKER_FINALLY_START = "finallyStart";
    private static final String INLINE_MARKER_FINALLY_END = "finallyEnd";
    public static final String INLINE_TRANSFORMATION_SUFFIX = "$inlined";
    public static final String INLINE_FUN_THIS_0_SUFFIX = "$inline_fun";
    public static final String INLINE_FUN_VAR_SUFFIX = "$iv";

    @Nullable
    public static SMAPAndMethodNode getMethodNode(
            byte[] classData,
            final String methodName,
            final String methodDescriptor,
            ClassId classId
    ) {
        ClassReader cr = new ClassReader(classData);
        final MethodNode[] node = new MethodNode[1];
        final String[] debugInfo = new String[2];
        final int[] lines = new int[2];
        lines[0] = Integer.MAX_VALUE;
        lines[1] = Integer.MIN_VALUE;
        //noinspection PointlessBitwiseExpression
        cr.accept(new ClassVisitor(API) {
            @Override
            public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
                assertVersionNotGreaterThanJava6(version, name);
            }

            @Override
            public void visitSource(String source, String debug) {
                super.visitSource(source, debug);
                debugInfo[0] = source;
                debugInfo[1] = debug;
            }

            @Override
            public MethodVisitor visitMethod(
                    int access,
                    @NotNull String name,
                    @NotNull String desc,
                    String signature,
                    String[] exceptions
            ) {
                if (methodName.equals(name) && methodDescriptor.equals(desc)) {
                    node[0] = new MethodNode(API, access, name, desc, signature, exceptions) {
                        @Override
                        public void visitLineNumber(int line, @NotNull Label start) {
                            super.visitLineNumber(line, start);
                            lines[0] = Math.min(lines[0], line);
                            lines[1] = Math.max(lines[1], line);
                        }
                    };
                    return node[0];
                }
                return null;
            }
        }, ClassReader.SKIP_FRAMES | (GENERATE_SMAP ? 0 : ClassReader.SKIP_DEBUG));

        if (node[0] == null) {
            return null;
        }

        if (classId.equals(IntrinsicArrayConstructorsKt.getClassId())) {
            // Don't load source map for intrinsic array constructors
            debugInfo[0] = null;
        }

        SMAP smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classId.asString(), lines[0], lines[1]);
        return new SMAPAndMethodNode(node[0], smap);
    }

    public static void assertVersionNotGreaterThanJava6(int version, String internalName) {
        // TODO: report a proper diagnostic
        if (version > Opcodes.V1_6 && !"true".equals(System.getProperty("kotlin.skip.bytecode.version.check"))) {
            throw new UnsupportedOperationException(
                    "Cannot inline bytecode of class " + internalName + " which has version " + version + ". " +
                    "This compiler can only inline Java 1.6 bytecode (version " + Opcodes.V1_6 + ")"
            );
        }
    }

    public static void initDefaultSourceMappingIfNeeded(
            @NotNull CodegenContext context, @NotNull MemberCodegen codegen, @NotNull GenerationState state
    ) {
        if (!state.isInlineEnabled()) return;

        CodegenContext parentContext = context.getParentContext();
        while (parentContext != null) {
            if (parentContext.isInlineMethodContext()) {
                //just init default one to one mapping
                codegen.getOrCreateSourceMapper();
                break;
            }
            parentContext = parentContext.getParentContext();
        }
    }

    @Nullable
    public static VirtualFile findVirtualFile(@NotNull GenerationState state, @NotNull ClassId classId) {
        return JvmVirtualFileFinder.SERVICE.getInstance(state.getProject()).findVirtualFileWithHeader(classId);
    }

    @Nullable
    private static VirtualFile findVirtualFileImprecise(@NotNull GenerationState state, @NotNull String internalClassName) {
        FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName();
        String classNameWithDollars = StringsKt.substringAfterLast(internalClassName, "/", internalClassName);
        //TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file
        // we construct valid.package.name/RelativeClassNameAsSingleName that should work in compiler, but fails for inner classes in IDE
        return findVirtualFile(state, new ClassId(packageFqName, Name.identifier(classNameWithDollars)));
    }

    @NotNull
    public static String getInlineName(
            @NotNull CodegenContext codegenContext,
            @NotNull KotlinTypeMapper typeMapper,
            @NotNull JvmFileClassesProvider fileClassesManager
    ) {
        return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper, fileClassesManager);
    }

    @NotNull
    private static String getInlineName(
            @NotNull CodegenContext codegenContext,
            @NotNull DeclarationDescriptor currentDescriptor,
            @NotNull KotlinTypeMapper typeMapper,
            @NotNull JvmFileClassesProvider fileClassesProvider
    ) {
        if (currentDescriptor instanceof PackageFragmentDescriptor) {
            PsiFile file = DescriptorToSourceUtils.getContainingFile(codegenContext.getContextDescriptor());

            Type implementationOwnerType;
            if (file == null) {
                implementationOwnerType = CodegenContextUtil.getImplementationOwnerClassType(codegenContext);
            }
            else {
                implementationOwnerType = FileClasses.getFileClassType(fileClassesProvider, (KtFile) file);
            }

            if (implementationOwnerType == null) {
                DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
                //noinspection ConstantConditions
                throw new RuntimeException(
                        "Couldn't find declaration for " +
                        contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() +
                        "; context: " + codegenContext
                );
            }

            return implementationOwnerType.getInternalName();
        }
        else if (currentDescriptor instanceof ClassifierDescriptor) {
            Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor);
            return type.getInternalName();
        }
        else if (currentDescriptor instanceof FunctionDescriptor) {
            ClassDescriptor descriptor =
                    typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_CALLABLE, (FunctionDescriptor) currentDescriptor);
            if (descriptor != null) {
                return typeMapper.mapType(descriptor).getInternalName();
            }
        }

        //TODO: add suffix for special case
        String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString();

        //noinspection ConstantConditions
        return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper, fileClassesProvider) + "$" + suffix;
    }

    public static boolean isInvokeOnLambda(@NotNull String owner, @NotNull String name) {
        return OperatorNameConventions.INVOKE.asString().equals(name) &&
               owner.startsWith(NUMBERED_FUNCTION_PREFIX) &&
               isInteger(owner.substring(NUMBERED_FUNCTION_PREFIX.length()));
    }

    public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) {
        return "".equals(methodName) && isAnonymousClass(internalName);
    }

    public static boolean isWhenMappingAccess(@NotNull String internalName, @NotNull String fieldName) {
        return fieldName.startsWith(WhenByEnumsMapping.MAPPING_ARRAY_FIELD_PREFIX) &&
               internalName.endsWith(WhenByEnumsMapping.MAPPINGS_CLASS_NAME_POSTFIX);
    }

    public static boolean isAnonymousSingletonLoad(@NotNull String internalName, @NotNull String fieldName) {
        return JvmAbi.INSTANCE_FIELD.equals(fieldName) && isAnonymousClass(internalName);
    }

    public static boolean isAnonymousClass(@NotNull String internalName) {
        String shortName = getLastNamePart(internalName);
        int index = shortName.lastIndexOf("$");

        if (index < 0) {
            return false;
        }

        String suffix = shortName.substring(index + 1);
        return isInteger(suffix);
    }

    @NotNull
    private static String getLastNamePart(@NotNull String internalName) {
        int index = internalName.lastIndexOf("/");
        return index < 0 ? internalName : internalName.substring(index + 1);
    }

    @NotNull
    public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
        return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode);
    }

    private static boolean isInteger(@NotNull String string) {
        if (string.isEmpty()) {
            return false;
        }

        for (int i = 0; i < string.length(); i++) {
             if (!Character.isDigit(string.charAt(i))) {
                 return false;
             }
        }

        return true;
    }

    public static boolean isCapturedFieldName(@NotNull String fieldName) {
        // TODO: improve this heuristic
        return fieldName.startsWith(CAPTURED_FIELD_PREFIX) &&
               !fieldName.startsWith(NON_CAPTURED_FIELD_PREFIX) ||
               THIS$0.equals(fieldName) ||
               RECEIVER$0.equals(fieldName);
    }

    public static boolean isReturnOpcode(int opcode) {
        return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN;
    }

    //marked return could be either non-local or local in case of labeled lambda self-returns
    public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) {
        return getMarkedReturnLabelOrNull(returnIns) != null;
    }

    @Nullable
    public static String getMarkedReturnLabelOrNull(@NotNull AbstractInsnNode returnInsn) {
        if (!isReturnOpcode(returnInsn.getOpcode())) {
            return null;
        }
        AbstractInsnNode previous = returnInsn.getPrevious();
        if (previous instanceof MethodInsnNode) {
            MethodInsnNode marker = (MethodInsnNode) previous;
            if (NON_LOCAL_RETURN.equals(marker.owner)) {
                return marker.name;
            }
        }
        return null;
    }

    public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) {
        iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false);
    }

    @NotNull
    public static Type getReturnType(int opcode) {
        switch (opcode) {
            case Opcodes.RETURN:
                return Type.VOID_TYPE;
            case Opcodes.IRETURN:
                return Type.INT_TYPE;
            case Opcodes.DRETURN:
                return Type.DOUBLE_TYPE;
            case Opcodes.FRETURN:
                return Type.FLOAT_TYPE;
            case Opcodes.LRETURN:
                return Type.LONG_TYPE;
            default:
                return AsmTypes.OBJECT_TYPE;
        }
    }

    public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) {
        ListIterator iterator = from.instructions.iterator();
        while (iterator.hasNext()) {
            AbstractInsnNode next = iterator.next();
            to.instructions.insertBefore(beforeNode, next);
        }
    }

    @NotNull
    public static MethodNode createEmptyMethodNode() {
        return new MethodNode(API, 0, "fake", "()V", null, null);
    }

    @NotNull
    public static LabelNode firstLabelInChain(@NotNull LabelNode node) {
        LabelNode curNode = node;
        while (curNode.getPrevious() instanceof LabelNode) {
            curNode = (LabelNode) curNode.getPrevious();
        }
        return curNode;
    }

    @NotNull
    public static String getNodeText(@Nullable MethodNode node) {
        Textifier textifier = new Textifier();
        if (node == null) {
            return "Not generated";
        }
        node.accept(new TraceMethodVisitor(textifier));
        StringWriter sw = new StringWriter();
        textifier.print(new PrintWriter(sw));
        sw.flush();
        return node.name + " " + node.desc + ":\n" + sw.getBuffer().toString();
    }

    @NotNull
    /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) {
        //try to find just compiled classes then in dependencies
        try {
            OutputFile outputFile = state.getFactory().get(internalName + ".class");
            if (outputFile != null) {
                return new ClassReader(outputFile.asByteArray());
            }
            VirtualFile file = findVirtualFileImprecise(state, internalName);
            if (file != null) {
                return new ClassReader(file.contentsToByteArray());
            }
            throw new RuntimeException("Couldn't find virtual file for " + internalName);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void generateFinallyMarker(@NotNull InstructionAdapter v, int depth, boolean start) {
        v.iconst(depth);
        v.invokestatic(INLINE_MARKER_CLASS_NAME, start ? INLINE_MARKER_FINALLY_START : INLINE_MARKER_FINALLY_END, "(I)V", false);
    }

    public static boolean isFinallyEnd(@NotNull AbstractInsnNode node) {
        return isFinallyMarker(node, INLINE_MARKER_FINALLY_END);
    }

    public static boolean isFinallyStart(@NotNull AbstractInsnNode node) {
        return isFinallyMarker(node, INLINE_MARKER_FINALLY_START);
    }

    public static boolean isFinallyMarker(@Nullable AbstractInsnNode node) {
        return node != null && (isFinallyStart(node) || isFinallyEnd(node));
    }

    private static boolean isFinallyMarker(@NotNull AbstractInsnNode node, String name) {
        if (!(node instanceof MethodInsnNode)) return false;
        MethodInsnNode method = (MethodInsnNode) node;
        return INLINE_MARKER_CLASS_NAME.equals(method.owner) && name.equals(method.name);
    }

    public static boolean isFinallyMarkerRequired(@NotNull MethodContext context) {
        return context.isInlineMethodContext() || context instanceof InlineLambdaContext;
    }

    public static int getConstant(@NotNull AbstractInsnNode ins) {
        int opcode = ins.getOpcode();
        Integer value;
        if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) {
            return opcode - Opcodes.ICONST_0;
        }
        else if (opcode == Opcodes.BIPUSH || opcode == Opcodes.SIPUSH) {
            return ((IntInsnNode) ins).operand;
        }
        else {
            LdcInsnNode index = (LdcInsnNode) ins;
            return (Integer) index.cst;
        }
    }

    public static void addInlineMarker(@NotNull InstructionAdapter v, boolean isStartNotEnd) {
        v.visitMethodInsn(
                Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME,
                isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME,
                "()V", false
        );
    }

    public static boolean isInlineMarker(@NotNull AbstractInsnNode insn) {
        return isInlineMarker(insn, null);
    }

    private static boolean isInlineMarker(@NotNull AbstractInsnNode insn, @Nullable String name) {
        if (!(insn instanceof MethodInsnNode)) {
            return false;
        }

        MethodInsnNode methodInsnNode = (MethodInsnNode) insn;
        return insn.getOpcode() == Opcodes.INVOKESTATIC &&
               methodInsnNode.owner.equals(INLINE_MARKER_CLASS_NAME) &&
               (name != null ? methodInsnNode.name.equals(name)
                             : methodInsnNode.name.equals(INLINE_MARKER_BEFORE_METHOD_NAME) ||
                               methodInsnNode.name.equals(INLINE_MARKER_AFTER_METHOD_NAME));
    }

    public static boolean isBeforeInlineMarker(@NotNull AbstractInsnNode insn) {
        return isInlineMarker(insn, INLINE_MARKER_BEFORE_METHOD_NAME);
    }

    public static boolean isAfterInlineMarker(@NotNull AbstractInsnNode insn) {
        return isInlineMarker(insn, INLINE_MARKER_AFTER_METHOD_NAME);
    }

    public static int getLoadStoreArgSize(int opcode) {
        return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1;
    }

    public static boolean isStoreInstruction(int opcode) {
        return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE;
    }

    public static int calcMarkerShift(@NotNull Parameters parameters, @NotNull MethodNode node) {
        int markerShiftTemp = getIndexAfterLastMarker(node);
        return markerShiftTemp - parameters.getRealArgsSizeOnStack() + parameters.getArgsSizeOnStack();
    }

    private static int getIndexAfterLastMarker(@NotNull MethodNode node) {
        int result = -1;
        for (LocalVariableNode variable : node.localVariables) {
            if (isFakeLocalVariableForInline(variable.name)) {
                result = Math.max(result, variable.index + 1);
            }
        }
        return result;
    }

    public static boolean isFakeLocalVariableForInline(@NotNull String name) {
        return name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) ||
               name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT);
    }

    public static boolean isThis0(@NotNull String name) {
        return THIS$0.equals(name);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy