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