diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index bf6c4df626ba7..40867dfebd7f7 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -527,71 +527,29 @@ void GraphKit::uncommon_trap_if_should_post_on_exceptions(Deoptimization::DeoptR //------------------------------builtin_throw---------------------------------- void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { - bool must_throw = true; - - // 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)) { - 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; - } - } + 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 (treat_throw_as_hot && method()->can_omit_stack_trace()) { - // 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. - 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 we have a preconstructed exception object, use it. - 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 } @@ -622,6 +580,18 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { 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; } } @@ -633,27 +603,72 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { // 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*/); +} + +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; +} - uncommon_trap(reason, action, (ciKlass*)nullptr, (char*)nullptr, must_throw); +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::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(); + 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; + } +} //----------------------------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 d9a725fc10c0c..d1b58526a6dc1 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -276,6 +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, + 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 3bb432ac6077b..708225ba3aa9f 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 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 (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,12 +2024,14 @@ 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_none); + builtin_throw(Deoptimization::Reason_intrinsic, + env()->ArithmeticException_instance(), + /*allow_too_many_traps*/ false); } set_control(fast_path); set_result(math); + return true; } template @@ -2032,8 +2041,7 @@ bool LibraryCallKit::inline_math_overflow(Node* arg1, Node* arg2) { MathOp* mathOp = new MathOp(arg1, arg2); Node* operation = _gvn.transform( mathOp ); Node* ofcheck = _gvn.transform( new OverflowOp(arg1, arg2) ); - inline_math_mathExact(operation, ofcheck); - 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 1f83e28932b70..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 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(); 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..eba6686ee4746 --- /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:+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 + */ + +/* + * @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 -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 -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 -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 -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 -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:+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 + * 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 new file mode 100644 index 0000000000000..5558af08722f1 --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/compiler/MathExact.java @@ -0,0 +1,403 @@ +/* + * 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; + +@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 {} + + @Fork(value = 1, jvmArgs = {"-XX:-OmitStackTraceInFastThrow"}) + public static class C2_no_builtin_throw extends MathExact {} +}