package com.oracle.svm.hosted.c.codegen;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Scanner;
import java.util.function.Consumer;
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
import org.graalvm.nativeimage.ImageSingletons;
import com.oracle.svm.core.OS;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.SubstrateTargetDescription;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.c.libc.LibCBase;
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.hosted.c.util.FileUtils;
import jdk.vm.ci.aarch64.AArch64;
import jdk.vm.ci.amd64.AMD64;
import jdk.vm.ci.code.Architecture;
public abstract class CCompilerInvoker {
public final Path tempDirectory;
public final CompilerInfo compilerInfo;
protected CCompilerInvoker(Path tempDirectory) {
this.tempDirectory = tempDirectory;
try {
this.compilerInfo = getCCompilerInfo();
if (this.compilerInfo == null) {
UserError.abort("Unable to detect supported %s native software development toolchain.", OS.getCurrent().name());
}
} catch (UserError.UserException err) {
throw addSkipCheckingInfo(err);
}
}
public static CCompilerInvoker create(Path tempDirectory) {
OS hostOS = OS.getCurrent();
switch (hostOS) {
case LINUX:
return new LinuxCCompilerInvoker(tempDirectory);
case DARWIN:
return new DarwinCCompilerInvoker(tempDirectory);
case WINDOWS:
return new WindowsCCompilerInvoker(tempDirectory);
default:
throw UserError.abort("No CCompilerInvoker for operating system %s", hostOS.name());
}
}
public void verifyCompiler() {
if (SubstrateOptions.CheckToolchain.getValue()) {
try {
verify();
} catch (UserError.UserException err) {
throw addSkipCheckingInfo(err);
}
}
}
private static UserError.UserException addSkipCheckingInfo(UserError.UserException err) {
List<String> messages = new ArrayList<>();
err.getMessages().forEach(messages::add);
messages.add("To prevent native-toolchain checking provide command-line option " + SubstrateOptionsParser.commandArgument(SubstrateOptions.CheckToolchain, "-"));
return UserError.abort(messages);
}
private static class WindowsCCompilerInvoker extends CCompilerInvoker {
WindowsCCompilerInvoker(Path tempDirectory) {
super(tempDirectory);
}
@Override
public String asExecutableName(String basename) {
String suffix = ".exe";
if (basename.endsWith(suffix)) {
return basename;
}
return basename + suffix;
}
@Override
protected String getDefaultCompiler() {
return "cl";
}
@Override
protected List<String> addTarget(Path target) {
return Arrays.asList("/Fe" + target.toString());
}
@Override
protected InputStream getCompilerErrorStream(Process compilingProcess) {
return compilingProcess.getInputStream();
}
@Override
protected List<String> getVersionInfoOptions() {
return Collections.emptyList();
}
@Override
protected CompilerInfo createCompilerInfo(Path compilerPath, Scanner outerScanner) {
try (Scanner scanner = new Scanner(outerScanner.nextLine())) {
String targetArch = null;
if (scanner.hasNext("\u7528\u4E8E")) {
scanner.next();
targetArch = scanner.next();
}
if (scanner.findInLine("Microsoft.*\\(R\\) C/C\\+\\+") == null) {
return null;
}
scanner.useDelimiter("\\D");
while (!scanner.hasNextInt()) {
scanner.next();
}
int major = scanner.nextInt();
int minor0 = scanner.nextInt();
int minor1 = scanner.nextInt();
if (targetArch == null) {
scanner.reset();
while (scanner.hasNext()) {
targetArch = scanner.next();
}
}
return new CompilerInfo(compilerPath, "microsoft", "C/C++ Optimizing Compiler", "cl", major, minor0, minor1, targetArch);
} catch (NoSuchElementException e) {
return null;
}
}
@Override
protected void verify() {
if (compilerInfo.versionMajor < 19 || compilerInfo.versionMinor0 < 12) {
UserError.abort("Java %d native-image building on Windows requires Visual Studio 2017 version 15.5 or later (C/C++ Optimizing Compiler Version 19.12 or later).%nCompiler info detected: %s",
JavaVersionUtil.JAVA_SPEC, compilerInfo);
}
if (guessArchitecture(compilerInfo.targetArch) != AMD64.class) {
UserError.abort("Native-image building on Windows currently only supports target architecture: %s (%s unsupported)",
AMD64.class.getSimpleName(), compilerInfo.targetArch);
}
}
@Override
protected List<String> compileStrictOptions() {
return Arrays.asList("/WX", "/W4", "/wd4244", "/wd4245", "/wd4800", "/wd4804", "/wd4214");
}
}
private static class LinuxCCompilerInvoker extends CCompilerInvoker {
LinuxCCompilerInvoker(Path tempDirectory) {
super(tempDirectory);
}
@Override
protected String getDefaultCompiler() {
return LibCBase.singleton().getTargetCompiler();
}
@Override
protected CompilerInfo createCompilerInfo(Path compilerPath, Scanner scanner) {
try {
if (scanner.findInLine("icc version ") != null) {
scanner.useDelimiter("[. ]");
int major = scanner.nextInt();
int minor0 = scanner.nextInt();
int minor1 = scanner.nextInt();
return new CompilerInfo(compilerPath, "intel", "Intel(R) C++ Compiler", "icc", major, minor0, minor1, "x86_64");
}
if (scanner.findInLine("clang version ") != null) {
scanner.useDelimiter("[. ]");
int major = scanner.nextInt();
int minor0 = scanner.nextInt();
int minor1 = scanner.nextInt();
String[] triplet = guessTargetTriplet(scanner);
return new CompilerInfo(compilerPath, "llvm", "Clang C++ Compiler", "clang", major, minor0, minor1, triplet[0]);
}
String[] triplet = guessTargetTriplet(scanner);
while (scanner.findInLine("gcc version ") == null) {
scanner.nextLine();
}
scanner.useDelimiter("[. ]");
int major = scanner.nextInt();
int minor0 = scanner.nextInt();
int minor1 = scanner.nextInt();
return new CompilerInfo(compilerPath, triplet[1], "GNU project C and C++ compiler", "gcc", major, minor0, minor1, triplet[0]);
} catch (NoSuchElementException e) {
return null;
}
}
@Override
protected void verify() {
Class<? extends Architecture> substrateTargetArch = ImageSingletons.lookup(SubstrateTargetDescription.class).arch.getClass();
Class<? extends Architecture> guessed = guessArchitecture(compilerInfo.targetArch);
if (guessed == null) {
UserError.abort("Native toolchain (%s) has no matching native-image target architecture.", compilerInfo.targetArch);
}
if (guessed != substrateTargetArch) {
UserError.abort("Native toolchain (%s) implies native-image target architecture %s but configured native-image target architecture is %s.",
compilerInfo.targetArch, guessed, substrateTargetArch);
}
}
}
private static class DarwinCCompilerInvoker extends CCompilerInvoker {
DarwinCCompilerInvoker(Path tempDirectory) {
super(tempDirectory);
}
@Override
protected String getDefaultCompiler() {
return "cc";
}
@Override
@SuppressWarnings("try")
protected CompilerInfo createCompilerInfo(Path compilerPath, Scanner scanner) {
try {
while (scanner.findInLine("Apple (clang|LLVM) version ") == null) {
scanner.nextLine();
}
scanner.useDelimiter("[. ]");
int major = scanner.nextInt();
int minor0 = scanner.nextInt();
int minor1 = scanner.hasNextInt() ? scanner.nextInt() : 0;
scanner.reset();
String[] triplet = guessTargetTriplet(scanner);
return new CompilerInfo(compilerPath, triplet[1], "LLVM", "clang", major, minor0, minor1, triplet[0]);
} catch (NoSuchElementException e) {
return null;
}
}
@Override
protected void verify() {
if (guessArchitecture(compilerInfo.targetArch) != AMD64.class) {
UserError.abort("Native-image building on Darwin currently only supports target architecture: %s (%s unsupported)",
AMD64.class.getSimpleName(), compilerInfo.targetArch);
}
}
}
protected InputStream getCompilerErrorStream(Process compilingProcess) {
return compilingProcess.getErrorStream();
}
public static final class CompilerInfo {
public final Path compilerPath;
public final String name;
public final String shortName;
public final String vendor;
public final int versionMajor;
public final int versionMinor0;
public final int versionMinor1;
public final String targetArch;
public CompilerInfo(Path compilerPath, String vendor, String name, String shortName, int versionMajor, int versionMinor0, int versionMinor1, String targetArch) {
this.compilerPath = compilerPath;
this.name = name;
this.vendor = vendor;
this.shortName = shortName;
this.versionMajor = versionMajor;
this.versionMinor0 = versionMinor0;
this.versionMinor1 = versionMinor1;
this.targetArch = targetArch;
}
@Override
public String toString() {
return String.join("|", Arrays.asList(shortName, vendor, targetArch,
String.format("%d.%d.%d", versionMajor, versionMinor0, versionMinor1)));
}
public void dump(Consumer<String> sink) {
sink.accept("Name: " + name + " (" + shortName + ")");
sink.accept("Vendor: " + vendor);
sink.accept(String.format("Version: %d.%d.%d", versionMajor, versionMinor0, versionMinor1));
sink.accept("Target architecture: " + targetArch);
sink.accept("Path: " + compilerPath);
}
}
protected abstract void verify();
private CompilerInfo getCCompilerInfo() {
Path compilerPath = getCCompilerPath().toAbsolutePath();
if (!SubstrateOptions.CheckToolchain.getValue()) {
return new CompilerInfo(compilerPath, null, getClass().getSimpleName(), null, 0, 0, 0, null);
}
List<String> compilerCommand = createCompilerCommand(compilerPath, getVersionInfoOptions(), null);
CompilerInfo result = null;
Process compilerProcess = null;
try {
ProcessBuilder processBuilder = FileUtils.prepareCommand(compilerCommand, tempDirectory);
processBuilder.redirectErrorStream(true);
processBuilder.environment().put("LC_ALL", "C");
FileUtils.traceCommand(processBuilder);
compilerProcess = processBuilder.start();
try (InputStream inputStream = compilerProcess.getInputStream()) {
List<String> lines = FileUtils.readAllLines(inputStream);
FileUtils.traceCommandOutput(lines);
result = createCompilerInfo(compilerPath, new Scanner(String.join(System.lineSeparator(), lines)));
}
compilerProcess.waitFor();
} catch (InterruptedException ex) {
throw new InterruptImageBuilding("Interrupted during checking native-compiler " + compilerPath);
} catch (IOException e) {
UserError.abort(e, "Collecting native-compiler info with '%s' failed", SubstrateUtil.getShellCommandString(compilerCommand, false));
} finally {
if (compilerProcess != null) {
compilerProcess.destroy();
}
}
return result;
}
protected List<String> getVersionInfoOptions() {
return Arrays.asList("-v");
}
protected abstract CompilerInfo createCompilerInfo(Path compilerPath, Scanner scanner);
protected static String[] guessTargetTriplet(Scanner scanner) {
while (scanner.findInLine("Target: ") == null) {
scanner.nextLine();
}
scanner.useDelimiter("-");
String arch = scanner.next();
String vendor = scanner.next();
String os = scanner.nextLine();
os = os.startsWith("-") ? os.substring(1) : os;
scanner.reset();
return new String[]{arch, vendor, os};
}
@SuppressWarnings({"unchecked", "fallthrough"})
protected static Class<? extends Architecture> guessArchitecture(String archStr) {
switch (archStr) {
case "x86_64":
case "x64":
return AMD64.class;
case "aarch64":
return AArch64.class;
case "i686":
case "80x86":
default:
return null;
}
}
public interface CompilerErrorHandler {
void handle(ProcessBuilder current, Path source, String line);
}
@SuppressWarnings("try")
public void compileAndParseError(boolean strict, List<String> compileOptions, Path source, Path target, CompilerErrorHandler handler) {
List<String> options = strict ? createStrictOptions(compileOptions) : compileOptions;
Process compilingProcess = null;
try {
ProcessBuilder compileCommand = FileUtils.prepareCommand(createCompilerCommand(options, target.normalize(), source.normalize()), tempDirectory);
FileUtils.traceCommand(compileCommand);
compilingProcess = compileCommand.start();
List<String> lines;
try (InputStream compilerErrors = getCompilerErrorStream(compilingProcess)) {
lines = FileUtils.readAllLines(compilerErrors);
FileUtils.traceCommandOutput(lines);
}
boolean errorReported = false;
for (String line : lines) {
if (detectError(line)) {
if (handler != null) {
handler.handle(compileCommand, source, line);
}
errorReported = true;
}
}
int status = compilingProcess.waitFor();
if (status != 0 && !errorReported) {
if (handler != null) {
handler.handle(compileCommand, source, lines.toString());
}
}
} catch (InterruptedException ex) {
throw new InterruptImageBuilding("Interrupted during C-ABI query code compilation of " + source);
} catch (IOException ex) {
throw UserError.abort(ex, "Unable to compile C-ABI query code %s. Make sure native software development toolchain is installed on your system.", source);
} finally {
if (compilingProcess != null) {
compilingProcess.destroy();
}
}
}
private List<String> createStrictOptions(List<String> compileOptions) {
ArrayList<String> strictCompileOptions = new ArrayList<>(compileStrictOptions());
strictCompileOptions.addAll(compileOptions);
return strictCompileOptions;
}
protected List<String> compileStrictOptions() {
return Arrays.asList("-Wall", "-Werror");
}
protected boolean detectError(String line) {
return line.contains(": error:") || line.contains(": fatal error:");
}
public static Optional<Path> lookupSearchPath(String name) {
return Arrays.stream(System.getenv("PATH").split(File.pathSeparator))
.map(entry -> Paths.get(entry, name))
.filter(p -> Files.isExecutable(p) && !Files.isDirectory(p))
.findFirst();
}
public Path getCCompilerPath() {
Path compilerPath;
String userDefinedPath = SubstrateOptions.CCompilerPath.getValue();
if (userDefinedPath != null) {
compilerPath = Paths.get(userDefinedPath);
} else {
String executableName = asExecutableName(getDefaultCompiler());
Optional<Path> optCompilerPath = lookupSearchPath(executableName);
if (optCompilerPath.isPresent()) {
compilerPath = optCompilerPath.get();
} else {
throw UserError.abort("Default native-compiler executable '%s' not found via environment variable PATH", executableName);
}
}
if (Files.isDirectory(compilerPath) || !Files.isExecutable(compilerPath)) {
String msgSubject;
if (userDefinedPath != null) {
msgSubject = SubstrateOptionsParser.commandArgument(SubstrateOptions.CCompilerPath, userDefinedPath);
} else {
msgSubject = "Default native-compiler '" + compilerPath + "'";
}
throw UserError.abort("%s does not specify a path to an executable.", msgSubject);
}
return compilerPath;
}
protected abstract String getDefaultCompiler();
public String asExecutableName(String basename) {
return basename;
}
public List<String> createCompilerCommand(List<String> options, Path target, Path... input) {
return createCompilerCommand(compilerInfo.compilerPath, options, target, input);
}
private List<String> createCompilerCommand(Path compilerPath, List<String> options, Path target, Path... input) {
List<String> command = new ArrayList<>();
command.add(compilerPath.toString());
command.addAll(Arrays.asList(SubstrateOptions.CCompilerOption.getValue()));
command.addAll(options);
if (target != null) {
command.addAll(addTarget(target));
}
for (Path elem : input) {
command.add(elem.toString());
}
return command;
}
protected List<String> addTarget(Path target) {
return Arrays.asList("-o", target.toString());
}
}