package com.oracle.svm.hosted;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TimerTask;
import java.util.concurrent.ForkJoinPool;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.graalvm.collections.Pair;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.DebugContext.Builder;
import org.graalvm.compiler.options.OptionValues;
import org.graalvm.compiler.printer.GraalDebugHandlersFactory;
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
import org.graalvm.nativeimage.c.function.CEntryPoint;
import org.graalvm.nativeimage.c.type.CCharPointerPointer;
import com.oracle.graal.pointsto.infrastructure.SubstitutionProcessor;
import com.oracle.graal.pointsto.util.AnalysisError;
import com.oracle.graal.pointsto.util.AnalysisError.ParsingError;
import com.oracle.graal.pointsto.util.ParallelExecutionException;
import com.oracle.graal.pointsto.util.Timer;
import com.oracle.graal.pointsto.util.Timer.StopTimer;
import com.oracle.svm.core.FallbackExecutor;
import com.oracle.svm.core.JavaMainWrapper;
import com.oracle.svm.core.JavaMainWrapper.JavaMainSupport;
import com.oracle.svm.core.OS;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.InterruptImageBuilding;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.UserError.UserException;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.analysis.Inflation;
import com.oracle.svm.hosted.c.GraalAccess;
import com.oracle.svm.hosted.code.CEntryPointData;
import com.oracle.svm.hosted.image.AbstractBootImage.NativeImageKind;
import com.oracle.svm.hosted.option.HostedOptionParser;
import com.oracle.svm.util.ModuleSupport;
import com.oracle.svm.util.ReflectionUtil;
import com.oracle.svm.util.ReflectionUtil.ReflectionUtilError;
import jdk.vm.ci.aarch64.AArch64;
import jdk.vm.ci.amd64.AMD64;
import jdk.vm.ci.code.Architecture;
public class NativeImageGeneratorRunner implements ImageBuildTask {
private volatile NativeImageGenerator generator;
public static final String IMAGE_BUILDER_ARG_FILE_OPTION = "--image-args-file=";
public static void main(String[] args) {
List<String> arguments = new ArrayList<>(Arrays.asList(args));
arguments = extractDriverArguments(arguments);
final String[] classPath = extractImagePathEntries(arguments, SubstrateOptions.IMAGE_CLASSPATH_PREFIX);
int watchPID = extractWatchPID(arguments);
TimerTask timerTask = null;
if (watchPID >= 0) {
VMError.guarantee(OS.getCurrent().hasProcFS, SubstrateOptions.WATCHPID_PREFIX + " <pid> requires system with /proc");
timerTask = new TimerTask() {
int cmdlineHashCode = 0;
@Override
public void run() {
try {
int currentCmdlineHashCode = Arrays.hashCode(Files.readAllBytes(Paths.get("/proc/" + watchPID + "/cmdline")));
if (cmdlineHashCode == 0) {
cmdlineHashCode = currentCmdlineHashCode;
} else if (currentCmdlineHashCode != cmdlineHashCode) {
System.exit(1);
}
} catch (IOException e) {
System.exit(1);
}
}
};
java.util.Timer timer = new java.util.Timer("native-image pid watcher");
timer.scheduleAtFixedRate(timerTask, 0, 1000);
}
int exitStatus;
ClassLoader applicationClassLoader = Thread.currentThread().getContextClassLoader();
try {
ImageClassLoader imageClassLoader = installNativeImageClassLoader(classPath, new String[0]);
exitStatus = new NativeImageGeneratorRunner().build(arguments.toArray(new String[0]), imageClassLoader);
} finally {
uninstallNativeImageClassLoader();
Thread.currentThread().setContextClassLoader(applicationClassLoader);
if (timerTask != null) {
timerTask.cancel();
}
}
System.exit(exitStatus);
}
public static void uninstallNativeImageClassLoader() {
ClassLoader loader = ClassLoader.getSystemClassLoader();
if (loader instanceof NativeImageSystemClassLoader) {
((NativeImageSystemClassLoader) loader).setNativeImageClassLoader(null);
}
}
public static ImageClassLoader installNativeImageClassLoader(String[] classpath, String[] modulepath) {
NativeImageSystemClassLoader nativeImageSystemClassLoader = NativeImageSystemClassLoader.singleton();
NativeImageClassLoaderSupport nativeImageClassLoaderSupport = new NativeImageClassLoaderSupport(nativeImageSystemClassLoader.defaultSystemClassLoader, classpath, modulepath);
ClassLoader nativeImageClassLoader = nativeImageClassLoaderSupport.getClassLoader();
Thread.currentThread().setContextClassLoader(nativeImageClassLoader);
nativeImageSystemClassLoader.setNativeImageClassLoader(nativeImageClassLoader);
if (JavaVersionUtil.JAVA_SPEC >= 11 && !nativeImageClassLoaderSupport.imagecp.isEmpty()) {
ModuleSupport.openModule(JavaVersionUtil.class, null);
}
NativeImageGenerator.setSystemPropertiesForImageEarly();
return new ImageClassLoader(NativeImageGenerator.getTargetPlatform(nativeImageClassLoader), nativeImageClassLoaderSupport);
}
public static List<String> (List<String> args) {
ArrayList<String> result = args.stream().filter(arg -> !arg.startsWith(IMAGE_BUILDER_ARG_FILE_OPTION)).collect(Collectors.toCollection(ArrayList::new));
Optional<String> argsFile = args.stream().filter(arg -> arg.startsWith(IMAGE_BUILDER_ARG_FILE_OPTION)).findFirst();
if (argsFile.isPresent()) {
String argFilePath = argsFile.get().substring(IMAGE_BUILDER_ARG_FILE_OPTION.length());
try {
String options = new String(Files.readAllBytes(Paths.get(argFilePath)));
result.addAll(Arrays.asList(options.split("\0")));
} catch (IOException e) {
throw VMError.shouldNotReachHere("Exception occurred during image builder argument file processing.", e);
}
}
return result;
}
public static String[] (List<String> arguments, String pathPrefix) {
int cpArgIndex = arguments.indexOf(pathPrefix);
String msgTail = " '" + pathPrefix + " <Path entries separated by File.pathSeparator>' argument.";
if (cpArgIndex == -1) {
return new String[0];
}
arguments.remove(cpArgIndex);
try {
String imageClasspath = arguments.remove(cpArgIndex);
return imageClasspath.split(File.pathSeparator, Integer.MAX_VALUE);
} catch (IndexOutOfBoundsException e) {
throw UserError.abort("Missing path entries for %s", msgTail);
}
}
public static int (List<String> arguments) {
int cpIndex = arguments.indexOf(SubstrateOptions.WATCHPID_PREFIX);
if (cpIndex >= 0) {
if (cpIndex + 1 >= arguments.size()) {
throw UserError.abort("ProcessID must be provided after the '%s' argument.", SubstrateOptions.WATCHPID_PREFIX);
}
arguments.remove(cpIndex);
String pidStr = arguments.get(cpIndex);
arguments.remove(cpIndex);
return Integer.parseInt(pidStr);
}
return -1;
}
public static boolean isValidJavaVersion() {
return (Boolean.getBoolean("substratevm.IgnoreGraalVersionCheck") || JavaVersionUtil.JAVA_SPEC <= 8);
}
private static void reportToolUserError(String msg) {
reportUserError("native-image " + msg);
}
private static boolean isValidArchitecture() {
final Architecture originalTargetArch = GraalAccess.getOriginalTarget().arch;
return originalTargetArch instanceof AMD64 || originalTargetArch instanceof AArch64;
}
private static boolean isValidOperatingSystem() {
final OS currentOs = OS.getCurrent();
return currentOs == OS.LINUX || currentOs == OS.DARWIN || currentOs == OS.WINDOWS;
}
@SuppressWarnings("try")
private int buildImage(String[] arguments, ImageClassLoader classLoader) {
if (!verifyValidJavaVersionAndPlatform()) {
return 1;
}
Timer totalTimer = new Timer("[total]", false);
ForkJoinPool analysisExecutor = null;
ForkJoinPool compilationExecutor = null;
OptionValues parsedHostedOptions = null;
try (StopTimer ignored = totalTimer.start()) {
Timer classlistTimer = new Timer("classlist", false);
try (StopTimer ignored1 = classlistTimer.start()) {
classLoader.initAllClasses();
}
HostedOptionParser optionParser = new HostedOptionParser(classLoader);
String[] remainingArgs = optionParser.parse(arguments);
if (remainingArgs.length > 0) {
throw UserError.abort("Unknown options: %s", Arrays.toString(remainingArgs));
}
parsedHostedOptions = new OptionValues(optionParser.getHostedValues());
DebugContext debug = new Builder(parsedHostedOptions, new GraalDebugHandlersFactory(GraalAccess.getOriginalSnippetReflection())).build();
String imageName = SubstrateOptions.Name.getValue(parsedHostedOptions);
if (imageName.length() == 0) {
throw UserError.abort("No output file name specified. Use '%s'.", SubstrateOptionsParser.commandArgument(SubstrateOptions.Name, "<output-file>"));
}
totalTimer.setPrefix(imageName);
classlistTimer.setPrefix(imageName);
classlistTimer.print();
Map<Method, CEntryPointData> entryPoints = new HashMap<>();
Pair<Method, CEntryPointData> mainEntryPointData = Pair.empty();
JavaMainSupport javaMainSupport = null;
NativeImageKind imageKind;
boolean isStaticExecutable = SubstrateOptions.StaticExecutable.getValue(parsedHostedOptions);
boolean isSharedLibrary = SubstrateOptions.SharedLibrary.getValue(parsedHostedOptions);
if (isStaticExecutable && isSharedLibrary) {
throw UserError.abort("Cannot pass both option: %s and %s", SubstrateOptionsParser.commandArgument(SubstrateOptions.SharedLibrary, "+"),
SubstrateOptionsParser.commandArgument(SubstrateOptions.StaticExecutable, "+"));
} else if (isSharedLibrary) {
imageKind = NativeImageKind.SHARED_LIBRARY;
} else if (isStaticExecutable) {
imageKind = NativeImageKind.STATIC_EXECUTABLE;
} else {
imageKind = NativeImageKind.EXECUTABLE;
}
String className = SubstrateOptions.Class.getValue(parsedHostedOptions);
if (imageKind.isExecutable && className.isEmpty()) {
throw UserError.abort("Must specify main entry point class when building %s native image. Use '%s'.", imageKind,
SubstrateOptionsParser.commandArgument(SubstrateOptions.Class, "<fully-qualified-class-name>"));
}
if (!className.isEmpty()) {
Method mainEntryPoint;
Class<?> mainClass;
try {
Object jpmsModule = null;
mainClass = classLoader.loadClassFromModule(jpmsModule, className);
} catch (ClassNotFoundException ex) {
throw UserError.abort("Main entry point class '%s' not found.", className);
}
String mainEntryPointName = SubstrateOptions.Method.getValue(parsedHostedOptions);
if (mainEntryPointName.isEmpty()) {
throw UserError.abort("Must specify main entry point method when building %s native image. Use '%s'.", imageKind,
SubstrateOptionsParser.commandArgument(SubstrateOptions.Method, "<method-name>"));
}
try {
mainEntryPoint = mainClass.getDeclaredMethod(mainEntryPointName, int.class, CCharPointerPointer.class);
} catch (NoSuchMethodException ignored2) {
Method javaMainMethod;
try {
javaMainMethod = ReflectionUtil.lookupMethod(mainClass, mainEntryPointName, String[].class);
} catch (ReflectionUtilError ex) {
throw UserError.abort(ex.getCause(),
"Method '%s.%s' is declared as the main entry point but it can not be found. " +
"Make sure that class '%s' is on the classpath and that method '%s(String[])' exists in that class.",
mainClass.getName(),
mainEntryPointName,
mainClass.getName(),
mainEntryPointName);
}
if (javaMainMethod.getReturnType() != void.class) {
throw UserError.abort("Java main method '%s.%s(String[])' does not have the return type 'void'.", mainClass.getName(), mainEntryPointName);
}
final int mainMethodModifiers = javaMainMethod.getModifiers();
if (!Modifier.isStatic(mainMethodModifiers)) {
throw UserError.abort("Java main method '%s.%s(String[])' is not static.", mainClass.getName(), mainEntryPointName);
}
if (!Modifier.isPublic(mainMethodModifiers)) {
throw UserError.abort("Java main method '%s.%s(String[])' is not public.", mainClass.getName(), mainEntryPointName);
}
javaMainSupport = new JavaMainSupport(javaMainMethod);
mainEntryPoint = JavaMainWrapper.class.getDeclaredMethod("run", int.class, CCharPointerPointer.class);
}
CEntryPoint annotation = mainEntryPoint.getAnnotation(CEntryPoint.class);
if (annotation == null) {
throw UserError.abort("Entry point must have the '@%s' annotation", CEntryPoint.class.getSimpleName());
}
Class<?>[] pt = mainEntryPoint.getParameterTypes();
if (pt.length != 2 || pt[0] != int.class || pt[1] != CCharPointerPointer.class || mainEntryPoint.getReturnType() != int.class) {
throw UserError.abort("Main entry point must have signature 'int main(int argc, CCharPointerPointer argv)'.");
}
mainEntryPointData = Pair.create(mainEntryPoint, CEntryPointData.create(mainEntryPoint, imageKind.mainEntryPointName));
}
int maxConcurrentThreads = NativeImageOptions.getMaximumNumberOfConcurrentThreads(parsedHostedOptions);
analysisExecutor = Inflation.createExecutor(debug, NativeImageOptions.getMaximumNumberOfAnalysisThreads(parsedHostedOptions));
compilationExecutor = Inflation.createExecutor(debug, maxConcurrentThreads);
generator = new NativeImageGenerator(classLoader, optionParser, mainEntryPointData);
generator.run(entryPoints, javaMainSupport, imageName, imageKind, SubstitutionProcessor.IDENTITY,
compilationExecutor, analysisExecutor, optionParser.getRuntimeOptionNames());
} catch (InterruptImageBuilding e) {
if (analysisExecutor != null) {
analysisExecutor.shutdownNow();
}
if (compilationExecutor != null) {
compilationExecutor.shutdownNow();
}
if (e.getReason().isPresent()) {
NativeImageGeneratorRunner.info(e.getReason().get());
return 0;
} else {
return 3;
}
} catch (FallbackFeature.FallbackImageRequest e) {
if (FallbackExecutor.class.getName().equals(SubstrateOptions.Class.getValue())) {
NativeImageGeneratorRunner.reportFatalError(e, "FallbackImageRequest while building fallback image.");
return 1;
}
reportUserException(e, parsedHostedOptions, NativeImageGeneratorRunner::warn);
return 2;
} catch (ParsingError e) {
NativeImageGeneratorRunner.reportFatalError(e);
return 1;
} catch (UserException | AnalysisError e) {
reportUserError(e, parsedHostedOptions);
return 1;
} catch (ParallelExecutionException pee) {
boolean hasUserError = false;
for (Throwable exception : pee.getExceptions()) {
if (exception instanceof UserException) {
reportUserError(exception, parsedHostedOptions);
hasUserError = true;
} else if (exception instanceof AnalysisError && !(exception instanceof ParsingError)) {
reportUserError(exception, parsedHostedOptions);
hasUserError = true;
}
}
if (hasUserError) {
return 1;
}
if (pee.getExceptions().size() > 1) {
System.err.println(pee.getExceptions().size() + " fatal errors detected:");
}
for (Throwable exception : pee.getExceptions()) {
NativeImageGeneratorRunner.reportFatalError(exception);
}
return 1;
} catch (Throwable e) {
NativeImageGeneratorRunner.reportFatalError(e);
return 1;
} finally {
NativeImageGenerator.clearSystemPropertiesForImage();
ImageSingletonsSupportImpl.HostedManagement.clearInThread();
}
totalTimer.print();
return 0;
}
public static boolean verifyValidJavaVersionAndPlatform() {
if (!isValidJavaVersion()) {
reportToolUserError("supports only Java 1.8 with an update version 40+. Detected Java version is: " + getJavaVersion());
return false;
}
if (!isValidArchitecture()) {
reportToolUserError("runs only on architecture AMD64. Detected architecture: " + GraalAccess.getOriginalTarget().arch.getClass().getSimpleName());
}
if (!isValidOperatingSystem()) {
reportToolUserError("runs on Linux, Mac OS X and Windows only. Detected OS: " + System.getProperty("os.name"));
return false;
}
return true;
}
public static String getJavaVersion() {
return System.getProperty("java.version");
}
private static void reportFatalError(Throwable e) {
System.err.print("Fatal error:");
e.printStackTrace();
}
private static void reportFatalError(Throwable e, String msg) {
System.err.print("Fatal error: " + msg);
e.printStackTrace();
}
public static void reportUserError(String msg) {
System.err.println("Error: " + msg);
}
public static void reportUserError(Throwable e, OptionValues parsedHostedOptions) {
reportUserException(e, parsedHostedOptions, NativeImageGeneratorRunner::reportUserError);
}
private static void reportUserException(Throwable e, OptionValues parsedHostedOptions, Consumer<String> report) {
if (e instanceof UserException) {
UserException ue = (UserException) e;
for (String message : ue.getMessages()) {
report.accept(message);
}
} else {
report.accept(e.getMessage());
}
if (parsedHostedOptions != null && NativeImageOptions.ReportExceptionStackTraces.getValue(parsedHostedOptions)) {
e.printStackTrace();
} else {
report.accept("Use " + SubstrateOptionsParser.commandArgument(NativeImageOptions.ReportExceptionStackTraces, "+") +
" to print stacktrace of underlying exception");
}
}
private static void info(String msg) {
System.out.println("Info: " + msg);
}
private static void warn(String msg) {
System.err.println("Warning: " + msg);
}
@Override
public int build(String[] args, ImageClassLoader imageClassLoader) {
return buildImage(args, imageClassLoader);
}
@Override
public void interruptBuild() {
final NativeImageGenerator generatorInstance = generator;
if (generatorInstance != null) {
generatorInstance.interruptBuild();
}
}
public static class JDK9Plus {
public static void main(String[] args) {
ModuleSupport.exportAndOpenAllPackagesToUnnamed("org.graalvm.truffle", false);
ModuleSupport.exportAndOpenAllPackagesToUnnamed("jdk.internal.vm.ci", false);
ModuleSupport.exportAndOpenAllPackagesToUnnamed("jdk.internal.vm.compiler", false);
ModuleSupport.exportAndOpenAllPackagesToUnnamed("jdk.internal.vm.compiler.management", true);
ModuleSupport.exportAndOpenAllPackagesToUnnamed("com.oracle.graal.graal_enterprise", true);
ModuleSupport.exportAndOpenPackageToUnnamed("java.base", "jdk.internal.loader", false);
if (JavaVersionUtil.JAVA_SPEC >= 15) {
ModuleSupport.exportAndOpenPackageToUnnamed("java.base", "jdk.internal.misc", false);
}
ModuleSupport.exportAndOpenPackageToUnnamed("java.base", "sun.text.spi", false);
ModuleSupport.exportAndOpenPackageToUnnamed("java.base", "jdk.internal.org.objectweb.asm", false);
NativeImageGeneratorRunner.main(args);
}
}
}