/*
* 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.vm.code;
import static com.sun.max.vm.MaxineVM.*;
import static com.sun.max.vm.VMOptions.*;
import java.util.*;
import com.sun.max.annotate.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.compiler.target.TargetBundleLayout.ArrayField;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.heap.debug.*;
import com.sun.max.vm.layout.*;
import com.sun.max.vm.reference.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.tele.*;
import com.sun.max.vm.type.*;
/**
* Target machine code cache management.
*
* All generated code is position independent as a whole, but target methods may contain direct call references between
* each other and these must be within 32-bit offsets! Therefore all code regions must be within 32-bit offsets from
* each other. A concrete implementation of this class must enforce this invariant.
*/
public abstract class CodeManager {
/**
* VM option for specifying the amount of memory to be reserved for the runtime baseline code region cache.
* Experiments have shown that a vast amount of baseline code is generated, so this region is rather large until
* code eviction logic is properly in place.
*/
public static final VMSizeOption runtimeBaselineCodeRegionSize =
register(new VMSizeOption("-XX:ReservedBaselineCodeCacheSize=", Size.M.times(128),
"Memory allocated for runtime code region cache."), MaxineVM.Phase.PRISTINE);
/**
* VM option for specifying the amount of memory to be reserved for the runtime opt code region cache.
* Experiments have shown that very little such code is generated, so this region is rather small.
*/
public static final VMSizeOption runtimeOptCodeRegionSize =
register(new VMSizeOption("-XX:ReservedOptCodeCacheSize=", Size.M.times(16),
"Memory allocated for runtime code region cache."), MaxineVM.Phase.PRISTINE);
private int nAllocations = 0;
private int lastSurvivorSize;
private int largestSurvivorSize = 0;
protected void recordSurvivorSize(int survivorSize) {
lastSurvivorSize = survivorSize;
if (survivorSize > largestSurvivorSize) {
largestSurvivorSize = survivorSize;
}
}
/**
* Performs code cache validation.
* This has a return type of {@code boolean} so that it can be used as the condition in an
* assertion. That is, code cache validation is predicated on assertions being enabled.
*/
private boolean validateCodeCache() {
CodeCacheValidation.instance.submit();
return true;
}
/**
* This option can be used to force baseline code cache contention every N method allocations.
* It monitors the amount of surviving methods/bytes after each contention and dumps those, including the largest.
*/
public static int CodeCacheContentionFrequency;
static {
VMOptions.addFieldOption("-XX:", "CodeCacheContentionFrequency", CodeManager.class,
"Enforce baseline code cache contention every N method allocations.", MaxineVM.Phase.STARTING);
}
/**
* Categorization of how long a method is destined to stay around.
*/
public enum Lifespan {
/**
* Class initializers etc.
*/
ONE_SHOT,
/**
* Methods that will likely be removed after some time (e.g., compiled by a baseline compiler).
*/
SHORT,
/**
* Methods that stay (e.g., compiled by an optimizing compiler).
*/
LONG;
}
/**
* The baseline code region contains machine code generated by the baseline compiler.
*/
@INSPECTED
protected static final SemiSpaceCodeRegion runtimeBaselineCodeRegion = new SemiSpaceCodeRegion("Code-Runtime-Baseline");
/**
* The opt code region contains machine code generated by the optimising compiler as well as adapters and trampolines.
*/
@INSPECTED
protected static final CodeRegion runtimeOptCodeRegion = new CodeRegion("Code-Runtime-Opt");
/**
* Get the runtime baseline code region.
* @return the runtime baseline code region
*/
public SemiSpaceCodeRegion getRuntimeBaselineCodeRegion() {
return runtimeBaselineCodeRegion;
}
/**
* Get the runtime opt code region.
* @return the runtime baseline code region
*/
public CodeRegion getRuntimeOptCodeRegion() {
return runtimeOptCodeRegion;
}
/**
* Initialize this code manager.
*/
void initialize() {
}
private static int BOOT_TO_BASELINE_INITIAL_SIZE = 10;
/**
* Records all direct call links from the boot code region to the baseline code region.
*/
private static TargetMethod[] bootToBaseline = new TargetMethod[BOOT_TO_BASELINE_INITIAL_SIZE];
private static int nBootToBaseline = 0;
public static int bootToBaselineSize() {
return nBootToBaseline;
}
public static synchronized void recordBootToBaselineCaller(final TargetMethod tm) {
if (CodeEviction.logging()) {
CodeEviction.codeEvictionLogger.logBootToBaseline(tm);
}
if (nBootToBaseline == bootToBaseline.length) {
bootToBaseline = Arrays.copyOf(bootToBaseline, bootToBaseline.length * 2);
}
bootToBaseline[nBootToBaseline] = tm;
++nBootToBaseline;
}
public static TargetMethod[] bootToBaselineCallers() {
return bootToBaseline;
}
public static void bootToBaselineDo(final TargetMethod.Closure closure) {
for (int i = 0; i < nBootToBaseline; i++) {
if (!closure.doTargetMethod(bootToBaseline[i])) {
return;
}
}
}
/**
* Allocates memory for the code-related arrays of a given target method
* and {@linkplain TargetMethod#setCodeArrays(byte[], Pointer, byte[], Object[]) initializes} them.
*
* @param targetBundleLayout describes the layout of the arrays in the allocated space
* @param targetMethod the target method for which the code-related arrays are allocated
* @param inHeap specifies if the memory should be allocated in a code region or on the heap
*/
synchronized void allocate(TargetBundleLayout targetBundleLayout, TargetMethod targetMethod, boolean inHeap, Lifespan lifespan) {
final Size bundleSize = targetBundleLayout.bundleSize();
int codeLength = targetBundleLayout.length(ArrayField.code);
int scalarLiteralsLength = targetBundleLayout.length(ArrayField.scalarLiterals);
int referenceLiteralsLength = targetBundleLayout.length(ArrayField.referenceLiterals);
final Size allocationSize;
CodeRegion currentCodeRegion = null;
allocationSize = bundleSize;
Object allocationTraceDescription = Code.TraceCodeAllocation ? (targetMethod.classMethodActor() == null ? targetMethod.regionName() : targetMethod.classMethodActor()) : null;
Pointer start;
boolean mustReenableSafepoints = false;
if (inHeap) {
assert !isHosted();
int byteArraySize = allocationSize.minus(Layout.byteArrayLayout().headerSize()).toInt();
byte[] buf = new byte[byteArraySize];
// 'buf' must not move until it has been reformatted
mustReenableSafepoints = !SafepointPoll.disable();
start = Layout.originToCell(Reference.fromJava(buf).toOrigin());
} else {
if (!isHosted()) {
// The allocation and initialization of objects in a code region must be atomic with respect to garbage collection.
mustReenableSafepoints = !SafepointPoll.disable();
Heap.disableAllocationForCurrentThread();
if (lifespan == Lifespan.LONG) {
currentCodeRegion = runtimeOptCodeRegion;
} else {
currentCodeRegion = runtimeBaselineCodeRegion;
}
} else {
currentCodeRegion = Code.bootCodeRegion();
}
if (currentCodeRegion == runtimeBaselineCodeRegion && CodeCacheContentionFrequency > 0 && ++nAllocations % CodeCacheContentionFrequency == 0) {
start = Pointer.zero();
} else {
start = currentCodeRegion.allocate(allocationSize, false);
}
// Allocation in the baseline code region may take another attempt upon contention, after compaction.
if (start.isZero() && currentCodeRegion == runtimeBaselineCodeRegion) {
CodeEviction.run();
assert validateCodeCache();
start = currentCodeRegion.allocate(allocationSize, false);
if (CodeCacheContentionFrequency > 0 && CodeEviction.logging()) {
CodeEviction.codeEvictionLogger.logStats_Surviving(lastSurvivorSize, largestSurvivorSize);
}
}
}
traceChunkAllocation(allocationTraceDescription, allocationSize, start, inHeap);
if (start.isZero()) {
if (mustReenableSafepoints) {
SafepointPoll.enable();
}
Heap.enableAllocationForCurrentThread();
Log.print("Out of memory allocating in code region named " + currentCodeRegion.regionName());
if (currentCodeRegion == runtimeBaselineCodeRegion) {
Log.println(" - try larger value for " + runtimeBaselineCodeRegionSize.toString() + "<n>");
} else if (currentCodeRegion == runtimeOptCodeRegion) {
Log.println(" - try larger value for " + runtimeOptCodeRegionSize.toString() + "<n>");
}
MaxineVM.exit(11);
}
targetMethod.setStart(start);
targetMethod.setSize(allocationSize);
// Initialize the objects in the allocated space so that they appear as a set of contiguous
// well-formed objects that can be traversed.
byte[] code;
byte[] scalarLiterals = null;
Object[] referenceLiterals = null;
if (MaxineVM.isHosted()) {
code = new byte[codeLength];
scalarLiterals = scalarLiteralsLength == 0 ? null : new byte[scalarLiteralsLength];
referenceLiterals = referenceLiteralsLength == 0 ? null : new Object[referenceLiteralsLength];
} else {
final Pointer codeCell = targetBundleLayout.cell(start, ArrayField.code);
code = (byte[]) Cell.plantArray(codeCell, ClassRegistry.BYTE_ARRAY.dynamicHub(), codeLength);
if (scalarLiteralsLength != 0) {
final Pointer scalarLiteralsCell = targetBundleLayout.cell(start, ArrayField.scalarLiterals);
scalarLiterals = (byte[]) Cell.plantArray(scalarLiteralsCell, ClassRegistry.BYTE_ARRAY.dynamicHub(), scalarLiteralsLength);
}
if (referenceLiteralsLength != 0) {
final Pointer referenceLiteralsCell = targetBundleLayout.cell(start, ArrayField.referenceLiterals);
referenceLiterals = (Object[]) Cell.plantArray(referenceLiteralsCell, ClassActor.fromJava(Object[].class).dynamicHub(), referenceLiteralsLength);
}
if (Code.TraceCodeAllocation) {
traceAllocation(targetBundleLayout, bundleSize, scalarLiteralsLength, referenceLiteralsLength, start, codeCell);
}
}
final Pointer codeStart = targetBundleLayout.firstElementPointer(start, ArrayField.code);
targetMethod.setCodeArrays(code, codeStart, scalarLiterals, referenceLiterals);
if (currentCodeRegion == runtimeBaselineCodeRegion) {
targetMethod.protect();
}
if (!MaxineVM.isHosted()) {
// It is now safe again to perform operations that may block and/or trigger a garbage collection
if (mustReenableSafepoints) {
SafepointPoll.enable();
}
if (!inHeap) {
Heap.enableAllocationForCurrentThread();
}
}
if (currentCodeRegion != null) {
currentCodeRegion.add(targetMethod);
}
}
private void traceAllocation(TargetBundleLayout targetBundleLayout, Size bundleSize, int scalarLiteralsLength, int referenceLiteralsLength, Pointer start, Pointer codeCell) {
final boolean lockDisabledSafepoints = Log.lock();
Log.printCurrentThread(false);
Log.print(": Code arrays: code=[");
Log.print(codeCell);
Log.print(" - ");
Log.print(targetBundleLayout.cellEnd(start, ArrayField.code));
Log.print("], scalarLiterals=");
if (scalarLiteralsLength > 0) {
Log.print(targetBundleLayout.cell(start, ArrayField.scalarLiterals));
Log.print(" - ");
Log.print(targetBundleLayout.cellEnd(start, ArrayField.scalarLiterals));
Log.print("], referenceLiterals=");
} else {
Log.print("0, referenceLiterals=");
}
if (referenceLiteralsLength > 0) {
Log.print(targetBundleLayout.cell(start, ArrayField.referenceLiterals));
Log.print(" - ");
Log.print(targetBundleLayout.cellEnd(start, ArrayField.referenceLiterals));
Log.println("]");
} else {
Log.println(0);
}
Log.unlock(lockDisabledSafepoints);
}
private void traceChunkAllocation(Object purpose, Size size, Pointer cell, boolean inHeap) {
if (!cell.isZero() && purpose != null) {
final boolean lockDisabledSafepoints = Log.lock();
Log.printCurrentThread(false);
if (inHeap) {
Log.print(": Allocated chunk in heap for ");
} else {
Log.print(": Allocated chunk in code cache for ");
}
if (purpose instanceof MethodActor) {
Log.printMethod((MethodActor) purpose, false);
} else {
Log.print(purpose);
}
Log.print(" at ");
Log.print(cell);
Log.print(" [size ");
Log.print(size.wordAligned().toInt());
Log.print(", end=");
Log.print(cell.plus(size.wordAligned()));
Log.println(']');
Log.unlock(lockDisabledSafepoints);
}
}
/**
* Looks up the code region in which the specified code pointer lies. This lookup includes
* the boot code region.
*
* @param codePointer the code pointer
* @return a reference to the code region that contains the specified code pointer, if one exists; {@code null} if
* the code pointer lies outside of all code regions
*/
CodeRegion codePointerToCodeRegion(Address codePointer) {
if (Code.bootCodeRegion().contains(codePointer)) {
return Code.bootCodeRegion();
}
if (runtimeBaselineCodeRegion.contains(codePointer)) {
return runtimeBaselineCodeRegion;
}
if (runtimeOptCodeRegion.contains(codePointer)) {
return runtimeOptCodeRegion;
}
return null;
}
/**
* Looks up the target method that contains the specified code pointer.
*
* @param codePointer the code pointer to lookup
* @return the target method that contains the specified code pointer, if it exists; {@code null}
* if no target method contains the specified code pointer
*/
TargetMethod codePointerToTargetMethod(Address codePointer) {
TargetMethod result = null;
final CodeRegion codeRegion = codePointerToCodeRegion(codePointer);
if (codeRegion != null) {
result = codeRegion.find(codePointer);
}
return result;
}
/**
* Visit the cells in all the code regions in this code manager.
*
* @param cellVisitor the visitor to call back for each cell in each region
* @param includeBootCode specifies if the cells in the {@linkplain Code#bootCodeRegion() boot code region} should
* also be visited
*/
void visitCells(CellVisitor cellVisitor, boolean includeBootCode) {
if (includeBootCode) {
visitAllIn(cellVisitor, Code.bootCodeRegion());
}
visitAllIn(cellVisitor, runtimeBaselineCodeRegion);
visitAllIn(cellVisitor, runtimeOptCodeRegion);
}
void visitAllIn(CellVisitor v, CodeRegion cr) {
Pointer firstCell = cr.gcstart().asPointer();
Pointer cell = firstCell;
while (cell.lessThan(cr.getAllocationMark())) {
cell = DebugHeap.checkDebugCellTag(firstCell, cell);
cell = v.visitCell(cell);
}
}
/**
* Return size of runtime baseline code region.
* @return size of runtime baseline code region
*/
public Size getRuntimeBaselineCodeRegionSize() {
return runtimeBaselineCodeRegionSize.getValue();
}
/**
* Return size of runtime opt code region.
* @return size of runtime opt code region
*/
public Size getRuntimeOptCodeRegionSize() {
return runtimeOptCodeRegionSize.getValue();
}
/**
* By definition, short-lived methods go to the baseline code region.
*/
public static boolean isShortlived(TargetMethod tm) {
return runtimeBaselineCodeRegion.contains(tm.start());
}
/**
* A collection of methods that support certain inspection services.
* The public methods are to be called by all implementations when
* the specified events occur.
*
*/
public static final class Inspect {
/**
* Announces that a code eviction is about to begin. It does almost
* nothing, but it must be called by managed code region implementations for
* certain Inspector services to work.
* <p>
* This should be called after any preliminary steps that do not modify
* the code region have been completed.
*
* @param codeRegion the region of VM code cache in which eviction is about to start
*/
public static void notifyEvictionStarted(CodeRegion codeRegion) {
InspectableCodeInfo.notifyEvictionStarted(codeRegion);
inspectableCodeEvictionStarted();
}
/**
* Announces that a code eviction has finished. It does almost
* nothing, but it must be called by managed code region implementations for
* certain Inspector services to work.
* <p>
* This should be called as soon as possible after all changes have been
* made, for example before any non-destructive verification..
*
* @param codeRegion the region of VM code cache in which eviction just completed
*/
public static void notifyEvictionCompleted(CodeRegion codeRegion) {
InspectableCodeInfo.notifyEvictionCompleted(codeRegion);
inspectableCodeEvictionCompleted();
}
private Inspect() {
}
/**
* An empty method whose purpose is to be interrupted by the Inspector
* at the beginning of a code eviction.
* <p>
* This particular method is intended for use by users of the Inspector, and
* is distinct from a method used by the Inspector for internal use.
* <p>
* <strong>Important:</strong> The Inspector assumes that this method is loaded
* and compiled in the boot image and that it will never be dynamically recompiled.
*/
@INSPECTED
@NEVER_INLINE
private static void inspectableCodeEvictionStarted() {
}
/**
* An empty method whose purpose is to be interrupted by the Inspector
* at the completion of a code eviction.
* <p>
* This particular method is intended for use by users of the Inspector, and
* is distinct from a method used by the Inspector for internal use.
* <p>
* <strong>Important:</strong> The Inspector assumes that this method is loaded
* and compiled in the boot image and that it will never be dynamically recompiled.
*/
@INSPECTED
@NEVER_INLINE
private static void inspectableCodeEvictionCompleted() {
}
}
}