/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.adaptive.measurements.organizers; import org.jikesrvm.VM; import org.jikesrvm.adaptive.controller.Controller; import org.jikesrvm.adaptive.measurements.RuntimeMeasurements; import org.jikesrvm.adaptive.measurements.listeners.EdgeListener; import org.jikesrvm.classloader.RVMMethod; import org.jikesrvm.compilers.baseline.BaselineCompiledMethod; import org.jikesrvm.compilers.common.CompiledMethod; import org.jikesrvm.compilers.common.CompiledMethods; import org.jikesrvm.compilers.opt.OptimizingCompilerException; import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod; import org.jikesrvm.compilers.opt.runtimesupport.OptMachineCodeMap; import org.jikesrvm.scheduler.RVMThread; import org.jikesrvm.runtime.Magic; import org.vmmagic.unboxed.Offset; import org.vmmagic.pragma.NonMoving; /** * An organizer to build a dynamic call graph from call graph edge * samples. * <p> * It communicates with an edge listener through a * integer array, denoted buffer. When this organizer is woken up * via threshold reached, it processes the sequence of triples * that are contained in buffer. * <p> * After processing the buffer and updating the dynamic call graph, * it optionally notifies the AdaptiveInliningOrganizer who is responsible * for analyzing the dynamic call graph for the purposes of * feedback-directed inlining. * <p> * Note: Since this information is intended to drive feedback-directed inlining, * the organizer drops edges that are not relevant. For example, one of * the methods is a native method, or the callee is a runtime service * routine and thus can't be inlined into its caller even if it is reported * as hot. Thus, the call graph may not contain some hot edges since they * aren't viable inlining candidates. One may argue that this is not the right * design. Perhaps instead the edges should be present for profiling purposes, * but not reported as inlining candidates to the * <p> * EXPECTATION: buffer is filled all the way up with triples. */ @NonMoving public class DynamicCallGraphOrganizer extends Organizer { private static final boolean DEBUG = false; /** * buffer provides the communication channel between the edge listener * and the organizer.<p> * The buffer contains an array of triples {@code <callee, caller, address>} where * the caller and callee are CompiledMethodID's, and address identifies * the call site. * The edge listener adds triples. * At some point the listener deregisters itself and notifies the organizer * by calling thresholdReached(). */ private int[] buffer; /** the buffer's size, i.e. length of {@link #buffer} */ private int bufferSize; /** the maximum number of triples contained in buffer */ private int numberOfBufferTriples; /** * Countdown of times we have to have called thresholdReached before * we believe the call graph has enough samples that it is reasonable * to use it to guide profile-directed inlining. When this value reaches 0, * we stop decrementing it and start letting other parts of the adaptive * system use the profile data. */ private int thresholdReachedCount; /** * Constructs a new dynamic call graph organizer that will get its data from the given edge listener. * @param edgeListener the listener that provides data for this organizer */ public DynamicCallGraphOrganizer(EdgeListener edgeListener) { listener = edgeListener; edgeListener.setOrganizer(this); } /** * Initialization: set up data structures and sampling objects. * <p> * Uses either timer based sampling or counter based sampling, * depending on {@link Controller#options}. */ @Override public void initialize() { if (Controller.options.cgCBS()) { numberOfBufferTriples = Controller.options.DCG_SAMPLE_SIZE * VM.CBSCallSamplesPerTick; } else { numberOfBufferTriples = Controller.options.DCG_SAMPLE_SIZE; } numberOfBufferTriples *= RVMThread.availableProcessors; bufferSize = numberOfBufferTriples * 3; buffer = new int[bufferSize]; ((EdgeListener) listener).setBuffer(buffer); /* We're looking for a thresholdReachedCount such that when we reach the count, * a single sample contributes less than the AI_HOT_CALLSITE_THRESHOLD. In other words, we * want the inequality * thresholdReachedCount * samplesPerInvocationOfThresholdReached > 1 / AI_HOT_CALLSITE_THRESHOLD * to be true. */ thresholdReachedCount = (int) Math.ceil(1.0 / (numberOfBufferTriples * Controller.options.INLINE_AI_HOT_CALLSITE_THRESHOLD));; // Install the edge listener if (Controller.options.cgTimer()) { RuntimeMeasurements.installTimerContextListener((EdgeListener) listener); } else if (Controller.options.cgCBS()) { RuntimeMeasurements.installCBSContextListener((EdgeListener) listener); } else { if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED, "Unexpected value of call_graph_listener_trigger"); } } /** * Process contents of buffer: * add call graph edges and increment their weights. */ @Override void thresholdReached() { if (DEBUG) VM.sysWriteln("DCG_Organizer.thresholdReached()"); for (int i = 0; i < bufferSize; i = i + 3) { int calleeCMID = 0; // FIXME: This is necessary but hacky and may not even be correct. while (calleeCMID == 0) { calleeCMID = buffer[i + 0]; } Magic.isync(); CompiledMethod compiledMethod = CompiledMethods.getCompiledMethod(calleeCMID); if (compiledMethod == null) continue; RVMMethod callee = compiledMethod.getMethod(); if (callee.isRuntimeServiceMethod()) { if (DEBUG) VM.sysWrite("Skipping sample with runtime service callee"); continue; } int callerCMID = buffer[i + 1]; compiledMethod = CompiledMethods.getCompiledMethod(callerCMID); if (compiledMethod == null) continue; RVMMethod stackFrameCaller = compiledMethod.getMethod(); int MCOff = buffer[i + 2]; Offset MCOffset = Offset.fromIntSignExtend(buffer[i + 2]); int bytecodeIndex = -1; RVMMethod caller = null; switch (compiledMethod.getCompilerType()) { case CompiledMethod.TRAP: case CompiledMethod.JNI: if (DEBUG) VM.sysWrite("Skipping sample with TRAP/JNI caller"); continue; case CompiledMethod.BASELINE: { BaselineCompiledMethod baseCompiledMethod = (BaselineCompiledMethod) compiledMethod; // note: the following call expects the offset in INSTRUCTIONS! bytecodeIndex = baseCompiledMethod.findBytecodeIndexForInstruction(MCOffset); caller = stackFrameCaller; } break; case CompiledMethod.OPT: { OptCompiledMethod optCompiledMethod = (OptCompiledMethod) compiledMethod; OptMachineCodeMap mc_map = optCompiledMethod.getMCMap(); try { bytecodeIndex = mc_map.getBytecodeIndexForMCOffset(MCOffset); if (bytecodeIndex == -1) { // this can happen we we sample a call // to a runtimeSerivce routine. // We aren't setup to inline such methods anyways, // so skip the sample. if (DEBUG) { VM.sysWrite(" *** SKIP SAMPLE ", stackFrameCaller.toString()); VM.sysWrite("@", compiledMethod.toString()); VM.sysWrite(" at MC offset ", MCOff); VM.sysWrite(" calling ", callee.toString()); VM.sysWriteln(" due to invalid bytecodeIndex"); } continue; // skip sample. } } catch (java.lang.ArrayIndexOutOfBoundsException e) { VM.sysWrite(" ***ERROR: getBytecodeIndexForMCOffset(", MCOffset); VM.sysWriteln(") ArrayIndexOutOfBounds!"); e.printStackTrace(); if (VM.ErrorsFatal) VM.sysFail("Exception in AI organizer."); caller = stackFrameCaller; continue; // skip sample } catch (OptimizingCompilerException e) { VM.sysWrite("***Error: SKIP SAMPLE: can't find bytecode index in OPT compiled " + stackFrameCaller + "@" + compiledMethod + " at MC offset ", MCOff); VM.sysWriteln("!"); if (VM.ErrorsFatal) VM.sysFail("Exception in AI organizer."); continue; // skip sample } try { caller = mc_map.getMethodForMCOffset(MCOffset); } catch (java.lang.ArrayIndexOutOfBoundsException e) { VM.sysWrite(" ***ERROR: getMethodForMCOffset(", MCOffset); VM.sysWriteln(") ArrayIndexOutOfBounds!"); e.printStackTrace(); if (VM.ErrorsFatal) VM.sysFail("Exception in AI organizer."); caller = stackFrameCaller; continue; } catch (OptimizingCompilerException e) { VM.sysWrite("***Error: SKIP SAMPLE: can't find caller in OPT compiled " + stackFrameCaller + "@" + compiledMethod + " at MC offset ", MCOff); VM.sysWriteln("!"); if (VM.ErrorsFatal) VM.sysFail("Exception in AI organizer."); continue; // skip sample } if (caller == null) { VM.sysWrite(" ***ERROR: getMethodForMCOffset(", MCOffset); VM.sysWriteln(") returned null!"); caller = stackFrameCaller; continue; // skip sample } } break; } // increment the call graph edge, adding it if needed Controller.dcg.incrementEdge(caller, bytecodeIndex, callee); } if (thresholdReachedCount > 0) { thresholdReachedCount--; } } /** * Checks if the dynamic call graph organizer has gathered and processed enough samples to support decisions. * @return {@code true} if enough data is available, {@code false} otherwise */ public boolean someDataAvailable() { return thresholdReachedCount == 0; } }