/* * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.max.tele.method; import java.io.*; import java.lang.ref.*; import java.text.*; import java.util.*; import com.sun.max.lang.*; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.interpreter.*; import com.sun.max.tele.object.*; import com.sun.max.tele.reference.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.value.*; /** * The singleton manager for representations of machine code locations, both VM method * compilations in the VM's code cache and blocks of native function code about which less is known. */ public final class VmMachineCodeAccess extends AbstractVmHolder implements MaxMachineCode, TeleVMCache { private static final int TRACE_VALUE = 1; private static VmMachineCodeAccess vmMachineCodeAccess; public static VmMachineCodeAccess make(TeleVM vm) { if (vmMachineCodeAccess == null) { vmMachineCodeAccess = new VmMachineCodeAccess(vm); } return vmMachineCodeAccess; } private final TimedTrace updateTracer; private long lastUpdateEpoch = -1L; private final String entityName = "Machine Code"; private final String entityDescription; /** * A collection of {@link TeleTargetMethod} instances that * have been created for inspection (and registered here) before having been * allocated memory in the VM. The registration of these will be completed during the * next update cycle after their location and size become known. */ private final Set<TeleTargetMethod> unallocatedTeleTargetMethods = new HashSet<TeleTargetMethod>(); /** * A manager for code pointers that appear not to refer to any known loaded libraries. */ private final DisconnectedRemoteCodePointerManager disconnectedCodePointerManager; private final Object statsPrinter = new Object() { @Override public String toString() { final StringBuilder msg = new StringBuilder(); // TODO (mlvdv) add some stats? return msg.toString(); } }; public VmMachineCodeAccess(TeleVM vm) { super(vm); final TimedTrace tracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " creating"); tracer.begin(); this.entityDescription = "Remote code pointer creation and management for the " + vm.entityName(); this.disconnectedCodePointerManager = new DisconnectedRemoteCodePointerManager(vm); this.updateTracer = new TimedTrace(TRACE_VALUE, tracePrefix() + " updating"); tracer.end(statsPrinter); } /** {@inheritDoc} * <p> * Updates the representation of every <strong>method compilation</strong> surrogate * (represented as instances of subclasses of {@link TeleTargetMethod}, * in case any of the information in the VM's representation has changed since the previous update. This should not be * attempted until all information about allocated regions that might contain objects has been updated. */ public void updateCache(long epoch) { if (epoch > lastUpdateEpoch) { updateTracer.begin(); assert vm().lockHeldByCurrentThread(); for (TeleTargetMethod teleTargetMethod : unallocatedTeleTargetMethods) { if (teleTargetMethod.getRegionStart().isNotZero() && teleTargetMethod.getRegionNBytes() != 0) { // The compilation has been allocated memory in the VM since the last time we looked; complete its registration. unallocatedTeleTargetMethods.remove(teleTargetMethod); registerCompilation(teleTargetMethod); } } // Update the details within each code cache region. This must be done // separately from the general code cache update, once a general update // on all code and heap regions is complete. for (VmCodeCacheRegion region : codeCache().vmCodeCacheRegions()) { region.updateCache(epoch); } // Don't need to update the details separately of native function code for // each TeleNativeLibrary; that // happens as part of the general update for native function code. lastUpdateEpoch = epoch; updateTracer.end(null); } else { Trace.line(TRACE_VALUE, tracePrefix() + "redundant update epoch=" + epoch); } } public String entityName() { return entityName; } public String entityDescription() { return entityDescription; } public MaxEntityMemoryRegion<MaxMachineCode> memoryRegion() { // This represents no memory allocation; code resides in regions managed by the code cache. return null; } public boolean contains(Address address) { // There's no real notion of containing an address here. return false; } public TeleObject representation() { // No distinguished object in VM runtime represents this. return null; } public MaxMachineCodeRoutine<? extends MaxMachineCodeRoutine> findMachineCode(Address address) { TeleCompilation compilation = findCompilation(address); return (compilation != null) ? compilation : findNativeFunction(address); } /** * Get the method compilation, if any, whose code cache allocation includes * a given address in the VM, whether or not there is target code at the * specific location. * * @param address memory location in the VM * @return a method compilation whose code cache allocation includes the address, null if none */ private TeleCompilation findCompilationByAllocation(Address address) { TeleCompilation teleCompilation = null; for (VmCodeCacheRegion codeCacheRegion : codeCache().vmCodeCacheRegions()) { teleCompilation = codeCacheRegion.findCompilation(address); if (teleCompilation != null) { break; } } if (teleCompilation == null) { // Not a known method compilation. if (!codeCache().contains(address)) { // The address is not in the code cache. return null; } // Not a known method compilation, but in a code cache region. // Use the interpreter to see if the code manager in the VM knows about it. try { final RemoteReference targetMethodReference = (RemoteReference) methods().Code_codePointerToTargetMethod.interpret(new WordValue(address)).asReference(); // Possible that the address points to an unallocated area of a code region. if (!targetMethodReference.isZero()) { objects().makeTeleObject(targetMethodReference); // Constructor will register the compiled method if successful } } catch (MaxVMBusyException maxVMBusyException) { } catch (TeleInterpreterException e) { // This sometimes happens when the VM process terminates; ignore in those cases if (vm().state().processState() != MaxProcessState.TERMINATED) { throw TeleError.unexpected(e); } } // If a new method was discovered, the cache will now know about it for (VmCodeCacheRegion codeCacheRegion : codeCache().vmCodeCacheRegions()) { teleCompilation = codeCacheRegion.findCompilation(address); if (teleCompilation != null) { break; } } } return teleCompilation; } public TeleCompilation findCompilation(Address address) { TeleCompilation teleCompilation = findCompilationByAllocation(address); if (teleCompilation != null && teleCompilation.isValidCodeLocation(address)) { return teleCompilation; } return null; } public List<MaxCompilation> compilations(TeleClassMethodActor teleClassMethodActor) { final List<MaxCompilation> compilations = new ArrayList<MaxCompilation>(teleClassMethodActor.compilationCount()); for (TeleTargetMethod teleTargetMethod : teleClassMethodActor.compilations()) { compilations.add(findCompilationByAllocation(teleTargetMethod.getRegionStart())); } return Collections.unmodifiableList(compilations); } public TeleCompilation latestCompilation(TeleClassMethodActor teleClassMethodActor) throws MaxVMBusyException { if (!vm().tryLock()) { throw new MaxVMBusyException(); } try { final TeleTargetMethod teleTargetMethod = teleClassMethodActor.getCurrentCompilation(); return teleTargetMethod == null ? null : findCompilationByAllocation(teleTargetMethod.getRegionStart()); } finally { vm().unlock(); } } public TeleNativeFunction registerNativeFunction(Address codeStart, long nBytes, String name) throws MaxVMBusyException, IllegalArgumentException, MaxInvalidAddressException { return vm().nativeCode().registerNativeFunction(codeStart, nBytes, name); } public TeleNativeFunction findNativeFunction(Address address) { return vm().nativeCode().findNativeFunction(address); } /** * Adds a {@link MaxCompilation} to the registration of compilations for the code region containing it. * This should only be called from a constructor of a {@link TeleTargetMethod} subclass. * * @param teleTargetMethod the compiled method whose memory region is to be added to this registry * @throws IllegalArgumentException when the memory region of {@link TeleTargetMethod} overlaps one already in this registry. */ public void registerCompilation(TeleTargetMethod teleTargetMethod) { if (teleTargetMethod.getRegionStart().isZero() || teleTargetMethod.getRegionNBytes() == 0) { // The compilation is being constructed, which is to say it exists in the VM but has not // had an allocation of memory assigned to it. unallocatedTeleTargetMethods.add(teleTargetMethod); TeleWarning.message(tracePrefix() + " unallocated TargetMethod registered"); } else { // Find the code cache region in which the compilation has been allocated, and add it to // the registry we keep for that code region. final VmCodeCacheRegion codeCacheRegion = codeCache().findCodeCacheRegion(teleTargetMethod.getRegionStart()); assert codeCacheRegion != null; teleTargetMethod.setCodeCacheRegion(codeCacheRegion); codeCacheRegion.register(teleTargetMethod); } } /** * Creates a canonical pointer to a location in VM memory containing * machine code, null if there is no machine code at that location. * <p> * The absolute address of the code pointer may change over time, for * example if the code is a managed code cache region. * * @throws InvalidCodeAddressException if the location is known to be * illegal: null, zero, in a non-code holding region, or a * non-code area of a code region. */ public RemoteCodePointer makeCodePointer(Address address) throws InvalidCodeAddressException { if (address == null) { throw new InvalidCodeAddressException(null, "Null address"); } final VmCodeCacheRegion codeCacheRegion = codeCache().findCodeCacheRegion(address); if (codeCacheRegion != null) { final RemoteCodePointer codePointer = codeCacheRegion.codePointerManager().makeCodePointer(address); if (codePointer == null) { final String message = "Points into non-code memory in \"" + codeCacheRegion.entityName() + "\""; throw new InvalidCodeAddressException(address, message); } return codePointer; } final TeleNativeLibrary nativeLibrary = vm().nativeCode().findNativeLibrary(address); if (nativeLibrary != null) { return nativeLibrary.codePointerManager().makeCodePointer(address); } // Not in any known code holding region, does it point somewhere it shouldn't? if (address.isZero()) { throw new InvalidCodeAddressException(address, "Zero address"); } final MaxMemoryRegion memoryRegion = vm().addressSpace().find(address); if (memoryRegion != null) { throw new InvalidCodeAddressException(address, "points into non-code region=\"" + memoryRegion.regionName() + "\""); } // Completely unknown location, just make a code pointer and carry on return disconnectedCodePointerManager.makeCodePointer(address); } /** * Treats a long value as if it were a tagged code pointer and creates a legitimate * pointer at what would be the actual location, if that location contains code. * This is a heuristic and may produce false positives. * * @param value contents of a word in VM memory * @return a VM location containing code pointed at by the value, if treated as a code pointer; null if no code at that location. * @see CodePointer */ public RemoteCodePointer makeCodePointerFromTaggedLong(long value) { // First check: can only be a legitimate tagged pointer if low order bit is set if ((value & 1L) != 0) { // Create an instance of the (hosted only) VM code pointer object final CodePointer vmCodePointer = CodePointer.fromTaggedLong(value); // Compute the equivalent address for the pointer and see if it points at code. try { return makeCodePointer(vmCodePointer.toAddress()); } catch (InvalidCodeAddressException e) { return null; } } return null; } public TeleCompilation findCompilation(RemoteCodePointer codePointer) { return findCompilation(codePointer.getAddress()); } /** * Gets all target methods that encapsulate code compiled for a given method, either as a top level compilation or * as a result of inlining. * * TODO: Once inlining dependencies are tracked, this method needs to use them. * * @param methodKey the key denoting a method for which the target methods are being requested * @return local surrogates for all {@link TargetMethod}s in the VM that include code compiled for the method * matching {@code methodKey} */ public List<TeleTargetMethod> findCompilations(MethodKey methodKey) { final TeleClassMethodActor teleClassMethodActor = methods().findClassMethodActor(methodKey); if (teleClassMethodActor != null) { final List<TeleTargetMethod> result = new LinkedList<TeleTargetMethod>(); for (TeleTargetMethod teleTargetMethod : teleClassMethodActor.compilations()) { result.add(teleTargetMethod); } return result; } return Collections.emptyList(); } public void printSessionStats(PrintStream printStream, int indent, boolean verbose) { final String indentation = Strings.times(' ', indent); final NumberFormat formatter = NumberFormat.getInstance(); int compilationCount = 0; int loadedCompilationCount = 0; for (VmCodeCacheRegion codeCacheRegion : codeCache().vmCodeCacheRegions()) { compilationCount += codeCacheRegion.compilationCount(); loadedCompilationCount += codeCacheRegion.loadedCompilationCount(); } printStream.print(indentation + "Total compilations: " + formatter.format(compilationCount)); if (!unallocatedTeleTargetMethods.isEmpty()) { printStream.print(" (" + formatter.format(unallocatedTeleTargetMethods.size()) + " unallocated"); } printStream.print(" (code loaded: " + formatter.format(loadedCompilationCount) + ")\n"); printStream.println(indentation + "By region:"); for (VmCodeCacheRegion codeCacheRegion : codeCache().vmCodeCacheRegions()) { final StringBuffer sb = new StringBuffer(); sb.append(codeCacheRegion.entityName() + ": "); sb.append("Compilations=" + formatter.format(codeCacheRegion.compilationCount())); sb.append(", code loaded=" + formatter.format(codeCacheRegion.loadedCompilationCount())); printStream.println(indentation + " " + sb.toString()); } vm().nativeCode().printSessionStats(printStream, indent + 4, verbose); } public void writeSummary(PrintStream printStream) { for (VmCodeCacheRegion codeCacheRegion : codeCache().vmCodeCacheRegions()) { codeCacheRegion.writeSummary(printStream); } vm().nativeCode().writeSummary(printStream); } /** * A manager for pointers to disconnected machine code not in any known region, presumed to be constant. */ private class DisconnectedRemoteCodePointerManager extends AbstractRemoteCodePointerManager { /** * Map: address in VM --> a {@link RemoteCodePointer} that refers to the machine code at that location. */ private Map<Long, WeakReference<RemoteCodePointer>> addressToCodePointer = new HashMap<Long, WeakReference<RemoteCodePointer>>(); public DisconnectedRemoteCodePointerManager(TeleVM vm) { super(vm); } /** * {@inheritDoc} * <p> * This manager is designed for disconnected code, i.e. code that is not in any * region known. */ public MaxCodeHoldingRegion codeRegion() { return null; } /** * {@inheritDoc} * <p> * Since we don't know anything about this code, all we can ensure is that it is really * external to anything else we know about. */ public boolean isValidCodePointer(Address address) { return vm().addressSpace().find(address) == null; } public RemoteCodePointer makeCodePointer(Address address) { RemoteCodePointer codePointer = null; final WeakReference<RemoteCodePointer> existingRef = addressToCodePointer.get(address.toLong()); if (existingRef != null) { codePointer = existingRef.get(); } if (codePointer == null) { codePointer = new ConstantRemoteCodePointer(address); addressToCodePointer.put(address.toLong(), new WeakReference<RemoteCodePointer>(codePointer)); } return codePointer; } public int activePointerCount() { int count = 0; for (WeakReference<RemoteCodePointer> weakRef : addressToCodePointer.values()) { if (weakRef.get() != null) { count++; } } return count; } public int totalPointerCount() { return addressToCodePointer.size(); } } }