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
