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

org.robovm.compiler.DependencyGraph Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 RoboVM AB
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.robovm.compiler;

import java.util.*;

import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.robovm.compiler.clazz.Clazz;
import org.robovm.compiler.clazz.ClazzInfo;
import org.robovm.compiler.clazz.Dependency;
import org.robovm.compiler.clazz.InvokeMethodDependency;
import org.robovm.compiler.clazz.MethodDependency;
import org.robovm.compiler.clazz.MethodInfo;
import org.robovm.compiler.clazz.SuperMethodDependency;
import org.robovm.compiler.config.Config.TreeShakerMode;

/**
 * Used to build a graph of dependencies between classes and methods. By
 * traversing this graph the compiler can determine the minimum set of classes
 * that need to be compiled in to the final binary given a specific
 * {@link TreeShakerMode}.
 */
public class DependencyGraph {

    /**
     * Root {@link Node}s used as starting points when traversing reachable
     * nodes.
     */
    private final Set roots = new HashSet<>();
    /**
     * {@link Node}s for classes added using {@link #add(Clazz, boolean)}.
     */
    private final Map classNodes = new HashMap<>();
    /**
     * {@link Node}s for methods.
     */
    private final Map methodNodes = new HashMap<>();
    /**
     * Used to cache reachable nodes between calls to
     * {@link #findReachableClasses(TreeShakerMode)} /
     * {@link #findReachableMethods(TreeShakerMode)} when no call to
     * {@link #add(Clazz, boolean)} has been done in between.
     */
    private final Set reachableNodes = new HashSet<>();

    private final TreeShakerMode treeShakerMode;

    public DependencyGraph(TreeShakerMode treeShakerMode) {
        this.treeShakerMode = treeShakerMode;
    }

    /**
     * Adds the specified {@link Clazz} to the graph after it has been compiled.
     * If {@code root == true} the class will be added to the root set and it as
     * well as its methods will always be reachable.
     * @param forcedLinkedMethods specifies list of methods that has to be forced linked to
     *                            survive aggressive tree shaker
     */
    public void add(Clazz clazz, boolean root, Collection forcedLinkedMethods) {
        reachableNodes.clear();

        ClassNode classNode = getClassNode(clazz.getInternalName());
        if (root) {
            roots.add(classNode);
        }

        ClazzInfo ci = clazz.getClazzInfo();

        for (Dependency dep : ci.getDependencies()) {
            if (dep instanceof InvokeMethodDependency) {
                InvokeMethodDependency mdep = (InvokeMethodDependency) dep;
                classNode.addEgde(getMethodNode(mdep), mdep.isWeak());
            } else if (dep instanceof SuperMethodDependency) {
                SuperMethodDependency mdep = (SuperMethodDependency) dep;
                classNode.addEgde(getMethodNode(mdep), mdep.isWeak());
            } else {
                classNode.addEgde(getClassNode(dep.getClassName()), dep.isWeak());
            }
        }

        for (MethodInfo mi : ci.getMethods()) {
            boolean strong = root
                    // Keep callback methods
                    || mi.isCallback()
                    // Keep class initializers
                    || (mi.isStatic() && "".equals(mi.getName()) && "()V".equals(mi.getDesc()))
                    // Keep the values() method generated by the Java compiler
                    // in enum classes
                    || (ci.isEnum() && mi.isStatic() && "values".equals(mi.getName()) && mi.getDesc().equals(
                            "()[L" + clazz.getInternalName() + ";"))
                    // Keep the sizeOf() and $attr$stretMetadata() methods generated by the RoboVM compiler
                    // in Struct classes
                    || (ci.isStruct() && mi.isStatic() && ("sizeOf".equals(mi.getName()) ||
                            StructMemberMethodCompiler.STRUCT_ATTRIBUTES_METHOD.equals(mi.getName())) &&
                            "()I".equals(mi.getDesc())
                    // keep methods specified as force linked in robovm.xml
                    || forcedLinkedMethods.contains(mi));

            MethodNode methodNode = getMethodNode(clazz, mi);
            classNode.addEgde(methodNode, !strong);
            methodNode.addEgde(classNode, false);

            for (Dependency dep : mi.getDependencies()) {
                if (dep instanceof InvokeMethodDependency) {
                    InvokeMethodDependency mdep = (InvokeMethodDependency) dep;
                    methodNode.addEgde(getMethodNode(mdep), mdep.isWeak());
                } else if (dep instanceof SuperMethodDependency) {
                    // Reverse the dependency so that the method is strongly
                    // linked if the super method is invoked.
                    SuperMethodDependency mdep = (SuperMethodDependency) dep;
                    getMethodNode(mdep).addEgde(methodNode, false);
                } else {
                    methodNode.addEgde(getClassNode(dep.getClassName()), dep.isWeak());
                }
            }
        }
    }

    private ClassNode getClassNode(String className) {
        ClassNode node = classNodes.get(className);
        if (node == null) {
            node = new ClassNode(className);
            classNodes.put(className, node);
        }
        return node;
    }

