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