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 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 collectBenchs() { List result = new ArrayList<>(); for (File file : FileUtil.findJavaliFiles(BENCH_DIR)) result.add(file); return result; } /** * Run all benchmarks found in BENCH_DIR 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 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 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 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 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); } } }