From 224f88b357fc9edb8e17260e4cdc9e6125e46973 Mon Sep 17 00:00:00 2001 From: David Linus Briemann Date: Mon, 8 Jun 2026 14:58:04 +0200 Subject: [PATCH] 8386637: PPC64: Implement Thread.onSpinWait() intrinsic and SpinPause() using SMT priority hints --- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp | 8 +- src/hotspot/cpu/ppc/ppc.ad | 18 ++ src/hotspot/cpu/ppc/vm_version_ppc.hpp | 2 + src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp | 10 +- .../onSpinWait/TestOnSpinWaitPPC64.java | 182 ++++++++++++++++++ 5 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/onSpinWait/TestOnSpinWaitPPC64.java diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index 1270471d150fc..78fae5c267753 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -2717,7 +2717,13 @@ void LIR_Assembler::membar_storeload() { } void LIR_Assembler::on_spin_wait() { - Unimplemented(); + // SMT priority hint: drop to low for the spin, then restore to medium so + // subsequent code is not penalised. + // Yield (or 27,27,27) is not used because it was never implemented on Power CPUs, see JDK-8201218. + __ block_comment("spin_wait {"); + __ smt_prio_low(); + __ smt_prio_medium(); + __ block_comment("}"); } void LIR_Assembler::leal(LIR_Opr addr_opr, LIR_Opr dest, LIR_PatchCode patch_code, CodeEmitInfo* info) { diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 7360ed604f177..12e831f1766ff 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -2138,6 +2138,9 @@ bool Matcher::match_rule_supported(int opcode) { case Op_CacheWBPreSync: case Op_CacheWBPostSync: return VM_Version::supports_data_cache_line_flush(); + + case Op_OnSpinWait: + return VM_Version::supports_on_spin_wait(); } return true; // Per default match rules are supported. @@ -6927,6 +6930,21 @@ instruct membar_CPUOrder() %{ ins_pipe(pipe_class_default); %} +instruct onspinwait() %{ + match(OnSpinWait); + ins_cost(DEFAULT_COST); + + format %{ "OnSpinWait (smt_prio_low ; smt_prio_medium)" %} + size(8); + ins_encode %{ + __ block_comment("spin_wait {"); + __ smt_prio_low(); + __ smt_prio_medium(); + __ block_comment("}"); + %} + ins_pipe(pipe_class_default); +%} + //----------Conditional Move--------------------------------------------------- // Cmove using isel. diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.hpp b/src/hotspot/cpu/ppc/vm_version_ppc.hpp index 0f4eb3593a3fc..4f3b6d3791136 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.hpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.hpp @@ -67,6 +67,8 @@ class VM_Version: public Abstract_VM_Version { static bool supports_float16() { return PowerArchitecturePPC64 >= 9; } + static bool supports_on_spin_wait() { return true; } + static bool is_determine_features_test_running() { return _is_determine_features_test_running; } // CPU instruction support static bool has_mfdscr() { return (_features & mfdscr_m) != 0; } // Power8, but may be unavailable (QEMU) diff --git a/src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp b/src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp index 6854fb805a9a7..5185199373061 100644 --- a/src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp +++ b/src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2025 SAP SE. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. 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 @@ -509,7 +509,13 @@ void os::print_register_info(outputStream *st, const void *context, int& continu extern "C" { int SpinPause() { - return 0; + // Setting prio low, then prio medium results in a pseudo yield. + // Yield (or 27,27,27) was never implemented on PPC. + // or 1,1,1 = smt_prio_low + // or 2,2,2 = smt_prio_medium + asm volatile ("or 1,1,1\n\t" + "or 2,2,2" : : : "memory"); + return 1; } } diff --git a/test/hotspot/jtreg/compiler/onSpinWait/TestOnSpinWaitPPC64.java b/test/hotspot/jtreg/compiler/onSpinWait/TestOnSpinWaitPPC64.java new file mode 100644 index 0000000000000..6c9f4c405d0fb --- /dev/null +++ b/test/hotspot/jtreg/compiler/onSpinWait/TestOnSpinWaitPPC64.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2026 SAP SE. 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. + */ + +/** + * @test TestOnSpinWaitPPC64 + * @summary Checks that java.lang.Thread.onSpinWait is intrinsified on PPC64 + * and emits the SMT priority-low / priority-medium nop pair. + * @library /test/lib + * + * @requires vm.flagless + * @requires os.arch=="ppc64" | os.arch=="ppc64le" + * @requires vm.debug + * + * @run driver compiler.onSpinWait.TestOnSpinWaitPPC64 c2 + * @run driver compiler.onSpinWait.TestOnSpinWaitPPC64 c1 + */ + +package compiler.onSpinWait; + +import java.util.ArrayList; +import java.util.Iterator; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestOnSpinWaitPPC64 { + + public static void main(String[] args) throws Exception { + String compiler = args[0]; + ArrayList command = new ArrayList(); + command.add("-XX:+IgnoreUnrecognizedVMOptions"); + command.add("-showversion"); + command.add("-XX:-BackgroundCompilation"); + command.add("-XX:+UnlockDiagnosticVMOptions"); + command.add("-XX:+PrintCompilation"); + command.add("-XX:+PrintInlining"); + if (compiler.equals("c2")) { + command.add("-XX:-TieredCompilation"); + } else if (compiler.equals("c1")) { + command.add("-XX:+TieredCompilation"); + command.add("-XX:TieredStopAtLevel=1"); + } else { + throw new RuntimeException("Unknown compiler: " + compiler); + } + command.add("-Xbatch"); + command.add("-XX:CompileCommand=compileonly," + Launcher.class.getName() + "::test"); + command.add("-XX:CompileCommand=print," + Launcher.class.getName() + "::test"); + command.add(Launcher.class.getName()); + + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(command); + + OutputAnalyzer analyzer = new OutputAnalyzer(pb.start()); + + analyzer.shouldHaveExitValue(0); + + System.out.println(analyzer.getOutput()); + + checkOutput(analyzer); + } + + // Hex encoding of the SMT priority-hint instructions emitted by the + // PPC `Thread.onSpinWait()` intrinsic. + // X-forms used: + // or 1,1,1 -> 0x7C21_0B78 (smt_prio_low) + // or 2,2,2 -> 0x7C42_1378 (smt_prio_medium) + private static String getSpinWaitInstructionHexLE(String name) { + if ("smt_prio_low".equals(name)) + return "780b217c"; + if ("smt_prio_medium".equals(name)) + return "7813427c"; + throw new RuntimeException("Unknown spin wait instruction: " + name); + } + + private static String getSpinWaitInstructionHexBE(String name) { + if ("smt_prio_low".equals(name)) + return "7c210b78"; + if ("smt_prio_medium".equals(name)) + return "7c421378"; + throw new RuntimeException("Unknown spin wait instruction: " + name); + } + + private static boolean lineContainsInstruction(String compact, String name) { + return compact.contains(getSpinWaitInstructionHexLE(name)) || + compact.contains(getSpinWaitInstructionHexBE(name)); + } + + // The expected output for the spin wait body if the hsdis library is available: + // + // ;; spin_wait { + // 0x...: or r1,r1,r1 + // 0x...: or r2,r2,r2 + // ;; } + // + // When hsdis is absent the disassembler dumps raw bytes which have to matched. + private static void checkOutput(OutputAnalyzer output) { + Iterator iter = output.asLines().listIterator(); + + // 1. Check whether printed instructions are disassembled. + boolean isDisassembled = false; + while (iter.hasNext()) { + String line = iter.next(); + if (line.contains("[Disassembly]")) { + isDisassembled = true; + break; + } + if (line.contains("[MachCode]")) { + break; + } + } + + // 2. Look for the spin_wait block comment. + boolean foundHead = false; + while (iter.hasNext()) { + String line = iter.next().trim(); + if (line.contains(";; spin_wait {")) { + foundHead = true; + break; + } + } + if (!foundHead) { + throw new RuntimeException("spin_wait block comment not found"); + } + + // 3. Expect prio low and prio med instructions inside the block. + boolean sawLow = false, sawMed = false; + while (iter.hasNext()) { + String line = iter.next().trim(); + if (line.startsWith(";; }")) { + break; + } + if (isDisassembled) { + // hsdis output: look for `or r1,r1,r1` and `or r2,r2,r2`. + String norm = line.replaceAll("\\s+", " "); + if (norm.contains("or r1,r1,r1")) + sawLow = true; + if (norm.contains("or r2,r2,r2")) + sawMed = true; + } else { + // without hsdis output: raw 4-byte instruction words. + String compact = line.replaceAll("\\s", "").toLowerCase(); + if (lineContainsInstruction(compact, "smt_prio_low")) + sawLow = true; + if (lineContainsInstruction(compact, "smt_prio_medium")) + sawMed = true; + } + } + + if (!sawLow) { + throw new RuntimeException("Did not find smt_prio_low (or r1,r1,r1) inside spin_wait block"); + } + if (!sawMed) { + throw new RuntimeException("Did not find smt_prio_medium (or r2,r2,r2) inside spin_wait block"); + } + } + + static class Launcher { + public static void main(final String[] args) throws Exception { + for (int i = 0; i < 20_000; i++) { + test(); + } + } + + static void test() { + java.lang.Thread.onSpinWait(); + } + } +}