    private MethodNode getMethodNode(String owner, String name, String desc, boolean weaklyLinked,
            boolean stronglyLinked) {
        String key = owner + "." + name + desc;
        MethodNode node = methodNodes.get(key);
        if (node == null) {
            node = new MethodNode(owner, name, desc, weaklyLinked, stronglyLinked);
            methodNodes.put(key, node);
        } else {
            if (weaklyLinked) {
                node.weaklyLinked = true;
            }
            if (stronglyLinked) {
                node.stronglyLinked = true;
            }
        }
        return node;
    }

    private MethodNode getMethodNode(Clazz clazz, MethodInfo mi) {
        return getMethodNode(clazz.getInternalName(), mi.getName(), mi.getDesc(), mi.isWeaklyLinked(),
                mi.isStronglyLinked());
    }

    private MethodNode getMethodNode(MethodDependency dep) {
        return getMethodNode(dep.getOwner(), dep.getMethodName(), dep.getMethodDesc(), false, false);
    }

    /**
     * Finds reachable classes given the {@link TreeShakerMode} set when
     * creating this {@link DependencyGraph}.
     */
    public Set findReachableClasses() {
        validateReachableNodes();
        Set classes = new HashSet<>();
        for (Node node : reachableNodes) {
            if (node instanceof ClassNode) {
                classes.add(((ClassNode) node).className);
            }
        }
        return classes;
    }

    /**
     * Finds reachable methods given {@link TreeShakerMode} set when creating
     * this {@link DependencyGraph}. The returned {@link Triple}s contain the
     * method owner, method name and method descriptor.
     */
    public Set> findReachableMethods() {
        validateReachableNodes();
        Set> methods = new HashSet<>();
        for (Node node : reachableNodes) {
            if (node instanceof MethodNode) {
                MethodNode mnode = (MethodNode) node;
                methods.add(new ImmutableTriple(mnode.owner, mnode.name, mnode.desc));
            }
        }
        return methods;
    }

    private void validateReachableNodes() {
        if (reachableNodes.isEmpty()) {
            for (ClassNode node : roots) {
                visitReachableNodes(node, reachableNodes);
            }
        }
    }

    private void visitReachableNodes(Node node, Set visited) {
        Set pending = new HashSet<>();
        Queue queue = new LinkedList<>();
        pending.add(node);
        queue.add(node);
        Node visiting;
        while ((visiting = queue.poll()) != null) {
            if (!pending.remove(visiting))
                throw new IllegalStateException();
            if (visited.add(visiting)) {
                for (Node child : visiting.strongEdges) {
                    if (pending.add(child)) {
                        queue.add(child);
                    }
                }
                for (Node child : visiting.weakEdges) {
                    if (treeShakerMode == TreeShakerMode.conservative && child instanceof MethodNode) {
                        MethodNode mnode = (MethodNode) child;
                        if (!mnode.isWeaklyLinked()) {
                            if (pending.add(child)) {
                                queue.add(child);
                            }
                        }
                    } else if (treeShakerMode == TreeShakerMode.aggressive) {
                        if (child instanceof MethodNode) {
                            MethodNode mnode = (MethodNode) child;
                            if (mnode.isStronglyLinked() || (!mnode.isWeaklyLinked() && "".equals(mnode.name))) {
                                if (pending.add(child)) {
                                    queue.add(child);
                                }
                            }
                        }
                    } else {
                        if (pending.add(child)) {
                            queue.add(child);
                        }
                    }
                }
            }
        }
    }

    public TreeSet getAllClasses() {
        TreeSet result = new TreeSet();
        for (ClassNode node : classNodes.values()) {
            result.add(node.className);
        }
        return result;
    }

    private static abstract class Node {
        private final Set weakEdges = new HashSet<>();
        private final Set strongEdges = new HashSet<>();

        public void addEgde(Node to, boolean weak) {
            (weak ? weakEdges : strongEdges).add(to);
        }
    }

    private static class ClassNode extends Node {
        private final String className;

        private ClassNode(String className) {
            this.className = className;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((className == null) ? 0 : className.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            ClassNode other = (ClassNode) obj;
            if (className == null) {
                if (other.className != null) {
                    return false;
                }
            } else if (!className.equals(other.className)) {
                return false;
            }
            return true;
        }
    }

    private static class MethodNode extends Node {
        private final String owner;
        private final String name;
        private final String desc;
        private boolean weaklyLinked;
        private boolean stronglyLinked;

        private MethodNode(String owner, String name, String desc, boolean weaklyLinked, boolean stronglyLinked) {
            this.owner = owner;
            this.name = name;
            this.desc = desc;
            this.weaklyLinked = weaklyLinked;
            this.stronglyLinked = stronglyLinked;
        }

        public boolean isWeaklyLinked() {
            return weaklyLinked;
        }

        public boolean isStronglyLinked() {
            return stronglyLinked;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((desc == null) ? 0 : desc.hashCode());
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            result = prime * result + ((owner == null) ? 0 : owner.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            MethodNode other = (MethodNode) obj;
            if (desc == null) {
                if (other.desc != null) {
                    return false;
                }
            } else if (!desc.equals(other.desc)) {
                return false;
            }
            if (name == null) {
                if (other.name != null) {
                    return false;
                }
            } else if (!name.equals(other.name)) {
                return false;
            }
            if (owner == null) {
                if (other.owner != null) {
                    return false;
                }
            } else if (!owner.equals(other.owner)) {
                return false;
            }
            return true;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy