From 7fd9d6c760c66d3e2f4034cf1a6b1b583ff829a9 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Thu, 23 May 2024 16:04:56 +0000 Subject: [PATCH] 8332340: Add JavacBench as a test case for CDS Reviewed-by: ccheung, matsaave --- .../cds/appcds/applications/JavacBench.java | 76 ++++++ .../appcds/applications/JavacBenchApp.java | 228 +++++++++++++++++ test/lib/jdk/test/lib/StringArrayUtils.java | 63 +++++ test/lib/jdk/test/lib/cds/CDSAppTester.java | 240 ++++++++++++++++++ 4 files changed, 607 insertions(+) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBench.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBenchApp.java create mode 100644 test/lib/jdk/test/lib/StringArrayUtils.java create mode 100644 test/lib/jdk/test/lib/cds/CDSAppTester.java diff --git a/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBench.java b/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBench.java new file mode 100644 index 00000000000..50696c8c1fc --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBench.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=static + * @summary Run JavacBenchApp with the classic static archive workflow + * @requires vm.cds + * @library /test/lib + * @run driver JavacBench STATIC + */ + +/* + * @test id=dynamic + * @summary Run JavacBenchApp with the classic dynamic archive workflow + * @requires vm.cds + * @library /test/lib + * @run driver JavacBench DYNAMIC + */ + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; + +public class JavacBench { + static String mainClass = JavacBenchApp.class.getName(); + static String appJar; + + public static void main(String args[]) throws Exception { + appJar = ClassFileInstaller.writeJar("JavacBenchApp.jar", + "JavacBenchApp", + "JavacBenchApp$ClassFile", + "JavacBenchApp$FileManager", + "JavacBenchApp$SourceFile"); + JavacBenchTester tester = new JavacBenchTester(); + tester.run(args); + } + + static class JavacBenchTester extends CDSAppTester { + public JavacBenchTester() { + super("JavacBench"); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + "90", + }; + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBenchApp.java b/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBenchApp.java new file mode 100644 index 00000000000..a32069883af --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/applications/JavacBenchApp.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +import java.lang.invoke.MethodHandles; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticCollector; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.SimpleJavaFileObject; +import javax.tools.ToolProvider; + +/** + * This program tries to compile a large number of classes that exercise a fair amount of + * features in javac. + */ +public class JavacBenchApp { + static class ClassFile extends SimpleJavaFileObject { + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + protected ClassFile(String name) { + super(URI.create("memo:///" + name.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS); + } + @Override + public ByteArrayOutputStream openOutputStream() { + return this.baos; + } + byte[] toByteArray() { + return baos.toByteArray(); + } + } + + static class FileManager extends ForwardingJavaFileManager { + private Map classesMap = new HashMap(); + protected FileManager(JavaFileManager fileManager) { + super(fileManager); + } + @Override + public ClassFile getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject source) { + ClassFile classFile = new ClassFile(name); + classesMap.put(name, classFile); + return classFile; + } + public Map getCompiledClasses() { + Map result = new HashMap<>(); + for (Map.Entry entry : classesMap.entrySet()) { + result.put(entry.getKey(), entry.getValue().toByteArray()); + } + return result; + } + } + + static class SourceFile extends SimpleJavaFileObject { + private CharSequence sourceCode; + public SourceFile(String name, CharSequence sourceCode) { + super(URI.create("memo:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); + this.sourceCode = sourceCode; + } + @Override + public CharSequence getCharContent(boolean ignore) { + return this.sourceCode; + } + } + + public Map compile() { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + DiagnosticCollector ds = new DiagnosticCollector<>(); + Collection sourceFiles = sources; + + try (FileManager fileManager = new FileManager(compiler.getStandardFileManager(ds, null, null))) { + JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, sourceFiles); + if (task.call()) { + return fileManager.getCompiledClasses(); + } else { + for (Diagnostic d : ds.getDiagnostics()) { + System.out.format("Line: %d, %s in %s", d.getLineNumber(), d.getMessage(null), d.getSource().getName()); + } + throw new InternalError("compilation failure"); + } + } catch (IOException e) { + throw new InternalError(e); + } + } + + List sources; + + static final String imports = """ + import java.lang.*; + import java.util.*; + """; + + static final String testClassBody = """ + // Some comments + static long x; + static final long y; + static { + y = System.currentTimeMillis(); + } + /* More comments */ + @Deprecated + String func() { return "String " + this + y; } + public static void main(String args[]) { + try { + x = Long.parseLong(args[0]); + } catch (Throwable t) { + t.printStackTrace(); + } + doit(() -> { + System.out.println("Hello Lambda"); + Thread.dumpStack(); + }); + } + static List list = List.of("1", "2"); + class InnerClass1 { + static final long yy = y; + } + static void doit(Runnable r) { + for (var x : list) { + r.run(); + } + } + static String patternMatch(String arg, Object o) { + if (o instanceof String s) { + return "1234"; + } + final String b = "B"; + return switch (arg) { + case "A" -> "a"; + case b -> "b"; + default -> "c"; + }; + } + public sealed class SealedInnerClass {} + public final class Foo extends SealedInnerClass {} + enum Expression { + ADDITION, + SUBTRACTION, + MULTIPLICATION, + DIVISION + } + public record Point(int x, int y) { + public Point(int x) { + this(x, 0); + } + } + """; + + String sanitySource = """ + public class Sanity implements java.util.concurrent.Callable { + public String call() { + return "this is a test"; + } + } + """; + + void setup(int count) { + sources = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + String source = imports + "public class Test" + i + " {" + testClassBody + "}"; + sources.add(new SourceFile("Test" + i, source)); + } + + sources.add(new SourceFile("Sanity", sanitySource)); + } + + @SuppressWarnings("unchecked") + static void validate(byte[] sanityClassFile) throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + Class cls = lookup.defineClass(sanityClassFile); + Callable obj = (Callable)cls.getDeclaredConstructor().newInstance(); + String s = obj.call(); + if (!s.equals("this is a test")) { + throw new RuntimeException("Expected \"this is a test\", but got \"" + s + "\""); + } + } + + public static void main(String args[]) throws Throwable { + long started = System.currentTimeMillis(); + JavacBenchApp bench = new JavacBenchApp(); + + int count = 0; + if (args.length > 0) { + count = Integer.parseInt(args[0]); + if (count >= 0) { + bench.setup(count); + Map allClasses = bench.compile(); + validate(allClasses.get("Sanity")); + } + } + if (System.getProperty("JavacBenchApp.silent") == null) { + // Set this property when running with "perf stat", etc + long elapsed = System.currentTimeMillis() - started; + System.out.println("Generated source code for " + bench.sources.size() + " classes and compiled them in " + elapsed + " ms"); + } + } +} + diff --git a/test/lib/jdk/test/lib/StringArrayUtils.java b/test/lib/jdk/test/lib/StringArrayUtils.java new file mode 100644 index 00000000000..11124701dce --- /dev/null +++ b/test/lib/jdk/test/lib/StringArrayUtils.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.test.lib; + +import java.util.ArrayList; + +public class StringArrayUtils { + /** + * The various concat() functions in this class can be used for building + * a command-line argument array for ProcessTools.createTestJavaProcessBuilder(), + * etc. When some of the arguments are conditional, this is more convenient + * than alternatives like ArrayList. + * + * Example: + * + *
+     *     String args[] = StringArrayUtils.concat("-Xint", "-Xmx32m");
+     *     if (verbose) {
+     *         args = StringArrayUtils.concat(args, "-verbose");
+     *     }
+     *     args = StringArrayUtils.concat(args, "HelloWorld");
+     *     ProcessTools.createTestJavaProcessBuilder(args);
+     * 
+ */ + public static String[] concat(String... args) { + return args; + } + + public static String[] concat(String[] prefix, String... extra) { + String[] ret = new String[prefix.length + extra.length]; + System.arraycopy(prefix, 0, ret, 0, prefix.length); + System.arraycopy(extra, 0, ret, prefix.length, extra.length); + return ret; + } + + public static String[] concat(String prefix, String[] extra) { + String[] ret = new String[1 + extra.length]; + ret[0] = prefix; + System.arraycopy(extra, 0, ret, 1, extra.length); + return ret; + } +} diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java new file mode 100644 index 00000000000..c39e6bb8e94 --- /dev/null +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.test.lib.cds; + +import java.io.File; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.StringArrayUtils; + +/* + * This is a base class used for testing CDS functionalities with complex applications. + * You can define the application by overridding the vmArgs(), classpath() and appCommandLine() + * methods. Application-specific validation checks can be implemented with checkExecution(). +*/ +abstract public class CDSAppTester { + private final String name; + private final String classListFile; + private final String classListFileLog; + private final String staticArchiveFile; + private final String staticArchiveFileLog; + private final String dynamicArchiveFile; + private final String dynamicArchiveFileLog; + private final String productionRunLog; + + public CDSAppTester(String name) { + // Old workflow + this.name = name; + classListFile = name() + ".classlist"; + classListFileLog = classListFile + ".log"; + staticArchiveFile = name() + ".static.jsa"; + staticArchiveFileLog = staticArchiveFile + ".log"; + dynamicArchiveFile = name() + ".dynamic.jsa"; + dynamicArchiveFileLog = dynamicArchiveFile + ".log"; + productionRunLog = name() + ".production.log"; + } + + private enum Workflow { + STATIC, // classic -Xshare:dump workflow + DYNAMIC, // classic -XX:ArchiveClassesAtExit + } + + public enum RunMode { + CLASSLIST, + DUMP_STATIC, + DUMP_DYNAMIC, + PRODUCTION; + + public boolean isStaticDump() { + return this == DUMP_STATIC; + } + public boolean isProductionRun() { + return this == PRODUCTION; + } + } + + public final String name() { + return this.name; + } + + // optional + public String[] vmArgs(RunMode runMode) { + return new String[0]; + } + + // optional + public String classpath(RunMode runMode) { + return null; + } + + // must override + // main class, followed by arguments to the main class + abstract public String[] appCommandLine(RunMode runMode); + + // optional + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception {} + + private Workflow workflow; + + public final boolean isStaticWorkflow() { + return workflow == Workflow.STATIC; + } + + public final boolean isDynamicWorkflow() { + return workflow == Workflow.DYNAMIC; + } + + private String logToFile(String logFile, String... logTags) { + StringBuilder sb = new StringBuilder("-Xlog:"); + String prefix = ""; + for (String tag : logTags) { + sb.append(prefix); + sb.append(tag); + prefix = ","; + } + sb.append(":file=" + logFile + "::filesize=0"); + return sb.toString(); + } + + private void listOutputFile(String file) { + File f = new File(file); + if (f.exists()) { + System.out.println("[output file: " + file + " " + f.length() + " bytes]"); + } else { + System.out.println("[output file: " + file + " does not exist]"); + } + } + + private OutputAnalyzer executeAndCheck(String[] cmdLine, RunMode runMode, String... logFiles) throws Exception { + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmdLine); + Process process = pb.start(); + OutputAnalyzer output = CDSTestUtils.executeAndLog(process, runMode.toString()); + for (String logFile : logFiles) { + listOutputFile(logFile); + } + output.shouldHaveExitValue(0); + CDSTestUtils.checkCommonExecExceptions(output); + checkExecution(output, runMode); + return output; + } + + private OutputAnalyzer createClassList() throws Exception { + RunMode runMode = RunMode.CLASSLIST; + String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), + "-Xshare:off", + "-XX:DumpLoadedClassList=" + classListFile, + "-cp", classpath(runMode), + logToFile(classListFileLog, + "class+load=debug")); + cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); + return executeAndCheck(cmdLine, runMode, classListFile, classListFileLog); + } + + private OutputAnalyzer dumpStaticArchive() throws Exception { + RunMode runMode = RunMode.DUMP_STATIC; + String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), + "-Xlog:cds", + "-Xlog:cds+heap=error", + "-Xshare:dump", + "-XX:SharedArchiveFile=" + staticArchiveFile, + "-XX:SharedClassListFile=" + classListFile, + "-cp", classpath(runMode), + logToFile(staticArchiveFileLog, + "cds=debug", + "cds+class=debug", + "cds+heap=warning", + "cds+resolve=debug")); + return executeAndCheck(cmdLine, runMode, staticArchiveFile, staticArchiveFileLog); + } + + private OutputAnalyzer dumpDynamicArchive() throws Exception { + RunMode runMode = RunMode.DUMP_DYNAMIC; + String[] cmdLine = new String[0]; + if (isDynamicWorkflow()) { + // "classic" dynamic archive + cmdLine = StringArrayUtils.concat(vmArgs(runMode), + "-Xlog:cds", + "-XX:ArchiveClassesAtExit=" + dynamicArchiveFile, + "-cp", classpath(runMode), + logToFile(dynamicArchiveFileLog, + "cds=debug", + "cds+class=debug", + "cds+resolve=debug", + "class+load=debug")); + } + cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); + return executeAndCheck(cmdLine, runMode, dynamicArchiveFile, dynamicArchiveFileLog); + } + + private OutputAnalyzer productionRun() throws Exception { + RunMode runMode = RunMode.PRODUCTION; + String[] cmdLine = StringArrayUtils.concat(vmArgs(runMode), + "-cp", classpath(runMode), + logToFile(productionRunLog, "cds")); + + if (isStaticWorkflow()) { + cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + staticArchiveFile); + } else if (isDynamicWorkflow()) { + cmdLine = StringArrayUtils.concat(cmdLine, "-XX:SharedArchiveFile=" + dynamicArchiveFile); + } + + cmdLine = StringArrayUtils.concat(cmdLine, appCommandLine(runMode)); + return executeAndCheck(cmdLine, runMode, productionRunLog); + } + + public void run(String args[]) throws Exception { + String err = "Must have exactly one command line argument of the following: "; + String prefix = ""; + for (Workflow wf : Workflow.values()) { + err += prefix; + err += wf; + prefix = ", "; + } + if (args.length != 1) { + throw new RuntimeException(err); + } else { + if (args[0].equals("STATIC")) { + runStaticWorkflow(); + } else if (args[0].equals("DYNAMIC")) { + runDynamicWorkflow(); + } else { + throw new RuntimeException(err); + } + } + } + + private void runStaticWorkflow() throws Exception { + this.workflow = Workflow.STATIC; + createClassList(); + dumpStaticArchive(); + productionRun(); + } + + private void runDynamicWorkflow() throws Exception { + this.workflow = Workflow.DYNAMIC; + dumpDynamicArchive(); + productionRun(); + } +}