/* * Copyright (c) 2016, 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. */ // TODO add bugid and summary /* * @test * @library /testlibrary /test/lib /compiler/whitebox / * @build compiler.valhalla.valuetypes.ValueTypeTestBench * @run main ClassFileInstaller sun.hotspot.WhiteBox * @run main ClassFileInstaller jdk.test.lib.Platform * @run main/othervm -ea -noverify -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -XX:+UnlockExperimentalVMOptions -XX:+ValueTypePassFieldsAsArgs * -XX:-TieredCompilation compiler.valhalla.valuetypes.ValueTypeTestBench * @run main/othervm -ea -noverify -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -XX:+UnlockExperimentalVMOptions -XX:-ValueTypePassFieldsAsArgs * -XX:-TieredCompilation compiler.valhalla.valuetypes.ValueTypeTestBench */ package compiler.valhalla.valuetypes; import compiler.whitebox.CompilerWhiteBoxTest; import jdk.internal.misc.Unsafe; import jdk.test.lib.Asserts; import jdk.test.lib.Platform; import jdk.test.lib.ProcessTools; import jdk.test.lib.OutputAnalyzer; import jdk.test.lib.Utils; import sun.hotspot.WhiteBox; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Repeatable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; // Test value types __ByValue final class MyValue1 { static int s; static final long sf = ValueTypeTestBench.rL; final int x; final long y; final MyValue2 v1; final MyValue2 v2; static final MyValue2 v3 = MyValue2.createInline(ValueTypeTestBench.rI, true); final int c; private MyValue1(int x, long y, MyValue2 v1, MyValue2 v2, int c) { s = x; this.x = x; this.y = y; this.v1 = v1; this.v2 = v2; this.c = c; } @DontInline public static MyValue1 createDontInline(int x, long y) { return __Make MyValue1(x, y, MyValue2.createInline(x, x < y), MyValue2.createInline(x, x > y), ValueTypeTestBench.rI); } @ForceInline public static MyValue1 createInline(int x, long y) { return __Make MyValue1(x, y, MyValue2.createInline(x, x < y), MyValue2.createInline(x, x > y), ValueTypeTestBench.rI); } @ForceInline public long hash() { return s + sf + x + y + c + v1.hash() + v2.hash() + v3.hash(); } @DontCompile public long hashInterpreted() { return s + sf + x + y + c + v1.hashInterpreted() + v2.hashInterpreted() + v3.hashInterpreted(); } } __ByValue final class MyValue2 { final int x; final boolean b; final long c; private MyValue2(int x, boolean b, long c) { this.x = x; this.b = b; this.c = c; } @ForceInline public static MyValue2 createInline(int x, boolean b) { return __Make MyValue2(x, b, ValueTypeTestBench.rL); } @ForceInline public long hash() { return x + (b ? 0 : 1) + c; } @DontInline public long hashInterpreted() { return x + (b ? 0 : 1) + c; } } public class ValueTypeTestBench { // Print ideal graph after execution of each test private static final boolean PRINT_GRAPH = true; // Random test values public static final int rI = Utils.getRandomInstance().nextInt() % 1000; public static final long rL = Utils.getRandomInstance().nextLong() % 1000; public ValueTypeTestBench() { val3 = MyValue1.createInline(rI, rL); } // ========== Helper methods ========== public long hash() { return hash(rI, rL); } public long hash(int x, long y) { return MyValue1.createInline(x, y).hash(); } // ========== Test definitions ========== // Receive value type through call to interpreter @Test(failOn = ALLOC + STORE + TRAP) public long test1() { MyValue1 v = MyValue1.createDontInline(rI, rL); return v.hash(); } @DontCompile public void test1_verifier(boolean warmup) { long result = test1(); Asserts.assertEQ(result, hash()); } // Receive value type from interpreter via parameter @Test(failOn = ALLOC + STORE + TRAP) public long test2(MyValue1 v) { return v.hash(); } @DontCompile public void test2_verifier(boolean warmup) { MyValue1 v = MyValue1.createDontInline(rI, rL); long result = test2(v); Asserts.assertEQ(result, hash()); } // Return incoming value type without accessing fields @Test(valid = ValueTypePassFieldsAsArgsOn, match = {ALLOC, STORE}, matchCount = {1, 9}, failOn = LOAD + TRAP) @Test(valid = ValueTypePassFieldsAsArgsOff, failOn = ALLOC + LOAD + STORE + TRAP) public MyValue1 test3(MyValue1 v) { return v; } @DontCompile public void test3_verifier(boolean warmup) { MyValue1 v1 = MyValue1.createDontInline(rI, rL); MyValue1 v2 = test3(v1); Asserts.assertEQ(v1.x, v2.x); Asserts.assertEQ(v1.y, v2.y); } // Create a value type in compiled code and only use fields. // Allocation should go away because value type does not escape. @Test(failOn = ALLOC + LOAD + STORE + TRAP) public long test4() { MyValue1 v = MyValue1.createInline(rI, rL); return v.hash(); } @DontCompile public void test4_verifier(boolean warmup) { long result = test4(); Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to // an inlined compiled method via a call. @Test(failOn = ALLOC + LOAD + STORE + TRAP) public long test5() { MyValue1 v = MyValue1.createInline(rI, rL); return test5Inline(v); } @ForceInline public long test5Inline(MyValue1 v) { return v.hash(); } @DontCompile public void test5_verifier(boolean warmup) { long result = test5(); Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to // the interpreter via a call. @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = LOAD + TRAP + ALLOC) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) public long test6() { MyValue1 v = MyValue1.createInline(rI, rL); // Pass to interpreter return v.hashInterpreted(); } @DontCompile public void test6_verifier(boolean warmup) { long result = test6(); Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to // the interpreter by returning. @Test(match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) public MyValue1 test7(int x, long y) { return MyValue1.createInline(x, y); } @DontCompile public void test7_verifier(boolean warmup) { MyValue1 v = test7(rI, rL); Asserts.assertEQ(v.hash(), hash()); } // Merge value types created from two branches @Test(failOn = ALLOC + STORE + TRAP) public long test8(boolean b) { MyValue1 v; if (b) { v = MyValue1.createInline(rI, rL); } else { v = MyValue1.createDontInline(rI + 1, rL + 1); } return v.hash(); } @DontCompile public void test8_verifier(boolean warmup) { Asserts.assertEQ(test8(true), hash()); Asserts.assertEQ(test8(false), hash(rI + 1, rL + 1)); } // Merge value types created from two branches @Test(valid = ValueTypePassFieldsAsArgsOn, match = {LOAD}, matchCount = {9}, failOn = TRAP + ALLOC + STORE) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC, STORE}, matchCount = {1, 9}, failOn = LOAD + TRAP) public MyValue1 test9(boolean b) { MyValue1 v; if (b) { // Value type is not allocated v = MyValue1.createInline(rI, rL); } else { // Value type is allocated by the callee v = MyValue1.createDontInline(rI + 1, rL + 1); } // Need to allocate value type if 'b' is true long sum = v.hashInterpreted(); if (b) { v = MyValue1.createDontInline(rI, sum); } else { v = MyValue1.createDontInline(rI, sum + 1); } // Don't need to allocate value type because both branches allocate return v; } @DontCompile public void test9_verifier(boolean warmup) { MyValue1 v = test9(true); Asserts.assertEQ(v.x, rI); Asserts.assertEQ(v.y, hash()); v = test9(false); Asserts.assertEQ(v.x, rI); Asserts.assertEQ(v.y, hash(rI + 1, rL + 1) + 1); } // Merge value types created in a loop (not inlined) @Test(failOn = ALLOC + STORE + TRAP) public long test10(int x, long y) { MyValue1 v = MyValue1.createDontInline(x, y); for (int i = 0; i < 10; ++i) { v = MyValue1.createDontInline(v.x + 1, v.y + 1); } return v.hash(); } @DontCompile public void test10_verifier(boolean warmup) { long result = test10(rI, rL); Asserts.assertEQ(result, hash(rI + 10, rL + 10)); } // Merge value types created in a loop (inlined) @Test(failOn = ALLOC + LOAD + STORE + LOOP + TRAP) public long test11(int x, long y) { MyValue1 v = MyValue1.createInline(x, y); for (int i = 0; i < 10; ++i) { v = MyValue1.createInline(v.x + 1, v.y + 1); } return v.hash(); } @DontCompile public void test11_verifier(boolean warmup) { long result = test11(rI, rL); Asserts.assertEQ(result, hash(rI + 10, rL + 10)); } // Test loop with uncommon trap referencing a value type @Test(match = {TRAP, SCOBJ}, matchCount = {1, 1}, failOn = ALLOC + LOAD + STORE) public long test12(boolean b) { MyValue1 v = MyValue1.createInline(rI, rL); long result = 42; for (int i = 0; i < 1000; ++i) { if (b) { result += v.x; } else { // Uncommon trap referencing v. We delegate allocation to the // interpreter by adding a SafePointScalarObjectNode. result = v.hashInterpreted(); } } return result; } @DontCompile public void test12_verifier(boolean warmup) { long result = test12(warmup); Asserts.assertEQ(result, warmup ? 42 + (1000*rI) : hash()); } // Test loop with uncommon trap referencing a value type @Test(match = {TRAP, LOAD}, matchCount = {1, 1}, failOn = ALLOC + STORE + SCOBJ) public long test13(boolean b) { MyValue1 v = MyValue1.createDontInline(rI, rL); long result = 42; for (int i = 0; i < 1000; ++i) { if (b) { result += v.x; } else { // Uncommon trap referencing v. Should not allocate // but just pass the existing oop to the uncommon trap. result = v.hashInterpreted(); } } return result; } @DontCompile public void test13_verifier(boolean warmup) { long result = test13(warmup); Asserts.assertEQ(result, warmup ? 42 + (1000*rI) : hash()); } // Create a value type in a non-inlined method and then call a // non-inlined method on that value type. @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = (ALLOC + STORE + TRAP), match = {LOAD}, matchCount = {9}) @Test(valid = ValueTypePassFieldsAsArgsOff, failOn = (ALLOC + LOAD + STORE + TRAP)) public long test14() { MyValue1 v = MyValue1.createDontInline(rI, rL); return v.hashInterpreted(); } @DontCompile public void test14_verifier(boolean b) { long result = test14(); Asserts.assertEQ(result, hash()); } // Create a value type in an inlined method and then call a // non-inlined method on that value type. @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = (LOAD + TRAP + ALLOC)) @Test(valid = ValueTypePassFieldsAsArgsOff, failOn = (LOAD + TRAP), match = {ALLOC}, matchCount = {1}) public long test15() { MyValue1 v = MyValue1.createInline(rI, rL); return v.hashInterpreted(); } @DontCompile public void test15_verifier(boolean b) { long result = test15(); Asserts.assertEQ(result, hash()); } // Create a value type in a non-inlined method and then call an // inlined method on that value type. @Test(failOn = (ALLOC + STORE + TRAP)) public long test16() { MyValue1 v = MyValue1.createDontInline(rI, rL); return v.hash(); } @DontCompile public void test16_verifier(boolean b) { long result = test16(); Asserts.assertEQ(result, hash()); } // Create a value type in an inlined method and then call an // inlined method on that value type. @Test(failOn = (ALLOC + LOAD + STORE + TRAP)) public long test17() { MyValue1 v = MyValue1.createInline(rI, rL); return v.hash(); } @DontCompile public void test17_verifier(boolean b) { long result = test17(); Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to the // interpreter via a call. The value is live at the first call so // debug info should include a reference to all its fields. @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = ALLOC + LOAD + TRAP) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) public long test18() { MyValue1 v = MyValue1.createInline(rI, rL); v.hashInterpreted(); return v.hashInterpreted(); } @DontCompile public void test18_verifier(boolean warmup) { long result = test18(); Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to the // interpreter via a call. The value type is passed twice but // should only be allocated once. @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = ALLOC + LOAD + TRAP) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC}, matchCount = {1}, failOn = LOAD + TRAP) public long test19() { MyValue1 v = MyValue1.createInline(rI, rL); return sumValue(v, v); } @DontCompile public long sumValue(MyValue1 v, MyValue1 dummy) { return v.hash(); } @DontCompile public void test19_verifier(boolean warmup) { long result = test19(); Asserts.assertEQ(result, hash()); } // Create a value type in compiled code and pass it to the // interpreter via a call. The value type is live at the uncommon // trap: verify that deoptimization causes the value type to be // correctly allocated. @Test(valid = ValueTypePassFieldsAsArgsOn, failOn = LOAD + ALLOC + STORE) @Test(valid = ValueTypePassFieldsAsArgsOff, match = {ALLOC}, matchCount = {1}, failOn = LOAD) public long test20(boolean flag) { MyValue1 v = MyValue1.createInline(rI, rL); if (flag) { // uncommon trap WHITE_BOX.deoptimizeMethod(tests.get("ValueTypeTestBench::test20")); } return v.hashInterpreted(); } @DontCompile public void test20_verifier(boolean warmup) { long result = test20(false); Asserts.assertEQ(result, hash()); if (!warmup) { result = test20(true); Asserts.assertEQ(result, hash()); } } // Value type fields in regular object MyValue1 val1; MyValue2 val2; final MyValue1 val3; static MyValue1 val4; static final MyValue1 val5 = MyValue1.createInline(rI, rL); // Test value type fields in objects @Test(match = {ALLOC}, matchCount = {1}, failOn = (TRAP)) public long test21(int x, long y) { // Compute hash of value type fields long result = val1.hash() + val2.hash() + val3.hash() + val4.hash() + val5.hash(); // Update fields val1 = MyValue1.createInline(x, y); val2 = MyValue2.createInline(x, true); val4 = MyValue1.createInline(x, y); return result; } @DontCompile public void test21_verifier(boolean warmup) { // Check if hash computed by test18 is correct val1 = MyValue1.createInline(rI, rL); val2 = val1.v2; // val3 is initialized in the constructor val4 = val1; // val5 is initialized in the static initializer long hash = val1.hash() + val2.hash() + val3.hash() + val4.hash() + val5.hash(); long result = test21(rI + 1, rL + 1); Asserts.assertEQ(result, hash); // Check if value type fields were updated Asserts.assertEQ(val1.hash(), hash(rI + 1, rL + 1)); Asserts.assertEQ(val2.hash(), MyValue2.createInline(rI + 1, true).hash()); Asserts.assertEQ(val4.hash(), hash(rI + 1, rL + 1)); } // Test folding of constant value type fields @Test(failOn = ALLOC + LOAD + STORE + LOOP + TRAP) public long test22() { // This should be constant folded return val5.hash() + val5.v3.hash(); } @DontCompile public void test22_verifier(boolean warmup) { long result = test22(); Asserts.assertEQ(result, val5.hash() + val5.v3.hash()); } // Test OSR compilation @Test(failOn = ALLOC + LOAD + STORE + LOOP + TRAP) public long test23() { MyValue1 v = MyValue1.createInline(rI, rL); long result = 0; // Long loop to trigger OSR compilation for (int i = 0 ; i < 100_000 ; ++i) { // Reference local value type in interpreter state result = v.hash(); } return result; } @DontCompile public void test23_verifier(boolean warmup) { long result = test23(); Asserts.assertEQ(result, hash()); } // Test interpreter to compiled code with various signatures @Test(failOn = ALLOC + STORE + TRAP) public long test24(MyValue2 v) { return v.hash(); } @DontCompile public void test24_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test24(v); Asserts.assertEQ(result, v.hashInterpreted()); } @Test(failOn = ALLOC + STORE + TRAP) public long test25(int i1, MyValue2 v, int i2) { return v.hash() + i1 - i2; } @DontCompile public void test25_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test25(rI, v, 2*rI); Asserts.assertEQ(result, v.hashInterpreted() - rI); } @Test(failOn = ALLOC + STORE + TRAP) public long test26(long l1, MyValue2 v, long l2) { return v.hash() + l1 - l2; } @DontCompile public void test26_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test26(rL, v, 2*rL); Asserts.assertEQ(result, v.hashInterpreted() - rL); } @Test(failOn = ALLOC + STORE + TRAP) public long test27(int i, MyValue2 v, long l) { return v.hash() + i + l; } @DontCompile public void test27_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test27(rI, v, rL); Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); } @Test(failOn = ALLOC + STORE + TRAP) public long test28(long l, MyValue2 v, int i) { return v.hash() + i + l; } @DontCompile public void test28_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test28(rL, v, rI); Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); } @Test(failOn = ALLOC + STORE + TRAP) public long test29(long l, MyValue1 v1, int i, MyValue2 v2) { return v1.hash() + i + l + v2.hash(); } @DontCompile public void test29_verifier(boolean warmup) { MyValue1 v1 = MyValue1.createDontInline(rI, rL); MyValue2 v2 = MyValue2.createInline(rI, true); long result = test29(rL, v1, rI, v2); Asserts.assertEQ(result, v1.hashInterpreted() + rL + rI + v2.hashInterpreted()); } // Test compiled code to interpreter with various signatures @DontCompile public long test30_interp(MyValue2 v) { return v.hash(); } @Test(failOn = ALLOC + STORE + TRAP) public long test30(MyValue2 v) { return test30_interp(v); } @DontCompile public void test30_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test30(v); Asserts.assertEQ(result, v.hashInterpreted()); } @DontCompile public long test31_interp(int i1, MyValue2 v, int i2) { return v.hash() + i1 - i2; } @Test(failOn = ALLOC + STORE + TRAP) public long test31(int i1, MyValue2 v, int i2) { return test31_interp(i1, v, i2); } @DontCompile public void test31_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test31(rI, v, 2*rI); Asserts.assertEQ(result, v.hashInterpreted() - rI); } @DontCompile public long test32_interp(long l1, MyValue2 v, long l2) { return v.hash() + l1 - l2; } @Test(failOn = ALLOC + STORE + TRAP) public long test32(long l1, MyValue2 v, long l2) { return test32_interp(l1, v, l2); } @DontCompile public void test32_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test32(rL, v, 2*rL); Asserts.assertEQ(result, v.hashInterpreted() - rL); } @DontCompile public long test33_interp(int i, MyValue2 v, long l) { return v.hash() + i + l; } @Test(failOn = ALLOC + STORE + TRAP) public long test33(int i, MyValue2 v, long l) { return test33_interp(i, v, l); } @DontCompile public void test33_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test33(rI, v, rL); Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); } @DontCompile public long test34_interp(long l, MyValue2 v, int i) { return v.hash() + i + l; } @Test(failOn = ALLOC + STORE + TRAP) public long test34(long l, MyValue2 v, int i) { return test34_interp(l, v, i); } @DontCompile public void test34_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test34(rL, v, rI); Asserts.assertEQ(result, v.hashInterpreted() + rL + rI); } @DontCompile public long test35_interp(long l, MyValue1 v1, int i, MyValue2 v2) { return v1.hash() + i + l + v2.hash(); } @Test(failOn = ALLOC + STORE + TRAP) public long test35(long l, MyValue1 v1, int i, MyValue2 v2) { return test35_interp(l, v1, i, v2); } @DontCompile public void test35_verifier(boolean warmup) { MyValue1 v1 = MyValue1.createDontInline(rI, rL); MyValue2 v2 = MyValue2.createInline(rI, true); long result = test35(rL, v1, rI, v2); Asserts.assertEQ(result, v1.hashInterpreted() + rL + rI + v2.hashInterpreted()); } // test that debug info at a call is correct @DontCompile public long test36_interp(MyValue2 v, boolean flag) { if (flag) { // uncommon trap WHITE_BOX.deoptimizeMethod(tests.get("ValueTypeTestBench::test36")); } return v.hash(); } @Test(failOn = ALLOC + STORE + TRAP) public long test36(MyValue2 v, boolean flag, long l) { return test36_interp(v, flag) + l; } @DontCompile public void test36_verifier(boolean warmup) { MyValue2 v = MyValue2.createInline(rI, true); long result = test36(v, false, rL); Asserts.assertEQ(result, v.hashInterpreted() + rL); if (!warmup) { result = test36(v, true, rL); Asserts.assertEQ(result, v.hashInterpreted() + rL); } } // ========== Test infrastructure ========== private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); private static final int ValueTypePassFieldsAsArgsOn = 0x1; private static final int ValueTypePassFieldsAsArgsOff = 0x2; static final int AllFlags = ValueTypePassFieldsAsArgsOn | ValueTypePassFieldsAsArgsOff; private static final boolean ValueTypePassFieldsAsArgs = (Boolean)WHITE_BOX.getVMFlag("ValueTypePassFieldsAsArgs"); private static final int COMP_LEVEL_ANY = -1; private static final int COMP_LEVEL_FULL_OPTIMIZATION = 4; private static final Hashtable tests = new Hashtable(); private static final int WARMUP = 10; private static boolean USE_COMPILER = WHITE_BOX.getBooleanVMFlag("UseCompiler"); private static boolean PRINT_IDEAL = WHITE_BOX.getBooleanVMFlag("PrintIdeal"); // TODO use Platform.isComp() after merge with JDK 9 private static boolean XCOMP = System.getProperty("java.vm.info").startsWith("compiled "); // Regular expressions used to match nodes in the PrintIdeal output private static final String START = "(\\d+\\t(.*"; private static final String MID = ".*)+\\t===.*"; private static final String END = ")|"; private static final String ALLOC = START + "CallStaticJava" + MID + "_new_instance_Java" + END; private static final String LOAD = START + "Load" + MID + "valuetype\\*" + END; private static final String STORE = START + "Store" + MID + "valuetype\\*" + END; private static final String LOOP = START + "Loop" + MID + "" + END; private static final String TRAP = START + "CallStaticJava" + MID + "uncommon_trap" + END; // TODO: match field values of scalar replaced object private static final String SCOBJ = "(.*# ScObj.*" + END; static { // Gather all test methods and put them in Hashtable for (Method m : ValueTypeTestBench.class.getDeclaredMethods()) { Test[] annos = m.getAnnotationsByType(Test.class); if (annos.length != 0) { tests.put("ValueTypeTestBench::" + m.getName(), m); } } } private static void execute_vm(String... extra_args) throws Throwable { ArrayList all_args = new ArrayList(List.of( "-noverify", "-XX:+UnlockDiagnosticVMOptions", "-Xbootclasspath/a:.", "-XX:+WhiteBoxAPI", "-XX:-TieredCompilation", "-XX:-BackgroundCompilation", "-XX:+IgnoreUnrecognizedVMOptions", "-XX:+PrintCompilation", "-XX:+PrintIdeal", "-XX:+PrintOptoAssembly", "-XX:CompileCommand=quiet", "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.ValueTypeTestBench::*", "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue1::*", "-XX:CompileCommand=compileonly,compiler.valhalla.valuetypes.MyValue2::*" )); all_args.addAll(List.of(extra_args)); // Run tests in own process and verify output all_args.add(ValueTypeTestBench.class.getName()); all_args.add("run"); OutputAnalyzer oa = ProcessTools.executeTestJvm(all_args.toArray(new String[0])); // If ideal graph printing is enabled/supported, verify output String output = oa.getOutput(); oa.shouldHaveExitValue(0); if (output.contains("PrintIdeal enabled")) { parseOutput(output); } else { System.out.println("WARNING: IR verification disabled! Running with -Xint, -Xcomp or release build?"); } } public static void main(String[] args) throws Throwable { if (args.length == 0) { String field_as_args; if ((Boolean)WHITE_BOX.getVMFlag("ValueTypePassFieldsAsArgs")) { field_as_args = "-XX:+ValueTypePassFieldsAsArgs"; } else { field_as_args = "-XX:-ValueTypePassFieldsAsArgs"; } execute_vm("-XX:+UnlockExperimentalVMOptions", field_as_args); execute_vm("-XX:+AlwaysIncrementalInline", "-XX:+UnlockExperimentalVMOptions", field_as_args); } else { if (USE_COMPILER && PRINT_IDEAL && !XCOMP) { System.out.println("PrintIdeal enabled"); } // Execute tests ValueTypeTestBench bench = new ValueTypeTestBench(); bench.run(); } } public static void parseOutput(String output) throws Exception { String split = "b compiler.valhalla.valuetypes."; String[] compilations = output.split(split); // Print header System.out.println(compilations[0]); // Iterate over compilation output for (String graph : compilations) { String[] lines = graph.split("\\n"); if (lines[0].contains("@")) { continue; // Ignore OSR compilations } String testName = lines[0].split(" ")[0]; Method test = tests.get(testName); if (test == null) { // Skip helper methods continue; } if (PRINT_GRAPH) { System.out.println("\nGraph for " + graph); } // Parse graph using regular expressions to determine if it contains forbidden nodes Test[] annos = test.getAnnotationsByType(Test.class); Test anno = null; for (Test a : annos) { if ((a.valid() & ValueTypePassFieldsAsArgsOn) != 0 && ValueTypePassFieldsAsArgs) { assert anno == null; anno = a; } else if ((a.valid() & ValueTypePassFieldsAsArgsOff) != 0 && !ValueTypePassFieldsAsArgs) { assert anno == null; anno = a; } } assert anno != null; String regexFail = anno.failOn(); if (!regexFail.isEmpty()) { Pattern pattern = Pattern.compile(regexFail.substring(0, regexFail.length()-1)); Matcher matcher = pattern.matcher(graph); boolean fail = false; while (matcher.find()) { System.out.println("Graph for '" + testName + "' contains forbidden node:"); System.out.println(matcher.group()); fail = true; } Asserts.assertFalse(fail, "Graph for '" + testName + "' contains forbidden nodes"); } String[] regexMatch = anno.match(); int[] matchCount = anno.matchCount(); for (int i = 0; i < regexMatch.length; ++i) { Pattern pattern = Pattern.compile(regexMatch[i].substring(0, regexMatch[i].length()-1)); Matcher matcher = pattern.matcher(graph); int count = 0; String nodes = ""; while (matcher.find()) { count++; nodes += matcher.group() + "\n"; } if (matchCount[i] != count) { System.out.println("Graph for '" + testName + "' contains different number of match nodes:"); System.out.println(nodes); } Asserts.assertEQ(matchCount[i], count, "Graph for '" + testName + "' contains different number of match nodes"); } tests.remove(testName); System.out.println(testName + " passed"); } // Check if all tests were compiled if (tests.size() != 0) { for (String name : tests.keySet()) { System.out.println("Test '" + name + "' not compiled!"); } throw new RuntimeException("Not all tests were compiled"); } } public void setup(Method[] methods) { for (Method m : methods) { if (m.isAnnotationPresent(Test.class)) { // Don't inline tests WHITE_BOX.testSetDontInlineMethod(m, true); } if (m.isAnnotationPresent(DontCompile.class)) { WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, true); WHITE_BOX.makeMethodNotCompilable(m, COMP_LEVEL_ANY, false); } if (m.isAnnotationPresent(ForceInline.class)) { WHITE_BOX.testSetForceInlineMethod(m, true); } else if (m.isAnnotationPresent(DontInline.class)) { WHITE_BOX.testSetDontInlineMethod(m, true); } } } public void run() throws Exception { System.out.format("rI = %d, rL = %d\n", rI, rL); setup(this.getClass().getDeclaredMethods()); setup(MyValue1.class.getDeclaredMethods()); setup(MyValue2.class.getDeclaredMethods()); // TODO enable this after JDK 9 merge and verify that it's compiled //WHITE_BOX.enqueueInitializerForCompilation(this.getClass(), COMP_LEVEL_FULL_OPTIMIZATION); // Execute tests for (Method test : tests.values()) { Method verifier = getClass().getDeclaredMethod(test.getName() + "_verifier", boolean.class); // Warmup using verifier method for (int i = 0; i < WARMUP; ++i) { verifier.invoke(this, true); } // Trigger compilation WHITE_BOX.enqueueMethodForCompilation(test, COMP_LEVEL_FULL_OPTIMIZATION); Asserts.assertTrue(!USE_COMPILER || WHITE_BOX.isMethodCompiled(test, false), test + " not compiled"); // Check result verifier.invoke(this, false); } } } // Mark method as test @Retention(RetentionPolicy.RUNTIME) @Repeatable(Tests.class) @interface Test { // Regular expression used to match forbidden IR nodes // in the C2 IR emitted for this test. String failOn() default ""; // Regular expressions used to match and count IR nodes. String[] match() default { }; int[] matchCount() default { }; int valid() default ValueTypeTestBench.AllFlags; } @Retention(RetentionPolicy.RUNTIME) @interface Tests { Test[] value(); } // Force method inlining during compilation @Retention(RetentionPolicy.RUNTIME) @interface ForceInline { } // Prevent method inlining during compilation @Retention(RetentionPolicy.RUNTIME) @interface DontInline { } // Prevent method compilation @Retention(RetentionPolicy.RUNTIME) @interface DontCompile { }