Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-25 Thread Aleksey Shipilev
On Thu, 25 Jan 2024 14:48:16 GMT, Maurizio Cimadamore  
wrote:

> I don't 100% buy the `MethodHandleImpl` analogy. In that case the check is 
> not simply used to save a branch, but to spare spinning of a completely new 
> lambda form.

Doing this to save a few intructions would not likely to worth the hassle 
outside the _really performance critical paths_, but even then it might be 
useful for hot JDK code. On larger examples, you can avoid memory accesses, 
allocations, etc. by coding up the constant-foldable path that you know 
compiler would not be able to extract when propagating constants through the 
generic code. For example, giving quantitative substance to my previous example:


diff --git a/src/java.base/share/classes/java/lang/Integer.java 
b/src/java.base/share/classes/java/lang/Integer.java
index 1c5b3c414ba..d50748c369e 100644
--- a/src/java.base/share/classes/java/lang/Integer.java
+++ b/src/java.base/share/classes/java/lang/Integer.java
@@ -28,4 +28,5 @@
 import jdk.internal.misc.CDS;
 import jdk.internal.misc.VM;
+import jdk.internal.vm.ConstantSupport;
 import jdk.internal.vm.annotation.ForceInline;
 import jdk.internal.vm.annotation.IntrinsicCandidate;
@@ -416,4 +417,7 @@ private static void formatUnsignedIntUTF16(int val, int 
shift, byte[] buf, int l
 }
 
+@Stable
+static final String[] TO_STRINGS = { "-1", "0", "1" };
+
 /**
  * Returns a {@code String} object representing the
@@ -428,4 +432,8 @@ private static void formatUnsignedIntUTF16(int val, int 
shift, byte[] buf, int l
 @IntrinsicCandidate
 public static String toString(int i) {
+if (ConstantSupport.isCompileConstant(i) &&
+(i >= -1) && (i <= 1)) {
+return TO_STRINGS[i + 1];
+}
 int size = stringSize(i);
 if (COMPACT_STRINGS) {
diff --git a/test/micro/org/openjdk/bench/java/lang/Integers.java 
b/test/micro/org/openjdk/bench/java/lang/Integers.java
index 43ceb5d18d2..28248593a73 100644
--- a/test/micro/org/openjdk/bench/java/lang/Integers.java
+++ b/test/micro/org/openjdk/bench/java/lang/Integers.java
@@ -91,4 +91,18 @@ public void decode(Blackhole bh) {
 }
 
+@Benchmark
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+public String toStringConstYay() {
+return Integer.toString(0);
+}
+
+int v = 0;
+
+@Benchmark
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+public String toStringConstNope() {
+return Integer.toString(v);
+}
+
 /** Performs toString on small values, just a couple of digits. */
 @Benchmark



Benchmark  (size)  Mode  Cnt  Score 
Error   Units
Integers.toStringConstNope500  avgt   15  3,599 ?   
0,034   ns/op
Integers.toStringConstNope:gc.alloc.rate.norm 500  avgt   15 48,000 ?   
0,001B/op
Integers.toStringConstNope:gc.time500  avgt   15223,000 
   ms
Integers.toStringConstYay 500  avgt   15  0,568 ?   
0,046   ns/op
Integers.toStringConstYay:gc.alloc.rate.norm  500  avgt   15 ? 10?? 
 B/op


Think about it as simplifying/avoiding the need for full compiler intrinsics. I 
could, in principle, do this by intrinsifying `Integer.toString` completely, 
check the same `isCon`, and then either construct the access to some String 
constant, or arrange the call to actual toString slow path. That would not be 
as simple as doing the similar thing in plain Java, with just a little of 
compiler support in form of `ConstantSupport`.

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1910449450


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-25 Thread Maurizio Cimadamore
On Thu, 25 Jan 2024 12:52:21 GMT, Maurizio Cimadamore  
wrote:

