1 /* 2 * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 import java.lang.invoke.MethodHandles; 26 import java.lang.management.ManagementFactory; 27 import java.lang.management.RuntimeMXBean; 28 import java.io.ByteArrayOutputStream; 29 import java.io.IOException; 30 import java.net.URI; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.concurrent.Callable; 37 import javax.tools.Diagnostic; 38 import javax.tools.DiagnosticCollector; 39 import javax.tools.FileObject; 40 import javax.tools.ForwardingJavaFileManager; 41 import javax.tools.JavaCompiler; 42 import javax.tools.JavaFileManager; 43 import javax.tools.JavaFileObject; 44 import javax.tools.SimpleJavaFileObject; 45 import javax.tools.ToolProvider; 46 47 /** 48 * This program tries to compile a large number of classes that exercise a fair amount of 49 * features in javac. 50 */ 51 public class JavacBenchApp { 52 static class ClassFile extends SimpleJavaFileObject { 53 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 54 protected ClassFile(String name) { 55 super(URI.create("memo:///" + name.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS); 56 } 57 @Override 58 public ByteArrayOutputStream openOutputStream() { 59 return this.baos; 60 } 61 byte[] toByteArray() { 62 return baos.toByteArray(); 63 } 64 } 65 66 static class FileManager extends ForwardingJavaFileManager<JavaFileManager> { 67 private Map<String, ClassFile> classesMap = new HashMap<String, ClassFile>(); 68 protected FileManager(JavaFileManager fileManager) { 69 super(fileManager); 70 } 71 @Override 72 public ClassFile getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject source) { 73 ClassFile classFile = new ClassFile(name); 74 classesMap.put(name, classFile); 75 return classFile; 76 } 77 public Map<String, byte[]> getCompiledClasses() { 78 Map<String, byte[]> result = new HashMap<>(); 79 for (Map.Entry<String, ClassFile> entry : classesMap.entrySet()) { 80 result.put(entry.getKey(), entry.getValue().toByteArray()); 81 } 82 return result; 83 } 84 } 85 86 static class SourceFile extends SimpleJavaFileObject { 87 private CharSequence sourceCode; 88 public SourceFile(String name, CharSequence sourceCode) { 89 super(URI.create("memo:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 90 this.sourceCode = sourceCode; 91 } 92 @Override 93 public CharSequence getCharContent(boolean ignore) { 94 return this.sourceCode; 95 } 96 } 97 98 public Map<String, byte[]> compile() { 99 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 100 DiagnosticCollector<JavaFileObject> ds = new DiagnosticCollector<>(); 101 Collection<SourceFile> sourceFiles = sources; 102 103 try (FileManager fileManager = new FileManager(compiler.getStandardFileManager(ds, null, null))) { 104 JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, sourceFiles); 105 if (task.call()) { 106 return fileManager.getCompiledClasses(); 107 } else { 108 for (Diagnostic<? extends JavaFileObject> d : ds.getDiagnostics()) { 109 System.out.format("Line: %d, %s in %s", d.getLineNumber(), d.getMessage(null), d.getSource().getName()); 110 } 111 throw new InternalError("compilation failure"); 112 } 113 } catch (IOException e) { 114 throw new InternalError(e); 115 } 116 } 117 118 List<SourceFile> sources; 119 120 static final String imports = """ 121 import java.lang.*; 122 import java.util.*; 123 """; 124 125 static final String testClassBody = """ 126 // Some comments 127 static long x; 128 static final long y; 129 static { 130 y = System.currentTimeMillis(); 131 } 132 /* More comments */ 133 @Deprecated 134 String func() { return "String " + this + y; } 135 public static void main(String args[]) { 136 try { 137 x = Long.parseLong(args[0]); 138 } catch (Throwable t) { 139 t.printStackTrace(); 140 } 141 doit(() -> { 142 System.out.println("Hello Lambda"); 143 Thread.dumpStack(); 144 }); 145 } 146 static List<String> list = List.of("1", "2"); 147 class InnerClass1 { 148 static final long yy = y; 149 } 150 static void doit(Runnable r) { 151 for (var x : list) { 152 r.run(); 153 } 154 } 155 static String patternMatch(String arg, Object o) { 156 if (o instanceof String s) { 157 return "1234"; 158 } 159 final String b = "B"; 160 return switch (arg) { 161 case "A" -> "a"; 162 case b -> "b"; 163 default -> "c"; 164 }; 165 } 166 public sealed class SealedInnerClass {} 167 public final class Foo extends SealedInnerClass {} 168 enum Expression { 169 ADDITION, 170 SUBTRACTION, 171 MULTIPLICATION, 172 DIVISION 173 } 174 public record Point(int x, int y) { 175 public Point(int x) { 176 this(x, 0); 177 } 178 } 179 """; 180 181 String sanitySource = """ 182 public class Sanity implements java.util.concurrent.Callable<String> { 183 public String call() { 184 return "this is a test"; 185 } 186 } 187 """; 188 189 void setup(int count) { 190 sources = new ArrayList<>(count); 191 for (int i = 0; i < count; i++) { 192 String source = imports + "public class Test" + i + " {" + testClassBody + "}"; 193 sources.add(new SourceFile("Test" + i, source)); 194 } 195 196 sources.add(new SourceFile("Sanity", sanitySource)); 197 } 198 199 @SuppressWarnings("unchecked") 200 static void validate(byte[] sanityClassFile) throws Throwable { 201 MethodHandles.Lookup lookup = MethodHandles.lookup(); 202 Class<?> cls = lookup.defineClass(sanityClassFile); 203 Callable<String> obj = (Callable<String>)cls.getDeclaredConstructor().newInstance(); 204 String s = obj.call(); 205 if (!s.equals("this is a test")) { 206 throw new RuntimeException("Expected \"this is a test\", but got \"" + s + "\""); 207 } 208 } 209 210 public static void main(String args[]) throws Throwable { 211 if (System.getProperty("JavacBenchApp.leyden.bench") == null) { 212 run(args); 213 } else { 214 leydenDriver(args); 215 } 216 } 217 218 // Write the same output as other leyden benchmarks for easy benchmarking. 219 // See https://github.com/openjdk/leyden/blob/premain/test/hotspot/jtreg/premain/lib/Bench.gmk 220 static void leydenDriver(String args[]) throws Throwable { 221 long mainStart = System.currentTimeMillis(); 222 RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean(); 223 // This includes all the time spent inside the JVM before main() is reached 224 // (since os::Posix::init is called and initial_time_count is initialized). 225 long vmStart = runtimeMXBean.getStartTime(); 226 long maxBeanOverHead = System.currentTimeMillis() - mainStart; 227 228 run(args); 229 230 long end = System.currentTimeMillis(); 231 System.out.println("#### Booted and returned in " + (end - vmStart - maxBeanOverHead) + "ms"); 232 System.out.println("#### (debug) mainStart = " + mainStart); 233 System.out.println("#### (debug) vmStart = " + vmStart); 234 System.out.println("#### (debug) before main (mainStart - vmStart) = " + (mainStart - vmStart)); 235 System.out.println("#### (debug) maxBeanOverHead = " + maxBeanOverHead); 236 System.out.println("#### (debug) end = " + end); 237 } 238 239 static void run(String args[]) throws Throwable { 240 long started = System.currentTimeMillis(); 241 JavacBenchApp bench = new JavacBenchApp(); 242 243 int count = 0; 244 if (args.length > 0) { 245 count = Integer.parseInt(args[0]); 246 if (count >= 0) { 247 bench.setup(count); 248 Map<String, byte[]> allClasses = bench.compile(); 249 validate(allClasses.get("Sanity")); 250 } 251 } 252 if (System.getProperty("JavacBenchApp.silent") == null) { 253 // Set this property when running with "perf stat", etc 254 long elapsed = System.currentTimeMillis() - started; 255 System.out.println("Generated source code for " + bench.sources.size() + " classes and compiled them in " + elapsed + " ms"); 256 } 257 } 258 } 259