From b06cc2eccff1ffdabd3db126195a10e23260b47b Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Wed, 5 Mar 2025 10:53:25 +0100 Subject: [PATCH 1/7] Limit inlining of math Exact operations in case of too many deopts --- src/hotspot/share/opto/library_call.cpp | 5 +- .../bench/vm/compiler/MultiplyExact.java | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 test/micro/org/openjdk/bench/vm/compiler/MultiplyExact.java diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 05efda3c64b74..2accf52e37418 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -1961,7 +1961,7 @@ void LibraryCallKit::inline_math_mathExact(Node* math, Node *test) { set_i_o(i_o()); uncommon_trap(Deoptimization::Reason_intrinsic, - Deoptimization::Action_none); + Deoptimization::Action_maybe_recompile); } set_control(fast_path); @@ -1971,6 +1971,9 @@ void LibraryCallKit::inline_math_mathExact(Node* math, Node *test) { template bool LibraryCallKit::inline_math_overflow(Node* arg1, Node* arg2) { typedef typename OverflowOp::MathOp MathOp; + if (too_many_traps(Deoptimization::Reason_intrinsic)) { + return false; + } MathOp* mathOp = new MathOp(arg1, arg2); Node* operation = _gvn.transform( mathOp ); diff --git a/test/micro/org/openjdk/bench/vm/compiler/MultiplyExact.java b/test/micro/org/openjdk/bench/vm/compiler/MultiplyExact.java new file mode 100644 index 0000000000000..68c8cec538baf --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/compiler/MultiplyExact.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025, 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. + */ +package org.openjdk.bench.vm.compiler; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.*; + +import java.util.concurrent.TimeUnit; +import java.util.Random; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.SECONDS) +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +public abstract class MultiplyExact { + @Param({"1000000"}) + public int SIZE; + + public int square(int a) { + return Math.multiplyExact(a, a); + } + + public int test(int i) { + try { + return square(i); + } catch (Throwable e) { + return 0; + } + } + + @Benchmark + public void loop() { + for (int i = 0; i < SIZE; i++) { + test(i); + } + } + + + + @Fork(value = 1) + public static class C2 extends MultiplyExact {} + + @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=1"}) + public static class C1_1 extends MultiplyExact {} + + @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=2"}) + public static class C1_2 extends MultiplyExact {} + + @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=3"}) + public static class C1_3 extends MultiplyExact {} +} From 2317919f09afb8e05e7155b0a71cf93720bf7771 Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Fri, 7 Mar 2025 12:12:40 +0100 Subject: [PATCH 2/7] More exhaustive bench --- .../openjdk/bench/vm/compiler/MathExact.java | 401 ++++++++++++++++++ .../bench/vm/compiler/MultiplyExact.java | 72 ---- 2 files changed, 401 insertions(+), 72 deletions(-) create mode 100644 test/micro/org/openjdk/bench/vm/compiler/MathExact.java delete mode 100644 test/micro/org/openjdk/bench/vm/compiler/MultiplyExact.java diff --git a/test/micro/org/openjdk/bench/vm/compiler/MathExact.java b/test/micro/org/openjdk/bench/vm/compiler/MathExact.java new file mode 100644 index 0000000000000..093bf00722417 --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/compiler/MathExact.java @@ -0,0 +1,401 @@ +/* + * Copyright (c) 2025, 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. + */ +package org.openjdk.bench.vm.compiler; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.*; + +import java.util.concurrent.TimeUnit; +import java.util.Random; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.MILLISECONDS) +public abstract class MathExact { + @Param({"1000000"}) + public int SIZE; + + + // === multiplyExact(int, int) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int testMultiplyI(int i) { + try { + return Math.multiplyExact(i, i); + } catch (ArithmeticException e) { + return 0; + } + } + + @Benchmark + public void loopMultiplyIOverflow() { + for (int i = 0; i < SIZE; i++) { + testMultiplyI(i); + } + } + + @Benchmark + public void loopMultiplyIInBounds() { + for (int i = 0; i < SIZE; i++) { + // 46_340 < 2 ^ 15.5 (< 46_341, but that's not important) + // so + // 46_340 ^ 2 < 2 ^ 31 + testMultiplyI(i % 46_341); + } + } + + + // === multiplyExact(long, long) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public long testMultiplyL(long i) { + try { + return Math.multiplyExact(i, i); + } catch (ArithmeticException e) { + return 0L; + } + } + + @Benchmark + public void loopMultiplyLOverflow() { + for (long i = 0L; i < (long)SIZE; i++) { + // (2 ^ 63 - 1)^0.5 ~= 3_037_000_499.9761 + // Starting at 3_037_000_000 so that almost all computations overflow + testMultiplyL(3_037_000_000L + i); + } + } + + @Benchmark + public void loopMultiplyLInBounds() { + for (long i = 0L; i < (long)SIZE; i++) { + testMultiplyL(i); + } + } + + + // === negateExact(int) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int testNegateI(int i) { + try { + return Math.negateExact(i); + } catch (ArithmeticException e) { + return 0; + } + } + + @Benchmark + public void loopNegateIOverflow() { + for (int i = 0; i < SIZE; i++) { + testNegateI(i); + } + for (int i = 0; i < SIZE; i++) { + testNegateI(Integer.MIN_VALUE); + } + } + + @Benchmark + public void loopNegateIInBounds() { + for (int i = 0; i < SIZE; i++) { + testNegateI(i); + } + for (int i = 0; i < SIZE; i++) { + testNegateI(Integer.MAX_VALUE); + } + } + + + // === negateExact(long) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public long testNegateL(long i) { + try { + return Math.negateExact(i); + } catch (ArithmeticException e) { + return 0L; + } + } + + @Benchmark + public void loopNegateLOverflow() { + for (long i = 0L; i < (long)SIZE; i++) { + testNegateL(i); + } + for (long i = 0L; i < (long)SIZE; i++) { + testNegateL(Long.MIN_VALUE); + } + } + + @Benchmark + public void loopNegateLInBounds() { + for (long i = 0L; i < (long)SIZE; i++) { + testNegateL(i); + } + for (long i = 0L; i < (long)SIZE; i++) { + testNegateL(Long.MAX_VALUE); + } + } + + + // === incrementExact(int) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int testIncrementI(int i) { + try { + return Math.incrementExact(i); + } catch (ArithmeticException e) { + return 0; + } + } + + @Benchmark + public void loopIncrementIOverflow() { + for (int i = 0; i < SIZE; i++) { + testIncrementI(i); + } + for (int i = 0; i < SIZE; i++) { + testIncrementI(Integer.MAX_VALUE); + } + } + + @Benchmark + public void loopIncrementIInBounds() { + for (int i = 0; i < SIZE; i++) { + testIncrementI(i); + } + for (int i = 0; i < SIZE; i++) { + testIncrementI(Integer.MIN_VALUE + i); + } + } + + + // === incrementExact(long) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public long testIncrementL(long i) { + try { + return Math.incrementExact(i); + } catch (ArithmeticException e) { + return 0L; + } + } + + @Benchmark + public void loopIncrementLOverflow() { + for (long i = 0L; i < (long)SIZE; i++) { + testIncrementL(i); + } + for (long i = 0L; i < (long)SIZE; i++) { + testIncrementL(Long.MAX_VALUE); + } + } + + @Benchmark + public void loopIncrementLInBounds() { + for (long i = 0L; i < (long)SIZE; i++) { + testIncrementL(i); + } + for (long i = 0L; i < (long)SIZE; i++) { + testIncrementL(Long.MIN_VALUE + i); + } + } + + + // === decrementExact(int) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int testDecrementI(int i) { + try { + return Math.decrementExact(i); + } catch (ArithmeticException e) { + return 0; + } + } + + @Benchmark + public void loopDecrementIOverflow() { + for (int i = 0; i < SIZE; i++) { + testDecrementI(i); + } + for (int i = 0; i < SIZE; i++) { + testDecrementI(Integer.MIN_VALUE); + } + } + + @Benchmark + public void loopDecrementIInBounds() { + for (int i = 0; i < SIZE; i++) { + testDecrementI(i); + } + for (int i = 0; i < SIZE; i++) { + testDecrementI(Integer.MAX_VALUE - i); + } + } + + + // === decrementExact(long) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public long testDecrementL(long i) { + try { + return Math.decrementExact(i); + } catch (ArithmeticException e) { + return 0L; + } + } + + @Benchmark + public void loopDecrementLOverflow() { + for (long i = 0L; i < (long)SIZE; i++) { + testDecrementL(i); + } + for (long i = 0L; i < (long)SIZE; i++) { + testDecrementL(Long.MIN_VALUE); + } + } + + @Benchmark + public void loopDecrementLInBounds() { + for (long i = 0L; i < (long)SIZE; i++) { + testDecrementL(i); + } + for (long i = 0L; i < (long)SIZE; i++) { + testDecrementL(Long.MAX_VALUE - i); + } + } + + + // === addExact(int) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int testAddI(int l, int r) { + try { + return Math.addExact(l, r); + } catch (ArithmeticException e) { + return 0; + } + } + + @Benchmark + public void loopAddIOverflow() { + for (int i = 0; i < SIZE; i++) { + testAddI(Integer.MAX_VALUE - 1_000, i); + } + } + + @Benchmark + public void loopAddIInBounds() { + for (int i = 0; i < SIZE; i++) { + testAddI(i * 5, i); + } + } + + + // === addExact(long) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public long testAddL(long l, long r) { + try { + return Math.addExact(l, r); + } catch (ArithmeticException e) { + return 0L; + } + } + + @Benchmark + public void loopAddLOverflow() { + for (long i = 0L; i < (long)SIZE; i++) { + testAddL(Long.MAX_VALUE - 1_000L, i); + } + } + + @Benchmark + public void loopAddLInBounds() { + for (long i = 0L; i < (long)SIZE; i++) { + testAddL(i * 5L, i); + } + } + + + // === subtractExact(int) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public int testSubtractI(int l, int r) { + try { + return Math.subtractExact(l, r); + } catch (ArithmeticException e) { + return 0; + } + } + + @Benchmark + public void loopSubtractIOverflow() { + for (int i = 0; i < SIZE; i++) { + testSubtractI(Integer.MIN_VALUE + 1_000, i); + } + } + + @Benchmark + public void loopSubtractIInBounds() { + for (int i = 0; i < SIZE; i++) { + testSubtractI(i * 5, i); + } + } + + + // === subtractExact(long) === + @CompilerControl(CompilerControl.Mode.DONT_INLINE) + public long testSubtractL(long l, long r) { + try { + return Math.subtractExact(l, r); + } catch (ArithmeticException e) { + return 0L; + } + } + + @Benchmark + public void loopSubtractLOverflow() { + for (long i = 0L; i < (long)SIZE; i++) { + testSubtractL(Long.MIN_VALUE + 1_000L, i); + } + } + + @Benchmark + public void loopSubtractLInBounds() { + for (long i = 0L; i < (long)SIZE; i++) { + testSubtractL(i * 5L, i); + } + } + + + + @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=1"}) + public static class C1_1 extends MathExact {} + + @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=2"}) + public static class C1_2 extends MathExact {} + + @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=3"}) + public static class C1_3 extends MathExact {} + + @Fork(value = 1) + public static class C2 extends MathExact {} + + @Fork(value = 1, + jvmArgs = { + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableIntrinsic=_addExactI,_incrementExactI,_addExactL,_incrementExactL,_subtractExactI,_decrementExactI,_subtractExactL,_decrementExactL,_negateExactI,_negateExactL,_multiplyExactI,_multiplyExactL", + }) + public static class C2_no_intrinsics extends MathExact {} +} diff --git a/test/micro/org/openjdk/bench/vm/compiler/MultiplyExact.java b/test/micro/org/openjdk/bench/vm/compiler/MultiplyExact.java deleted file mode 100644 index 68c8cec538baf..0000000000000 --- a/test/micro/org/openjdk/bench/vm/compiler/MultiplyExact.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2025, 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. - */ -package org.openjdk.bench.vm.compiler; - -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.infra.*; - -import java.util.concurrent.TimeUnit; -import java.util.Random; - -@BenchmarkMode(Mode.AverageTime) -@OutputTimeUnit(TimeUnit.SECONDS) -@State(Scope.Thread) -@Warmup(iterations = 2, time = 1, timeUnit = TimeUnit.SECONDS) -@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) -public abstract class MultiplyExact { - @Param({"1000000"}) - public int SIZE; - - public int square(int a) { - return Math.multiplyExact(a, a); - } - - public int test(int i) { - try { - return square(i); - } catch (Throwable e) { - return 0; - } - } - - @Benchmark - public void loop() { - for (int i = 0; i < SIZE; i++) { - test(i); - } - } - - - - @Fork(value = 1) - public static class C2 extends MultiplyExact {} - - @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=1"}) - public static class C1_1 extends MultiplyExact {} - - @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=2"}) - public static class C1_2 extends MultiplyExact {} - - @Fork(value = 1, jvmArgs = {"-XX:TieredStopAtLevel=3"}) - public static class C1_3 extends MultiplyExact {} -} From 9372228d8a41902f160d0783c9a117b0307b7045 Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Mon, 24 Mar 2025 13:57:28 +0100 Subject: [PATCH 3/7] Use builtin_throw --- src/hotspot/share/opto/graphKit.cpp | 62 ++-- src/hotspot/share/opto/graphKit.hpp | 5 +- src/hotspot/share/opto/library_call.cpp | 21 +- src/hotspot/share/opto/library_call.hpp | 2 +- .../intrinsics/mathexact/OverflowTest.java | 274 ++++++++++++++++++ .../openjdk/bench/vm/compiler/MathExact.java | 4 +- 6 files changed, 332 insertions(+), 36 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/intrinsics/mathexact/OverflowTest.java diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index bb225eeabf554..0a8f0defc6eeb 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -525,17 +525,13 @@ void GraphKit::uncommon_trap_if_should_post_on_exceptions(Deoptimization::DeoptR } -//------------------------------builtin_throw---------------------------------- -void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { - bool must_throw = true; - +Pair GraphKit::builtin_throw_applies(Deoptimization::DeoptReason reason) { // If this particular condition has not yet happened at this // bytecode, then use the uncommon trap mechanism, and allow for // a future recompilation if several traps occur here. // If the throw is hot, try to use a more complicated inline mechanism // which keeps execution inside the compiled code. bool treat_throw_as_hot = false; - ciMethodData* md = method()->method_data(); if (ProfileTraps) { if (too_many_traps(reason)) { @@ -547,12 +543,18 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { // Also, if there is a local exception handler, treat all throws // as hot if there has been at least one in this method. - if (C->trap_count(reason) != 0 - && method()->method_data()->trap_count(reason) != 0 - && has_exception_handler()) { - treat_throw_as_hot = true; + if (C->trap_count(reason) != 0 && method()->method_data()->trap_count(reason) != 0 && has_exception_handler()) { + treat_throw_as_hot = true; } } + return {treat_throw_as_hot && method()->can_omit_stack_trace(), treat_throw_as_hot}; +} + +//------------------------------builtin_throw---------------------------------- +void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, ciInstance* exception_object) { + bool must_throw = true; + Pair applies_and_treat_throw_as_hot = builtin_throw_applies(reason); + bool treat_throw_as_hot = applies_and_treat_throw_as_hot.second; // If this throw happens frequently, an uncommon trap might cause // a performance pothole. If there is a local exception handler, @@ -560,7 +562,7 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { // let us handle the throw inline, with a preconstructed instance. // Note: If the deopt count has blown up, the uncommon trap // runtime is going to flush this nmethod, not matter what. - if (treat_throw_as_hot && method()->can_omit_stack_trace()) { + if (applies_and_treat_throw_as_hot.first) { // If the throw is local, we use a pre-existing instance and // punt on the backtrace. This would lead to a missing backtrace // (a repeat of 4292742) if the backtrace object is ever asked @@ -568,24 +570,28 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { // Fixing this remaining case of 4292742 requires some flavor of // escape analysis. Leave that for the future. ciInstance* ex_obj = nullptr; - switch (reason) { - case Deoptimization::Reason_null_check: - ex_obj = env()->NullPointerException_instance(); - break; - case Deoptimization::Reason_div0_check: - ex_obj = env()->ArithmeticException_instance(); - break; - case Deoptimization::Reason_range_check: - ex_obj = env()->ArrayIndexOutOfBoundsException_instance(); - break; - case Deoptimization::Reason_class_check: - ex_obj = env()->ClassCastException_instance(); - break; - case Deoptimization::Reason_array_check: - ex_obj = env()->ArrayStoreException_instance(); - break; - default: - break; + if (exception_object != nullptr) { + ex_obj = exception_object; + } else { + switch (reason) { + case Deoptimization::Reason_null_check: + ex_obj = env()->NullPointerException_instance(); + break; + case Deoptimization::Reason_div0_check: + ex_obj = env()->ArithmeticException_instance(); + break; + case Deoptimization::Reason_range_check: + ex_obj = env()->ArrayIndexOutOfBoundsException_instance(); + break; + case Deoptimization::Reason_class_check: + ex_obj = env()->ClassCastException_instance(); + break; + case Deoptimization::Reason_array_check: + ex_obj = env()->ArrayStoreException_instance(); + break; + default: + break; + } } if (failing()) { stop(); return; } // exception allocation might fail if (ex_obj != nullptr) { diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index b0150df04ed2b..09505c6c15852 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -275,7 +275,10 @@ class GraphKit : public Phase { // Helper to throw a built-in exception. // The JVMS must allow the bytecode to be re-executed via an uncommon trap. - void builtin_throw(Deoptimization::DeoptReason reason); + // If `exception_object` is nullptr, the exception to throw will be guessed based on `reason` + void builtin_throw(Deoptimization::DeoptReason reason, ciInstance* exception_object = nullptr); + // Returns the pair (builtin_throw_applies, throw_is_hot) for builtin_throw usage. + Pair builtin_throw_applies(Deoptimization::DeoptReason reason); // Helper to check the JavaThread::_should_post_on_exceptions flag // and branch to an uncommon_trap if it is true (with the specified reason and must_throw) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 8d35bcd722b0d..0943c0786d6af 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -2017,7 +2017,7 @@ bool LibraryCallKit::inline_min_max(vmIntrinsics::ID id) { return true; } -void LibraryCallKit::inline_math_mathExact(Node* math, Node *test) { +void LibraryCallKit::inline_math_mathExact(Node* math, Node* test, bool use_builtin_throw) { Node* bol = _gvn.transform( new BoolNode(test, BoolTest::overflow) ); IfNode* check = create_and_map_if(control(), bol, PROB_UNLIKELY_MAG(3), COUNT_UNKNOWN); Node* fast_path = _gvn.transform( new IfFalseNode(check)); @@ -2031,8 +2031,12 @@ void LibraryCallKit::inline_math_mathExact(Node* math, Node *test) { set_control(slow_path); set_i_o(i_o()); - uncommon_trap(Deoptimization::Reason_intrinsic, - Deoptimization::Action_maybe_recompile); + if (use_builtin_throw) { + builtin_throw(Deoptimization::Reason_intrinsic, env()->ArithmeticException_instance()); + } else { + uncommon_trap(Deoptimization::Reason_intrinsic, + Deoptimization::Action_maybe_recompile); + } } set_control(fast_path); @@ -2042,14 +2046,21 @@ void LibraryCallKit::inline_math_mathExact(Node* math, Node *test) { template bool LibraryCallKit::inline_math_overflow(Node* arg1, Node* arg2) { typedef typename OverflowOp::MathOp MathOp; - if (too_many_traps(Deoptimization::Reason_intrinsic)) { + bool use_builtin_throw = false; + if (builtin_throw_applies(Deoptimization::Reason_intrinsic).first) { + // If builtin_throw would work (notably, the throw is hot and we don't care about backtraces), + // instead of bailing out on intrinsic or potentially deopting, let's do that! + use_builtin_throw = true; + } else if (too_many_traps(Deoptimization::Reason_intrinsic)) { + // It has been already too many times, but we cannot use builtin_throw care (e.g. we care about backtraces), + // so let's bail out intrinsic rather than risking deopting again. return false; } MathOp* mathOp = new MathOp(arg1, arg2); Node* operation = _gvn.transform( mathOp ); Node* ofcheck = _gvn.transform( new OverflowOp(arg1, arg2) ); - inline_math_mathExact(operation, ofcheck); + inline_math_mathExact(operation, ofcheck, use_builtin_throw); return true; } diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 790c03be7ca51..b0626da97d451 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -210,7 +210,7 @@ class LibraryCallKit : public GraphKit { bool inline_math_pow(); template bool inline_math_overflow(Node* arg1, Node* arg2); - void inline_math_mathExact(Node* math, Node* test); + void inline_math_mathExact(Node* math, Node* test, bool use_builtin_throw); bool inline_math_addExactI(bool is_increment); bool inline_math_addExactL(bool is_increment); bool inline_math_multiplyExactI(); diff --git a/test/hotspot/jtreg/compiler/intrinsics/mathexact/OverflowTest.java b/test/hotspot/jtreg/compiler/intrinsics/mathexact/OverflowTest.java new file mode 100644 index 0000000000000..da9a808ae29c1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/intrinsics/mathexact/OverflowTest.java @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2025, 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. + */ + +/* + * @test + * @summary Math.*Exact intrinsics, especially in case of overflow + * The base case + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* + * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* + * compiler.intrinsics.mathexact.OverflowTest + */ + +/* + * @test + * @summary Math.*Exact intrinsics, especially in case of overflow + * With ProfileTraps enabled to allow builtin_throw to work + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* + * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* + * -XX:+ProfileTraps -XX:+StackTraceInThrowable -XX:+OmitStackTraceInFastThrow + * compiler.intrinsics.mathexact.OverflowTest + */ + +/* + * @test + * @summary Math.*Exact intrinsics, especially in case of overflow + * ProfileTraps off => throw will never be hot for builtin_throw + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* + * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* + * -XX:-ProfileTraps + * compiler.intrinsics.mathexact.OverflowTest + */ + +/* + * @test + * @summary Math.*Exact intrinsics, especially in case of overflow + * OmitStackTraceInFastThrow off => can_omit_stack_trace is false => no builtin_throw + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* + * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* + * -XX:+ProfileTraps -XX:+StackTraceInThrowable -XX:-OmitStackTraceInFastThrow + * compiler.intrinsics.mathexact.OverflowTest + */ + +/* + * @test + * @summary Math.*Exact intrinsics, especially in case of overflow + * StackTraceInThrowable off => can_omit_stack_trace is true => yes builtin_throw + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* + * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* + * -XX:+ProfileTraps -XX:-StackTraceInThrowable -XX:+OmitStackTraceInFastThrow + * compiler.intrinsics.mathexact.OverflowTest + */ + +/* + * @test + * @summary Math.*Exact intrinsics, especially in case of overflow + * StackTraceInThrowable off && OmitStackTraceInFastThrow off => can_omit_stack_trace is true => yes builtin_throw + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* + * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* + * -XX:+ProfileTraps -XX:-StackTraceInThrowable -XX:-OmitStackTraceInFastThrow + * compiler.intrinsics.mathexact.OverflowTest + */ + +/* + * @test + * @summary Math.*Exact intrinsics, especially in case of overflow + * Without intrinsics + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* + * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* + * -XX:DisableIntrinsic=_addExactI,_incrementExactI,_addExactL,_incrementExactL,_subtractExactI,_decrementExactI,_subtractExactL,_decrementExactL,_negateExactI,_negateExactL,_multiplyExactI,_multiplyExactL + * compiler.intrinsics.mathexact.OverflowTest + */ + +package compiler.intrinsics.mathexact; + +import java.lang.reflect.Method; + +import compiler.lib.generators.RestrictableGenerator; +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import static compiler.lib.generators.Generators.G; + +public class OverflowTest { + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + private static final RestrictableGenerator int_gen = G.ints(); + private static final int LIMIT = 10_000; + + public static void main(String... args) throws NoSuchMethodException { + OverflowTest t = new OverflowTest(); + t.run(); + } + + void run() throws NoSuchMethodException { + check_compilation(); + check_multiplyI(); + } + + void check_compilation() throws NoSuchMethodException { + // Force Math loading + Math.min(2, 3); + comp_multiplyI(0, 0); + comp_multiplyI_no_catch(0, 0); + int_multiplyI(0, 0); + + Method comp_multiplyI_meth = OverflowTest.class.getDeclaredMethod("comp_multiplyI", int.class, int.class); + Asserts.assertTrue(WHITE_BOX.isMethodCompiled(comp_multiplyI_meth), "comp_multiplyI(int, int) is not compiled"); + + Method comp_multiplyI_no_catch_meth = OverflowTest.class.getDeclaredMethod("comp_multiplyI_no_catch", int.class, int.class); + Asserts.assertTrue(WHITE_BOX.isMethodCompiled(comp_multiplyI_no_catch_meth), "comp_multiplyI_no_catch(int, int) is not compiled"); + + Method int_multiplyI_meth = OverflowTest.class.getDeclaredMethod("int_multiplyI", int.class, int.class); + Asserts.assertFalse(WHITE_BOX.isMethodCompiled(int_multiplyI_meth), "int_multiplyI(int, int) is compiled"); + } + + void assert_consistent(Integer comp_res, Integer int_res) { + if (int_res == null) { + Asserts.assertNull(comp_res); + } else { + Asserts.assertNotNull(comp_res); + Asserts.assertEquals(comp_res, int_res); + } + } + + Integer comp_multiplyI(int a, int b) { + try { + return Math.multiplyExact(a, b); + } catch (ArithmeticException e) { + return null; + } + } + + int comp_multiplyI_no_catch(int a, int b) { + return Math.multiplyExact(a, b); + } + + Integer int_multiplyI(int a, int b) { + try { + return Math.multiplyExact(a, b); + } catch (ArithmeticException e) { + return null; + } + } + + void check_multiplyI() { + // 46_340 < 2 ^ 15.5 < 46_341 => + // 46_340^2 < 2 ^ 31 < 46_341^2 + int limit_square_do_not_overflow = 46_340; + + // In bound cases + for (int i = 0; i < LIMIT; i++) { + int a = limit_square_do_not_overflow - i; + Integer comp_res = comp_multiplyI(a, a); + Integer int_res = int_multiplyI(a, a); + Asserts.assertNotNull(int_res); + assert_consistent(comp_res, int_res); + } + for (int i = 0; i < LIMIT; i++) { + int a = limit_square_do_not_overflow - i; + Integer comp_res; + try { + comp_res = comp_multiplyI_no_catch(a, a); + } catch (ArithmeticException _) { + comp_res = null; + } + Integer int_res = int_multiplyI(a, a); + Asserts.assertNotNull(int_res); + assert_consistent(comp_res, int_res); + } + + // Out of bound cases + for (int i = 0; i < LIMIT; i++) { + int a = limit_square_do_not_overflow + 1 + i; + Integer comp_res = comp_multiplyI(a, a); + Integer int_res = int_multiplyI(a, a); + Asserts.assertNull(int_res); + assert_consistent(comp_res, int_res); + } + for (int i = 0; i < LIMIT; i++) { + int a = limit_square_do_not_overflow + 1 + i; + Integer comp_res; + try { + comp_res = comp_multiplyI_no_catch(a, a); + } catch (ArithmeticException _) { + comp_res = null; + } + Integer int_res = int_multiplyI(a, a); + Asserts.assertNull(int_res); + assert_consistent(comp_res, int_res); + } + + // Random slice + int lhs = int_gen.next(); + int rhs_start = int_gen.next() & 0xff_ff_00_00; + for (int i = 0; i < 0x1_00_00; i++) { + int rhs = rhs_start | i; + Integer comp_res = comp_multiplyI(lhs, rhs); + Integer int_res = int_multiplyI(lhs, rhs); + assert_consistent(comp_res, int_res); + } + for (int i = 0; i < 0x1_00_00; i++) { + int rhs = rhs_start | i; + Integer comp_res; + try { + comp_res = comp_multiplyI_no_catch(lhs, rhs); + } catch (ArithmeticException _) { + comp_res = null; + } + Integer int_res = int_multiplyI(lhs, rhs); + assert_consistent(comp_res, int_res); + } + } +} diff --git a/test/micro/org/openjdk/bench/vm/compiler/MathExact.java b/test/micro/org/openjdk/bench/vm/compiler/MathExact.java index 093bf00722417..5558af08722f1 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/MathExact.java +++ b/test/micro/org/openjdk/bench/vm/compiler/MathExact.java @@ -26,7 +26,6 @@ import org.openjdk.jmh.infra.*; import java.util.concurrent.TimeUnit; -import java.util.Random; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @@ -398,4 +397,7 @@ public static class C2 extends MathExact {} "-XX:DisableIntrinsic=_addExactI,_incrementExactI,_addExactL,_incrementExactL,_subtractExactI,_decrementExactI,_subtractExactL,_decrementExactL,_negateExactI,_negateExactL,_multiplyExactI,_multiplyExactL", }) public static class C2_no_intrinsics extends MathExact {} + + @Fork(value = 1, jvmArgs = {"-XX:-OmitStackTraceInFastThrow"}) + public static class C2_no_builtin_throw extends MathExact {} } From 41d7a1d4f12a4068df889e6d8735c4049cd26c63 Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Mon, 31 Mar 2025 10:00:35 +0200 Subject: [PATCH 4/7] guess_exception_from_deopt_reason out of builtin_throw --- src/hotspot/share/opto/graphKit.cpp | 46 +++++++++++++---------------- src/hotspot/share/opto/graphKit.hpp | 6 ++-- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 0a8f0defc6eeb..95cf322ab8fe3 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -551,7 +551,7 @@ Pair GraphKit::builtin_throw_applies(Deoptimization::DeoptReason rea } //------------------------------builtin_throw---------------------------------- -void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, ciInstance* exception_object) { +void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, ciInstance* ex_obj) { bool must_throw = true; Pair applies_and_treat_throw_as_hot = builtin_throw_applies(reason); bool treat_throw_as_hot = applies_and_treat_throw_as_hot.second; @@ -569,30 +569,6 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, ciInstance* exc // for its backtrace. // Fixing this remaining case of 4292742 requires some flavor of // escape analysis. Leave that for the future. - ciInstance* ex_obj = nullptr; - if (exception_object != nullptr) { - ex_obj = exception_object; - } else { - switch (reason) { - case Deoptimization::Reason_null_check: - ex_obj = env()->NullPointerException_instance(); - break; - case Deoptimization::Reason_div0_check: - ex_obj = env()->ArithmeticException_instance(); - break; - case Deoptimization::Reason_range_check: - ex_obj = env()->ArrayIndexOutOfBoundsException_instance(); - break; - case Deoptimization::Reason_class_check: - ex_obj = env()->ClassCastException_instance(); - break; - case Deoptimization::Reason_array_check: - ex_obj = env()->ArrayStoreException_instance(); - break; - default: - break; - } - } if (failing()) { stop(); return; } // exception allocation might fail if (ex_obj != nullptr) { if (env()->jvmti_can_post_on_exceptions()) { @@ -660,6 +636,26 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, ciInstance* exc uncommon_trap(reason, action, (ciKlass*)nullptr, (char*)nullptr, must_throw); } +ciInstance* GraphKit::guess_exception_from_deopt_reason(Deoptimization::DeoptReason reason) const { + switch (reason) { + case Deoptimization::Reason_null_check: + return env()->NullPointerException_instance(); + case Deoptimization::Reason_div0_check: + return env()->ArithmeticException_instance(); + case Deoptimization::Reason_range_check: + return env()->ArrayIndexOutOfBoundsException_instance(); + case Deoptimization::Reason_class_check: + return env()->ClassCastException_instance(); + case Deoptimization::Reason_array_check: + return env()->ArrayStoreException_instance(); + default: + return nullptr; + } +} + +void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { + builtin_throw(reason, guess_exception_from_deopt_reason(reason)); +} //----------------------------PreserveJVMState--------------------------------- PreserveJVMState::PreserveJVMState(GraphKit* kit, bool clone_map) { diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 09505c6c15852..97c7809aa7883 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -275,10 +275,10 @@ class GraphKit : public Phase { // Helper to throw a built-in exception. // The JVMS must allow the bytecode to be re-executed via an uncommon trap. - // If `exception_object` is nullptr, the exception to throw will be guessed based on `reason` - void builtin_throw(Deoptimization::DeoptReason reason, ciInstance* exception_object = nullptr); - // Returns the pair (builtin_throw_applies, throw_is_hot) for builtin_throw usage. + void builtin_throw(Deoptimization::DeoptReason reason); + void builtin_throw(Deoptimization::DeoptReason reason, ciInstance* exception_object); Pair builtin_throw_applies(Deoptimization::DeoptReason reason); + ciInstance* guess_exception_from_deopt_reason(Deoptimization::DeoptReason reason) const; // Helper to check the JavaThread::_should_post_on_exceptions flag // and branch to an uncommon_trap if it is true (with the specified reason and must_throw) From 34b3b75c1b82f632d1416a2a2a42bd90d70cd142 Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Wed, 2 Apr 2025 13:48:00 +0200 Subject: [PATCH 5/7] Apply @iwanowww's refactoring --- src/hotspot/share/opto/graphKit.cpp | 129 +++++++++++++----------- src/hotspot/share/opto/graphKit.hpp | 13 ++- src/hotspot/share/opto/library_call.cpp | 32 +++--- src/hotspot/share/opto/library_call.hpp | 2 +- 4 files changed, 95 insertions(+), 81 deletions(-) diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 2ac2db99ba27c..40867dfebd7f7 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -525,54 +525,31 @@ void GraphKit::uncommon_trap_if_should_post_on_exceptions(Deoptimization::DeoptR } -Pair GraphKit::builtin_throw_applies(Deoptimization::DeoptReason reason) { - // If this particular condition has not yet happened at this - // bytecode, then use the uncommon trap mechanism, and allow for - // a future recompilation if several traps occur here. - // If the throw is hot, try to use a more complicated inline mechanism - // which keeps execution inside the compiled code. - bool treat_throw_as_hot = false; - - if (ProfileTraps) { - if (too_many_traps(reason)) { - treat_throw_as_hot = true; - } - // (If there is no MDO at all, assume it is early in - // execution, and that any deopts are part of the - // startup transient, and don't need to be remembered.) - - // Also, if there is a local exception handler, treat all throws - // as hot if there has been at least one in this method. - if (C->trap_count(reason) != 0 && method()->method_data()->trap_count(reason) != 0 && has_exception_handler()) { - treat_throw_as_hot = true; - } - } - return {treat_throw_as_hot && method()->can_omit_stack_trace(), treat_throw_as_hot}; -} - //------------------------------builtin_throw---------------------------------- -void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, ciInstance* ex_obj) { - bool must_throw = true; - Pair applies_and_treat_throw_as_hot = builtin_throw_applies(reason); - bool treat_throw_as_hot = applies_and_treat_throw_as_hot.second; +void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { + builtin_throw(reason, builtin_throw_exception(reason), /*allow_too_many_traps*/ true); +} +void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, + ciInstance* ex_obj, + bool allow_too_many_traps) { // If this throw happens frequently, an uncommon trap might cause // a performance pothole. If there is a local exception handler, // and if this particular bytecode appears to be deoptimizing often, // let us handle the throw inline, with a preconstructed instance. // Note: If the deopt count has blown up, the uncommon trap // runtime is going to flush this nmethod, not matter what. - if (applies_and_treat_throw_as_hot.first) { - // If the throw is local, we use a pre-existing instance and - // punt on the backtrace. This would lead to a missing backtrace - // (a repeat of 4292742) if the backtrace object is ever asked - // for its backtrace. - // Fixing this remaining case of 4292742 requires some flavor of - // escape analysis. Leave that for the future. - if (ex_obj != nullptr) { + if (is_builtin_throw_hot(reason)) { + if (method()->can_omit_stack_trace() && ex_obj != nullptr) { + // If the throw is local, we use a pre-existing instance and + // punt on the backtrace. This would lead to a missing backtrace + // (a repeat of 4292742) if the backtrace object is ever asked + // for its backtrace. + // Fixing this remaining case of 4292742 requires some flavor of + // escape analysis. Leave that for the future. if (env()->jvmti_can_post_on_exceptions()) { // check if we must post exception events, take uncommon trap if so - uncommon_trap_if_should_post_on_exceptions(reason, must_throw); + uncommon_trap_if_should_post_on_exceptions(reason, true /*must_throw*/); // here if should_post_on_exceptions is false // continue on with the normal codegen } @@ -603,6 +580,18 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, ciInstance* ex_ add_exception_state(make_exception_state(ex_node)); return; + } else if (builtin_throw_too_many_traps(reason, ex_obj)) { + // We cannot afford to take too many traps here. Suffer in the interpreter instead. + assert(allow_too_many_traps, "not allowed"); + if (C->log() != nullptr) { + C->log()->elem("hot_throw preallocated='0' reason='%s' mcount='%d'", + Deoptimization::trap_reason_name(reason), + C->trap_count(reason)); + } + uncommon_trap(reason, Deoptimization::Action_none, + (ciKlass*) nullptr, (char*) nullptr, + true /*must_throw*/); + return; } } @@ -614,29 +603,57 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, ciInstance* ex_ // Usual case: Bail to interpreter. // Reserve the right to recompile if we haven't seen anything yet. - ciMethod* m = Deoptimization::reason_is_speculate(reason) ? C->method() : nullptr; - Deoptimization::DeoptAction action = Deoptimization::Action_maybe_recompile; - if (treat_throw_as_hot - && (method()->method_data()->trap_recompiled_at(bci(), m) - || C->too_many_traps(reason))) { - // We cannot afford to take more traps here. Suffer in the interpreter. - if (C->log() != nullptr) - C->log()->elem("hot_throw preallocated='0' reason='%s' mcount='%d'", - Deoptimization::trap_reason_name(reason), - C->trap_count(reason)); - action = Deoptimization::Action_none; - } - // "must_throw" prunes the JVM state to include only the stack, if there // are no local exception handlers. This should cut down on register // allocation time and code size, by drastically reducing the number // of in-edges on the call to the uncommon trap. + uncommon_trap(reason, Deoptimization::Action_maybe_recompile, + (ciKlass*) nullptr, (char*) nullptr, + true /*must_throw*/); +} - uncommon_trap(reason, action, (ciKlass*)nullptr, (char*)nullptr, must_throw); +bool GraphKit::is_builtin_throw_hot(Deoptimization::DeoptReason reason) { + // If this particular condition has not yet happened at this + // bytecode, then use the uncommon trap mechanism, and allow for + // a future recompilation if several traps occur here. + // If the throw is hot, try to use a more complicated inline mechanism + // which keeps execution inside the compiled code. + if (ProfileTraps) { + if (too_many_traps(reason)) { + return true; + } + // (If there is no MDO at all, assume it is early in + // execution, and that any deopts are part of the + // startup transient, and don't need to be remembered.) + + // Also, if there is a local exception handler, treat all throws + // as hot if there has been at least one in this method. + if (C->trap_count(reason) != 0 && + method()->method_data()->trap_count(reason) != 0 && + has_exception_handler()) { + return true; + } + } + return false; +} + +bool GraphKit::builtin_throw_too_many_traps(Deoptimization::DeoptReason reason, + ciInstance* ex_obj) { + if (is_builtin_throw_hot(reason)) { + if (method()->can_omit_stack_trace() && ex_obj != nullptr) { + return false; // no traps; throws preallocated exception instead + } + ciMethod* m = Deoptimization::reason_is_speculate(reason) ? C->method() : nullptr; + if (method()->method_data()->trap_recompiled_at(bci(), m) || + C->too_many_traps(reason)) { + return true; + } + } + return false; } -ciInstance* GraphKit::guess_exception_from_deopt_reason(Deoptimization::DeoptReason reason) const { - // Preconstructed exception objects, to use when we don't need the backtrace. +ciInstance* GraphKit::builtin_throw_exception(Deoptimization::DeoptReason reason) const { + // Preallocated exception objects to use when we don't need the backtrace. switch (reason) { case Deoptimization::Reason_null_check: return env()->NullPointerException_instance(); @@ -653,10 +670,6 @@ ciInstance* GraphKit::guess_exception_from_deopt_reason(Deoptimization::DeoptRea } } -void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { - builtin_throw(reason, guess_exception_from_deopt_reason(reason)); -} - //----------------------------PreserveJVMState--------------------------------- PreserveJVMState::PreserveJVMState(GraphKit* kit, bool clone_map) { debug_only(kit->verify_map()); diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index a05a4160d7d0e..d1b58526a6dc1 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -276,9 +276,16 @@ class GraphKit : public Phase { // Helper to throw a built-in exception. // The JVMS must allow the bytecode to be re-executed via an uncommon trap. void builtin_throw(Deoptimization::DeoptReason reason); - void builtin_throw(Deoptimization::DeoptReason reason, ciInstance* exception_object); - Pair builtin_throw_applies(Deoptimization::DeoptReason reason); - ciInstance* guess_exception_from_deopt_reason(Deoptimization::DeoptReason reason) const; + void builtin_throw(Deoptimization::DeoptReason reason, + ciInstance* exception_object, + bool allow_too_many_traps); + bool builtin_throw_too_many_traps(Deoptimization::DeoptReason reason, + ciInstance* exception_object); + private: + bool is_builtin_throw_hot(Deoptimization::DeoptReason reason); + ciInstance* builtin_throw_exception(Deoptimization::DeoptReason reason) const; + + public: // Helper to check the JavaThread::_should_post_on_exceptions flag // and branch to an uncommon_trap if it is true (with the specified reason and must_throw) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 4c652cb80b1a6..46a04e851e512 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -2003,7 +2003,14 @@ bool LibraryCallKit::inline_min_max(vmIntrinsics::ID id) { return true; } -void LibraryCallKit::inline_math_mathExact(Node* math, Node* test, bool use_builtin_throw) { +bool LibraryCallKit::inline_math_mathExact(Node* math, Node* test) { + if (builtin_throw_too_many_traps(Deoptimization::Reason_intrinsic, + env()->ArithmeticException_instance())) { + // It has been already too many times, but we cannot use builtin_throw care (e.g. we care about backtraces), + // so let's bail out intrinsic rather than risking deopting again. + return false; + } + Node* bol = _gvn.transform( new BoolNode(test, BoolTest::overflow) ); IfNode* check = create_and_map_if(control(), bol, PROB_UNLIKELY_MAG(3), COUNT_UNKNOWN); Node* fast_path = _gvn.transform( new IfFalseNode(check)); @@ -2017,37 +2024,24 @@ void LibraryCallKit::inline_math_mathExact(Node* math, Node* test, bool use_buil set_control(slow_path); set_i_o(i_o()); - if (use_builtin_throw) { - builtin_throw(Deoptimization::Reason_intrinsic, env()->ArithmeticException_instance()); - } else { - uncommon_trap(Deoptimization::Reason_intrinsic, - Deoptimization::Action_maybe_recompile); - } + builtin_throw(Deoptimization::Reason_intrinsic, + env()->ArithmeticException_instance(), + /*allow_too_many_traps*/ false); } set_control(fast_path); set_result(math); + return true; } template bool LibraryCallKit::inline_math_overflow(Node* arg1, Node* arg2) { typedef typename OverflowOp::MathOp MathOp; - bool use_builtin_throw = false; - if (builtin_throw_applies(Deoptimization::Reason_intrinsic).first) { - // If builtin_throw would work (notably, the throw is hot and we don't care about backtraces), - // instead of bailing out on intrinsic or potentially deopting, let's do that! - use_builtin_throw = true; - } else if (too_many_traps(Deoptimization::Reason_intrinsic)) { - // It has been already too many times, but we cannot use builtin_throw care (e.g. we care about backtraces), - // so let's bail out intrinsic rather than risking deopting again. - return false; - } MathOp* mathOp = new MathOp(arg1, arg2); Node* operation = _gvn.transform( mathOp ); Node* ofcheck = _gvn.transform( new OverflowOp(arg1, arg2) ); - inline_math_mathExact(operation, ofcheck, use_builtin_throw); - return true; + return inline_math_mathExact(operation, ofcheck); } bool LibraryCallKit::inline_math_addExactI(bool is_increment) { diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 94e8536d59d93..ad1ce71c374bf 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -209,7 +209,7 @@ class LibraryCallKit : public GraphKit { bool inline_math_pow(); template bool inline_math_overflow(Node* arg1, Node* arg2); - void inline_math_mathExact(Node* math, Node* test, bool use_builtin_throw); + bool inline_math_mathExact(Node* math, Node* test); bool inline_math_addExactI(bool is_increment); bool inline_math_addExactL(bool is_increment); bool inline_math_multiplyExactI(); From 238b129d8ec5ef9de6c910e80949c50b298a1e33 Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Wed, 2 Apr 2025 19:18:57 +0200 Subject: [PATCH 6/7] fix typo in comment --- src/hotspot/share/opto/library_call.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 46a04e851e512..708225ba3aa9f 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -2006,7 +2006,7 @@ bool LibraryCallKit::inline_min_max(vmIntrinsics::ID id) { bool LibraryCallKit::inline_math_mathExact(Node* math, Node* test) { if (builtin_throw_too_many_traps(Deoptimization::Reason_intrinsic, env()->ArithmeticException_instance())) { - // It has been already too many times, but we cannot use builtin_throw care (e.g. we care about backtraces), + // It has been already too many times, but we cannot use builtin_throw (e.g. we care about backtraces), // so let's bail out intrinsic rather than risking deopting again. return false; } From e7c8f3e06f46e85cb3c2dc974db84b10a57bd086 Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Thu, 3 Apr 2025 14:04:25 +0200 Subject: [PATCH 7/7] Remove useless flags in tests --- .../intrinsics/mathexact/OverflowTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/hotspot/jtreg/compiler/intrinsics/mathexact/OverflowTest.java b/test/hotspot/jtreg/compiler/intrinsics/mathexact/OverflowTest.java index da9a808ae29c1..eba6686ee4746 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/mathexact/OverflowTest.java +++ b/test/hotspot/jtreg/compiler/intrinsics/mathexact/OverflowTest.java @@ -29,8 +29,8 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm - * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI - * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -XX:-TieredCompilation * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* * compiler.intrinsics.mathexact.OverflowTest @@ -45,7 +45,7 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI - * -Xcomp -Xbatch -XX:-TieredCompilation + * -Xcomp -XX:-TieredCompilation * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* * -XX:+ProfileTraps -XX:+StackTraceInThrowable -XX:+OmitStackTraceInFastThrow @@ -61,7 +61,7 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI - * -Xcomp -Xbatch -XX:-TieredCompilation + * -Xcomp -XX:-TieredCompilation * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* * -XX:-ProfileTraps @@ -77,7 +77,7 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI - * -Xcomp -Xbatch -XX:-TieredCompilation + * -Xcomp -XX:-TieredCompilation * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* * -XX:+ProfileTraps -XX:+StackTraceInThrowable -XX:-OmitStackTraceInFastThrow @@ -93,7 +93,7 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI - * -Xcomp -Xbatch -XX:-TieredCompilation + * -Xcomp -XX:-TieredCompilation * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* * -XX:+ProfileTraps -XX:-StackTraceInThrowable -XX:+OmitStackTraceInFastThrow @@ -109,7 +109,7 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI - * -Xcomp -Xbatch -XX:-TieredCompilation + * -Xcomp -XX:-TieredCompilation * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* * -XX:+ProfileTraps -XX:-StackTraceInThrowable -XX:-OmitStackTraceInFastThrow @@ -124,8 +124,8 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm - * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI - * -Xcomp -Xbatch -XX:-TieredCompilation + * -XX:+UnlockDiagnosticVMOptions -Xbootclasspath/a:. -XX:+WhiteBoxAPI + * -Xcomp -XX:-TieredCompilation * -XX:CompileCommand=compileonly,compiler.intrinsics.mathexact.OverflowTest::comp_* * -XX:CompileCommand=dontinline,compiler.intrinsics.mathexact.OverflowTest::* * -XX:DisableIntrinsic=_addExactI,_incrementExactI,_addExactL,_incrementExactL,_subtractExactI,_decrementExactI,_subtractExactL,_decrementExactL,_negateExactI,_negateExactL,_multiplyExactI,_multiplyExactL