8346989: C2: deoptimization and re-execution cycle with Math.*Exact in case of frequent overflow

Reviewed-by: thartmann, vlivanov
This commit is contained in:
Marc Chevalier 2025-04-07 05:21:44 +00:00
parent 660b17a6b9
commit 97ed536125
6 changed files with 787 additions and 77 deletions

View File

@ -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, action, (ciKlass*)nullptr, (char*)nullptr, must_throw);
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;
}
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) {

View File

@ -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)

View File

@ -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 <typename OverflowOp>
@ -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) {

View File

@ -209,7 +209,7 @@ class LibraryCallKit : public GraphKit {
bool inline_math_pow();
template <typename OverflowOp>
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();

View File

@ -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<Integer> 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);
}
}
}

View File

@ -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 {}
}