/*
* 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.tools.bootImageWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel.MapMode;
import org.jikesrvm.VM;
import org.jikesrvm.SizeConstants;
import org.jikesrvm.classloader.RVMArray;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.mm.mminterface.MemoryManager;
import org.jikesrvm.mm.mmtk.ScanBootImage;
import org.jikesrvm.objectmodel.BootImageInterface;
import org.jikesrvm.objectmodel.JavaHeader;
import org.jikesrvm.objectmodel.ObjectModel;
import org.jikesrvm.runtime.Statics;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Word;
/**
* Memory image of virtual machine that will be written to disk file and later
* "booted".
*/
public class BootImage extends BootImageWriterMessages
implements BootImageWriterConstants, BootImageInterface, SizeConstants {
/**
* Talk while we work?
*/
private final boolean trace;
/**
* The data portion of the actual boot image
*/
private final ByteBuffer bootImageData;
/**
* The code portion of the actual boot image
*/
private final ByteBuffer bootImageCode;
/**
* The reference map for the boot image
*/
private final byte[] referenceMap;
private int referenceMapReferences = 0;
private int referenceMapLimit = 0;
private byte[] bootImageRMap;
private int rMapSize = 0;
/**
* Offset of next free data word, in bytes
*/
private Offset freeDataOffset = Offset.zero();
/**
* Offset of next free code word, in bytes
*/
private Offset freeCodeOffset = Offset.zero();
/**
* Number of objects appearing in bootimage
*/
private int numObjects;
/**
* Number of non-null object addresses appearing in bootimage
*/
private int numAddresses;
/**
* Number of object addresses set to null because they referenced objects
* that are not part of bootimage
*/
private int numNulledReferences;
/**
* Data output file
*/
private final RandomAccessFile dataOut;
/**
* Code output file
*/
private final RandomAccessFile codeOut;
/**
* Code map file name
*/
private final String imageCodeFileName;
/**
* Data map file name
*/
private final String imageDataFileName;
/**
* Root map file name
*/
private final String imageRMapFileName;
/**
* Use mapped byte buffers? We need to truncate the byte buffer
* before writing it to disk. This operation is support on UNIX but
* not Windows.
*/
private static final boolean mapByteBuffers = false;
/**
* @param ltlEndian write words low-byte first?
* @param t turn tracing on?
*/
BootImage(boolean ltlEndian, boolean t, String imageCodeFileName, String imageDataFileName, String imageRMapFileName) throws IOException {
this.imageCodeFileName = imageCodeFileName;
this.imageDataFileName = imageDataFileName;
this.imageRMapFileName = imageRMapFileName;
dataOut = new RandomAccessFile(imageDataFileName,"rw");
codeOut = new RandomAccessFile(imageCodeFileName,"rw");
if (mapByteBuffers) {
bootImageData = dataOut.getChannel().map(MapMode.READ_WRITE, 0, BOOT_IMAGE_DATA_SIZE);
bootImageCode = codeOut.getChannel().map(MapMode.READ_WRITE, 0, BOOT_IMAGE_CODE_SIZE);
} else {
bootImageData = ByteBuffer.allocate(BOOT_IMAGE_DATA_SIZE);
bootImageCode = ByteBuffer.allocate(BOOT_IMAGE_CODE_SIZE);
}
ByteOrder endian = ltlEndian ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
bootImageData.order(endian);
bootImageCode.order(endian);
referenceMap = new byte[BOOT_IMAGE_DATA_SIZE >> LOG_BYTES_IN_ADDRESS];
trace = t;
}
/**
* Write boot image to disk.
*/
public void write() throws IOException {
if (trace) {
say((numObjects / 1024) + "k objects");
say((numAddresses / 1024) + "k non-null object references");
say(numNulledReferences + " references nulled because they are "+
"non-jdk fields or point to non-bootimage objects");
say(((Statics.getNumberOfReferenceSlots()+ Statics.getNumberOfNumericSlots()) / 1024) + "k jtoc slots");
say((getDataSize() / 1024) + "k data in image");
say((getCodeSize() / 1024) + "k code in image");
say("writing " + imageDataFileName);
}
if (!mapByteBuffers) {
dataOut.write(bootImageData.array(), 0, getDataSize());
} else {
dataOut.getChannel().truncate(getDataSize());
}
dataOut.close();
if (trace) {
say("writing " + imageCodeFileName);
}
if (!mapByteBuffers) {
codeOut.write(bootImageCode.array(), 0, getCodeSize());
} else {
codeOut.getChannel().truncate(getCodeSize());
}
codeOut.close();
if (trace) {
say("writing " + imageRMapFileName);
}
/* Now we generate a compressed reference map. Typically we get 4 bits/address, but
we'll create the in-memory array assuming worst case 1:1 compression. Only the
used portion of the array actually gets written into the image. */
bootImageRMap = new byte[referenceMapReferences<<LOG_BYTES_IN_WORD];
rMapSize = ScanBootImage.encodeRMap(bootImageRMap, referenceMap, referenceMapLimit);
FileOutputStream rmapOut = new FileOutputStream(imageRMapFileName);
rmapOut.write(bootImageRMap, 0, rMapSize);
rmapOut.flush();
rmapOut.close();
if (trace) {
say("total refs: "+ referenceMapReferences);
}
ScanBootImage.encodingStats();
}
/**
* Get image data size, in bytes.
* @return image size
*/
public int getDataSize() {
return freeDataOffset.toInt();
}
/**
* Get image code size, in bytes.
* @return image size
*/
public int getCodeSize() {
return freeCodeOffset.toInt();
}
/**
* return the size of the rmap
*/
public int getRMapSize() {
return rMapSize;
}
/**
* Allocate a scalar object.
*
* @param klass RVMClass object of scalar being allocated
* @param needsIdentityHash needs an identity hash value
* @param identityHashValue the value for the identity hash
* @return address of object within bootimage
*/
public Address allocateScalar(RVMClass klass, boolean needsIdentityHash, int identityHashValue) {
numObjects++;
BootImageWriter.logAllocation(klass, klass.getInstanceSize());
return ObjectModel.allocateScalar(this, klass, needsIdentityHash, identityHashValue);
}
/**
* Allocate an array object.
*
* @param array RVMArray object of array being allocated.
* @param numElements number of elements
* @param needsIdentityHash needs an identity hash value
* @param identityHashValue the value for the identity hash
* @return address of object within bootimage
*/
public Address allocateArray(RVMArray array, int numElements, boolean needsIdentityHash, int identityHashValue) {
numObjects++;
BootImageWriter.logAllocation(array, array.getInstanceSize(numElements));
return ObjectModel.allocateArray(this, array, numElements, needsIdentityHash, identityHashValue);
}
/**
* Allocate an array object.
*
* @param array RVMArray object of array being allocated.
* @param numElements number of elements
* @param needsIdentityHash needs an identity hash value
* @param identityHashValue the value for the identity hash
* @param alignment special alignment value
* @return address of object within bootimage
*/
public Address allocateArray(RVMArray array, int numElements, boolean needsIdentityHash, int identityHashValue, int alignment) {
numObjects++;
BootImageWriter.logAllocation(array, array.getInstanceSize(numElements));
return ObjectModel.allocateArray(this, array, numElements, needsIdentityHash, identityHashValue, alignment);
}
/**
* Allocate an array object.
*
* @param array RVMArray object of array being allocated.
* @param numElements number of elements
* @return address of object within bootimage
*/
public Address allocateCode(RVMArray array, int numElements) {
numObjects++;
BootImageWriter.logAllocation(array, array.getInstanceSize(numElements));
return ObjectModel.allocateCode(this, array, numElements);
}
/**
* Allocate space in bootimage. Moral equivalent of
* memory managers allocating raw storage at runtime.
*
* @param size the number of bytes to allocate
* @param align the alignment requested; must be a power of 2.
* @param offset the offset at which the alignment is desired.
*/
public Address allocateDataStorage(int size, int align, int offset) {
size = roundAllocationSize(size);
Offset unalignedOffset = freeDataOffset;
freeDataOffset = MemoryManager.alignAllocation(freeDataOffset, align, offset);
if (VM.ExtremeAssertions) {
VM._assert(freeDataOffset.plus(offset).toWord().and(Word.fromIntSignExtend(align -1)).isZero());
VM._assert(freeDataOffset.toWord().and(Word.fromIntSignExtend(3)).isZero());
}
Offset lowAddr = freeDataOffset;
freeDataOffset = freeDataOffset.plus(size);
if (freeDataOffset.sGT(Offset.fromIntZeroExtend(BOOT_IMAGE_DATA_SIZE)))
fail("bootimage full (need at least " + size + " more bytes for data)");
ObjectModel.fillAlignmentGap(this, BOOT_IMAGE_DATA_START.plus(unalignedOffset),
lowAddr.minus(unalignedOffset).toWord().toExtent());
return BOOT_IMAGE_DATA_START.plus(lowAddr);
}
/**
* Round a size in bytes up to the next value of MIN_ALIGNMENT
*/
private int roundAllocationSize(int size) {
return size + ((-size) & ((1 << JavaHeader.LOG_MIN_ALIGNMENT) - 1));
}
/**
* Allocate space in bootimage. Moral equivalent of
* memory managers allocating raw storage at runtime.
*
* @param size the number of bytes to allocate
* @param align the alignment requested; must be a power of 2.
* @param offset the offset at which the alignment is desired.
*/
public Address allocateCodeStorage(int size, int align, int offset) {
size = roundAllocationSize(size);
Offset unalignedOffset = freeCodeOffset;
freeCodeOffset = MemoryManager.alignAllocation(freeCodeOffset, align, offset);
if (VM.ExtremeAssertions) {
VM._assert(freeCodeOffset.plus(offset).toWord().and(Word.fromIntSignExtend(align -1)).isZero());
VM._assert(freeCodeOffset.toWord().and(Word.fromIntSignExtend(3)).isZero());
}
Offset lowAddr = freeCodeOffset;
freeCodeOffset = freeCodeOffset.plus(size);
if (freeCodeOffset.sGT(Offset.fromIntZeroExtend(BOOT_IMAGE_CODE_SIZE)))
fail("bootimage full (need at least " + size + " more bytes for code)");
ObjectModel.fillAlignmentGap(this, BOOT_IMAGE_CODE_START.plus(unalignedOffset),
lowAddr.minus(unalignedOffset).toWord().toExtent());
return BOOT_IMAGE_CODE_START.plus(lowAddr);
}
/**
* Reset the allocator as if no allocation had occured. This is
* useful to allow a "trial run", as is done to establish the offset
* of the JTOC for the entry in the boot image record---so its
* actual address can be computed early in the build process.
*/
public void resetAllocator() {
freeDataOffset = Offset.zero();
freeCodeOffset = Offset.zero();
}
/**
* Fill in 1 byte of bootimage.
*
* @param address address of target
* @param value value to write
*/
public void setByte(Address address, int value) {
int idx;
ByteBuffer data;
if (address.GE(BOOT_IMAGE_CODE_START) && address.LE(BOOT_IMAGE_CODE_END)) {
idx = address.diff(BOOT_IMAGE_CODE_START).toInt();
data = bootImageCode;
} else {
idx = address.diff(BOOT_IMAGE_DATA_START).toInt();
data = bootImageData;
}
data.put(idx, (byte)value);
}
/**
* Set a byte in the reference bytemap to indicate that there is an
* address in the boot image at this offset. This can be used for
* relocatability and for fast boot image scanning at GC time.
*
* @param address The offset into the boot image which contains an
* address.
*/
private void markReferenceMap(Address address) {
int referenceIndex = address.diff(BOOT_IMAGE_DATA_START).toInt()>>LOG_BYTES_IN_ADDRESS;
if (referenceMap[referenceIndex] == 0) {
referenceMap[referenceIndex] = 1;
referenceMapReferences++;
if (referenceIndex > referenceMapLimit) referenceMapLimit = referenceIndex;
}
}
/**
* Fill in 2 bytes of bootimage.
*
* @param address address of target
* @param value value to write
*/
public void setHalfWord(Address address, int value) {
int idx = address.diff(BOOT_IMAGE_DATA_START).toInt();
bootImageData.putChar(idx, (char)value);
}
/**
* Fill in 4 bytes of bootimage, as numeric.
*
* @param address address of target
* @param value value to write
*/
public void setFullWord(Address address, int value) {
int idx;
ByteBuffer data;
if (address.GE(BOOT_IMAGE_CODE_START) && address.LE(BOOT_IMAGE_CODE_END)) {
idx = address.diff(BOOT_IMAGE_CODE_START).toInt();
data = bootImageCode;
} else {
idx = address.diff(BOOT_IMAGE_DATA_START).toInt();
data = bootImageData;
}
data.putInt(idx, value);
}
/**
* Fill in 4/8 bytes of bootimage, as object reference.
* @param address address of target
* @param value value to write
* @param objField true if this word is an object field (as opposed
* to a static, or tib, or some other metadata)
* @param root Does this slot contain a possible reference into the heap?
* (objField must also be true)
*/
public void setAddressWord(Address address, Word value, boolean objField, boolean root) {
if (VM.VerifyAssertions) VM._assert(!root || objField);
if (objField) value = MemoryManager.bootTimeWriteBarrier(value);
if (root) markReferenceMap(address);
if (VM.BuildFor32Addr)
setFullWord(address, value.toInt());
else
setDoubleWord(address, value.toLong());
numAddresses++;
}
/**
* Fill in 4/8 bytes of bootimage, as null object reference.
*
* @param address address of target
* @param objField true if this word is an object field (as opposed
* to a static, or tib, or some other metadata)
* @param root Does this slot contain a possible reference into the heap? (objField must also be true)
* @param genuineNull true if the value is a genuine null and
* shouldn't be counted as a blanked field
*/
public void setNullAddressWord(Address address, boolean objField, boolean root, boolean genuineNull) {
setAddressWord(address, Word.zero(), objField, root);
if (!genuineNull)
numNulledReferences += 1;
}
/**
* Fill in 4/8 bytes of bootimage, as null object reference.
* @param address address of target
* @param objField true if this word is an object field (as opposed
* to a static, or tib, or some other metadata)
* @param root Does this slot contain a possible reference into the heap? (objField must also be true)
*/
public void setNullAddressWord(Address address, boolean objField, boolean root) {
setNullAddressWord(address, objField, root, true);
}
/**
* Fill in 8 bytes of bootimage.
*
* @param address address of target
* @param value value to write
*/
public void setDoubleWord(Address address, long value) {
int idx;
ByteBuffer data;
if (address.GE(BOOT_IMAGE_CODE_START) && address.LE(BOOT_IMAGE_CODE_END)) {
idx = address.diff(BOOT_IMAGE_CODE_START).toInt();
data = bootImageCode;
} else {
idx = address.diff(BOOT_IMAGE_DATA_START).toInt();
data = bootImageData;
}
data.putLong(idx, value);
}
/**
* Keep track of how many references were set null because they pointed to
* non-bootimage objects.
*/
public void countNulledReference() {
numNulledReferences += 1;
}
}