package com.oracle.truffle.js.snapshot;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.graalvm.polyglot.Context;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSContextOptions;
import com.oracle.truffle.js.runtime.JSRealm;
public class SnapshotTool {
private final TimeStats timeStats = new TimeStats();
public SnapshotTool() {
}
public static void main(String[] args) throws IOException {
boolean binary = true;
boolean wrapped = false;
String outDir = null;
String inDir = null;
List<String> srcFiles = new ArrayList<>();
for (String arg : args) {
if (arg.startsWith("--")) {
if (arg.equals("--java")) {
binary = false;
} else if (arg.equals("--binary")) {
binary = true;
} else if (arg.equals("--wrapped")) {
wrapped = true;
} else if (arg.startsWith("--file=")) {
srcFiles.add(arg.substring(arg.indexOf('=') + 1));
} else if (arg.startsWith("--outdir=")) {
outDir = requireDirectory(arg.substring(arg.indexOf('=') + 1));
} else if (arg.startsWith("--indir=")) {
inDir = requireDirectory(arg.substring(arg.indexOf('=') + 1));
}
}
}
SnapshotTool snapshotTool = new SnapshotTool();
if (!srcFiles.isEmpty() && outDir != null) {
try (Context polyglotContext = Context.newBuilder(JavaScriptLanguage.ID).allowIO(true).allowExperimentalOptions(true).option(JSContextOptions.CLASS_FIELDS_NAME, "true").option(
JSContextOptions.LAZY_TRANSLATION_NAME, "false").build()) {
polyglotContext.initialize(JavaScriptLanguage.ID);
polyglotContext.enter();
for (String srcFile : srcFiles) {
File sourceFile = inDir == null ? new File(srcFile) : Paths.get(inDir, srcFile).toFile();
File outputFile = Paths.get(outDir, srcFile + (binary ? ".bin" : ".java")).toFile();
if (!sourceFile.isFile()) {
throw new IllegalArgumentException("Not a file: " + sourceFile);
}
snapshotTool.snapshotScriptFileTo(srcFile, sourceFile, outputFile, binary, wrapped);
}
snapshotTool.timeStats.print();
polyglotContext.leave();
}
} else {
System.out.println("Usage: [--java|--binary] --outdir=DIR [--indir=DIR] --file=FILE [--file=FILE ...]");
}
}
private static String requireDirectory(String dir) {
if (dir != null && !new File(dir).isDirectory()) {
throw new IllegalArgumentException("Not a directory: " + dir);
}
return dir;
}
private void snapshotScriptFileTo(String fileName, File sourceFile, File outputFile, boolean binary, boolean wrapped) throws IOException {
JSRealm realm = JavaScriptLanguage.getCurrentJSRealm();
JSContext context = realm.getContext();
Recording.logv("recording snapshot of %s", fileName);
Source.SourceBuilder builder = Source.newBuilder(JavaScriptLanguage.ID, realm.getEnv().getPublicTruffleFile(sourceFile.getPath())).name(fileName);
Source source = builder.build();
String prefix;
String suffix;
if (wrapped) {
String code = source.getCharacters().toString();
char delimiter = code.charAt(0);
int prefixEnd = code.indexOf(delimiter, 1);
int suffixStart = code.indexOf(delimiter, prefixEnd + 1);
prefix = code.substring(1, prefixEnd);
String body = code.substring(prefixEnd + 1, suffixStart);
suffix = code.substring(suffixStart + 1);
source = Source.newBuilder(JavaScriptLanguage.ID, body, fileName).build();
} else {
prefix = "";
suffix = "";
}
try (TimerCloseable timer = timeStats.file(fileName)) {
final Recording rec = Recording.recordSource(source, context, false, prefix, suffix);
outputFile.getParentFile().mkdirs();
try (FileOutputStream outs = new FileOutputStream(outputFile)) {
rec.saveToStream(fileName, outs, binary);
}
} catch (RuntimeException e) {
throw new RuntimeException(fileName, e);
}
}
private interface TimerCloseable extends AutoCloseable {
@Override
void close();
}
private static class TimeStats {
private final List<Map.Entry<String, Long>> entries = new ArrayList<>();
public TimerCloseable file(String fileName) {
long startTime = System.nanoTime();
return () -> {
long endTime = System.nanoTime();
entries.add(new AbstractMap.SimpleImmutableEntry<>(fileName, endTime - startTime));
};
}
public void print() {
if (entries.isEmpty()) {
return;
}
long total = 0;
for (Map.Entry<String, Long> entry : entries) {
System.out.printf("%s: %.02f ms\n", entry.getKey(), entry.getValue() / 1e6);
total += entry.getValue();
}
System.out.printf("Total: %.02f ms\n", total / 1e6);
}
}
}