package com.oracle.truffle.llvm.launcher;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.graalvm.launcher.AbstractLanguageLauncher;
import org.graalvm.options.OptionCategory;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
public class LLVMLauncher extends AbstractLanguageLauncher {
public static void main(String[] args) {
new LLVMLauncher().launch(args);
}
private enum ToolchainAPIFunction {
TOOL,
PATHS,
IDENTIFIER;
@Override
public String toString() {
return super.toString().toLowerCase();
}
}
String[] programArgs;
File file;
private VersionAction versionAction = VersionAction.None;
private ToolchainAPIFunction toolchainAPI = null;
private String toolchainAPIArg = null;
@Override
protected void launch(Context.Builder contextBuilder) {
System.exit(execute(contextBuilder));
}
@Override
protected String getLanguageId() {
return "llvm";
}
@Override
protected List<String> preprocessArguments(List<String> arguments, Map<String, String> polyglotOptions) {
final List<String> unrecognizedOptions = new ArrayList<>();
List<String> path = new ArrayList<>();
List<String> libs = new ArrayList<>();
ListIterator<String> iterator = arguments.listIterator();
while (iterator.hasNext()) {
String option = iterator.next();
if (option.length() < 2 || !option.startsWith("-")) {
iterator.previous();
break;
}
switch (option) {
case "--":
break;
case "--show-version":
versionAction = VersionAction.PrintAndContinue;
break;
case "--version":
versionAction = VersionAction.PrintAndExit;
break;
case "--print-toolchain-path":
toolchainAPI = ToolchainAPIFunction.PATHS;
toolchainAPIArg = "PATH";
break;
case "--print-toolchain-api-tool":
toolchainAPI = ToolchainAPIFunction.TOOL;
if (!iterator.hasNext()) {
throw abort("Missing argument for " + option);
}
toolchainAPIArg = iterator.next();
break;
case "--print-toolchain-api-paths":
toolchainAPI = ToolchainAPIFunction.PATHS;
if (!iterator.hasNext()) {
throw abort("Missing argument for " + option);
}
toolchainAPIArg = iterator.next();
break;
case "--print-toolchain-api-identifier":
toolchainAPI = ToolchainAPIFunction.IDENTIFIER;
break;
default:
String optionName = option;
String argument;
int equalsIndex = option.indexOf('=');
if (equalsIndex > 0) {
argument = option.substring(equalsIndex + 1);
optionName = option.substring(0, equalsIndex);
} else if (iterator.hasNext()) {
argument = iterator.next();
} else {
argument = null;
}
switch (optionName) {
case "-L":
if (argument == null) {
throw abort("Missing argument for " + optionName);
}
path.add(argument);
iterator.remove();
if (equalsIndex < 0) {
iterator.previous();
iterator.remove();
}
break;
case "--lib":
if (argument == null) {
throw abort("Missing argument for " + optionName);
}
libs.add(argument);
iterator.remove();
if (equalsIndex < 0) {
iterator.previous();
iterator.remove();
}
break;
default:
unrecognizedOptions.add(option);
if (equalsIndex < 0 && argument != null) {
iterator.previous();
}
break;
}
break;
}
}
if (!path.isEmpty()) {
polyglotOptions.put("llvm.libraryPath", path.stream().collect(Collectors.joining(":")));
}
if (!libs.isEmpty()) {
polyglotOptions.put("llvm.libraries", libs.stream().collect(Collectors.joining(":")));
}
if (file == null && iterator.hasNext()) {
file = Paths.get(iterator.next()).toFile();
}
List<String> programArgumentsList = arguments.subList(iterator.nextIndex(), arguments.size());
programArgs = programArgumentsList.toArray(new String[programArgumentsList.size()]);
return unrecognizedOptions;
}
@Override
protected void validateArguments(Map<String, String> polyglotOptions) {
if (file == null && versionAction != VersionAction.PrintAndExit && toolchainAPI == null) {
throw abort("No bitcode file provided.", 6);
}
}
@Override
protected void printHelp(OptionCategory maxCategory) {
System.out.println();
System.out.println("Usage: lli [OPTION]... [FILE] [PROGRAM ARGS]");
System.out.println("Run LLVM bitcode files on the GraalVM's lli.\n");
System.out.println("Mandatory arguments to long options are mandatory for short options too.\n");
System.out.println("Options:");
printOption("-L <path>", "set path where lli searches for libraries");
printOption("--lib <libraries>", "add library (*.bc or precompiled library *.so/*.dylib)");
printOption("--version", "print the version and exit");
printOption("--show-version", "print the version and continue");
printOption("--print-toolchain-path", "print the toolchain path and exit (shortcut for `--print-toolchain-api-paths PATH`)");
printOption("--print-toolchain-api-tool <name>", "print the location of a toolchain API tool and exit");
printOption("--print-toolchain-api-paths <name>", "print toolchain API paths and exit");
printOption("--print-toolchain-api-identifier", "print the toolchain API identifier and exit");
}
@Override
protected void collectArguments(Set<String> args) {
args.addAll(Arrays.asList(
"-L", "--lib",
"--version",
"--show-version",
"--print-toolchain-path",
"--print-toolchain-api-paths",
"--print-toolchain-api-tool",
"--print-toolchain-api-identifier"));
}
protected static void printOption(String option, String description) {
String opt;
if (option.length() >= 22) {
System.out.println(String.format("%s%s", " ", option));
opt = "";
} else {
opt = option;
}
System.out.println(String.format(" %-22s%s", opt, description));
}
protected int execute(Context.Builder contextBuilder) {
contextBuilder.arguments(getLanguageId(), programArgs);
try (Context context = contextBuilder.build()) {
runVersionAction(versionAction, context.getEngine());
if (toolchainAPI != null) {
printToolchainAPI(context);
return 0;
}
Value library = context.eval(Source.newBuilder(getLanguageId(), file).build());
if (!library.canExecute()) {
throw abort("no main function found");
}
return library.execute().asInt();
} catch (PolyglotException e) {
if (e.isExit()) {
throw e;
} else if (!e.isInternalError()) {
printStackTraceSkipTrailingHost(e);
return 1;
} else {
throw e;
}
} catch (IOException e) {
throw abort(String.format("Error loading file '%s' (%s)", file, e.getMessage()));
}
}
private void printToolchainAPI(Context context) {
Value bindings = context.getBindings(getLanguageId());
final Value result;
switch (toolchainAPI) {
case TOOL:
result = bindings.getMember("toolchain_api_tool").execute(toolchainAPIArg);
break;
case PATHS:
result = bindings.getMember("toolchain_api_paths").execute(toolchainAPIArg);
break;
case IDENTIFIER:
result = bindings.getMember("toolchain_api_identifier").execute();
break;
default:
throw abort("Unknown --print-toolchain-api function: " + toolchainAPI);
}
if (result.isNull()) {
throw abort("Unknown entry for --print-toolchain-api-" + toolchainAPI + ": " + toolchainAPIArg);
}
if (result.hasArrayElements()) {
for (int i = 0; i < result.getArraySize(); i++) {
System.out.println(result.getArrayElement(i).asString());
}
} else {
System.out.println(result.asString());
}
}
private static void printStackTraceSkipTrailingHost(PolyglotException e) {
List<PolyglotException.StackFrame> stackTrace = new ArrayList<>();
for (PolyglotException.StackFrame s : e.getPolyglotStackTrace()) {
stackTrace.add(s);
}
for (ListIterator<PolyglotException.StackFrame> iterator = stackTrace.listIterator(stackTrace.size()); iterator.hasPrevious();) {
PolyglotException.StackFrame s = iterator.previous();
if (s.isHostFrame()) {
iterator.remove();
} else {
break;
}
}
System.err.println(e.isHostException() ? e.asHostException().toString() : e.getMessage());
for (PolyglotException.StackFrame s : stackTrace) {
System.err.println("\tat " + s);
}
}
}