/* * This file is part of JOP, the Java Optimized Processor * see <http://www.jopdesign.com/> * * Copyright (C) 2011, Stefan Hepp (stefan@stefant.org). * * 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 3 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 <http://www.gnu.org/licenses/>. */ package com.jopdesign.jcopter; import com.jopdesign.common.AppInfo; import com.jopdesign.common.MethodInfo; import com.jopdesign.common.code.CallGraph; import com.jopdesign.common.code.CallGraph.DUMPTYPE; import com.jopdesign.common.code.DefaultCallgraphBuilder; import com.jopdesign.common.code.ExecutionContext; import com.jopdesign.common.config.BooleanOption; import com.jopdesign.common.config.Config; import com.jopdesign.common.config.Config.BadConfigurationException; import com.jopdesign.common.config.EnumOption; import com.jopdesign.common.config.Option; import com.jopdesign.common.config.OptionGroup; import com.jopdesign.common.graphutils.MethodTraverser; import com.jopdesign.common.graphutils.MethodTraverser.MethodVisitor; import com.jopdesign.common.misc.AppInfoError; import com.jopdesign.common.tools.ClinitOrder; import com.jopdesign.common.tools.ConstantPoolRebuilder; import com.jopdesign.dfa.DFATool; import com.jopdesign.dfa.framework.DFACallgraphBuilder; import com.jopdesign.jcopter.greedy.GreedyConfig; import com.jopdesign.jcopter.greedy.GreedyOptimizer; import com.jopdesign.jcopter.inline.InlineConfig; import com.jopdesign.jcopter.inline.InlineOptimizer; import com.jopdesign.jcopter.inline.SimpleInliner; import com.jopdesign.jcopter.optimizer.LoadStoreOptimizer; import com.jopdesign.jcopter.optimizer.PeepholeOptimizer; import com.jopdesign.jcopter.optimizer.RelinkInvokesuper; import com.jopdesign.jcopter.optimizer.UnusedCodeRemover; import org.apache.log4j.Logger; import java.io.IOException; import java.util.LinkedHashSet; import java.util.Set; /** * This class executes various optimizations and analyses in the appropriate order, depending on the configuration. * * @author Stefan Hepp (stefan@stefant.org) */ public class PhaseExecutor { public static final Logger logger = Logger.getLogger(JCopter.LOG_ROOT + ".PhaseExecutor"); private static final BooleanOption REMOVE_UNUSED_MEMBERS = new BooleanOption("remove-unused-members", "Remove unreachable code", true); private static final BooleanOption CLEANUP_CONSTANT_POOL = new BooleanOption("cleanup-cp", "Remove unused constant pool entries", true); private static final EnumOption<DUMPTYPE> DUMP_CALLGRAPH = new EnumOption<DUMPTYPE>("dump-callgraph", "Dump the app callgraph (with or without callstrings)", CallGraph.DUMPTYPE.merged); private static final EnumOption<DUMPTYPE> DUMP_JVM_CALLGRAPH = new EnumOption<DUMPTYPE>("dump-jvm-callgraph", "Dump the jvm callgraph (with or without callstrings)", CallGraph.DUMPTYPE.off); private static final BooleanOption DUMP_NOIM_CALLS = new BooleanOption("dump-noim-calls", "Include calls to JVMHelp.noim() in the jvm callgraph dump", false); private static final BooleanOption SIMPLE_INLINER = new BooleanOption("simple-inliner", "Use fast inliner to inline getter,setter and wrapper", true); private static final Option[] optimizeOptions = { SIMPLE_INLINER, REMOVE_UNUSED_MEMBERS, CLEANUP_CONSTANT_POOL }; private static final Option[] debugOptions = { DUMP_CALLGRAPH, DUMP_JVM_CALLGRAPH, DUMP_NOIM_CALLS, }; private static final String GROUP_OPTIMIZE = "opt"; private static final String GROUP_GREEDY = "greedy"; private static final String GROUP_INLINE = "inline"; public static void registerOptions(Config config) { OptionGroup options = config.getOptions(); CallGraph.registerOptions(config); // Add phase options // .. nothing to configure yet .. // add debug options config.getDebugGroup().addOptions(debugOptions); // Add options of all used optimizations OptionGroup opt = options.getGroup(GROUP_OPTIMIZE); opt.addOptions(optimizeOptions); opt.addOptions(UnusedCodeRemover.optionList); OptionGroup greedy = options.getGroup(GROUP_GREEDY); GreedyConfig.registerOptions(greedy); OptionGroup inline = options.getGroup(GROUP_INLINE); InlineConfig.registerOptions(inline); } private final JCopter jcopter; private final OptionGroup options; private final AppInfo appInfo; private boolean updateDFA; private GreedyConfig greedyConfig; private InlineConfig inlineConfig; public PhaseExecutor(JCopter jcopter, OptionGroup options) throws BadConfigurationException { this.jcopter = jcopter; this.options = options; appInfo = AppInfo.getSingleton(); loadOptions(); } private void loadOptions() throws BadConfigurationException { if (getJConfig().doOptimizeNormal()) { greedyConfig = new GreedyConfig(jcopter, getGreedyOptions()); } inlineConfig = new InlineConfig(jcopter, getInlineOptions()); if (getOptimizeOptions().getOption(REMOVE_UNUSED_MEMBERS) && !getOptimizeOptions().getOption(CLEANUP_CONSTANT_POOL)) { // we cannot remove members if we do not cleanup the constantpool, this would // break the transitive-hull loader logger.warn("Disabling unused code remover because constant-pool cleanup is disabled."); } } public Config getConfig() { return options.getConfig(); } public OptionGroup getDebugConfig() { return jcopter.getJConfig().getConfig().getDebugGroup(); } public JCopterConfig getJConfig() { return jcopter.getJConfig(); } public OptionGroup getPhaseOptions() { return options; } public OptionGroup getOptimizeOptions() { return options.getGroup(GROUP_OPTIMIZE); } public OptionGroup getGreedyOptions() { return options.getGroup(GROUP_GREEDY); } public OptionGroup getInlineOptions() { return options.getGroup(GROUP_INLINE); } public boolean useCodeRemover() { return getOptimizeOptions().getOption(REMOVE_UNUSED_MEMBERS) && !getJConfig().doAssumeReflection() && useConstantPoolCleanup(); } public boolean useConstantPoolCleanup() { return getOptimizeOptions().getOption(CLEANUP_CONSTANT_POOL); } public void setUpdateDFA(boolean updateDFA) { // TODO bit of a hack, we currently only support updating if callstrings are empty if (updateDFA && appInfo.getCallstringLength() != 0) { logger.warn("Not updating DFA cache since callstrings are not of zero length"); } this.updateDFA = updateDFA && appInfo.getCallstringLength() == 0; } ///////////////////////////////////////////////////////////////////////////////////// // Dump Callgraph ///////////////////////////////////////////////////////////////////////////////////// public void dumpCallgraph(String graphName) { if (getDebugConfig().getOption(DUMP_CALLGRAPH) == CallGraph.DUMPTYPE.off && getDebugConfig().getOption(DUMP_JVM_CALLGRAPH) == CallGraph.DUMPTYPE.off) { return; } try { // Dumping the full graph is a bit much, we split it into several graphs Set<ExecutionContext> appRoots = new LinkedHashSet<ExecutionContext>(); Set<ExecutionContext> jvmRoots = new LinkedHashSet<ExecutionContext>(); Set<ExecutionContext> clinitRoots = new LinkedHashSet<ExecutionContext>(); Set<String> jvmClasses = new LinkedHashSet<String>(); if (appInfo.getProcessorModel() != null) { jvmClasses.addAll( appInfo.getProcessorModel().getJVMClasses() ); jvmClasses.addAll( appInfo.getProcessorModel().getNativeClasses() ); } CallGraph graph = appInfo.getCallGraph(); for (ExecutionContext ctx : graph.getRootNodes()) { if (ctx.getMethodInfo().getMethodSignature().equals(ClinitOrder.clinitSig)) { clinitRoots.add(ctx); } else if (jvmClasses.contains(ctx.getMethodInfo().getClassName())) { jvmRoots.add(ctx); } else if (appInfo.isJVMThread(ctx.getMethodInfo().getClassInfo())) { // This is to add Runnables like Scheduler and RtThread to the JVM classes. jvmRoots.add(ctx); } else { appRoots.add(ctx); } } OptionGroup debug = getDebugConfig(); // TODO to keep the CG size down, we could add options to exclude methods (like '<init>') or packages // from dumping and skip dumping methods reachable only over excluded methods graph.dumpCallgraph(getConfig(), graphName, "app", appRoots, debug.getOption(DUMP_CALLGRAPH), false); graph.dumpCallgraph(getConfig(), graphName, "clinit", clinitRoots, debug.getOption(DUMP_CALLGRAPH), false); graph.dumpCallgraph(getConfig(), graphName, "jvm", jvmRoots, debug.getOption(DUMP_JVM_CALLGRAPH), !debug.getOption(DUMP_NOIM_CALLS)); } catch (IOException e) { throw new AppInfoError("Unable to export to .dot file", e); } } ///////////////////////////////////////////////////////////////////////////////////// // Perform analyses ///////////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("unchecked") public void dataflowAnalysis(boolean loopBounds) { int callstringLength = appInfo.getCallstringLength(); // TODO this code is the same as in WCETTool .. DFATool dfaTool = jcopter.getDfaTool(); logger.info("Starting DFA analysis"); dfaTool.setAnalyzeBootMethod(true); dfaTool.load(); logger.info("Receiver analysis"); dfaTool.runReceiverAnalysis(callstringLength); if (loopBounds) { logger.info("Loop bound analysis"); dfaTool.runLoopboundAnalysis(callstringLength); // need to tell this to the WCA tool if (jcopter.useWCA()) { jcopter.getWcetTool().setHasDfaResults(true); } } dfaTool.cleanup(); } public void buildCallGraph(boolean useDFA) { if (useDFA) { // build the callgraph using DFA results DFACallgraphBuilder builder = new DFACallgraphBuilder(jcopter.getDfaTool(), appInfo.getCallstringLength()); builder.setSkipNatives(true); appInfo.buildCallGraph(builder); } else { DefaultCallgraphBuilder builder = new DefaultCallgraphBuilder(); builder.setSkipNatives(true); // rebuild without using the existing callgraph, because the callstrings are not updated by SimpleInliner builder.setUseCallgraph(false); appInfo.buildCallGraph(builder); // reduce the callgraph old-school reduceCallGraph(); } } /** * Reduce the callgraph stored with AppInfo. * Called by {@link AppInfo#buildCallGraph(boolean)}. */ public void reduceCallGraph() { // TODO perform callgraph thinning analysis // logger.info("Starting callgraph thinning"); // logger.info("Finished callgraph thinning"); } ///////////////////////////////////////////////////////////////////////////////////// // Perform optimizations ///////////////////////////////////////////////////////////////////////////////////// public void relinkInvokesuper() { appInfo.iterate(new RelinkInvokesuper()); } /** * Inline all methods which do not increase the code size. */ public void performSimpleInline() { // We never increase codesize, we do not copy methods, we are quite fast.. so just do this always .. // .. almost if (!getOptimizeOptions().getOption(SIMPLE_INLINER)) { logger.info("SimpleInliner has been disabled."); return; } // .. well, almost always. if (getJConfig().doAssumeDynamicClassLoader()) { logger.info("Skipping simple-inliner since dynamic class loading is assumed."); return; } logger.info("Starting simple-inliner"); new SimpleInliner(jcopter, inlineConfig).optimize(); logger.info("Finished simple-inliner"); } /** * Inline all InvokeSites which are marked for inlining by an inline strategy. */ public void performGreedyOptimizer() { // this is a more elaborate optimization which may increase codesize. if (!getJConfig().doOptimizeNormal()) return; GreedyOptimizer optimizer = new GreedyOptimizer( greedyConfig ); if (getJConfig().doAssumeDynamicClassLoader()) { logger.info("Skipping inliner since dynamic class loading is assumed."); // we do not have any other CodeOptimizers for now, remove return when we have some more return; } else { InlineOptimizer inliner = new InlineOptimizer(jcopter, inlineConfig); inliner.setUpdateDFA(updateDFA); optimizer.addOptimizer(inliner); } logger.info("Starting greedy optimizer"); optimizer.optimize(); logger.info("Finished greedy optimizer"); } /** * Run some simple optimizations to cleanup the bytecode without increasing its size. */ public void cleanupMethodCode() { logger.info("Starting code cleanup"); // perform some simple and safe peephole optimizations new PeepholeOptimizer(jcopter).optimize(); // optimize load/store // TODO implement this .. new LoadStoreOptimizer(jcopter).optimize(); // (more complex optimizations (dead-code elimination, constant-folding,..) should // go into another method..) logger.info("Finished code cleanup"); } public void removeDebugAttributes() { logger.info("Starting removal of debug attributes"); MethodVisitor visitor = new MethodVisitor() { @Override public void visitMethod(MethodInfo method) { method.getCode().removeDebugAttributes(); } }; appInfo.iterate(new MethodTraverser(visitor, true)); logger.info("Finished removal of debug attributes"); } /** * Find and remove unused classes, methods and fields */ public void removeUnusedMembers() { if (!useCodeRemover()) { return; } // If reflection is used, we cannot remove unreferenced code since we might miss references by reflection if (getJConfig().doAssumeReflection()) { logger.info("Skipping removal of unused code because usage of reflection is assumed."); return; } logger.info("Starting removal of unused members"); UnusedCodeRemover codeRemover = new UnusedCodeRemover(jcopter, getOptimizeOptions()); if (useCodeRemover()) { } codeRemover.execute(); logger.info("Finished removal of unused members"); } /** * Rebuild all constant pools. */ public void cleanupConstantPool() { if (!useConstantPoolCleanup()) { return; } logger.info("Starting cleanup of constant pools"); appInfo.iterate(new ConstantPoolRebuilder()); logger.info("Finished cleanup of constant pools"); } public void writeResults() { if (updateDFA) { if (useConstantPoolCleanup()) { logger.warn("Dumping DFA results, but this only works if cleanup-cp is disabled."); } logger.info("Writing updated DFA results to cache"); // TODO recreating the prologue after we cleaned up the code is a big messy hack // we could remove the prologue afterwards again // but we need it while the checksum is calculated, because we need the constant-pool // entries in place, the same way as in the WCA later, else the key is different. jcopter.getDfaTool().writeCachedResults(true); } } }