/*
* 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.hosted;
import java.io.*;
import java.util.*;
import com.sun.max.*;
import com.sun.max.ide.*;
import com.sun.max.lang.*;
import com.sun.max.profile.*;
import com.sun.max.program.*;
import com.sun.max.program.option.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.classfile.*;
import com.sun.max.vm.classfile.constant.*;
import com.sun.max.vm.code.*;
import com.sun.max.vm.compiler.*;
import com.sun.max.vm.compiler.deps.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.heap.*;
import com.sun.max.vm.jdk.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.type.*;
import com.sun.max.vm.verifier.*;
/**
* Construction of a virtual machine image begins here by running on a host virtual
* machine (e.g. Hotspot). This process involves creating a target VM configuration
* and loading and initializing the classes that implement VM services. The representation
* of the virtual machine being built is referred to as the "prototype", and the final
* product, a binary image that contains the compiled machine code of the virtual machine
* as well as objects and metadata that implement the virtual machine, is called the
* "image".
*/
public final class BootImageGenerator {
public static final String IMAGE_OBJECT_TREE_FILE_NAME = "maxine.object.tree";
public static final String IMAGE_METHOD_TREE_FILE_NAME = "maxine.method.tree";
public static final String IMAGE_JAR_FILE_NAME = "maxine.jar";
public static final String IMAGE_FILE_NAME = "maxine.vm";
public static final String STATS_FILE_NAME = "maxine.stats";
public static final String DEPS_FILE_NAME = "maxine.deps";
public static final String DEFAULT_VM_DIRECTORY = Prototype.TARGET_GENERATED_ROOT;
private static final OptionSet options = new OptionSet();
private static final Option<Boolean> help = options.newBooleanOption("help", false,
"Show help message and exit.");
private static final Option<Boolean> treeOption = options.newBooleanOption("tree", false,
"Create a file showing the connectivity of objects in the image.");
private static final Option<Boolean> statsOption = options.newBooleanOption("stats", false,
"Create a file detailing the number and size of each type of object in the image.");
private static final Option<Boolean> testNative = options.newBooleanOption("native-tests", false,
"For the Java tester, this option specifies that " + System.mapLibraryName("javatest") + " should be dynamically loaded.");
private static final Option<String> compilationBrokerClassOption = options.newStringOption("compilationBrokerClass", null,
"The CompilationBroker subclass to use.");
private static final Option<Boolean> debugClassIDOption = options.newBooleanOption("debug-classid", false,
"Trace array class id creation and prints reserved class id without array class actors.");
// TODO: clean this up. Just for getting perf numbers.
private static final Option<Boolean> inlinedTLABOption = options.newBooleanOption("inline-tlabs", true,
"Generate inline TLAB allocation code in boot image.");
// TODO: clean this up. Just for getting perf numbers.
private static final Option<Boolean> useOutOfLineStubs = options.newBooleanOption("out-stubs", true,
"Uses out of line runtime stubs when generating inlined TLAB allocations with XIR");
// Options shared with the Inspector
public static final OptionSet inspectorSharedOptions = new OptionSet();
/**
* Partial override of the location of the target VM and associated files, relative to a new workspace root.
* Abstracts project names, OS and platform details.
*/
private static final Option<String> targetWSRootDirectoryOption = inspectorSharedOptions.newStringOption("target-ws-root", null,
"Alternate workspace root for output of the binary image generator.");
/**
* A complete override of the location of the target VM and associated files. Must be absolutely specific
* about location, i.e., project names, OS and platform.
*/
private static final Option<File> vmDirectoryOption = inspectorSharedOptions.newFileOption("vmdir", getDefaultVMDirectory(),
"The output directory for the binary image generator.");
public static final Option<Boolean> checkGeneratedCodeOption = inspectorSharedOptions.newBooleanOption("checkautogen", true,
"Check the automically generated code for consistency");
static {
options.addOptions(inspectorSharedOptions);
}
public static boolean nativeTests;
/**
* Gets the default VM directory where the VM executable, shared libraries, boot image
* and related files are located.
*/
public static File getDefaultVMDirectory() {
return getDefaultVMDirectory(false);
}
/**
* Gets the default VM directory where the VM executable, shared libraries, boot image
* and related files are located, with optional override.
*/
public static File getDefaultVMDirectory(boolean checkOptions) {
File wsRoot = JavaProject.findWorkspace();
if (checkOptions) {
if (vmDirectoryOption.isAssigned()) {
return vmDirectoryOption.getValue();
} else {
String path = targetWSRootDirectoryOption.getValue();
if (path != null) {
wsRoot = new File(path);
}
}
}
return new File(wsRoot, DEFAULT_VM_DIRECTORY);
}
/**
* Gets the boot image file given a VM directory.
*
* @param vmdir a VM directory. If {@code null}, then {@link #getDefaultVMDirectory()} is used.
*/
public static File getBootImageFile(File vmdir) {
if (vmdir == null) {
vmdir = getDefaultVMDirectory();
}
return new File(vmdir, IMAGE_FILE_NAME);
}
/**
* Gets the boot image jar file given a VM directory.
*
* @param vmdir a VM directory. If {@code null}, then {@link #getDefaultVMDirectory()} is used.
*/
public static File getBootImageJarFile(File vmdir) {
if (vmdir == null) {
vmdir = getDefaultVMDirectory();
}
return new File(vmdir, IMAGE_JAR_FILE_NAME);
}
/**
* Gets the object tree file given a VM directory.
*
* @param vmdir a VM directory. If {@code null}, then {@link #getDefaultVMDirectory()} is used.
*/
public static File getBootImageObjectTreeFile(File vmdir) {
if (vmdir == null) {
vmdir = getDefaultVMDirectory();
}
return new File(vmdir, IMAGE_OBJECT_TREE_FILE_NAME);
}
/**
* Gets the method tree file given a VM directory.
*
* @param vmdir a VM directory. If {@code null}, then {@link #getDefaultVMDirectory()} is used.
*/
public static File getBootImageMethodTreeFile(File vmdir) {
if (vmdir == null) {
vmdir = getDefaultVMDirectory();
}
return new File(vmdir, IMAGE_METHOD_TREE_FILE_NAME);
}
/**
* Creates and runs the binary image generator with the specified command line arguments.
*
* @param programArguments the arguments from the command line
*/
public BootImageGenerator(String[] programArguments) {
final long start = System.currentTimeMillis();
try {
VMConfigurator configurator = new VMConfigurator(options);
PrototypeGenerator prototypeGenerator = new PrototypeGenerator(options);
Trace.addTo(options);
options.addOptions(RuntimeCompiler.compilers);
programArguments = VMOption.extractVMArgs(programArguments);
options.parseArguments(programArguments);
if (help.getValue()) {
options.printHelp(System.out, 80);
return;
}
if (compilationBrokerClassOption.getValue() != null) {
System.setProperty(CompilationBroker.COMPILATION_BROKER_CLASS_PROPERTY_NAME, compilationBrokerClassOption.getValue());
}
ClassIDManager.traceArrayClassIDs = debugClassIDOption.getValue();
String[] extraClassesAndPackages = options.getArguments();
if (extraClassesAndPackages.length != 0) {
System.setProperty(JavaPrototype.EXTRA_CLASSES_AND_PACKAGES_PROPERTY_NAME, Utils.toString(extraClassesAndPackages, " "));
}
enableProxyClassFileDumping();
nativeTests = testNative.getValue();
final File vmDirectory = getDefaultVMDirectory(true);
vmDirectory.mkdirs();
// Create and installs the VM
configurator.create();
// Initialize the Java prototype
JavaPrototype.initialize(prototypeGenerator.threadsOption.getValue(), checkGeneratedCodeOption.getValue());
Heap.genInlinedTLAB = inlinedTLABOption.getValue(); // TODO: cleanup. Just for evaluating impact on performance of inlined tlab alloc.
Heap.useOutOfLineStubs = useOutOfLineStubs.getValue(); // TODO: cleanup.
DataPrototype dataPrototype = prototypeGenerator.createDataPrototype(treeOption.getValue());
final GraphPrototype graphPrototype = dataPrototype.graphPrototype();
VMOptions.beforeExit();
// write the statistics
if (statsOption.getValue()) {
writeStats(graphPrototype, new File(vmDirectory, STATS_FILE_NAME));
}
if (DependenciesManager.dependenciesLogger.traceEnabled()) {
DependenciesStats.dump(new PrintStream(new File(vmDirectory, DEPS_FILE_NAME)));
}
// ClassID debugging
ClassIDManager.validateUsedClassIds();
writeJar(new File(vmDirectory, IMAGE_JAR_FILE_NAME));
writeImage(dataPrototype, new File(vmDirectory, IMAGE_FILE_NAME));
verifyBootClasses();
if (treeOption.getValue()) {
// write the tree file only if specified by the user.
writeObjectTree(dataPrototype, graphPrototype, new File(vmDirectory, IMAGE_OBJECT_TREE_FILE_NAME));
writeMethodTree(graphPrototype.compiledPrototype, new File(vmDirectory, IMAGE_METHOD_TREE_FILE_NAME));
}
if (statsOption.getValue()) {
writeMiscStatistics(Trace.stream());
}
} catch (IOException ioException) {
throw ProgramError.unexpected("could not write file ", ioException);
} finally {
final long timeInMilliseconds = System.currentTimeMillis() - start;
Trace.line(1, "Total time: " + (timeInMilliseconds / 1000.0f) + " seconds");
System.out.flush();
}
}
/**
* This is an array of directory names relative to the current working directory (system property "user.dir")
* to which proxies will be dumped. These directories are created before boot image construction, and deleted
* afterwards, if they had not existed beforehand. Note that all directories of a path have to be given.
*/
private static String[] proxyDirs = new String[] {
"java",
"java/lang",
"java/lang/invoke",
};
/**
* Add the current working directory to the class path that will be created when HostedBootClassLoader.classpath() is
* first called. This is the directory where ProxyGenerator dumps out the class files it generates.
*/
private static void enableProxyClassFileDumping() {
final String cwd = System.getProperty("user.dir");
String javaClassPath = System.getProperty("java.class.path");
System.setProperty("java.class.path", cwd + File.pathSeparatorChar + javaClassPath);
final File cwdFile = new File(cwd);
// See sun.misc.ProxyGenerator.saveGeneratedFiles field
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
final Set<File> existingProxyClassFilesAndDirectories = collectProxyClassFilesAndDirectories(cwdFile);
// create proxy directories
for (String d : proxyDirs) {
File proxyDir = new File(cwdFile, d);
if (!proxyDir.exists()) {
proxyDir.mkdirs();
proxyDir.deleteOnExit();
}
}
Runtime.getRuntime().addShutdownHook(new Thread("RemovingProxyClassFilesAndDirectories") {
@Override
public void run() {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "false");
for (File f : collectProxyClassFilesAndDirectories(cwdFile)) {
if (!existingProxyClassFilesAndDirectories.contains(f)) {
f.delete();
}
}
}
});
}
private static Set<File> collectProxyClassFilesAndDirectories(File cwd) {
final FilenameFilter proxyFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(JDK_java_lang_reflect_Proxy.proxyClassNamePrefix) && name.endsWith(".class");
}
};
// proxy files without package name
Set<File> filesAndDirs = new HashSet<File>(Arrays.asList(cwd.listFiles(proxyFilter)));
// recurse into given directories and identify proxy files there
for (String d : proxyDirs) {
File proxyDir = new File(cwd, d);
if (proxyDir.exists()) {
filesAndDirs.add(proxyDir);
filesAndDirs.addAll(Arrays.asList(proxyDir.listFiles(proxyFilter)));
}
}
return filesAndDirs;
}
/**
* Writes the image data to the specified file.
*
* @param dataPrototype the data prototype containing a data-level representation of the image
* @param file the file to which to write the data prototype
*/
private void writeImage(DataPrototype dataPrototype, File file) {
try {
final FileOutputStream outputStream = new FileOutputStream(file);
final BootImage bootImage = new BootImage(dataPrototype);
try {
Trace.begin(1, "writing boot image file: " + file);
bootImage.write(outputStream);
Trace.end(1, "end boot image file: " + file + " (" + Longs.toUnitsString(file.length(), false) + ")");
} catch (IOException ioException) {
throw ProgramError.unexpected("could not write file: " + file, ioException);
} finally {
try {
outputStream.close();
} catch (IOException ioException) {
ProgramWarning.message("could not close file: " + file);
}
}
} catch (FileNotFoundException fileNotFoundException) {
throw ProgramError.unexpected("could not open file: " + file);
} catch (BootImageException bootImageException) {
throw ProgramError.unexpected("could not construct proper boot image", bootImageException);
}
}
/**
* Writes a jar file containing all of the (potentially rewritten) VM class files to the specified file.
*
* @param file the file to which to write the jar
* @throws IOException if there is a problem writing the jar
*/
private void writeJar(File file) throws IOException {
Trace.begin(1, "writing boot image jar file: " + file);
ClassfileReader.writeClassfilesToJar(file);
Trace.end(1, "end boot image jar file: " + file + " (" + Longs.toUnitsString(file.length(), false) + ")");
}
private void verifyBootClasses() {
Trace.begin(1, "verifying boot image classes");
// The verification process may loaded extra classes which may in turn require generation of
// more vtrampoline which in turn requires the ability to allocate code. Since the boot image
// has already been written, resetting the boot code region is now safe.
Code.resetBootCodeRegion();
long start = System.currentTimeMillis();
for (ClassActor ca : ClassRegistry.allBootImageClasses()) {
if (!ca.kind.isWord) {
Verifier.verifierFor(ca).verify();
}
}
Trace.end(1, "verifying boot image classes", start);
}
/**
* Writes miscellaneous statistics about the boot image creation process to a file.
*
* @param graphPrototype the graph (i.e. nodes/edges) representation of the prototype
* @param file the file to which to write the statistics
* @throws IOException if there is a problem writing to the file
*/
private void writeStats(GraphPrototype graphPrototype, File file) throws IOException {
Trace.begin(1, "writing boot image statistics file: " + file);
final FileOutputStream fileOutputStream = new FileOutputStream(file);
GraphStats stats = new GraphStats(graphPrototype);
stats.dumpStats(new PrintStream(fileOutputStream));
fileOutputStream.close();
Trace.end(1, "end boot image statistics file: " + file + " (" + Longs.toUnitsString(file.length(), false) + ")");
new SavingsEstimator(stats).report(Trace.stream());
}
/**
* Writes the object tree to a file. The object tree helps to diagnose space usage problems,
* typically caused by including too much into the image.
*
* @param dataPrototype the data representation of the prototype
* @param graphPrototype the graph representation of the prototype
* @param file the file to which to write the object
* @throws IOException
*/
private void writeObjectTree(DataPrototype dataPrototype, GraphPrototype graphPrototype, File file) throws IOException {
Trace.begin(1, "writing boot image object tree file: " + file);
final FileOutputStream fileOutputStream = new FileOutputStream(file);
final DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(fileOutputStream, 1000000));
BootImageObjectTree.saveTree(dataOutputStream, graphPrototype.links(), dataPrototype.allocationMap());
dataOutputStream.flush();
fileOutputStream.close();
Trace.end(1, "writing boot image object tree file: " + file + " (" + Longs.toUnitsString(file.length(), false) + ")");
}
/**
* Writes the method tree to a file. The method tree helps to diagnose the inclusion
* of methods into the image that should not be included.
*
* @param compiledPrototype the compiled-code representation of the prototype
* @param file the file to which to write the tree
* @throws IOException if there is a problem writing to the file
*/
private void writeMethodTree(CompiledPrototype compiledPrototype, File file) throws IOException {
Trace.begin(1, "writing boot image method tree file: " + file);
final FileOutputStream fileOutputStream = new FileOutputStream(file);
final DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(fileOutputStream, 1000000));
BootImageMethodTree.saveTree(dataOutputStream, compiledPrototype.links());
dataOutputStream.flush();
fileOutputStream.close();
Trace.end(1, "writing boot image method tree file: " + file + " (" + Longs.toUnitsString(file.length(), false) + ")");
}
/**
* The main entry point, which creates a binary image generator and then runs it.
*
* @param programArguments the arguments from the command line
*/
public static void main(String[] programArguments) {
new BootImageGenerator(programArguments);
}
/**
* Writes various statistics about the image creation process to the standard output.
* @param out the output stream to which to write the statistics
*/
private static void writeMiscStatistics(PrintStream out) {
Log.println();
Log.println("==== All Loaded Classes ====");
TreeSet<String> names = new TreeSet<String>();
for (ClassActor classActor : ClassRegistry.allBootImageClasses()) {
if (classActor.isInstanceClass() && !classActor.isReflectionStub()) {
names.add(classActor.toString());
}
}
for (String name : names) {
Log.println(name);
}
int numClasses = names.size();
Log.println();
Log.println();
Log.println("==== All Compiled Method ====");
names.clear();
for (TargetMethod m : Code.bootCodeRegion().copyOfTargetMethods()) {
if (m.stubType() == null) {
String name = m.toString();
names.add(name);
}
}
for (String name : names) {
Log.println(name);
}
int numMethods = names.size();
Log.println();
Log.println("==== Unresolved Calls and Fields in Target Methods ====");
TreeMap<String, TreeSet<String>> elements = new TreeMap<String, TreeSet<String>>();
for (TargetMethod targetMethod : Code.bootCodeRegion().copyOfTargetMethods()) {
if (targetMethod.referenceLiterals() != null) {
for (Object o : targetMethod.referenceLiterals()) {
if (o instanceof ResolutionGuard) {
String method = targetMethod.classMethodActor().toString();
String element = o.toString();
if (!elements.containsKey(method)) {
elements.put(method, new TreeSet<String>());
}
elements.get(method).add(element);
}
}
}
}
int numUnresolved = 0;
for (Map.Entry<String, TreeSet<String>> entry : elements.entrySet()) {
Log.println(entry.getKey());
numUnresolved += entry.getValue().size();
for (String value : entry.getValue()) {
Log.println(" " + value);
}
}
Log.println();
Log.println();
Log.println("==== Unlinked Direct Calls in Target Methods ====");
elements.clear();
for (TargetMethod targetMethod : Code.bootCodeRegion().copyOfTargetMethods()) {
if (!(targetMethod instanceof Adapter)) {
final Object[] directCallees = targetMethod.directCallees();
if (directCallees != null) {
for (Object directCallee : directCallees) {
TargetMethod callee = targetMethod.getTargetMethod(directCallee);
if (directCallee != null && callee == null) {
String method = targetMethod.classMethodActor().toString();
String element = directCallee.toString();
if (!elements.containsKey(method)) {
elements.put(method, new TreeSet<String>());
}
elements.get(method).add(element);
}
}
}
}
}
int numUnlinked = 0;
for (Map.Entry<String, TreeSet<String>> entry : elements.entrySet()) {
Log.println(entry.getKey());
numUnlinked += entry.getValue().size();
for (String value : entry.getValue()) {
Log.println(" " + value);
}
}
Log.println();
Log.println();
Log.println("# Classes: " + numClasses);
Log.println("# Compiled: " + numMethods);
Log.println("# Unresolved: " + numUnresolved);
Log.println("# Unlinked: " + numUnlinked);
Log.println();
Trace.line(1, "# utf8 constants: " + SymbolTable.length());
Trace.line(1, "# type descriptors: " + TypeDescriptor.numberOfDescriptors());
Trace.line(1, "# signature descriptors: " + SignatureDescriptor.totalNumberOfDescriptors());
GlobalMetrics.report(out);
}
}