compiler-design-eth/test/cd/BenchmarksRunner.java

346 lines
10 KiB
Java

package cd;
import static java.nio.file.Files.lines;
import static java.nio.file.Files.write;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.ProcessBuilder.Redirect;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import cd.backend.codegen.CfgCodeGenerator;
import cd.backend.interpreter.DynamicError;
import cd.backend.interpreter.Interpreter;
import cd.backend.interpreter.StaticError;
import cd.frontend.parser.ParseFailure;
import cd.frontend.semantic.SemanticFailure;
import cd.ir.Ast.ClassDecl;
import cd.util.FileUtil;
import cd.util.Pair;
public class BenchmarksRunner {
// MAIN_OVERHEAD is an estimate of instructions executed by an empty 'main' in Javali.
private static final long MAIN_OVERHEAD = 150000;
public static final File BENCH_DIR = new File("benchmarks");
//public static final File BENCH_DIR = new File("javali_tests");
public static void main(String[] args) {
BenchmarksRunner runner = new BenchmarksRunner();
runner.runBenchmarks();
}
private final Collection<File> benchmarks;
private File sFile;
private File binFile;
private File inFile;
private Main main;
private File optRefFile;
private File execRefFile;
private File sRefFile;
private File binRefFile;
public BenchmarksRunner() {
benchmarks = collectBenchs();
}
/**
* Return a list of all Javali-files in a directory (recursively)
*/
private static Collection<File> collectBenchs() {
List<File> result = new ArrayList<>();
for (File file : FileUtil.findJavaliFiles(BENCH_DIR))
result.add(file);
return result;
}
/**
* Run all benchmarks found in <code>BENCH_DIR</code> and compare with the reference compiler.
*/
public void runBenchmarks() {
System.out.println("Benchmark\tAST reduction\tValgrind reduction");
for (File file : benchmarks) {
this.sFile = new File(file.getPath() + Config.ASMEXT);
this.binFile = new File(file.getPath() + Config.BINARYEXT);
this.inFile = new File(file.getPath() + ".in");
this.execRefFile = new File(file.getPath() + ".exec.ref");
this.optRefFile = new File(file.getPath() + ".opt.ref");
this.sRefFile = new File(file.getPath() + ".ref" + Config.ASMEXT);
this.binRefFile = new File(file.getPath() + ".ref" + Config.BINARYEXT);
System.out.print(file.getName() + "\t");
try {
Pair<Double> improvement = runOneBench(file);
System.out.printf("%.1f%%\t%.1f%%\n",
improvement.a * 100, improvement.b * 100);
} catch (BenchmarkError e) {
System.out.println(e.getMessage());
} catch (Throwable t) {
System.out.println("ERROR");
}
}
}
/**
* Run a Javali-program on the reference compiler
*/
private void runReference(File file) throws IOException, InterruptedException {
String slash = File.separator;
String colon = File.pathSeparator;
String javaExe = System.getProperty("java.home") + slash + "bin" + slash + Config.JAVA_EXE;
String benchV = "BENCH2";
ProcessBuilder pb = new ProcessBuilder(javaExe,
"-Dcd.meta_hidden.Version="+benchV, "-cp",
"lib/frozenReferenceObf.jar" + colon + " lib/junit-4.12.jar" + colon
+ "lib/antlr-4.7.1-complete.jar",
"cd.FrozenReferenceMain", file.getAbsolutePath());
pb.redirectOutput(Redirect.INHERIT);
pb.redirectError(Redirect.INHERIT);
Process proc = pb.start();
proc.waitFor();
try (InputStream err = proc.getErrorStream()) {
if (err.available() > 0) {
byte b[] = new byte[err.available()];
err.read(b, 0, b.length);
System.err.println(new String(b));
}
}
}
/**
* Run and compare one javali-benchmark. Returns two "improvement" numbers that
* represent the reduction in instructions executed. (once on the interpreter, once
* as compiled code using valgrind)
*/
private Pair<Double> runOneBench(File file) throws BenchmarkError {
// Delete intermediate files from previous runs:
if (sFile.exists())
sFile.delete();
if (binFile.exists())
binFile.delete();
// Read test-input file
String input = "";
try {
if (inFile.exists())
input = FileUtil.read(inFile);
} catch (IOException e1) {
throw new BenchmarkError("Error while reading .in file");
}
// Run test on reference implementation
String execRef, optRef;
try {
runReference(file);
execRef = FileUtil.read(execRefFile);
optRef = FileUtil.read(optRefFile);
} catch (IOException | InterruptedException e) {
System.err.println(e);
throw new BenchmarkError("Error while running on reference");
}
// Call frontend of compiler-under-test (CUT)
List<ClassDecl> astRoots;
PrintStream oldStdOut = System.out;
try {
System.setOut(new PrintStream(new File("stdout-log.txt")));
this.main = new Main();
this.main.debug = new StringWriter();
try {
astRoots = main.parse(new FileReader(file));
} catch (ParseFailure | IOException pf) {
System.err.println(pf);
throw new BenchmarkError("ParseFailure");
}
try {
main.semanticCheck(astRoots);
} catch (SemanticFailure sf) {
System.err.println(sf);
throw new BenchmarkError("SemanticFailure");
}
} catch (FileNotFoundException e) {
System.err.println(e);
throw new BenchmarkError("Failed to redirect stdout");
} finally {
System.setOut(oldStdOut);
}
// Run IR of compiled test with interpreter to count
// instructions
final StringWriter outputWriter = new StringWriter();
final Reader inputReader = new StringReader(input);
final Interpreter interp = new Interpreter(astRoots, inputReader, outputWriter);
try {
interp.execute();
} catch (StaticError | DynamicError err) {
System.err.println(err);
throw new BenchmarkError("Error while running the interpreter");
}
// If output is the same, calculate the reduction in
// instructions by the CUT compared to the reference
// compiler
String execOut = outputWriter.toString();
if (!execOut.equals(execRef))
throw new BenchmarkError("Output is incorrect");
String optOut = interp.operationSummary();
double refCount = getTotalOps(optRef);
double outCount = getTotalOps(optOut);
double interpReduction = 0;
if (refCount != 0 || outCount != 0)
interpReduction = 1 - outCount / refCount;
double valgrindReduction = 0;
// on BENCH2, we also compare the generated assembly using valgrind
valgrindReduction = compareWithValgrind(astRoots, input);
return new Pair<>(interpReduction, valgrindReduction);
}
/**
* Generate assembly, assemble, and run the executables of the reference
* and the CUT on valgrind to get a deterministic instruction count. Return
* the reduction of instructions compared to the reference on BENCH2.
*/
private double compareWithValgrind(List<ClassDecl> astRoots, String input) throws BenchmarkError {
// generate ASM
try (FileWriter fout = new FileWriter(sFile)) {
CfgCodeGenerator cg = new CfgCodeGenerator(main, fout);
cg.go(astRoots);
} catch (IOException e) {
System.err.println(e);
throw new BenchmarkError("IOException when generating ASM");
}
// assemble the generated and the reference's assembly file
try {
FileUtil.runCommand(
Config.ASM_DIR, Config.ASM,
new String[] { binFile.getAbsolutePath(), sFile.getAbsolutePath() },
null, false);
if (!binFile.exists())
throw new BenchmarkError("Error while assembling ASM");
} catch (IOException e) {
System.err.println(e);
throw new BenchmarkError("Error while assembling ASM");
}
try {
FileUtil.runCommand(
Config.ASM_DIR, Config.ASM,
new String[] { binRefFile.getAbsolutePath(), sRefFile.getAbsolutePath() },
null, false);
if (!binRefFile.exists())
throw new BenchmarkError("Error while assembling ASM from reference");
} catch (IOException e) {
System.err.println(e);
throw new BenchmarkError("Error while assembling ASM from reference");
}
// run both binaries on valgrind
String execOut;
try {
String[] cmd = {"valgrind", "--tool=callgrind", binFile.getAbsolutePath()};
execOut = FileUtil.runCommand(new File("."),
cmd, new String[] {},
input, true);
} catch (IOException e) {
System.err.println(e);
throw new BenchmarkError("Error while running binary");
}
String execRefOut;
try {
String[] cmd = {"valgrind", "--tool=callgrind", binRefFile.getAbsolutePath()};
execRefOut = FileUtil.runCommand(new File("."),
cmd, new String[] {},
input, true);
} catch (IOException e) {
System.err.println(e);
throw new BenchmarkError("Error while running binary from reference");
}
// parse output of valgrind and return reduction in instructions
double instrCount = parseInstrCount(execOut) - MAIN_OVERHEAD;
double instrRefCount = parseInstrCount(execRefOut) - MAIN_OVERHEAD;
double valgrindReduction = 0;
if (instrCount != 0 || instrRefCount != 0)
valgrindReduction = 1 - instrCount / instrRefCount;
return valgrindReduction;
}
/**
* Parse output of valgrind and return total instruction count
*/
private long parseInstrCount(String execOut_) throws BenchmarkError {
String execOut = execOut_.replace("\r\n", "\n");
for (String line : execOut.split("\n")) {
if (line.contains(" I refs:")) {
String[] args = line.split(": ");
String number = args[1].trim();
number = number.replace(",", "");
return Long.valueOf(number);
}
}
throw new BenchmarkError("Error while parsing valgrind output");
}
/**
* Parse operation summary of interpreter and get the total count of instructions executed
* (Binary+Unary ops)
*/
private int getTotalOps(String optCount_) {
String optCount = optCount_.replace("\r\n", "\n");
int sum = 0;
for (String line : optCount.split("\n")) {
if (!line.equals("")) {
String[] args = line.split(": ");
sum += Integer.valueOf(args[1]);
}
}
return sum;
}
class BenchmarkError extends Exception {
private static final long serialVersionUID = 1L;
public BenchmarkError(String arg) {
super(arg);
}
}
}