On Mon, 9 Nov 2026 05:52:38 GMT, Eric Fang <[email protected]> wrote:

>> The expression `(A & B) ^ B` is algebraically equivalent to `~A & B`, which 
>> maps directly to the AArch64 `BIC` (bit clear) instruction.
>> 
>> Now the expression is lowered as two operations `AndV` and `XorV`, this 
>> patch adds C2 match rules to fold this pattern into a single `BIC` for both 
>> integral vectors and vector masks on AArch64 (NEON and SVE). JMH 
>> micro-benchmarks demonstrate a significant performance improvement.
>> 
>> On a Nvidia Grace (Neoverse-V2) machine with 128-bit SVE2:
>> <html xmlns:v="urn:schemas-microsoft-com:vml"
>> xmlns:o="urn:schemas-microsoft-com:office:office"
>> xmlns:x="urn:schemas-microsoft-com:office:excel"
>> xmlns="http://www.w3.org/TR/REC-html40";>
>> 
>> <head>
>> 
>> <meta name=ProgId content=Excel.Sheet>
>> <meta name=Generator content="Microsoft Excel 15">
>> <link id=Main-File rel=Main-File
>> href="file:////Users/erfang/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip.htm">
>> <link rel=File-List
>> href="file:////Users/erfang/Library/Group%20Containers/UBF8T346G9.Office/TemporaryItems/msohtmlclip/clip_filelist.xml">
>> 
>> </head>
>> 
>> <body link="#467886" vlink="#96607D">
>> 
>> 
>> Benchmark | size | Unit | Before | Error | After | Error | Uplift
>> -- | -- | -- | -- | -- | -- | -- | --
>> LogicOperationsBenchmark.byteNotAnd | 1024 | ops/ms | 20769.0 | 49.6 | 
>> 52065.1 | 684.0 | 2.51
>> LogicOperationsBenchmark.intNotAnd | 1024 | ops/ms | 3576.6 | 0.4 | 7980.8 | 
>> 4.0 | 2.23
>> LogicOperationsBenchmark.longNotAnd | 1024 | ops/ms | 1699.1 | 0.6 | 3575.1 
>> | 0.6 | 2.10
>> LogicOperationsBenchmark.shortNotAnd | 1024 | ops/ms | 7986.0 | 3.4 | 
>> 20523.8 | 13.6 | 2.57
>> MaskLogicOperationsBenchmark.byteMaskNotAnd | 256 | ops/ms | 82165.6 | 70.1 
>> | 108802.4 | 169.4 | 1.32
>> MaskLogicOperationsBenchmark.byteMaskNotAnd | 512 | ops/ms | 42859.7 | 27.1 
>> | 59531.1 | 65.5 | 1.39
>> MaskLogicOperationsBenchmark.byteMaskNotAnd | 1024 | ops/ms | 21703.1 | 9.3 
>> | 31475.2 | 5.5 | 1.45
>> MaskLogicOperationsBenchmark.intMaskNotAnd | 256 | ops/ms | 15467.4 | 50.9 | 
>> 27138.2 | 76.1 | 1.75
>> MaskLogicOperationsBenchmark.intMaskNotAnd | 512 | ops/ms | 7951.4 | 14.9 | 
>> 13962.2 | 4.9 | 1.76
>> MaskLogicOperationsBenchmark.intMaskNotAnd | 1024 | ops/ms | 3988.2 | 4.1 | 
>> 6876.1 | 2.1 | 1.72
>> MaskLogicOperationsBenchmark.longMaskNotAnd | 256 | ops/ms | 7933.8 | 6.4 | 
>> 13933.2 | 6.6 | 1.76
>> MaskLogicOperationsBenchmark.longMaskNotAnd | 512 | ops/ms | 3977.9 | 4.0 | 
>> 6865.8 | 4.2 | 1.73
>> MaskLogicOperationsBenchmark.longMaskNotAnd | 1024 | ops/ms | 1992.7 | 1...
>
> Eric Fang has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   Use explicit NodeTypeCon template arguments

@eme64 Thanks for your review. Some context may help explain the design.

The original implementation was vector-specific. During the earlier review, 
@merykitty pointed out that the same optimizations can apply to scalar, vector, 
and pvectmask nodes, and suggested sharing the optimization logic. We agreed 
not to add the scalar forms in this PR, but to make the implementation reusable 
by them.

So this PR only covers the VECTOR and PVECTOR parts to keep its functional 
change focused, but we can easily extend the optimization to `XorINode` and 
`XorLNode`. Supporting them would only require INT/LONG `NodeOps` 
specializations and calls from their existing `Ideal` methods; the optimization 
itself would remain unchanged.

Some optimizations (like this one) apply equally to scalar and vector nodes, so 
we need to decide where and how to do them. With the Vector API, vector nodes 
can already exist early before loop optimizations. Therefore, optimizing only 
the scalar form does not imply that the vector form will naturally benefit from 
the same optimization. In particular, IR produced directly by Vector API 
intrinsification may never have passed through the corresponding scalar nodes. 
Some optimizations therefore need to be applied explicitly to both scalar and 
vector forms.

I first encountered this problem while working on 
[JDK-8346964](https://bugs.openjdk.org/browse/JDK-8346964), that work was later 
put on hold. The same concern was raised again by @merykitty during the review 
of this PR, which led to the current shared framework.

The transformation in this PR is one example: `(A & B) ^ B -> ~A & B` applies 
to INT, LONG, VECTOR, and PVECTMASK, although this PR only adds the VECTOR and 
PVECTMASK parts. There are other existing C2 optimizations that appear to be 
candidates for sharing as well, for example:

* add/sub cancellation such as `(A + B) - B -> A`, currently implemented
  for scalar nodes but not unpredicated integral vector nodes;
* De Morgan transformations, currently duplicated between INT and LONG
  and also applicable lane-wise to unpredicated integral vectors and
  predicate masks;
* nested OR identities, currently implemented independently for scalar
  and vector nodes;
* reverse/reverse-bytes involution patterns such as `f(f(x)) -> x`,
  which currently have separate scalar and vector implementations.

I haven’t looked into it carefully, so this might not be accurate—though, then 
again, there are likely more such cases.

The type-specific restrictions can remain in `NodeOps<NT>`, while the common 
optimization logic is implemented only once. This is why I think a shared 
framework is useful rather than keeping every optimization inside a scalar- or 
vector-specific file.

The `GenericOpts` framework does not introduce a new optimization phase or 
registry. The existing `Ideal` and `Identity` methods remain the entry points. 
It only provides a compile-time mechanism for sharing an optimization across 
node types when their IR representations differ.

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

PR Review: https://git.openjdk.org/jdk/pull/32095#pullrequestreview-5852700255

Reply via email to