>>> > Naive question: the right way to use this would be almost invariably be 
>>> > like this:
>>> > ```
>>> > if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
>>> > // fast-path
>>> > }
>>> > // slow path
>>> > ```
>>> > 
>>> > 
>>> > 
>>> >   
>>> > 
>>> > 
>>> >   
>>> > 
>>> > 
>>> > 
>>> >   
>>> > Right?
>>> 
>>> Yes, I think so.
>> 
>> But then whatever is in the fast path and `fooHasCertainStaticProperties` 
>> are never profiled because never executed by the interpreter or c1. So 
>> `fooHasCertainStaticProperties` will likely not be inlined and c2 will do a 
>> poor (or rather not as good as you'd like) job of compiling whatever is in 
>> the fast path.
>
>> > > Naive question: the right way to use this would be almost invariably be 
>> > > like this:
>> > > ```
>> > > if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
>> > > // fast-path
>> > > }
>> > > // slow path
>> > > ```
>> > > 
>> > > 
>> > > 
>> > >   
>> > > 
>> > > 
>> > >   
>> > > 
>> > > 
>> > > 
>> > >   
>> > > Right?
>> > 
>> > 
>> > Yes, I think so.
>> 
>> But then whatever is in the fast path and `fooHasCertainStaticProperties` 
>> are never profiled because never executed by the interpreter or c1. So 
>> `fooHasCertainStaticProperties` will likely not be inlined and c2 will do a 
>> poor (or rather not as good as you'd like) job of compiling whatever is in 
>> the fast path.
> 
> I suppose perhaps it is implied that `fooHasCertainStaticProperties` should 
> have `@ForceInline` ? But yes, there seems to be several assumptions in how 
> this logic is supposed to be used, and at the moment, it seems to me more of 
> a footgun than something actually useful (but I admit my ignorance on the 
> subject).

> @mcimadamore Yes this is hard to use apart from the simple cases. Considering 
> we have already used this technique in the `MethodHandle` implementation, I 
> think there are valid use cases.

I don't 100% buy the `MethodHandleImpl` analogy. In that case the check is not 
simply used to save a branch, but to spare spinning of a completely new lambda 
form. That is a very heavy operation. What I'm trying to say is that I'm not 
too sure how robust of a mechanism this is in the context of 
micro(nano?)-optimizations (such as the one you are considering).

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1910359911


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-25 Thread Quan Anh Mai
On Thu, 25 Jan 2024 12:52:21 GMT, Maurizio Cimadamore  
wrote:

>>> > Naive question: the right way to use this would be almost invariably be 
>>> > like this:
>>> > ```
>>> > if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
>>> > // fast-path
>>> > }
>>> > // slow path
>>> > ```
>>> > 
>>> > 
>>> > 
>>> >   
>>> > 
>>> > 
>>> >   
>>> > 
>>> > 
>>> > 
>>> >   
>>> > Right?
>>> 
>>> Yes, I think so.
>> 
>> But then whatever is in the fast path and `fooHasCertainStaticProperties` 
>> are never profiled because never executed by the interpreter or c1. So 
>> `fooHasCertainStaticProperties` will likely not be inlined and c2 will do a 
>> poor (or rather not as good as you'd like) job of compiling whatever is in 
>> the fast path.
>
>> > > Naive question: the right way to use this would be almost invariably be 
>> > > like this:
>> > > ```
>> > > if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
>> > > // fast-path
>> > > }
>> > > // slow path
>> > > ```
>> > > 
>> > > 
>> > > 
>> > >   
>> > > 
>> > > 
>> > >   
>> > > 
>> > > 
>> > > 
>> > >   
>> > > Right?
>> > 
>> > 
>> > Yes, I think so.
>> 
>> But then whatever is in the fast path and `fooHasCertainStaticProperties` 
>> are never profiled because never executed by the interpreter or c1. So 
>> `fooHasCertainStaticProperties` will likely not be inlined and c2 will do a 
>> poor (or rather not as good as you'd like) job of compiling whatever is in 
>> the fast path.
> 
> I suppose perhaps it is implied that `fooHasCertainStaticProperties` should 
> have `@ForceInline` ? But yes, there seems to be several assumptions in how 
> this logic is supposed to be used, and at the moment, it seems to me more of 
> a footgun than something actually useful (but I admit my ignorance on the 
> subject).

@mcimadamore Yes this is hard to use apart from the simple cases. Considering 
we have already used this technique in the `MethodHandle` implementation, I 
think there are valid use cases.

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1910271891


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-25 Thread Maurizio Cimadamore
On Thu, 25 Jan 2024 07:41:27 GMT, Roland Westrelin  wrote:

> > > Naive question: the right way to use this would be almost invariably be 
> > > like this:
> > > ```
> > > if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
> > > // fast-path
> > > }
> > > // slow path
> > > ```
> > > 
> > > 
> > > 
> > >   
> > > 
> > > 
> > >   
> > > 
> > > 
> > > 
> > >   
> > > Right?
> > 
> > 
> > Yes, I think so.
> 
> But then whatever is in the fast path and `fooHasCertainStaticProperties` are 
> never profiled because never executed by the interpreter or c1. So 
> `fooHasCertainStaticProperties` will likely not be inlined and c2 will do a 
> poor (or rather not as good as you'd like) job of compiling whatever is in 
> the fast path.

I suppose perhaps it is implied that `fooHasCertainStaticProperties` should 
have `@ForceInline` ? But yes, there seems to be several assumptions in how 
this logic is supposed to be used, and at the moment, it seems to me more of a 
footgun than something actually useful (but I admit my ignorance on the 
subject).

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1910140980


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-24 Thread Roland Westrelin
On Wed, 24 Jan 2024 18:56:15 GMT, Aleksey Shipilev  wrote:

> > Naive question: the right way to use this would be almost invariably be 
> > like this:
> > ```
> > if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
> > // fast-path
> > }
> > // slow path
> > ```
> > 
> > 
> > 
> >   
> > 
> > 
> >   
> > 
> > 
> > 
> >   
> > Right?
> 
> Yes, I think so.

But then whatever is in the fast path and `fooHasCertainStaticProperties` are 
never profiled because never executed by the interpreter or c1. So 
`fooHasCertainStaticProperties` will likely not be inlined and c2 will do a 
poor (or rather not as good as you'd like) job of compiling whatever is in the 
fast path.

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1909531426


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-24 Thread Quan Anh Mai
On Wed, 24 Jan 2024 18:56:15 GMT, Aleksey Shipilev  wrote:

>>> Naive question: the right way to use this would be almost invariably be 
>>> like this:
>>> 
>>> ```
>>> if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
>>> // fast-path
>>> }
>>> // slow path
>>> ```
>>> 
>>> Right? Then the expectation is that during interpreter and C1, 
>>> `isCompileConstant` always returns false, so we just never take the fast 
>>> path (but we probably still pay for the branch, right?). And, when we get 
>>> to C2 and this method is inlined, at this point we know that either `foo` 
>>> is constant or not. If it is constant we can check other conditions on foo 
>>> (which presumably is cheap because `foo` is constant) and maybe take the 
>>> fast-path. In both cases, there's no branch in the generated code because 
>>> we know "statically" when inlining if `foo` has the right shape or not. 
>>> Correct?
>> 
>> P.S. if this is correct, please consider adding something along those lines 
>> in the javadoc of `isCompileConstant`; as it stands it is a bit obscure to 
>> understand how this thing might be used, and what are the common pitfalls to 
>> avoid when using it.
>
>> Naive question: the right way to use this would be almost invariably be like 
>> this:
>> 
>> ```
>> if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
>> // fast-path
>> }
>> // slow path
>> ```
>> 
>> Right? 
> 
> Yes, I think so.
> 
>> Then the expectation is that during interpreter and C1, `isCompileConstant` 
>> always returns false, so we just never take the fast path (but we probably 
>> still pay for the branch, right?). 
> 
> Yes, I think so. For C1, we would still prune the "dead" path, because C1 is 
> able to know that `if (false)` is never taken. We do pay with the branch and 
> the method call in interpreter. (There are ways to special-case these 
> intrinsics for interpreter too, if we choose to care.)
> 
>> And, when we get to C2 and this method is inlined, at this point we know 
>> that either `foo` is constant or not. If it is constant we can check other 
>> conditions on foo (which presumably is cheap because `foo` is constant) and 
>> maybe take the fast-path. In both cases, there's no branch in the generated 
>> code because we know "statically" when inlining if `foo` has the right shape 
>> or not. Correct?
> 
> Yes. I think the major use would be using `constexpr`-like code on "const" 
> path, so that the entire code constant-folds completely, _or_ just compiles 
> to branch-less "generic" version. In [my 
> experiments](https://github.com/openjdk/jdk/pull/17527#issuecomment-1906379544)
>  with `Integer.toString` that certainly happens. But that is not a 
> requirement, and we could probably still reap some benefits from partial 
> constant folds; but at that point we would need to prove that a "partially 
> const" path is better than generic "non-const" path under the same conditions.
> 
> I agree it would be convenient to put some examples in javadoc. @merykitty, I 
> can help you with that, if you want.

@shipilev I can come up with 2 examples that are pretty generic:

void checkIndex(int index, int length) {
boolean indexPositive = index >= 0;
if (ConstantSupport.isCompileConstant(indexPositive) && indexPositive) {
if (index >= length) {
throw;
}
return;
}
if (length < 0 || Integer.compareUnsigned(index, length) >= 0) {
throw;
}
}

bool equals(Point p1, Point p2) {
idEqual = p1 == p2;
if (ConstantSupport.isCompileConstant(idEqual) && idEqual) {
return true;
}
return p1.x == p2.x && p1.y == p2.y;
}

@mcimadamore Yes I believe your expectations are correct. Pitfalls may vary 
case-by-case, but I just realised that since we do not have profile information 
in the fast path, the compiler may be less willingly to inline the callees 
here. While it has not been an issue, a solution I can think of is to have 
something like `ConstantSupport::evaluate` in which the compiler will try to 
inline infinitely expecting constant-folding similar to how a `constexpr` 
variable behaves in C++ (and maybe bail-out compilation if the final result is 
not a constant, too).

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1909272480


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-24 Thread Aleksey Shipilev
On Wed, 24 Jan 2024 18:51:27 GMT, Maurizio Cimadamore  
wrote:

> Naive question: the right way to use this would be almost invariably be like 
> this:
> 
> ```
> if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
> // fast-path
> }
> // slow path
> ```
> 
> Right? 

Yes, I think so.

> Then the expectation is that during interpreter and C1, `isCompileConstant` 
> always returns false, so we just never take the fast path (but we probably 
> still pay for the branch, right?). 

Yes, I think so. For C1, we would still prune the "dead" path, because C1 is 
able to know that `if (false)` is never taken. We do pay with the branch and 
the method call in interpreter. (There are ways to special-case these 
intrinsics for interpreter too, if we choose to care.)

> And, when we get to C2 and this method is inlined, at this point we know that 
> either `foo` is constant or not. If it is constant we can check other 
> conditions on foo (which presumably is cheap because `foo` is constant) and 
> maybe take the fast-path. In both cases, there's no branch in the generated 
> code because we know "statically" when inlining if `foo` has the right shape 
> or not. Correct?

Yes. I think the major use would be using `constexpr`-like code on "const" 
path, so that the entire "const" branch constant-folds completely. In [my 
experiments](https://github.com/openjdk/jdk/pull/17527#issuecomment-1906379544) 
with `Integer.toString` that certainly happens. But that is not a requirement, 
and we could probably still reap some benefits from partial constant folds; but 
at that point we would need to prove that a "partially const" path is better 
than generic "non-const" path under the same conditions.

I agree it would be convenient to put some examples in javadoc.

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1908736651


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-24 Thread Maurizio Cimadamore
On Wed, 24 Jan 2024 18:48:03 GMT, Maurizio Cimadamore  
wrote:

> Naive question: the right way to use this would be almost invariably be like 
> this:
> 
> ```
> if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
> // fast-path
> }
> // slow path
> ```
> 
> Right? Then the expectation is that during interpreter and C1, 
> `isCompileConstant` always returns false, so we just never take the fast path 
> (but we probably still pay for the branch, right?). And, when we get to C2 
> and this method is inlined, at this point we know that either `foo` is 
> constant or not. If it is constant we can check other conditions on foo 
> (which presumably is cheap because `foo` is constant) and maybe take the 
> fast-path. In both cases, there's no branch in the generated code because we 
> know "statically" when inlining if `foo` has the right shape or not. Correct?

P.S. if this is correct, please consider adding something along those lines in 
the javadoc of `isCompileConstant`; as it stands it is a bit obscure to 
understand how this thing might be used, and what are the common pitfalls to 
avoid when using it.

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1908729766


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-24 Thread Aleksey Shipilev
On Wed, 24 Jan 2024 10:33:05 GMT, Quan Anh Mai  wrote:

>> Hi,
>> 
>> This patch introduces `JitCompiler::isConstantExpression` which can be used 
>> to statically determine whether an expression has been constant-folded by 
>> the Jit compiler, leading to more constant-folding opportunities. For 
>> example, it can be used in `MemorySessionImpl::checkValidStateRaw` to 
>> eliminate the lifetime check on global sessions without imposing additional 
>> branches on other non-global sessions. This is similar to 
>> `__builtin_constant_p` in GCC and clang.
>> 
>> Please kindly give your opinion as well as your reviews, thanks very much.
>
> Quan Anh Mai has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   address reviews

src/java.base/share/classes/jdk/internal/vm/ConstantSupport.java line 32:

> 30: /**
> 31:  * Just-in-time-compiler-related queries
> 32:  */

This looks like a stale comment.

-

PR Review Comment: https://git.openjdk.org/jdk/pull/17527#discussion_r1465397036


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-24 Thread Maurizio Cimadamore
On Wed, 24 Jan 2024 10:33:05 GMT, Quan Anh Mai  wrote:

>> Hi,
>> 
>> This patch introduces `JitCompiler::isConstantExpression` which can be used 
>> to statically determine whether an expression has been constant-folded by 
>> the Jit compiler, leading to more constant-folding opportunities. For 
>> example, it can be used in `MemorySessionImpl::checkValidStateRaw` to 
>> eliminate the lifetime check on global sessions without imposing additional 
>> branches on other non-global sessions. This is similar to 
>> `__builtin_constant_p` in GCC and clang.
>> 
>> Please kindly give your opinion as well as your reviews, thanks very much.
>
> Quan Anh Mai has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   address reviews

Naive question: the right way to use this would be almost invariably be like 
this:

if (isCompileConstant(foo) && fooHasCertainStaticProperties(foo)) {
// fast-path
}
// slow path


Right? Then the expectation is that during interpreter and C1, 
`isCompileConstant` always returns false, so we just never take the fast path 
(but we probably still pay for the branch, right?). And, when we get to C2 and 
this method is inlined, at this point we know that either `foo` is constant or 
not. If it is constant we can check other conditions on foo (which presumably 
is cheap because `foo` is constant) and maybe take the fast-path. In both 
cases, there's no branch in the generated code because we know "statically" 
when inlining if `foo` has the right shape or not. Correct?

-

PR Comment: https://git.openjdk.org/jdk/pull/17527#issuecomment-1908724632


Re: RFR: 8324433: Introduce a way to determine if an expression is evaluated as a constant by the Jit compiler [v6]

2024-01-24 Thread Quan Anh Mai
> Hi,
> 
> This patch introduces `JitCompiler::isConstantExpression` which can be used 
> to statically determine whether an expression has been constant-folded by the 
> Jit compiler, leading to more constant-folding opportunities. For example, it 
> can be used in `MemorySessionImpl::checkValidStateRaw` to eliminate the 
> lifetime check on global sessions without imposing additional branches on 
> other non-global sessions. This is similar to `__builtin_constant_p` in GCC 
> and clang.
> 
> Please kindly give your opinion as well as your reviews, thanks very much.

Quan Anh Mai has updated the pull request incrementally with one additional 
commit since the last revision:

  address reviews

-

Changes:
  - all: https://git.openjdk.org/jdk/pull/17527/files
  - new: https://git.openjdk.org/jdk/pull/17527/files/3ecb2c66..b4445e2e

Webrevs:
 - full: https://webrevs.openjdk.org/?repo=jdk&pr=17527&range=05
 - incr: https://webrevs.openjdk.org/?repo=jdk&pr=17527&range=04-05

  Stats: 36 lines in 4 files changed: 10 ins; 0 del; 26 mod
  Patch: https://git.openjdk.org/jdk/pull/17527.diff
  Fetch: git fetch https://git.openjdk.org/jdk.git pull/17527/head:pull/17527

PR: https://git.openjdk.org/jdk/pull/17527