1 /*
  2  * Copyright (c) 2018, 2020, Red Hat, Inc. All rights reserved.
  3  * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
  4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  5  *
  6  * This code is free software; you can redistribute it and/or modify it
  7  * under the terms of the GNU General Public License version 2 only, as
  8  * published by the Free Software Foundation.
  9  *
 10  * This code is distributed in the hope that it will be useful, but WITHOUT
 11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 13  * version 2 for more details (a copy is included in the LICENSE file that
 14  * accompanied this code).
 15  *
 16  * You should have received a copy of the GNU General Public License version
 17  * 2 along with this work; if not, write to the Free Software Foundation,
 18  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 19  *
 20  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 21  * or visit www.oracle.com if you need additional information or have any
 22  * questions.
 23  */
 24 
 25 /*
 26  * common code to run and validate tests of code generation for
 27  * volatile ops on AArch64
 28  *
 29  * incoming args are <testclass> <testtype>
 30  *
 31  * where <testclass> in {TestVolatileLoad,
 32  *                       TestVolatileStore,
 33  *                       TestUnsafeVolatileLoad,
 34  *                       TestUnsafeVolatileStore,
 35  *                       TestUnsafeVolatileCAS,
 36  *                       TestUnsafeVolatileWeakCAS,
 37  *                       TestUnsafeVolatileCAE,
 38  *                       TestUnsafeVolatileGAS}
 39  * and <testtype> in {G1,
 40  *                    Serial,
 41  *                    Parallel,
 42  *                    Shenandoah}
 43  */
 44 
 45 
 46 package compiler.c2.aarch64;
 47 
 48 import java.util.List;
 49 import java.util.ListIterator;
 50 import java.util.Iterator;
 51 import java.util.regex.Pattern;
 52 import java.io.*;
 53 
 54 import jdk.test.lib.Asserts;
 55 import jdk.test.lib.compiler.InMemoryJavaCompiler;
 56 import jdk.test.lib.process.OutputAnalyzer;
 57 import jdk.test.lib.process.ProcessTools;
 58 import jdk.test.whitebox.WhiteBox;
 59 
 60 // runner class that spawns a new JVM to exercises a combination of
 61 // volatile MemOp and GC. The ops are compiled with the dmb -->
 62 // ldar/stlr transforms either enabled or disabled. this runner parses
 63 // the PrintOptoAssembly output checking that the generated code is
 64 // correct.
 65 
 66 public class TestVolatiles {
 67     public void runtest(String classname, String testType) throws Throwable {
 68         // n.b. clients omit the package name for the class
 69         String fullclassname = "compiler.c2.aarch64." + classname;
 70         // build up a command line for the spawned JVM
 71         String[] procArgs;
 72         int argcount;
 73         // add one or two extra arguments according to test type
 74         // i.e. GC type plus GC conifg
 75         switch(testType) {
 76         case "G1":
 77             argcount = 9;
 78             procArgs = new String[argcount];
 79             procArgs[argcount - 2] = "-XX:+UseG1GC";
 80             break;
 81         case "Parallel":
 82             argcount = 9;
 83             procArgs = new String[argcount];
 84             procArgs[argcount - 2] = "-XX:+UseParallelGC";
 85             break;
 86         case "Serial":
 87             argcount = 9;
 88             procArgs = new String[argcount];
 89             procArgs[argcount - 2] = "-XX:+UseSerialGC";
 90             break;
 91         case "Shenandoah":
 92             argcount = 10;
 93             procArgs = new String[argcount];
 94             procArgs[argcount - 3] = "-XX:+UnlockExperimentalVMOptions";
 95             procArgs[argcount - 2] = "-XX:+UseShenandoahGC";
 96             break;
 97         default:
 98             throw new RuntimeException("unexpected test type " + testType);
 99         }
100 
101         // fill in arguments common to all cases
102 
103         // the first round of test enables transform of barriers to
104         // use acquiring loads and releasing stores by setting arg
105         // zero appropriately. this arg is reset in the second run to
106         // disable the transform.
107 
108         procArgs[0] = "-XX:+UseCompressedOops";
109         procArgs[1] = "-XX:-BackgroundCompilation";
110         procArgs[2] = "-XX:-TieredCompilation";
111         procArgs[3] = "-XX:+PrintOptoAssembly";
112         procArgs[4] = "-XX:CompileCommand=compileonly," + fullclassname + "::" + "test*";
113         procArgs[5] = "--add-exports";
114         procArgs[6] = "java.base/jdk.internal.misc=ALL-UNNAMED";
115         procArgs[argcount - 1] = fullclassname;
116 
117         runtest(classname, testType, true, procArgs);
118 
119         if (!classname.equals("TestUnsafeVolatileGAA")) {
120             procArgs[0] = "-XX:-UseCompressedOops";
121             runtest(classname, testType, false, procArgs);
122         }
123     }
124 
125 
126     public void runtest(String classname, String testType, boolean useCompressedOops, String[] procArgs) throws Throwable {
127         ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(procArgs);
128         OutputAnalyzer output = new OutputAnalyzer(pb.start());
129 
130         output.stderrShouldBeEmptyIgnoreVMWarnings();
131         output.stdoutShouldNotBeEmpty();
132         output.shouldHaveExitValue(0);
133 
134         // check the output for the correct asm sequence as
135         // appropriate to test class, test type and whether transform
136         // was applied
137 
138         checkoutput(output, classname, testType, useCompressedOops);
139     }
140 
141     // skip through output returning a line containing the desireed
142     // substring or null
143     private String skipTo(Iterator<String> iter, String substring)
144     {
145         while (iter.hasNext()) {
146             String nextLine = iter.next();
147             if (nextLine.matches(".*" + substring + ".*")) {
148                 return nextLine;
149             }
150         }
151         return null;
152     }
153 
154     // locate the start of compiler output for the desired method and
155     // then check that each expected instruction occurs in the output
156     // in the order supplied. throw an excpetion if not found.
157     // n.b. the spawned JVM's output is included in the exception
158     // message to make it easeir to identify what is missing.
159 
160     private boolean checkCompile(Iterator<String> iter, String methodname, String[] expected, OutputAnalyzer output, boolean do_throw)
161     {
162         // trace call to allow eyeball check of what we are checking against
163         System.out.println("checkCompile(" + methodname + ",");
164         String sepr = "  { ";
165         for (String s : expected) {
166             System.out.print(sepr);
167             System.out.print(s);
168             sepr = ",\n    ";
169         }
170         System.out.println(" })");
171 
172         // look for the start of an opto assembly print block
173         String match = skipTo(iter, Pattern.quote("{method}"));
174         if (match == null) {
175             if (do_throw) {
176                 throw new RuntimeException("Missing compiler output for " + methodname + "!\n\n" + output.getOutput());
177             }
178             return false;
179         }
180         // check the compiled method name is right
181         match = skipTo(iter, Pattern.quote("- name:"));
182         if (match == null) {
183             if (do_throw) {
184                 throw new RuntimeException("Missing compiled method name!\n\n" + output.getOutput());
185             }
186             return false;
187         }
188         if (!match.contains(methodname)) {
189             if (do_throw) {
190                 throw new RuntimeException("Wrong method " + match + "!\n  -- expecting " + methodname + "\n\n" + output.getOutput());
191             }
192             return false;
193         }
194         // make sure we can match each expected term in order
195         for (String s : expected) {
196             match = skipTo(iter, s);
197             if (match == null) {
198                 if (do_throw) {
199                     throw new RuntimeException("Missing expected output " + s + "!\n\n" + output.getOutput());
200                 }
201                 return false;
202             }
203         }
204         return true;
205     }
206 
207     // check for expected asm output from a volatile load
208 
209     private void checkload(OutputAnalyzer output, String testType, boolean useCompressedOops) throws Throwable
210     {
211         Iterator<String> iter = output.asLines().listIterator();
212 
213         // we shoud see this same sequence for normal or unsafe volatile load
214         // for both int and Object fields
215 
216         String[] matches;
217         matches = new String[] {
218             "ldarw",
219             "membar_acquire \\(elided\\)",
220             "ret"
221         };
222         checkCompile(iter, "testInt", matches, output, true);
223 
224         matches = new String[] {
225             useCompressedOops ? "ldarw?" : "ldar",
226             "membar_acquire \\(elided\\)",
227             "ret"
228         };
229         checkCompile(iter, "testObj", matches, output, true);
230 
231     }
232 
233     // check for expected asm output from a volatile store
234 
235     private void checkstore(OutputAnalyzer output, String testType, boolean useCompressedOops) throws Throwable
236     {
237         Iterator<String> iter = output.asLines().listIterator();
238 
239         String[] matches;
240 
241         // non object stores are straightforward
242         // this is the sequence of instructions for all cases
243         matches = new String[] {
244             "membar_release \\(elided\\)",
245             "stlrw",
246             "membar_volatile \\(elided\\)",
247             "ret"
248         };
249         checkCompile(iter, "testInt", matches, output, true);
250 
251         // object stores will be as above except for when the GC
252         // introduces barriers for card marking
253         switch (testType) {
254         default:
255             // this is the basic sequence of instructions
256             matches = new String[] {
257                 "membar_release \\(elided\\)",
258                 useCompressedOops ? "stlrw?" : "stlr",
259                 "membar_volatile \\(elided\\)",
260                 "ret"
261             };
262             break;
263         case "G1":
264             // a card mark volatile barrier should be generated
265             // before the card mark strb
266             //
267             // following the fix for 8225776 the G1 barrier is now
268             // scheduled out of line after the membar volatile and
269             // and subsequent return
270             matches = new String[] {
271                 "membar_release \\(elided\\)",
272                 useCompressedOops ? "stlrw?" : "stlr",
273                 "membar_volatile \\(elided\\)",
274                 "ret",
275                 "membar_volatile",
276                 "dmb ish",
277                 "strb"
278             };
279             break;
280         case "Shenandoah":
281              // Shenandoah generates normal object graphs for
282              // volatile stores
283             matches = new String[] {
284                 "membar_release \\(elided\\)",
285                 useCompressedOops ? "stlrw?" : "stlr",
286                 "membar_volatile \\(elided\\)",
287                 "ret"
288             };
289             break;
290         }
291 
292         checkCompile(iter, "testObj", matches, output, true);
293     }
294 
295     // check for expected asm output from a volatile cas
296 
297     private void checkcas(OutputAnalyzer output, String testType, boolean useCompressedOops) throws Throwable
298     {
299         Iterator<String> iter = output.asLines().listIterator();
300 
301         String[] matches;
302         String[][] tests = {
303             { "testInt", "cmpxchgw" },
304             { "testLong", "cmpxchg" },
305             { "testByte", "cmpxchgb" },
306             { "testShort", "cmpxchgs" },
307         };
308 
309         for (String[] test : tests) {
310             // non object stores are straightforward
311             // this is the sequence of instructions for all cases
312             matches = new String[] {
313                 "membar_release \\(elided\\)",
314                 test[1] + "_acq",
315                 "membar_acquire \\(elided\\)",
316                 "ret"
317             };
318             checkCompile(iter, test[0], matches, output, true);
319         }
320 
321         // object stores will be as above except for when the GC
322         // introduces barriers for card marking
323         switch (testType) {
324         default:
325             // this is the basic sequence of instructions
326             matches = new String[] {
327                 "membar_release \\(elided\\)",
328                 useCompressedOops ? "cmpxchgw?_acq" : "cmpxchg_acq",
329                 "strb",
330                 "membar_acquire \\(elided\\)",
331                 "ret"
332             };
333             break;
334         case "G1":
335             // a card mark volatile barrier should be generated
336             // before the card mark strb
337             //
338             // following the fix for 8225776 the G1 barrier is now
339             // scheduled out of line after the membar acquire and
340             // and subsequent return
341             matches = new String[] {
342                 "membar_release \\(elided\\)",
343                 useCompressedOops ? "cmpxchgw?_acq" : "cmpxchg_acq",
344                 "membar_acquire \\(elided\\)",
345                 "ret",
346                 "membar_volatile",
347                 "dmb ish",
348                 "strb"
349             };
350             break;
351         case "Shenandoah":
352             // For volatile CAS, Shenanodoah generates normal
353             // graphs with a shenandoah-specific cmpxchg
354             matches = new String[] {
355                 "membar_release \\(elided\\)",
356                 useCompressedOops ? "cmpxchgw?_acq_shenandoah" : "cmpxchg_acq_shenandoah",
357                 "membar_acquire \\(elided\\)",
358                 "ret"
359             };
360             break;
361         }
362         checkCompile(iter, "testObj", matches, output, true);
363     }
364 
365     private void checkcae(OutputAnalyzer output, String testType, boolean useCompressedOops) throws Throwable
366     {
367         ListIterator<String> iter = output.asLines().listIterator();
368 
369         String[] matches;
370         String[][] tests = {
371             { "testInt", "cmpxchgw" },
372             { "testLong", "cmpxchg" },
373             { "testByte", "cmpxchgb" },
374             { "testShort", "cmpxchgs" },
375         };
376 
377         for (String[] test : tests) {
378             // non object stores are straightforward
379             // this is the sequence of instructions for all cases
380             matches = new String[] {
381                 "membar_release \\(elided\\)",
382                 test[1] + "_acq",
383                 "membar_acquire \\(elided\\)",
384                 "ret"
385             };
386             checkCompile(iter, test[0], matches, output, true);
387         }
388 
389         // object stores will be as above except for when the GC
390         // introduces barriers for card marking
391         switch (testType) {
392         default:
393             // this is the basic sequence of instructions
394             matches = new String[] {
395                 "membar_release \\(elided\\)",
396                 "strb",
397                 useCompressedOops ? "cmpxchgw?_acq" : "cmpxchg_acq",
398                 "membar_acquire \\(elided\\)",
399                 "ret"
400             };
401 
402             // card marking store may be scheduled before or after
403             // the cmpxchg so try both sequences.
404             int idx = iter.nextIndex();
405             if (!checkCompile(iter, "testObj", matches, output, false)) {
406                 iter = output.asLines().listIterator(idx);
407 
408                 matches = new String[] {
409                     "membar_release \\(elided\\)",
410                     useCompressedOops ? "cmpxchgw?_acq" : "cmpxchg_acq",
411                     "strb",
412                     "membar_acquire \\(elided\\)",
413                     "ret"
414                 };
415 
416                 checkCompile(iter, "testObj", matches, output, true);
417             }
418             return;
419 
420         case "G1":
421             // a card mark volatile barrier should be generated
422             // before the card mark strb
423             //
424             // following the fix for 8225776 the G1 barrier is now
425             // scheduled out of line after the membar acquire and
426             // and subsequent return
427             matches = new String[] {
428                 "membar_release \\(elided\\)",
429                 useCompressedOops ? "cmpxchgw?_acq" : "cmpxchg_acq",
430                 "membar_acquire \\(elided\\)",
431                 "ret",
432                 "membar_volatile",
433                 "dmb ish",
434                 "strb"
435             };
436             break;
437         case "Shenandoah":
438             // For volatile CAS, Shenanodoah generates normal
439             // graphs with a shenandoah-specific cmpxchg
440             matches = new String[] {
441                 "membar_release \\(elided\\)",
442                 useCompressedOops ? "cmpxchgw?_acq_shenandoah" : "cmpxchg_acq_shenandoah",
443                 "membar_acquire \\(elided\\)",
444                 "ret"
445             };
446             break;
447         }
448         checkCompile(iter, "testObj", matches, output, true);
449     }
450 
451     private void checkgas(OutputAnalyzer output, String testType, boolean useCompressedOops) throws Throwable
452     {
453         Iterator<String> iter = output.asLines().listIterator();
454 
455         String[] matches;
456         String[][] tests = {
457             { "testInt", "atomic_xchgw" },
458             { "testLong", "atomic_xchg" },
459         };
460 
461         for (String[] test : tests) {
462             // non object stores are straightforward
463             // this is the sequence of instructions for all cases
464             matches = new String[] {
465                 "membar_release \\(elided\\)",
466                 test[1] + "_acq",
467                 "membar_acquire \\(elided\\)",
468                 "ret"
469             };
470             checkCompile(iter, test[0], matches, output, true);
471         }
472 
473         // object stores will be as above except for when the GC
474         // introduces barriers for card marking
475         switch (testType) {
476         default:
477             // this is the basic sequence of instructions
478             matches = new String[] {
479                 "membar_release \\(elided\\)",
480                 useCompressedOops ? "atomic_xchgw?_acq" : "atomic_xchg_acq",
481                 "strb",
482                 "membar_acquire \\(elided\\)",
483                 "ret"
484             };
485             break;
486         case "G1":
487             // a card mark volatile barrier should be generated
488             // before the card mark strb
489             //
490             // following the fix for 8225776 the G1 barrier is now
491             // scheduled out of line after the membar acquire and
492             // and subsequent return
493             matches = new String[] {
494                 "membar_release \\(elided\\)",
495                 useCompressedOops ? "atomic_xchgw?_acq" : "atomic_xchg_acq",
496                 "membar_acquire \\(elided\\)",
497                 "ret",
498                 "membar_volatile",
499                 "dmb ish",
500                 "strb"
501             };
502             break;
503         case "Shenandoah":
504             matches = new String[] {
505                 "membar_release \\(elided\\)",
506                 useCompressedOops ? "atomic_xchgw?_acq" : "atomic_xchg_acq",
507                 "membar_acquire \\(elided\\)",
508                 "ret"
509             };
510             break;
511         }
512 
513         checkCompile(iter, "testObj", matches, output, true);
514     }
515 
516     private void checkgaa(OutputAnalyzer output, String testType) throws Throwable
517     {
518         Iterator<String> iter = output.asLines().listIterator();
519 
520         String[] matches;
521         String[][] tests = {
522             { "testInt", "get_and_addI" },
523             { "testLong", "get_and_addL" },
524         };
525 
526         for (String[] test : tests) {
527             // non object stores are straightforward
528             // this is the sequence of instructions for all cases
529             matches = new String[] {
530                 "membar_release \\(elided\\)",
531                 test[1] + "_acq",
532                 "membar_acquire \\(elided\\)",
533                 "ret"
534             };
535             checkCompile(iter, test[0], matches, output, true);
536         }
537 
538     }
539 
540     // perform a check appropriate to the classname
541 
542     private void checkoutput(OutputAnalyzer output, String classname, String testType, boolean useCompressedOops) throws Throwable
543     {
544         // trace call to allow eyeball check of what is being checked
545         System.out.println("checkoutput(" +
546                            classname + ", " +
547                            testType + ")\n" +
548                            output.getOutput());
549 
550         switch (classname) {
551         case "TestVolatileLoad":
552             checkload(output, testType, useCompressedOops);
553             break;
554         case "TestVolatileStore":
555             checkstore(output, testType, useCompressedOops);
556             break;
557         case "TestUnsafeVolatileLoad":
558             checkload(output, testType, useCompressedOops);
559             break;
560         case "TestUnsafeVolatileStore":
561             checkstore(output, testType, useCompressedOops);
562             break;
563         case "TestUnsafeVolatileCAS":
564         case "TestUnsafeVolatileWeakCAS":
565             checkcas(output, testType, useCompressedOops);
566             break;
567         case "TestUnsafeVolatileCAE":
568             checkcae(output, testType, useCompressedOops);
569             break;
570         case "TestUnsafeVolatileGAS":
571             checkgas(output, testType, useCompressedOops);
572             break;
573         case "TestUnsafeVolatileGAA":
574             checkgaa(output, testType);
575             break;
576         }
577     }
578 }