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.database.callgraph;
014
015import org.jikesrvm.classloader.RVMMethod;
016import org.jikesrvm.runtime.RuntimeEntrypoints;
017
018/**
019 * A collection of weighted call targets.
020 * Depending on the size of the callee set, we use different subclasses
021 * that optimize the time/space tradeoffs.
022 */
023public abstract class WeightedCallTargets {
024
025  /**
026   * Iterate over all of the targets, evaluating the argument function on each edge.<p>
027   * NOTE: We guarantee that the targets will be iterated in monotonically decreasing
028   *       edge weight. This simplifies the coding of the inlining clients that consume
029   *       this information.
030   * @param func the function to evaluate on each target
031   */
032  public abstract void visitTargets(Visitor func);
033
034  /**
035   * Augment the weight associated with the argument method by 1.<p>
036   * NOTE: This method may change the representation of the target
037   * method.  The caller must be sure to update their backing store of
038   * WeightedCallTargets accordingly to avoid losing the update.
039   *
040   * @param target the method whose count is to be incremented
041   * @return the object representing the method's targets
042   */
043  public final WeightedCallTargets incrementCount(RVMMethod target) {
044    return augmentCount(target, 1);
045  }
046
047  /**
048   * Augment the weight associated with the argument method by the argument amount.
049   * NOTE: This method may change the representation of the target
050   * method.  The caller must be sure to update their backing store of
051   * WeightedCallTargets accordingly to avoid losing the update.
052   *
053   * @param target the method whose count is to be incremented
054   * @param amount amount to increment by
055   * @return the object representing the method's targets
056   */
057  public abstract WeightedCallTargets augmentCount(RVMMethod target, double amount);
058
059  /**
060   * Decay the weights of all call targets by the specified amount
061   * @param rate the value to decay by
062   */
063  public abstract void decay(double rate);
064
065  /**
066   * @return total weight of all call targets
067   */
068  public abstract double totalWeight();
069
070  /**
071   * @param goal RVMMethod that is the statically possible target
072   * @param isPrecise whether or not goal is a precise target, or should be
073   *        interpreted as being the root of a virtual method family, any of which
074   *        are statically possible.
075   * @return the filtered call targets or {@code null} if no such target exists
076   */
077  public abstract WeightedCallTargets filter(RVMMethod goal, boolean isPrecise);
078
079  public static WeightedCallTargets create(RVMMethod target, double weight) {
080    return new SingleTarget(target, weight);
081  }
082
083  /**
084   * Used by visitTargets
085   */
086  public interface Visitor {
087    void visit(RVMMethod target, double weight);
088  }
089
090  /**
091   * An implementation for storing a call site distribution that has a single target.
092   */
093  private static final class SingleTarget extends WeightedCallTargets {
094    private final RVMMethod target;
095    private float weight;
096
097    SingleTarget(RVMMethod t, double w) {
098      target = t;
099      weight = (float) w;
100    }
101
102    @Override
103    public void visitTargets(Visitor func) {
104      func.visit(target, weight);
105    }
106
107    @Override
108    public WeightedCallTargets augmentCount(RVMMethod t, double v) {
109      if (target.equals(t)) {
110        weight += v;
111        return this;
112      } else {
113        MultiTarget ms = new MultiTarget();
114        ms.augmentCount(target, weight);
115        ms.augmentCount(t, v);
116        return ms;
117      }
118    }
119
120    @Override
121    public void decay(double rate) {
122      weight /= rate;
123    }
124
125    @Override
126    public double totalWeight() {
127      return weight;
128    }
129
130    @Override
131    public WeightedCallTargets filter(RVMMethod goal, boolean isPrecise) {
132      if (isPrecise) {
133        return (goal.equals(target)) ? this : null;
134      } else {
135        if (goal.equals(target)) {
136          return this;
137        }
138        if (RuntimeEntrypoints.isAssignableWith(goal.getDeclaringClass(), target.getDeclaringClass())) {
139          return this;
140        } else {
141          return null;
142        }
143      }
144    }
145  }
146
147  /**
148   * An implementation for storing a call site distribution that has multiple targets.
149   */
150  private static final class MultiTarget extends WeightedCallTargets {
151    RVMMethod[] methods = new RVMMethod[5];
152    float[] weights = new float[5];
153
154    @Override
155    public synchronized void visitTargets(Visitor func) {
156      // Typically expect elements to be "almost" sorted due to previous sorting operations.
157      // When this is true, expected time for insertion sort is O(n).
158      for (int i = 1; i < methods.length; i++) {
159        RVMMethod m = methods[i];
160        if (m != null) {
161          float w = weights[i];
162          int j = i;
163          while (j > 0 && weights[j - 1] < w) {
164            methods[j] = methods[j - 1];
165            weights[j] = weights[j - 1];
166            j--;
167          }
168          methods[j] = m;
169          weights[j] = w;
170        }
171      }
172
173      for (int i = 0; i < methods.length; i++) {
174        if (methods[i] != null) {
175          func.visit(methods[i], weights[i]);
176        }
177      }
178    }
179
180    @Override
181    public synchronized WeightedCallTargets augmentCount(RVMMethod t, double v) {
182      int empty = -1;
183      for (int i = 0; i < methods.length; i++) {
184        if (methods[i] != null) {
185          if (methods[i].equals(t)) {
186            weights[i] += v;
187            return this;
188          }
189        } else {
190          if (empty == -1) {
191            empty = i;
192          }
193        }
194      }
195
196      // New target must be added
197      if (empty == -1) {
198        // must grow arrays
199        empty = methods.length;
200        RVMMethod[] newM = new RVMMethod[methods.length * 2];
201        System.arraycopy(methods, 0, newM, 0, methods.length);
202        methods = newM;
203        float[] newW = new float[weights.length * 2];
204        System.arraycopy(weights, 0, newW, 0, weights.length);
205        weights = newW;
206      }
207
208      methods[empty] = t;
209      weights[empty] = (float) v;
210      return this;
211    }
212
213    @Override
214    public synchronized void decay(double rate) {
215      for (int i = 0; i < weights.length; i++) {
216        weights[i] /= rate;
217      }
218    }
219
220    @Override
221    public synchronized double totalWeight() {
222      double sum = 0;
223      for (float weight : weights) {
224        sum += weight;
225      }
226      return sum;
227    }
228
229    @Override
230    public synchronized WeightedCallTargets filter(RVMMethod goal, boolean isPrecise) {
231      if (isPrecise) {
232        for (int i = 0; i < methods.length; i++) {
233          if (goal.equals(methods[i])) {
234            return WeightedCallTargets.create(methods[i], weights[i]);
235          }
236        }
237        return null;
238      } else {
239        int matchCount = 0;
240        int mismatchCount = 0;
241        for (RVMMethod method : methods) {
242          if (method != null) {
243            if (RuntimeEntrypoints.isAssignableWith(goal.getDeclaringClass(), method.getDeclaringClass())) {
244              matchCount++;
245            } else {
246              mismatchCount++;
247            }
248          }
249        }
250        if (mismatchCount == 0) {
251          return this;
252        }
253        if (matchCount == 0) {
254          return null;
255        }
256
257        MultiTarget filtered = new MultiTarget();
258        for (int i = 0; i < methods.length; i++) {
259          RVMMethod method = methods[i];
260          if (method != null && RuntimeEntrypoints.isAssignableWith(goal.getDeclaringClass(), method.getDeclaringClass())) {
261            filtered.augmentCount(method, weights[i]);
262          }
263        }
264        return filtered;
265      }
266    }
267  }
268}