1 /*
2 * Copyright (c) 2016, 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 // TODO add bugid and summary
25
26 /*
27 * @test
28 * @library /testlibrary /test/lib /compiler/whitebox /
29 * @build compiler.valhalla.valuetypes.ValueTypeTestBench
30 * @run main ClassFileInstaller sun.hotspot.WhiteBox
31 * @run main ClassFileInstaller jdk.test.lib.Platform
32 * @run main/othervm -noverify -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
33 * compiler.valhalla.valuetypes.ValueTypeTestBench
34 */
35
36 package compiler.valhalla.valuetypes;
37
38 import compiler.whitebox.CompilerWhiteBoxTest;
39 import jdk.internal.misc.Unsafe;
40 import jdk.test.lib.Asserts;
41 import jdk.test.lib.Platform;
42 import jdk.test.lib.ProcessTools;
43 import jdk.test.lib.OutputAnalyzer;
44 import jdk.test.lib.Utils;
45 import sun.hotspot.WhiteBox;
46
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.lang.reflect.Method;
50 import java.util.ArrayList;
51 import java.util.Hashtable;
52 import java.util.regex.Matcher;
53 import java.util.regex.Pattern;
54
55 // Test value type
56 __ByValue final class MyValue {
57 final int x;
58 final long y;
59 final double z;
60
61 private MyValue(int x, long y, double z) {
62 this.x = x;
63 this.y = y;
64 this.z = z;
65 }
66
67 @DontInline
68 public static MyValue createDontInline(int x, long y, double z) {
69 return __Make MyValue(x, y, z);
70 }
71
72 @ForceInline
73 public static MyValue createInline(int x, long y, double z) {
74 return __Make MyValue(x, y, z);
75 }
76 }
77
78 public class ValueTypeTestBench {
79 // Print ideal graph after execution of each test
80 private static final boolean PRINT_GRAPH = true;
81
82 // ========== Test definitions ==========
83
84 // Receive value type through call to interpreter
85 @Test(failOn = ALLOC + STORE)
86 public double test1() {
87 MyValue v = MyValue.createDontInline(rI, rL, rD);
88 return v.x + v.y + v.z;
89 }
90
91 @DontCompile
92 public void test1_verifier(boolean warmup) {
93 double result = test1();
94 Asserts.assertEQ(result, rI + rL + rD);
95 }
96
97 // Receive value type from interpreter via parameter
98 @Test(failOn = ALLOC + STORE)
99 public double test2(MyValue v) {
100 return v.x + v.y + v.z;
101 }
102
103 @DontCompile
104 public void test2_verifier(boolean warmup) {
105 MyValue v = MyValue.createDontInline(rI, rL, rD);
106 double result = test2(v);
107 Asserts.assertEQ(result, rI + rL + rD);
108 }
109
110 // Return incoming value type without accessing fields
111 @Test(failOn = ALLOC + LOAD + STORE)
112 public MyValue test3(MyValue v) {
113 return v;
114 }
115
116 @DontCompile
117 public void test3_verifier(boolean warmup) {
118 MyValue v1 = MyValue.createDontInline(rI, rL, rD);
119 MyValue v2 = test3(v1);
120 Asserts.assertEQ(v1.x, v2.x);
121 Asserts.assertEQ(v1.y, v2.y);
122 Asserts.assertEQ(v1.z, v2.z);
123 }
124
125 // Create a value type in compiled code and only use fields.
126 // Allocation should go away because value type does not escape.
127 @Test(failOn = ALLOC + LOAD + STORE)
128 public double test4() {
129 MyValue v = MyValue.createInline(rI, rL, rD);
130 return v.x + v.y + v.z;
131 }
132
133 @DontCompile
134 public void test4_verifier(boolean warmup) {
135 double result = test4();
136 Asserts.assertEQ(result, rI + rL + rD);
137 }
138
139 // Create a value type in compiled code and pass it to
140 // an inlined compiled method via a call.
141 @Test(failOn = ALLOC + LOAD + STORE)
142 public double test5() {
143 MyValue v = MyValue.createInline(rI, rL, rD);
144 return test5Inline(v);
145 }
146
147 @ForceInline
148 public double test5Inline(MyValue v) {
149 return v.x + v.y + v.z;
150 }
151
152 @DontCompile
153 public void test5_verifier(boolean warmup) {
154 double result = test5();
155 Asserts.assertEQ(result, rI + rL + rD);
156 }
157
158 // Create a value type in compiled code and pass it to
159 // the interpreter via a call.
160 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD)
161 public double test6() {
162 MyValue v = MyValue.createInline(rI, rL, rD);
163 // Pass to interpreter
164 return sumValue(v);
165 }
166
167 @DontCompile
168 public void test6_verifier(boolean warmup) {
169 double result = test6();
170 Asserts.assertEQ(result, rI + rL + rD);
171 }
172
173 // Create a value type in compiled code and pass it to
174 // the interpreter by returning.
175 @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD)
176 public MyValue test7(int x, long y, double z) {
177 return MyValue.createInline(x, y, z);
178 }
179
180 @DontCompile
181 public void test7_verifier(boolean warmup) {
182 MyValue v = test7(rI, rL, rD);
183 double result = v.x + v.y + v.z;
184 Asserts.assertEQ(result, rI + rL + rD);
185 }
186
187 // Merge value types created from two branches
188 @Test(failOn = ALLOC + STORE)
189 public double test8(boolean b) {
190 MyValue v;
191 if (b) {
192 v = MyValue.createInline(rI, rL, rD);
193 } else {
194 v = MyValue.createDontInline(rI + 1, rL + 1, rD + 1);
195 }
196 return v.x + v.y + v.z;
197 }
198
199 @DontCompile
200 public void test8_verifier(boolean warmup) {
201 Asserts.assertEQ(test8(true), rI + rL + rD);
202 Asserts.assertEQ(test8(false), rI + 1 + rL + 1 + ((double)rD + 1));
203 }
204
205 // Merge value types created from two branches
206 @Test(match = {ALLOC, STORE}, matchCount = {1, 3}, failOn = LOAD)
207 public MyValue test9(boolean b) {
208 MyValue v;
209 if (b) {
210 // Value type is not allocated
211 v = MyValue.createInline(rI, rL, rD);
212 } else {
213 // Value type is allocated by the callee
214 v = MyValue.createDontInline(rI + 1, rL + 1, rD + 1);
215 }
216 // Need to allocate value type if 'b' is true
217 double sum = sumValue(v);
218 if (b) {
219 v = MyValue.createDontInline(rI, rL, sum);
220 } else {
221 v = MyValue.createDontInline(rI, rL, sum + 1);
222 }
223 // Don't need to allocate value type because both branches allocate
224 return v;
225 }
226
227 @DontCompile
228 public void test9_verifier(boolean warmup) {
229 MyValue v = test9(true);
230 Asserts.assertEQ(v.x, rI);
231 Asserts.assertEQ(v.y, rL);
232 Asserts.assertEQ(v.z, rI + rL + rD);
233
234 v = test9(false);
235 Asserts.assertEQ(v.x, rI);
236 Asserts.assertEQ(v.y, rL);
237 Asserts.assertEQ(v.z, rI + rL + ((double)rD + 1));
238 }
239
240 // Merge value types created in a loop (not inlined)
241 @Test(failOn = ALLOC + STORE)
242 public double test10(int x, long y, double z) {
243 MyValue v = MyValue.createDontInline(x, y, z);
244 for (int i = 0; i < 10; ++i) {
245 v = MyValue.createDontInline(v.x + 1, v.y + 1, v.z + 1);
246 }
247 return v.x + v.y + v.z;
248 }
249
250 @DontCompile
251 public void test10_verifier(boolean warmup) {
252 double result = test10(rI, rL, rD);
253 Asserts.assertEQ(result, rI + rL + 20 + ((double)rD + 10));
254 }
255
256 // Merge value types created in a loop (inlined)
257 @Test(failOn = ALLOC + LOAD + STORE + LOOP)
258 public double test11(int x, long y, double z) {
259 MyValue v = MyValue.createInline(x, y, z);
260 for (int i = 0; i < 10; ++i) {
261 v = MyValue.createInline(v.x + 1, v.y + 1, v.z + 1);
262 }
263 return v.x + v.y + v.z;
264 }
265
266 @DontCompile
267 public void test11_verifier(boolean warmup) {
268 double result = test11(rI, rL, rD);
269 Asserts.assertEQ(result, rI + rL + 20 + ((double)rD + 10));
270 }
271
272 // Test loop with uncommon trap referencing a value type
273 @Test(match = {TRAP, SCOBJ}, matchCount = {1, 1}, failOn = ALLOC + LOAD + STORE)
274 public double test12(boolean b) {
275 MyValue v = MyValue.createInline(rI, rL, rD);
276 double result = 42;
277 for (int i = 0; i < 1000; ++i) {
278 if (b) {
279 result += v.x;
280 } else {
281 // Uncommon trap referencing v. We delegate allocation to the
282 // interpreter by adding a SafePointScalarObjectNode.
283 result = sumValue(v);
284 }
285 }
286 return result;
287 }
288
289 @DontCompile
290 public void test12_verifier(boolean warmup) {
291 double result = test12(warmup);
292 Asserts.assertEQ(result, warmup ? 42 + (1000*(double)rI) : (rI + rL + rD));
293 }
294
295 // Test loop with uncommon trap referencing a value type
296 @Test(match = {TRAP, LOAD}, matchCount = {1, 1}, failOn = ALLOC + STORE + SCOBJ)
297 public double test13(boolean b) {
298 MyValue v = MyValue.createDontInline(rI, rL, rD);
299 double result = 42;
300 for (int i = 0; i < 1000; ++i) {
301 if (b) {
302 result += v.x;
303 } else {
304 // Uncommon trap referencing v. Should not allocate
305 // but just pass the existing oop to the uncommon trap.
306 result = sumValue(v);
307 }
308 }
309 return result;
310 }
311
312 @DontCompile
313 public void test13_verifier(boolean warmup) {
314 double result = test13(warmup);
315 Asserts.assertEQ(result, warmup ? 42 + (1000*(double)rI) : (rI + rL + rD));
316 }
317
318
319 // ========== Helper methods ==========
320
321 @DontCompile
322 public double sumValue(MyValue v) {
323 return v.x + v.y + v.z;
324 }
325
326 // ========== Test infrastructure ==========
327
328 private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
329 private static final int COMP_LEVEL_ANY = -1;
330 private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4;
331 private static final Hashtable<String, Method> tests = new Hashtable<String, Method>();
332 private static final int WARMUP = 10;
333
334 // Regular expressions used to match nodes in the PrintIdeal output
335 private static final String START = "(\\d+\\t(.*";
336 private static final String MID = ".*)+\\t===.*";
337 private static final String END = ")|";
338 private static final String ALLOC = START + "CallStaticJava" + MID + "_new_instance_Java" + END;
339 private static final String LOAD = START + "Load" + MID + "valuetype\\*" + END;
340 private static final String STORE = START + "Store" + MID + "valuetype\\*" + END;
341 private static final String LOOP = START + "Loop" + MID + "" + END;
342 private static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap" + END;
343 // TODO: match field values of scalar replaced object
344 private static final String SCOBJ = "(.*# ScObj.*" + END;
345
346
347 // Random test values
348 private static final int rI = Utils.getRandomInstance().nextInt();
349 private static final long rL = Utils.getRandomInstance().nextLong();
350 private static final double rD = Utils.getRandomInstance().nextDouble();
351
352 static {
353 // Gather all test methods and put them in Hashtable
354 for (Method m : ValueTypeTestBench.class.getDeclaredMethods()) {
355 if (m.isAnnotationPresent(Test.class)) {
356 tests.put("ValueTypeTestBench::" + m.getName(), m);
357 }
358 }
359 }
360
361 public static void main(String[] args) throws Throwable {
362 if (args.length == 0) {
363 // Run tests in own process and verify output
364 OutputAnalyzer oa = ProcessTools.executeTestJvm("-noverify",
365 "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI",
366 "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:-UseOnStackReplacement",
367 "-XX:CompileCommand=quiet", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly",
368 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*",
369 "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue::*",
370 ValueTypeTestBench.class.getName(), "run");
371 String output = oa.getOutput();
372 oa.shouldHaveExitValue(0);
373 parseOutput(output);
374 } else {
375 // Execute tests
376 ValueTypeTestBench bench = new ValueTypeTestBench();
377 bench.run();
378 }
379 }
380
381 public static void parseOutput(String output) throws Exception {
382 String split = "b compiler.valhalla.valuetypes.";
383 String[] compilations = output.split(split);
384 // Print header
385 System.out.println(compilations[0]);
386 // Iterate over compilation output
387 for (String graph : compilations) {
388 String[] lines = graph.split("\\n");
389 String testName = lines[0].split(" ")[0];
390 Method test = tests.get(testName);
391 if (test == null) {
392 // Skip helper methods
393 continue;
394 }
395 if (PRINT_GRAPH) {
396 System.out.println("\nGraph for " + graph);
397 }
398 // Parse graph using regular expressions to determine if it contains forbidden nodes
399 Test anno = test.getAnnotation(Test.class);
400 String regexFail = anno.failOn();
401 if (!regexFail.isEmpty()) {
402 Pattern pattern = Pattern.compile(regexFail.substring(0, regexFail.length()-1));
403 Matcher matcher = pattern.matcher(graph);
404 boolean fail = false;
405 while (matcher.find()) {
406 System.out.println("Graph for '" + testName + "' contains forbidden node:");
407 System.out.println(matcher.group());
408 fail = true;
409 }
410 Asserts.assertFalse(fail, "Graph for '" + testName + "' contains forbidden nodes");
411 }
412 String[] regexMatch = anno.match();
413 int[] matchCount = anno.matchCount();
414 for (int i = 0; i < regexMatch.length; ++i) {
415 Pattern pattern = Pattern.compile(regexMatch[i].substring(0, regexMatch[i].length()-1));
416 Matcher matcher = pattern.matcher(graph);
417 int count = 0;
418 String nodes = "";
419 while (matcher.find()) {
420 count++;
421 nodes += matcher.group() + "\n";
422 }
423 if (matchCount[i] != count) {
424 System.out.println("Graph for '" + testName + "' contains different number of match nodes:");
425 System.out.println(nodes);
426 }
427 Asserts.assertEQ(matchCount[i], count, "Graph for '" + testName + "' contains different number of match nodes");
428 }
429 tests.remove(testName);
430 System.out.println(testName + " passed");
431 }
432 // Check if all tests were compiled
433 if (tests.size() != 0) {
434 for (String name : tests.keySet()) {
435 System.out.println("Test '" + name + "' not compiled!");
436 }
437 throw new RuntimeException("Not all tests were compiled");
438 }
439 }
440
441 public void setup(Method[] methods) {
442 for (Method m : methods) {
443 if (m.isAnnotationPresent(Test.class)) {
444 // Don't inline tests
445 WHITE_BOX.testSetDontInlineMethod(m, true);
446 }
447 if (m.isAnnotationPresent(DontCompile.class)) {
448 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, true);
449 WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, false);
450 }
451 if (m.isAnnotationPresent(ForceInline.class)) {
452 WHITE_BOX.testSetForceInlineMethod(m, true);
453 } else if (m.isAnnotationPresent(DontInline.class)) {
454 WHITE_BOX.testSetDontInlineMethod(m, true);
455 }
456 }
457 }
458
459 public void run() throws Exception {
460 System.out.format("rI = %d, rL = %d, rD = %f\n", rI, rL, rD);
461 setup(this.getClass().getDeclaredMethods());
462 setup(MyValue.class.getDeclaredMethods());
463
464 // Execute tests
465 for (Method test : tests.values()) {
466 Method verifier = getClass().getDeclaredMethod(test.getName() + "_verifier", boolean.class);
467 // Warmup using verifier method
468 for (int i = 0; i < WARMUP; ++i) {
469 verifier.invoke(this, true);
470 }
471 // Trigger compilation
472 WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION);
473 Asserts.assertTrue(WHITE_BOX.isMethodCompiled(test, false));
474 // Check result
475 verifier.invoke(this, false);
476 }
477 }
478 }
479
480 // Mark method as test
481 @Retention(RetentionPolicy.RUNTIME)
482 @interface Test {
483 // Regular expression used to match forbidden IR nodes
484 // in the C2 IR emitted for this test.
485 String failOn() default "";
486 // Regular expressions used to match and count IR nodes.
487 String[] match() default { };
488 int[] matchCount() default { };
489 }
490
491 // Force method inlining during compilation
492 @Retention(RetentionPolicy.RUNTIME)
493 @interface ForceInline { }
494
495 // Prevent method inlining during compilation
496 @Retention(RetentionPolicy.RUNTIME)
497 @interface DontInline { }
498
499 // Prevent method compilation
500 @Retention(RetentionPolicy.RUNTIME)
501 @interface DontCompile { }