On Mon, 12 Jan 2026 10:29:39 GMT, Damon Fenacci <[email protected]> wrote:

> ## Issue
> 
> This is a redo of [JDK-8361842](https://bugs.openjdk.org/browse/JDK-8361842) 
> which was backed out by 
> [JDK-8374210](https://bugs.openjdk.org/browse/JDK-8374210) due to C2-related 
> regressions. The original change moved input validation checks for 
> java.lang.StringCoding from the intrinsic to Java code (leaving the intrinsic 
> check only with the `VerifyIntrinsicChecks` flag). Refer to the [original 
> PR](https://github.com/openjdk/jdk/pull/25998) for details.
> 
> This additional issue happens because, in some cases, for instance when the 
> Java checking code is not inlined and we give an out-of-range constant as 
> input, we fold the data path but not the control path and we crash in the 
> backend.
> 
> ## Causes
> 
> The cause of this is that the out-of-range constant (e.g. -1) floats into the 
> intrinsic and there (assuming the input is valid) we add a constraint to its 
> type to positive integers (e.g. to compute the array address) which makes it 
> top.
> 
> ## Fix
> 
> A possible fix is to introduce an opaque node (OpaqueGuardNode) similar to 
> what we do in `must_be_not_null` for values that we know cannot be null:
> https://github.com/openjdk/jdk/blob/ce721665cd61d9a319c667d50d9917c359d6c104/src/hotspot/share/opto/graphKit.cpp#L1484
> This will temporarily add the range check to ensure that C2 figures that 
> out-of-range values cannot reach the intrinsic. Then, during macro expansion, 
> we replace the opaque node with the corresponding constant (true/false) in 
> product builds such that the actually unneeded guards are folded and do not 
> end up in the emitted code.
> 
> # Testing
> 
> * Tier 1-3+
> * 2 JTReg tests added
>   * `TestRangeCheck.java` as regression test for the reported issue
>   * `TestOpaqueGuardNodes.java` to check that opaque guard nodes are added 
> when parsing and removed at macro expansion

Marked as reviewed by vyazici (Committer).

Verified that 3c466d372b7 is a clean revert of 7e18de137c3 delivered in 
[JDK-8374210].

[JDK-8374210]: https://bugs.openjdk.org/browse/JDK-8374210

src/hotspot/share/opto/c2_globals.hpp line 680:

> 678:   develop(bool, VerifyIntrinsicChecks, false,                            
>    \
> 679:           "Verify in intrinsic that Java level checks work as expected") 
>    \
> 680:                                                                          
>    \

I suggest removing the `VerifyIntrinsicChecks` flag. Given `OpaqueGuard` 
already verifies the value when `#ifdef ASSERT`, does `VerifyIntrinsicChecks` 
serve any purpose anymore?

src/hotspot/share/opto/library_call.hpp line 170:

> 168:                                     Node* length, bool char_count,
> 169:                                     bool halt_on_oob = false,
> 170:                                     bool is_opaque = false);

Do we really need to introduce two new toggles: `halt_on_oob` and `is_opaque`? 
At all call-sites either one of the following is used:

1. `halt_on_oob=true, is_opaque=!VerifyIntrinsicChecks`
2. defaults (i.e., `halt_on_oob=is_opaque=false`)

Can we instead only settle one, e.g., `halt_on_oob=VerifyIntrinsicChecks`?

src/hotspot/share/opto/loopopts.cpp line 1:

> 1: /*

What is the reason that the new `OpaqueGuard` is not taken into account in 
`PhaseIdealLoop::clone_iff`?

src/hotspot/share/opto/macro.cpp line 2565:

> 2563:         // Tests with OpaqueGuard nodes are implicitly known to be true 
> or false. Replace the node with appropriate value. In debug builds,
> 2564:         // we leave the test in the graph to have an additional sanity 
> check at runtime. If the test fails (i.e. a bug),
> 2565:         // we will execute a Halt node.

*Nit:* Can we adhere to the max. 120 (or even better, 80!) characters per line 
limit of the file?

src/hotspot/share/opto/macro.cpp line 2569:

> 2567:         _igvn.replace_node(n, n->in(1));
> 2568: #else
> 2569:         _igvn.replace_node(n, _igvn.intcon(0));

Curious: why do we invoke `intcon(0)` for `OpaqueGuard`, whereas it was 
`intcon(1)` for `OpaqueNotNull` slightly above?

src/hotspot/share/opto/opaquenode.hpp line 160:

> 158: // we keep the actual checks as additional verification code (i.e. 
> removing OpaqueGuardNode and use the BoolNode
> 159: // inputs instead).
> 160: class OpaqueGuardNode : public Node {

With the `OpaqueGuardNode::is_positive` flag gone, `OpaqueGuardNode` looks 
pretty much identical to `OpaqueNotNullNode`. Is there a code reuse opportunity 
we can take advantage of?

test/hotspot/jtreg/compiler/intrinsics/TestVerifyIntrinsicChecks.java line 1:

> 1: /*

Since the `VerifyIntrinsicChecks` flag is gone, AFAICT, all following changes 
can be reverted:


git rm test/hotspot/jtreg/compiler/intrinsics/TestVerifyIntrinsicChecks.java
git checkout upstream/HEAD -- \
test/hotspot/jtreg/compiler/intrinsics/string/TestCountPositives.java \
test/hotspot/jtreg/compiler/intrinsics/string/TestEncodeIntrinsics.java \
test/hotspot/jtreg/compiler/intrinsics/string/TestHasNegatives.java \
test/hotspot/jtreg/compiler/patches/java.base/java/lang/Helper.java

test/hotspot/jtreg/compiler/intrinsics/string/TestRangeCheck.java line 32:

> 30:  *      -XX:CompileCommand=inline,java.lang.StringCoding::*
> 31:  *      
> -XX:CompileCommand=exclude,jdk.internal.util.Preconditions::checkFromIndexSize
> 32:  *      
> -XX:CompileCommand=compileonly,compiler.intrinsics.string.TestRangeCheck::test

Is this necessary? (This wasn't used in `TestStringConstruction`.)

test/hotspot/jtreg/compiler/intrinsics/string/TestRangeCheck.java line 58:

> 56:             // cut off the dead code. As a result, -1 is fed as input 
> into the
> 57:             // StringCoding::countPositives0 intrinsic which is replaced 
> by TOP and causes a
> 58:             // failure in the matcher.

I'd appreciate it if we can be more elaborate for less C2-illiterate people 
like myself. 😇

Suggestion:

            // Calling `StringCoding::countPositives`, which is a "front door"
            // to the `StringCoding::countPositives0` intrinsic.
            // `countPositives` validates its input using
            // `Preconditions::checkFromIndexSize`, which also maps to an
            // intrinsic. When `checkFromIndexSize` is not inlined, C2 does not
            // know about the explicit range checks, and does not cut off the
            // dead code. As a result, an invalid value (e.g., `-1`) can be fed
            // as input into the `countPositives0` intrinsic, got replaced
            // by TOP, and cause a failure in the matcher.

-------------

PR Review: https://git.openjdk.org/jdk/pull/29164#pullrequestreview-3681112226
PR Comment: https://git.openjdk.org/jdk/pull/29164#issuecomment-3738455817
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2689568427
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2687948444
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2685859575
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2685838328
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2705884654
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2705885810
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2704760982
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2689735070
PR Review Comment: https://git.openjdk.org/jdk/pull/29164#discussion_r2689780537

Reply via email to