001    /*
002     *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003     *
004     *  This file is licensed to You under the Eclipse Public License (EPL);
005     *  You may not use this file except in compliance with the License. You
006     *  may obtain a copy of the License at
007     *
008     *      http://www.opensource.org/licenses/eclipse-1.0.php
009     *
010     *  See the COPYRIGHT.txt file distributed with this work for information
011     *  regarding copyright ownership.
012     */
013    package org.jikesrvm.adaptive.controller;
014    
015    import org.jikesrvm.VM;
016    import org.jikesrvm.adaptive.recompilation.CompilerDNA;
017    import org.jikesrvm.adaptive.util.AOSLogging;
018    import org.jikesrvm.classloader.NormalMethod;
019    import org.jikesrvm.compilers.common.CompiledMethod;
020    
021    /**
022     * This class encapsulates the analytic model used by the controller
023     * to guide multi-level recompilation decisions.  An early version of
024     * this model is described in the OOPSLA'2000 paper, but we've made
025     * some improvements since then...
026     *
027     * @see MultiLevelAdaptiveModel
028     */
029    abstract class AnalyticModel extends RecompilationStrategy {
030    
031      //---- Interface ------
032      // Code that inherits from AnalyticModel must define the
033      // following behavior
034    
035      /**
036       * Initialize the set of "optimization choices" that the
037       * cost-benefit model will consider when using will consider when
038       * using adaptive compilation.
039       */
040      abstract void populateRecompilationChoices();
041    
042      /**
043       * Compute the set of optimization choices that should be
044       * considered by the cost-benefit model, given the previous compiler.
045       *
046       * @param prevCompiler The compiler compiler that was used to
047       *                     compile cmpMethod
048       * @param cmpMethod The compiled method being considered
049       */
050      abstract RecompilationChoice[] getViableRecompilationChoices(int prevCompiler, CompiledMethod cmpMethod);
051    
052      // -----------------------------------------------------
053      // Below code that is (currently) common to all recompilation
054      // strategies that use the analytic model.
055    
056      /**
057       * Initialize the analytic model:
058       *
059       *  NOTE: The call to super.init() uses the command line options to
060       *  set up the optimization plans, so this must be run after the
061       *  command line options are available.
062       */
063      @Override
064      void init() {
065        // Do the common initialization first
066        super.init();
067    
068        // setup the recompilation choices that are available to the
069        // analytic model
070        populateRecompilationChoices();
071      }
072    
073      /**
074       * This method is the main decision making loop for all
075       * recompilation strategies that use the analytic model.
076       * <p>
077       * Given a HotMethodRecompilationEvent, this code will determine
078       * IF the method should be recompiled, and if so, HOW to perform
079       * the recompilation, i.e., what compilation plan should be used.
080       * The method returns a controller plan, which contains the compilation
081       * plan and other goodies.
082       *
083       * @param cmpMethod the compiled method of interest
084       * @param hme       the HotMethodRecompilationEvent
085       * @return the controller plan to be used or NULL, if no
086       *                   compilation is to be performed.  */
087      @Override
088      ControllerPlan considerHotMethod(CompiledMethod cmpMethod, HotMethodEvent hme) {
089        // Compiler used for the previous compilation
090        int prevCompiler = getPreviousCompiler(cmpMethod);
091        if (prevCompiler == -1) {
092          return null; // Not a method that we can recompile (trap, JNI).
093        }
094    
095        ControllerPlan plan = ControllerMemory.findMatchingPlan(cmpMethod);
096    
097        // for a outdated hot method from baseline, we consider OSR,
098        // and execute plan in the routine, no more action here
099        if (considerOSRRecompilation(cmpMethod, hme, plan)) return null;
100    
101        if (!considerForRecompilation(hme, plan)) return null;
102    
103        // Now we know the compiler that generated the method (prevCompiler) and
104        // that the method is a potential candidate for additional recompilation.
105        // So, next decide what, if anything, should be done now.
106        // We consider doing nothing (ie leaving the method at the current
107        // opt level, which incurs no  compilation cost), and recompiling the
108        // method at each greater compilation level.
109        double futureTimeForMethod = futureTimeForMethod(hme);
110    
111        // initialize bestAction as doing nothing, which means we'll
112        // spend just as much time in the method in the future as we have so far.
113        RecompilationChoice bestActionChoice = null;
114        double bestActionTime = futureTimeForMethod;
115        double bestCost = 0.0;
116    
117        AOSLogging.logger.recordControllerEstimateCostDoNothing(cmpMethod.getMethod(),
118                                                            CompilerDNA.getOptLevel(prevCompiler),
119                                                            bestActionTime);
120    
121        // Get a vector of optimization choices to consider
122        RecompilationChoice[] recompilationChoices = getViableRecompilationChoices(prevCompiler, cmpMethod);
123    
124        // Consider all choices in the vector of possibilities
125        NormalMethod meth = (NormalMethod) hme.getMethod();
126        for (RecompilationChoice choice : recompilationChoices) {
127          // Get the cost and benefit of this choice
128          double cost = choice.getCost(meth);
129          double futureExecutionTime = choice.getFutureExecutionTime(prevCompiler, futureTimeForMethod);
130    
131          double curActionTime = cost + futureExecutionTime;
132    
133          AOSLogging.logger.recordControllerEstimateCostOpt(cmpMethod.getMethod(), choice.toString(), cost, curActionTime);
134    
135          if (curActionTime < bestActionTime) {
136            bestActionTime = curActionTime;
137            bestActionChoice = choice;
138            bestCost = cost;
139          }
140        }
141    
142        // if the best action is the previous than we don't need to recompile
143        if (bestActionChoice == null) {
144          plan = null;
145        } else {
146          plan =
147              bestActionChoice.makeControllerPlan(cmpMethod, prevCompiler, futureTimeForMethod, bestActionTime, bestCost);
148        }
149        return plan;
150      }
151    
152      /* check if a compiled method is outdated, then decide if it needs OSR from BASE to OPT
153       */
154      boolean considerOSRRecompilation(CompiledMethod cmpMethod, HotMethodEvent hme, ControllerPlan plan) {
155        boolean outdatedBaseline = false;
156        if (plan == null) {
157          // if plan is null, this method was not compiled by AOS; it was
158          // either in the boot image or compiled by the initial baseline
159          // compiler.  In either case, if we've completed any recompilation
160          // then the compiled method is outdated.
161          outdatedBaseline =
162              ControllerMemory.planWithStatus(cmpMethod.getMethod(), ControllerPlan.COMPLETED) &&
163              cmpMethod.getCompilerType() == CompiledMethod.BASELINE;
164          if (outdatedBaseline) {
165            AOSLogging.logger.debug("outdated Baseline " + cmpMethod.getMethod() + "(" + cmpMethod.getId() + ")");
166          }
167        }
168    
169        // consider OSR option for old baseline-compiled activation
170        if (outdatedBaseline) {
171          if (!hme.getCompiledMethod().getSamplesReset()) {
172            // the first time we see an outdated event, we clear the samples
173            // associated with the cmid.
174            hme.getCompiledMethod().setSamplesReset();
175            Controller.methodSamples.reset(hme.getCMID());
176            AOSLogging.logger.debug(" Resetting method samples " + hme);
177            return true;
178          } else {
179            plan = chooseOSRRecompilation(hme);
180            // insert the plan to memory, which sets up state in the system to trigger
181            // the OSR promotion
182            if (plan != null) {
183              ControllerMemory.insert(plan);
184              // to coordinate with OSRListener, it marks cmpMethod as outdated
185              if (VM.VerifyAssertions) {
186                VM._assert(cmpMethod.getCompilerType() == CompiledMethod.BASELINE);
187              }
188              cmpMethod.setOutdated();
189            }
190            // we don't do any more action on the controller side.
191            return true;
192          }
193        }
194        return false;
195      }
196    
197      /**
198       * @param hme sample data for an outdated cmid
199       * @return a plan representing recompilation with OSR, null if OSR not
200       * justified.
201       */
202      private ControllerPlan chooseOSRRecompilation(HotMethodEvent hme) {
203        if (!Controller.options.OSR_PROMOTION) return null;
204    
205        AOSLogging.logger.debug(" Consider OSR for " + hme);
206    
207        ControllerPlan prev = ControllerMemory.findLatestPlan(hme.getMethod());
208    
209        if (prev.getStatus() == ControllerPlan.OSR_BASE_2_OPT) {
210          AOSLogging.logger.debug(" Already have an OSR promotion plan for this method");
211          return null;
212        }
213    
214        double millis = prev.getTimeCompleted() - prev.getTimeInitiated();
215        double speedup = prev.getExpectedSpeedup();
216        double futureTimeForMethod = futureTimeForMethod(hme);
217    
218        double futureTimeOptimized = futureTimeForMethod / speedup;
219    
220        AOSLogging.logger.debug(" Estimated future time for method " + hme + " is " + futureTimeForMethod);
221        AOSLogging.logger.debug(" Estimated future time optimized " + hme + " is " + (futureTimeOptimized + millis));
222    
223        if (futureTimeForMethod > futureTimeOptimized + millis) {
224          AOSLogging.logger.recordOSRRecompilationDecision(prev);
225          ControllerPlan p =
226              new ControllerPlan(prev.getCompPlan(),
227                                    prev.getTimeCreated(),
228                                    hme.getCMID(),
229                                    prev.getExpectedSpeedup(),
230                                    millis,
231                                    prev.getPriority());
232          // set up state to trigger osr
233          p.setStatus(ControllerPlan.OSR_BASE_2_OPT);
234          return p;
235        } else {
236          return null;
237        }
238      }
239    
240      /**
241       * This function defines how the analytic model handles a
242       * AINewHotEdgeEvent.  The basic idea is to use the model to
243       * evaluate whether it would be better to do nothing or to recompile
244       * at the same opt level, assuming there would be some "boost" after
245       * performing inlining.
246       */
247      @Override
248      void considerHotCallEdge(CompiledMethod cmpMethod, AINewHotEdgeEvent event) {
249    
250        // Compiler used for the previous compilation
251        int prevCompiler = getPreviousCompiler(cmpMethod);
252        if (prevCompiler == -1) {
253          return; // Not a method we can recompile (trap, JNI).
254        }
255    
256        ControllerPlan plan = ControllerMemory.findMatchingPlan(cmpMethod);
257        if (!considerForRecompilation(event, plan)) return;
258        double prevCompileTime = cmpMethod.getCompilationTime();
259    
260        // Use the model to calculate expected cost of (1) doing nothing
261        // and (2) recompiling at the same opt level with the FDO boost
262        double futureTimeForMethod = futureTimeForMethod(event);
263        double futureTimeForFDOMethod = prevCompileTime + (futureTimeForMethod / event.getBoostFactor());
264    
265        int prevOptLevel = CompilerDNA.getOptLevel(prevCompiler);
266        AOSLogging.logger.recordControllerEstimateCostDoNothing(cmpMethod.getMethod(), prevOptLevel, futureTimeForMethod);
267        AOSLogging.logger.recordControllerEstimateCostOpt(cmpMethod.getMethod(),
268                                                      "O" + prevOptLevel + "AI",
269                                                      prevCompileTime,
270                                                      futureTimeForFDOMethod);
271    
272        if (futureTimeForFDOMethod < futureTimeForMethod) {
273          // Profitable to recompile with FDO, so do it.
274          int optLevel = CompilerDNA.getOptLevel(prevCompiler);
275          double priority = futureTimeForMethod - futureTimeForFDOMethod;
276          plan =
277              createControllerPlan(cmpMethod.getMethod(),
278                                   optLevel,
279                                   null,
280                                   cmpMethod.getId(),
281                                   event.getBoostFactor(),
282                                   futureTimeForFDOMethod,
283                                   priority);
284          plan.execute();
285        }
286      }
287    
288      /**
289       * How much time do we expect to spend in the method in the future if
290       * we take no recompilation action?
291       * The key assumption is that we'll spend just as much time
292       * executing in the the method in the future as we have done so far
293       * in the past.
294       *
295       * @param hme The HotMethodEvent in question
296       * @return estimate of future execution time to be spent in this method
297       */
298      double futureTimeForMethod(HotMethodEvent hme) {
299        double numSamples = hme.getNumSamples();
300        double timePerSample = VM.interruptQuantum;
301        if (!VM.UseEpilogueYieldPoints) {
302          // NOTE: we take two samples per timer interrupt, so we have to
303          // adjust here (otherwise we'd give the method twice as much time
304          // as it actually deserves).
305          timePerSample /= 2.0;
306        }
307        if (Controller.options.mlCBS()) {
308          // multiple method samples per timer interrupt. Divide accordingly.
309          timePerSample /= VM.CBSMethodSamplesPerTick;
310        }
311        double timeInMethodSoFar = numSamples * timePerSample;
312        return timeInMethodSoFar;
313      }
314    }