/*
* 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.util;
import static org.jikesrvm.runtime.JavaSizeConstants.LOG_BYTES_IN_CHAR;
import static org.jikesrvm.runtime.JavaSizeConstants.LOG_BYTES_IN_INT;
import static org.jikesrvm.runtime.UnboxedSizeConstants.BITS_IN_ADDRESS;
import static org.jikesrvm.runtime.UnboxedSizeConstants.LOG_BYTES_IN_ADDRESS;
import org.jikesrvm.VM;
import org.jikesrvm.mm.mminterface.Barriers;
import org.jikesrvm.runtime.Entrypoints;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.scheduler.Synchronization;
import org.vmmagic.pragma.Entrypoint;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.NoInline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.pragma.UninterruptibleNoWarn;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Word;
/**
* Various service utilities. This is a common place for some shared utility routines
*/
@Uninterruptible
public class Services {
/**
* Biggest buffer you would possibly need for {@link org.jikesrvm.scheduler.RVMThread#dump(char[], int)}
* Modify this if you modify that method.
*/
public static final int MAX_DUMP_LEN =
10 /* for thread ID */ + 7 + 5 + 5 + 11 + 5 + 10 + 13 + 17 + 10;
/** Pre-allocate the dump buffer, since dump() might get called inside GC. */
private static final char[] dumpBuffer = new char[MAX_DUMP_LEN];
@Entrypoint
private static int dumpBufferLock = 0;
/** Reset at boot time. */
@Entrypoint
private static Offset dumpBufferLockOffset = Offset.max();
/**
* A map of hexadecimal digit values to their character representations.
* <P>
* XXX We currently only use '0' through '9'. The rest are here pending
* possibly merging this code with the similar code in Log.java, or breaking
* this code out into a separate utility class.
*/
private static final char [] hexDigitCharacter =
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
'f' };
/**
* How many characters we need to have in a buffer for building string
* representations of <code>long</code>s, such as {@link #intBuffer}. A
* <code>long</code> is a signed 64-bit integer in the range -2^63 to
* 2^63+1. The number of digits in the decimal representation of 2^63 is
* ceiling(log10(2^63)) == ceiling(63 * log10(2)) == 19. An extra character
* may be required for a minus sign (-). So the maximum number of characters
* is 20.
*/
private static final int INT_BUFFER_SIZE = 20;
/** A buffer for building string representations of <code>long</code>s */
private static final char [] intBuffer = new char[INT_BUFFER_SIZE];
/** A lock for {@link #intBuffer} */
@SuppressWarnings({"unused", "CanBeFinal", "UnusedDeclaration"})// accessed via EntryPoints
@Entrypoint
private static int intBufferLock = 0;
/** The offset of {@link #intBufferLock} in this class's TIB.
* This is set properly at boot time, even though it's a
* <code>private</code> variable. . */
private static Offset intBufferLockOffset = Offset.max();
/**
* Called during the boot sequence, any time before we go multi-threaded. We
* do this so that we can leave the lockOffsets set to -1 until the VM
* actually needs the locking (and is running multi-threaded).
*/
public static void boot() {
dumpBufferLockOffset = Entrypoints.dumpBufferLockField.getOffset();
intBufferLockOffset = Entrypoints.intBufferLockField.getOffset();
}
public static char[] grabDumpBuffer() {
if (!dumpBufferLockOffset.isMax()) {
while (!Synchronization.testAndSet(Magic.getJTOC(), dumpBufferLockOffset, 1)) {
;
}
}
return dumpBuffer;
}
public static void releaseDumpBuffer() {
if (!dumpBufferLockOffset.isMax()) {
Synchronization.fetchAndStore(Magic.getJTOC(), dumpBufferLockOffset, 0);
}
}
/** Copy a String into a character array.
* <p>
* This function may be called during GC and may be used in conjunction
* with the MMTk {@link org.mmtk.utility.Log} class. It avoids write barriers and allocation.
* <p>
* XXX This function should probably be moved to a sensible location where
* we can use it as a utility. Suggestions welcome.
* <P>
*
* @param dest char array to copy into.
* @param destOffset Offset into <code>dest</code> where we start copying
* @param s string to print
*
* @return 1 plus the index of the last character written. If we were to
* write zero characters (which we won't) then we would return
* <code>offset</code>. This is intended to represent the first
* unused position in the array <code>dest</code>. However, it also
* serves as a pseudo-overflow check: It may have the value
* <code>dest.length</code>, if the array <code>dest</code> was
* completely filled by the call, or it may have a value greater
* than <code>dest.length</code>, if the info needs more than
* <code>dest.length - offset</code> characters of space. If
* <code>destOffset</code> is negative, return -1.
*
* the MMTk {@link org.mmtk.utility.Log} class).
*/
public static int sprintf(char[] dest, int destOffset, String s) {
final char[] sArray = java.lang.JikesRVMSupport.getBackingCharArray(s);
return sprintf(dest, destOffset, sArray);
}
public static int sprintf(char[] dest, int destOffset, char[] src) {
return sprintf(dest, destOffset, src, 0, src.length);
}
/**
* Copies characters from <code>src</code> into the destination character
* array <code>dest</code>.<p>
*
* The first character to be copied is at index <code>srcBegin</code>; the
* last character to be copied is at index <code>srcEnd-1</code>. (This is
* the same convention as followed by java.lang.String#getChars).
*
* @param dest char array to copy into.
* @param destOffset Offset into <code>dest</code> where we start copying
* @param src Char array to copy from
* @param srcStart index of the first character of <code>src</code> to copy.
* @param srcEnd index after the last character of <code>src</code> to copy.
*
* @return 1 plus the index of the last character written. If we were to
* write zero characters (which we won't) then we would return
* <code>offset</code>. This is intended to represent the first
* unused position in the array <code>dest</code>. However, it also
* serves as a pseudo-overflow check: It may have the value
* <code>dest.length</code>, if the array <code>dest</code> was
* completely filled by the call, or it may have a value greater
* than <code>dest.length</code>, if the info needs more than
* <code>dest.length - offset</code> characters of space. If
* <code>destOffset</code> is negative, return -1.
*/
public static int sprintf(char[] dest, int destOffset, char[] src, int srcStart, int srcEnd) {
for (int i = srcStart; i < srcEnd; ++i) {
char nextChar = getArrayNoBarrier(src, i);
destOffset = sprintf(dest, destOffset, nextChar);
}
return destOffset;
}
public static int sprintf(char[] dest, int destOffset, char c) {
if (destOffset < 0) {
// bounds check
return -1;
}
if (destOffset < dest.length) {
setArrayNoBarrier(dest, destOffset, c);
}
return destOffset + 1;
}
/**
* Copy the printed decimal representation of a long into
* a character array. The value is not padded and no
* thousands separator is copied. If the value is negative a
* leading minus sign (-) is copied.
* <p>
* This function may be called during GC and may be used in conjunction
* with the Log class. It avoids write barriers and allocation.
* <p>
* XXX This function should probably be moved to a sensible location where
* we can use it as a utility. Suggestions welcome.
* <p>
* XXX This method's implementation is stolen from the {@link org.mmtk.utility.Log} class.
*
* @param dest char array to copy into.
* @param offset Offset into <code>dest</code> where we start copying
* @param l a whole number to write before the string
*
* @return 1 plus the index of the last character written. If we were to
* write zero characters (which we won't) then we would return
* <code>offset</code>. This is intended to represent the first
* unused position in the array <code>dest</code>. However, it also
* serves as a pseudo-overflow check: It may have the value
* <code>dest.length</code>, if the array <code>dest</code> was
* completely filled by the call, or it may have a value greater
* than <code>dest.length</code>, if the info needs more than
* <code>dest.length - offset</code> characters of space. If
* <code>offset</code> is negative, return -1.
*/
public static int sprintf(char[] dest, int offset, long l) {
boolean negative = l < 0;
int nextDigit;
char nextChar;
int index = INT_BUFFER_SIZE - 1;
char[] intBuffer = grabIntBuffer();
nextDigit = (int) (l % 10);
nextChar = getArrayNoBarrier(hexDigitCharacter, negative ? -nextDigit : nextDigit);
setArrayNoBarrier(intBuffer, index--, nextChar);
l = l / 10;
while (l != 0) {
nextDigit = (int) (l % 10);
nextChar = getArrayNoBarrier(hexDigitCharacter, negative ? -nextDigit : nextDigit);
setArrayNoBarrier(intBuffer, index--, nextChar);
l = l / 10;
}
if (negative) {
setArrayNoBarrier(intBuffer, index--, '-');
}
int newOffset = sprintf(dest, offset, intBuffer, index + 1, INT_BUFFER_SIZE);
releaseIntBuffer();
return newOffset;
}
/**
* Gets exclusive access to {@link #intBuffer}, the buffer for building
* string representations of integers.
*
* @return a buffer to use for building representations of integers (e.g. longs or ints)
*/
private static char[] grabIntBuffer() {
if (!intBufferLockOffset.isMax()) {
while (!Synchronization.testAndSet(Magic.getJTOC(), intBufferLockOffset, 1)) {
;
}
}
return intBuffer;
}
/**
* Release {@link #intBuffer}, the buffer for building string
* representations of integers.
*/
private static void releaseIntBuffer() {
if (!intBufferLockOffset.isMax()) {
Synchronization.fetchAndStore(Magic.getJTOC(), intBufferLockOffset, 0);
}
}
@Interruptible
public static String getHexString(int i, boolean blank) {
StringBuilder buf = new StringBuilder(8);
for (int j = 0; j < 8; j++, i <<= 4) {
int n = i >>> 28;
if (blank && (n == 0) && (j != 7)) {
buf.append(' ');
} else {
buf.append(Character.forDigit(n, 16));
blank = false;
}
}
return buf.toString();
}
@NoInline
public static void breakStub() {
}
static void println() {
VM.sysWriteln();
}
static void print(String s) {
VM.sysWrite(s);
}
static void println(String s) {
print(s);
println();
}
static void print(int i) {
VM.sysWrite(i);
}
static void println(int i) {
print(i);
println();
}
static void print(String s, int i) {
print(s);
print(i);
}
static void println(String s, int i) {
print(s, i);
println();
}
public static void percentage(int numerator, int denominator, String quantity) {
print("\t");
if (denominator > 0) {
print((int) (((numerator) * 100.0) / (denominator)));
} else {
print("0");
}
print("% of ");
println(quantity);
}
static void percentage(long numerator, long denominator, String quantity) {
print("\t");
if (denominator > 0L) {
print((int) (((numerator) * 100.0) / (denominator)));
} else {
print("0");
}
print("% of ");
println(quantity);
}
/**
* Format a 32 bit number as "0x" followed by 8 hex digits.
* Do this without referencing Integer or Character classes,
* in order to avoid dynamic linking.
*
* @param number the number to format
* @return a String with the hex representation of the integer
*/
@Interruptible
public static String intAsHexString(int number) {
char[] buf = new char[10];
int index = 10;
while (--index > 1) {
int digit = number & 0x0000000f;
buf[index] = digit <= 9 ? (char) ('0' + digit) : (char) ('a' + digit - 10);
number >>= 4;
}
buf[index--] = 'x';
buf[index] = '0';
return new String(buf);
}
/**
* Format a 64 bit number as "0x" followed by 16 hex digits.
* Do this without referencing Long or Character classes,
* in order to avoid dynamic linking.
*
* @param number the number to format
* @return a String with the hex representation of the long
*/
@Interruptible
public static String longAsHexString(long number) {
char[] buf = new char[18];
int index = 18;
while (--index > 1) {
int digit = (int) (number & 0x000000000000000fL);
buf[index] = digit <= 9 ? (char) ('0' + digit) : (char) ('a' + digit - 10);
number >>= 4;
}
buf[index--] = 'x';
buf[index] = '0';
return new String(buf);
}
/**
* Format a 32/64 bit number as "0x" followed by 8/16 hex digits.
* Do this without referencing Integer or Character classes,
* in order to avoid dynamic linking.
*
* @param addr The 32/64 bit number to format.
* @return a String with the hex representation of an Address
*/
@Interruptible
public static String addressAsHexString(Address addr) {
int len = 2 + (BITS_IN_ADDRESS >> 2);
char[] buf = new char[len];
while (--len > 1) {
int digit = addr.toInt() & 0x0F;
buf[len] = digit <= 9 ? (char) ('0' + digit) : (char) ('a' + digit - 10);
addr = addr.toWord().rshl(4).toAddress();
}
buf[len--] = 'x';
buf[len] = '0';
return new String(buf);
}
@Interruptible
public static String unboxedValueString(Address val) {
return (VM.BuildFor32Addr) ? Integer.toString(val.toInt()) :
Long.toString(val.toLong());
}
@Interruptible
public static String unboxedValueString(Offset val) {
return (VM.BuildFor32Addr) ? Integer.toString(val.toInt()) :
Long.toString(val.toLong());
}
@Interruptible
public static String unboxedValueString(Word val) {
return (VM.BuildFor32Addr) ? Integer.toString(val.toInt()) :
Long.toString(val.toLong());
}
/**
* Sets an element of a object array without possibly losing control.
* NB doesn't perform checkstore or array index checking.
*
* @param dst the destination array
* @param index the index of the element to set
* @param value the new value for the element
*/
@UninterruptibleNoWarn("Interruptible code not reachable at runtime")
@Inline
public static void setArrayUninterruptible(Object[] dst, int index, Object value) {
if (VM.runningVM) {
if (Barriers.NEEDS_OBJECT_ASTORE_BARRIER) {
Barriers.objectArrayWrite(dst, index, value);
} else {
Magic.setObjectAtOffset(dst, Offset.fromIntZeroExtend(index << LOG_BYTES_IN_ADDRESS), value);
}
} else {
dst[index] = value;
}
}
/**
* Sets an element of a char array without invoking any write
* barrier. This method is called by the Log method, as it will be
* used during garbage collection and needs to manipulate character
* arrays without causing a write barrier operation.
*
* @param dst the destination array
* @param index the index of the element to set
* @param value the new value for the element
*/
public static void setArrayNoBarrier(char[] dst, int index, char value) {
if (VM.runningVM)
Magic.setCharAtOffset(dst, Offset.fromIntZeroExtend(index << LOG_BYTES_IN_CHAR), value);
else
dst[index] = value;
}
/**
* Gets an element of an Object array without invoking any read
* barrier or performing bounds checks.
*
* @param src the source array
* @param index the natural array index of the element to get
* @return the new value of element
*/
public static Object getArrayNoBarrier(Object[] src, int index) {
if (VM.runningVM)
return Magic.getObjectAtOffset(src, Offset.fromIntZeroExtend(index << LOG_BYTES_IN_ADDRESS));
else
return src[index];
}
/**
* Gets an element of an int array without invoking any read barrier
* or performing bounds checks.
*
* @param src the source array
* @param index the natural array index of the element to get
* @return the new value of element
*/
public static int getArrayNoBarrier(int[] src, int index) {
if (VM.runningVM)
return Magic.getIntAtOffset(src, Offset.fromIntZeroExtend(index << LOG_BYTES_IN_INT));
else
return src[index];
}
/**
* Gets an element of a char array without invoking any read barrier
* or performing bounds check.
*
* @param src the source array
* @param index the natural array index of the element to get
* @return the new value of element
*/
public static char getArrayNoBarrier(char[] src, int index) {
if (VM.runningVM)
return Magic.getCharAtOffset(src, Offset.fromIntZeroExtend(index << LOG_BYTES_IN_CHAR));
else
return src[index];
}
/**
* Gets an element of a byte array without invoking any read barrier
* or bounds check.
*
* @param src the source array
* @param index the natural array index of the element to get
* @return the new value of element
*/
public static byte getArrayNoBarrier(byte[] src, int index) {
if (VM.runningVM)
return Magic.getByteAtOffset(src, Offset.fromIntZeroExtend(index));
else
return src[index];
}
/**
* Gets an element of an array of byte arrays without causing the potential
* thread switch point that array accesses normally cause.
*
* @param src the source array
* @param index the index of the element to get
* @return the new value of element
*/
public static byte[] getArrayNoBarrier(byte[][] src, int index) {
if (VM.runningVM)
return Magic.addressAsByteArray(Magic.objectAsAddress(Magic.getObjectAtOffset(src, Offset.fromIntZeroExtend(index << LOG_BYTES_IN_ADDRESS))));
else
return src[index];
}
}