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