From d3073b53d34c47976191acaa0d07cd1d919f3251 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Sat, 30 May 2026 02:08:31 +0000 Subject: [PATCH 01/32] 8385610: Shenandoah: TestSoftMaxHeapSizeAvailableCalc should have more tolerance for other GC triggers Reviewed-by: kdnilsen, xpeng, syan, ruili --- .../gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java b/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java index e70f2f0849f..100666fe859 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java @@ -35,6 +35,7 @@ * -XX:ShenandoahGCMode=satb * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=adaptive + * -XX:ShenandoahLearningSteps=0 * TestSoftMaxHeapSizeAvailableCalc */ @@ -60,6 +61,7 @@ * -XX:+UseShenandoahGC -Xlog:gc=info * -XX:ShenandoahGCMode=generational * -XX:ShenandoahGCHeuristics=adaptive + * -XX:ShenandoahLearningSteps=0 * TestSoftMaxHeapSizeAvailableCalc * */ @@ -87,12 +89,13 @@ public class TestSoftMaxHeapSizeAvailableCalc { // Soft max: 512M, ShenandoahMinFreeThreshold: 10 (default), ShenandoahEvacReserve: 5 (default) // Soft max for mutator: 512M * (100.0 - 5) / 100 = 486.4M // Threshold to trigger gc: 486.4M - 512 * 10 / 100.0 = 435.2M, just above (300 + 100)M. - // Expect gc count to be less than 1 / sec. + // Expect gc count to be less than 1 / sec, but to allow for other trigger conditions (like allocation rate), + // we bump the max allowed gc count to 35. public static class Allocate { static final List longLived = new ArrayList<>(); public static void test() throws Exception { - final int expectedMaxGcCount = Integer.getInteger("expectedMaxGcCount", 30); + final int expectedMaxGcCount = Integer.getInteger("expectedMaxGcCount", 35); List collectors = ManagementFactory.getGarbageCollectorMXBeans(); java.lang.management.GarbageCollectorMXBean cycleCollector = null; for (java.lang.management.GarbageCollectorMXBean bean : collectors) { From 7b2845eab4011c4ceca01eecb8d8f920fe457d9d Mon Sep 17 00:00:00 2001 From: Ashay Rane <253344819+raneashay@users.noreply.github.com> Date: Mon, 1 Jun 2026 00:26:50 +0000 Subject: [PATCH 02/32] 8384087: Test tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MainTest.java fails Reviewed-by: almatvee, asemenyuk --- .../jdk/jpackage/internal/cli/MainTest.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MainTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MainTest.java index 93d22df4d26..69db18ccb3a 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MainTest.java +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/cli/MainTest.java @@ -50,6 +50,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.internal.util.OperatingSystem; import jdk.jpackage.internal.Globals; +import jdk.jpackage.internal.model.BundlingOperationDescriptor; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.ExecutableAttributesWithCapturedOutput; import jdk.jpackage.internal.model.JPackageException; @@ -491,8 +492,35 @@ public class MainTest extends JUnitAdapter { var os = OperatingSystem.current(); var exitCode = Main.run(os, () -> { - CliBundlingEnvironment bundlingEnv = JPackageMockUtils.createBundlingEnvironment(os); - return bundlingEnv; + return new CliBundlingEnvironment() { + @Override + public Optional defaultOperation() { + switch (os) { + case LINUX -> { + return Optional.of(StandardBundlingOperation.CREATE_LINUX_DEB.descriptor()); + } + case WINDOWS -> { + return Optional.of(StandardBundlingOperation.CREATE_WIN_MSI.descriptor()); + } + case MACOS -> { + return Optional.of(StandardBundlingOperation.CREATE_MAC_PKG.descriptor()); + } + default -> { + throw new AssertionError(); + } + } + } + + @Override + public void createBundle(BundlingOperationDescriptor op, Options cmdline) { + if (StandardBundlingOperation.CREATE_APP_IMAGE.contains(StandardBundlingOperation.valueOf(op).orElseThrow())) { + CliBundlingEnvironment bundlingEnv = JPackageMockUtils.createBundlingEnvironment(os); + bundlingEnv.createBundle(op, cmdline); + } else { + throw new AssertionError(); + } + } + }; }, new PrintWriter(stdout), new PrintWriter(stderr), args); return new ExecutionResult(lines(stdout.toString()), lines(stderr.toString()), exitCode); From 9489e12f42d34372fdeadc3461215552019d7083 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Mon, 1 Jun 2026 01:09:28 +0000 Subject: [PATCH 03/32] 8385656: Misplaced comment on enum value in jdk.internal.util.Architecture Reviewed-by: liach --- .../share/classes/jdk/internal/util/Architecture.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/util/Architecture.java b/src/java.base/share/classes/jdk/internal/util/Architecture.java index 4f193e75597..31f328c2ead 100644 --- a/src/java.base/share/classes/jdk/internal/util/Architecture.java +++ b/src/java.base/share/classes/jdk/internal/util/Architecture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -37,15 +37,15 @@ import java.util.Locale; * architecture values. */ public enum Architecture { - /* - * An unknown architecture not specifically named. - * The addrSize and ByteOrder values are those of the current architecture. - */ AARCH64(64, ByteOrder.LITTLE_ENDIAN), ARM(32, ByteOrder.LITTLE_ENDIAN), LOONGARCH64(64, ByteOrder.LITTLE_ENDIAN), MIPSEL(32, ByteOrder.LITTLE_ENDIAN), MIPS64EL(64, ByteOrder.LITTLE_ENDIAN), + /* + * An unknown architecture not specifically named. + * The addrSize and ByteOrder values are those of the current architecture. + */ OTHER(is64bit() ? 64 : 32, ByteOrder.nativeOrder()), PPC(32, ByteOrder.BIG_ENDIAN), PPC64(64, ByteOrder.BIG_ENDIAN), From 62843fcdcec870a920dadd30b5a9c4a695892ecf Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Mon, 1 Jun 2026 06:56:33 +0000 Subject: [PATCH 04/32] 8346420: C2: IfNode::fold_compares_helper() wrongly folds two CmpI nodes to a single CmpU node Reviewed-by: roland, qamai, galder --- src/hotspot/share/opto/ifnode.cpp | 727 ++++++++++++----- src/hotspot/share/opto/subnode.hpp | 7 +- .../rangechecks/TestFoldCompares.java | 346 +++++++++ .../rangechecks/TestFoldComparesFuzzer.java | 728 ++++++++++++++++++ 4 files changed, 1606 insertions(+), 202 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestFoldCompares.java create mode 100644 test/hotspot/jtreg/compiler/rangechecks/TestFoldComparesFuzzer.java diff --git a/src/hotspot/share/opto/ifnode.cpp b/src/hotspot/share/opto/ifnode.cpp index 9f99874d76a..347d63ef57c 100644 --- a/src/hotspot/share/opto/ifnode.cpp +++ b/src/hotspot/share/opto/ifnode.cpp @@ -654,6 +654,12 @@ Node* IfNode::up_one_dom(Node *curr, bool linear_only) { //------------------------------filtered_int_type-------------------------------- // Return a possibly more restrictive type for val based on condition control flow for an if +// +// Important: we only parse if val is on the lhs. This is a limitation, but it makes +// optimizations simpler. We rely on canonicalization to get us to this +// shape, which works well for comparisions with constants, as they are +// canonicalized to the rhs. This may not happen with variables, and so +// the optimization may not work for those cases, when val stays on the rhs. const TypeInt* IfNode::filtered_int_type(PhaseGVN* gvn, Node* val, Node* if_proj) { assert(if_proj && (if_proj->Opcode() == Op_IfTrue || if_proj->Opcode() == Op_IfFalse), "expecting an if projection"); @@ -663,11 +669,14 @@ const TypeInt* IfNode::filtered_int_type(PhaseGVN* gvn, Node* val, Node* if_proj BoolNode* bol = iff->in(1)->as_Bool(); if (bol->in(1) && bol->in(1)->is_Cmp()) { const CmpNode* cmp = bol->in(1)->as_Cmp(); + // Val is always the lhs of the comparision: val cmp2 if (cmp->in(1) == val) { + assert(cmp->Opcode() == Op_CmpI, "signed comparison required"); const TypeInt* cmp2_t = gvn->type(cmp->in(2))->isa_int(); if (cmp2_t != nullptr) { jint lo = cmp2_t->_lo; jint hi = cmp2_t->_hi; + // Negate the test if we are on the false branch. BoolTest::mask msk = if_proj->Opcode() == Op_IfTrue ? bol->_test._test : bol->_test.negate(); switch (msk) { case BoolTest::ne: { @@ -675,8 +684,12 @@ const TypeInt* IfNode::filtered_int_type(PhaseGVN* gvn, Node* val, Node* if_proj const TypeInt* val_t = gvn->type(val)->isa_int(); if (val_t != nullptr && !val_t->singleton() && cmp2_t->is_con()) { if (val_t->_lo == lo) { + // Condition leading to if_proj: val != val->lo + // val in [val->lo + 1, val->hi] return TypeInt::make(val_t->_lo + 1, val_t->_hi, val_t->_widen); } else if (val_t->_hi == hi) { + // Condition leading to if_proj: val != val->hi + // val in [val->lo, val->hi - 1] return TypeInt::make(val_t->_lo, val_t->_hi - 1, val_t->_widen); } } @@ -684,28 +697,38 @@ const TypeInt* IfNode::filtered_int_type(PhaseGVN* gvn, Node* val, Node* if_proj return nullptr; } case BoolTest::eq: + // Condition leading to if_proj: val == cmp2 + // val in cmp2_t return cmp2_t; case BoolTest::lt: - lo = TypeInt::INT->_lo; + // Condition leading to if_proj: val < cmp2 + // val in [min_int .. max(min_int, cmp2->_hi - 1)] + lo = min_jint; if (hi != min_jint) { hi = hi - 1; } break; case BoolTest::le: - lo = TypeInt::INT->_lo; + // Condition leading to if_proj: val <= cmp2 + // val in [min_int .. cmp2->_hi] + lo = min_jint; break; case BoolTest::gt: + // Condition leading to if_proj: val > cmp2 + // val in [min(cmp2->_lo + 1, max_int) .. max_int] if (lo != max_jint) { lo = lo + 1; } - hi = TypeInt::INT->_hi; + hi = max_jint; break; case BoolTest::ge: - // lo unchanged - hi = TypeInt::INT->_hi; + // Condition leading to if_proj: val >= cmp2 + // val in [cmp2->_lo .. max_int] + hi = max_jint; break; default: - break; + assert(false, "impossible case"); + return nullptr; } const TypeInt* rtn_t = TypeInt::make(lo, hi, cmp2_t->_widen); return rtn_t; @@ -902,219 +925,523 @@ bool IfNode::has_only_uncommon_traps(IfProjNode* proj, IfProjNode*& success, IfP return false; } -// Check that the 2 CmpI can be folded into as single CmpU and proceed with the folding -bool IfNode::fold_compares_helper(IfProjNode* proj, IfProjNode* success, IfProjNode* fail, PhaseIterGVN* igvn) { - Node* this_cmp = in(1)->in(1); - BoolNode* this_bool = in(1)->as_Bool(); - IfNode* dom_iff = proj->in(0)->as_If(); - BoolNode* dom_bool = dom_iff->in(1)->as_Bool(); - Node* lo = dom_iff->in(1)->in(1)->in(2); - Node* orig_lo = lo; - Node* hi = this_cmp->in(2); - Node* n = this_cmp->in(1); - IfProjNode* otherproj = proj->other_if_proj(); +// We are given the following code shape with two CmpI: +// +// n v1 +// | | +// cmp1 +// | +// entry bool1(test1) +// | | +// iff1 +// | \ +// middle fail1-------------+ +// | | +// | n v2 | +// | | | | +// maybe cmp2 | +// null-check | | +// | bool2(test2) | +// | | | +// iff2 | +// | \ v +// succ fail2----> go to same region +// or uncommon trap +// +// 1. In some cases, we can prove that succ cannot be reached, +// and we can fold away the iff2. Example: +// +// if (n < -1 && n > 1) { succ } else { fail } +// // 1st condition: n in [min_int .. -2] +// // 2nd condition: n in [2 .. max_int] +// // -> no overlap -> constant fold iff2 towards fail2 +// // +// // Equivalent, if we flip everything: +// if (n >= -1 || n <= 1) { fail } else { succ } +// +// 2. In other cases, we can replace the two CmpI with +// a single CmpU. We fold iff1 towards middle, and +// replace the iff2 condition with the CmpU. Example: +// +// if (n >= 0 && n < 10) { succ } else { fail } +// // transformed to: +// if (n = arr.length) { throw ArrayOutOfBoundsException } +// // transformed to: +// if (n >=u arr.length) { throw ArrayOutOfBoundsException } +// +// Note1: we assume that the CmpI nodes are canonicalized to the +// point where n is always on the lhs. This is a limitation, +// but as long as v1 and v2 are constants they will eventually +// be canonicalized to the rhs. For variables, this may not always +// happen. +// +// Note2: We are flexible about the IfProj nodes: middle and succ +// could both be either IfTrue or IfFalse. +// +// Note3: Surrounding code has a different naming scheme! +// In has_only_uncommon_traps, the path towards the +// uncommon trap (e.g. failed range check) is called +// "success", while the path that does not go to +// the uncommon trap (e.g. in-bounds access) is called +// "fail". I think that is counter-intuitive, so I now +// used a different naming scheme here. +// +// Return true iff we could perform one of the optimizations. +bool IfNode::fold_compares_helper(IfProjNode* middle, IfProjNode* fail2, IfProjNode* succ, PhaseIterGVN* igvn) { + assert(fail2->in(0) == this, "link iff2->fail2"); + assert(succ->in(0) == this, "link iff2->succ"); - const TypeInt* lo_type = IfNode::filtered_int_type(igvn, n, otherproj); - const TypeInt* hi_type = IfNode::filtered_int_type(igvn, n, success); + IfNode* iff1 = middle->in(0)->as_If(); + IfNode* iff2 = this; + BoolNode* bool1 = iff1->in(1)->as_Bool(); + BoolNode* bool2 = iff2->in(1)->as_Bool(); + CmpNode* cmp1 = bool1->in(1)->as_Cmp(); + CmpNode* cmp2 = bool2->in(1)->as_Cmp(); + assert(cmp1->Opcode() == Op_CmpI, "comparisons must be CmpI"); + assert(cmp2->Opcode() == Op_CmpI, "comparisons must be CmpI"); - BoolTest::mask lo_test = dom_bool->_test._test; - BoolTest::mask hi_test = this_bool->_test._test; - BoolTest::mask cond = hi_test; + IfProjNode* fail1 = middle->other_if_proj(); - PhaseTransform::SpeculativeProgressGuard progress_guard(igvn); - // convert: - // - // dom_bool = x {<,<=,>,>=} a - // / \ - // proj = {True,False} / \ otherproj = {False,True} - // / - // this_bool = x {<,<=} b - // / \ - // fail = {True,False} / \ success = {False,True} - // / - // - // (Second test guaranteed canonicalized, first one may not have - // been canonicalized yet) - // - // into: - // - // cond = (x - lo) {u,>=u} adjusted_lim - // / \ - // fail / \ success - // / - // + Node* v1 = cmp1->in(2); + Node* v2 = cmp2->in(2); + Node* n = cmp1->in(1); + assert(cmp2->in(1) == n, "n must be lhs in both CmpI"); - // Figure out which of the two tests sets the upper bound and which - // sets the lower bound if any. - Node* adjusted_lim = nullptr; - if (lo_type != nullptr && hi_type != nullptr && hi_type->_lo > lo_type->_hi && - hi_type->_hi == max_jint && lo_type->_lo == min_jint && lo_test != BoolTest::ne) { - assert((dom_bool->_test.is_less() && !proj->_con) || - (dom_bool->_test.is_greater() && proj->_con), "incorrect test"); - - // this_bool = < - // dom_bool = >= (proj = True) or dom_bool = < (proj = False) - // x in [a, b[ on the fail (= True) projection, b > a-1 (because of hi_type->_lo > lo_type->_hi test above): - // lo = a, hi = b, adjusted_lim = b-a, cond = (proj = True) or dom_bool = <= (proj = False) - // x in ]a, b[ on the fail (= True) projection, b > a: - // lo = a+1, hi = b, adjusted_lim = b-a-1, cond = = (proj = True) or dom_bool = < (proj = False) - // x in [a, b] on the fail (= True) projection, b+1 > a-1: - // lo = a, hi = b, adjusted_lim = b-a+1, cond = (proj = True) or dom_bool = <= (proj = False) - // x in ]a, b] on the fail (= True) projection b+1 > a: - // lo = a+1, hi = b, adjusted_lim = b-a, cond = transform(new AddINode(lo, igvn->intcon(1))); + // Optimization 1: try to prove that succ is not reachable. + // Which values of n can pass iff1 to middle AND iff2 to succ? + const TypeInt* type_middle = filtered_int_type(igvn, n, middle); + if (type_middle != nullptr) { + const TypeInt* type_succ = filtered_int_type(igvn, n, succ); + if (type_succ != nullptr) { + if (type_middle->filter(type_succ) == Type::TOP) { + // The intersection is empty -> succ is not reachable. + // Fold iff2 towards fail2 (and away from succ). + igvn->replace_input_of(iff2, 1, igvn->intcon(fail2->_con)); + return true; // success: succ not reachable } - } else if (hi_test == BoolTest::le) { - if (lo_test == BoolTest::ge || lo_test == BoolTest::lt) { - adjusted_lim = igvn->transform(new SubINode(hi, lo)); - adjusted_lim = igvn->transform(new AddINode(adjusted_lim, igvn->intcon(1))); - cond = BoolTest::lt; - } else if (lo_test == BoolTest::gt || lo_test == BoolTest::le) { - adjusted_lim = igvn->transform(new SubINode(hi, lo)); - lo = igvn->transform(new AddINode(lo, igvn->intcon(1))); - cond = BoolTest::lt; - } else { - assert(false, "unhandled lo_test: %d", lo_test); - return false; - } - } else { - assert(igvn->_worklist.member(in(1)) && in(1)->Value(igvn) != igvn->type(in(1)), "unhandled hi_test: %d", hi_test); - return false; } - // this test was canonicalized - assert(this_bool->_test.is_less() && fail->_con, "incorrect test"); - } else if (lo_type != nullptr && hi_type != nullptr && lo_type->_lo > hi_type->_hi && - lo_type->_hi == max_jint && hi_type->_lo == min_jint && lo_test != BoolTest::ne) { + } - // this_bool = < - // dom_bool = < (proj = True) or dom_bool = >= (proj = False) - // x in [b, a[ on the fail (= False) projection, a > b-1 (because of lo_type->_lo > hi_type->_hi above): - // lo = b, hi = a, adjusted_lim = a-b, cond = >=u - // dom_bool = <= (proj = True) or dom_bool = > (proj = False) - // x in [b, a] on the fail (= False) projection, a+1 > b-1: - // lo = b, hi = a, adjusted_lim = a-b+1, cond = >=u - // lo = b, hi = a, adjusted_lim = a-b, cond = >u doesn't work because a = b - 1 is possible, then b-a = -1 - // this_bool = <= - // dom_bool = < (proj = True) or dom_bool = >= (proj = False) - // x in ]b, a[ on the fail (= False) projection, a > b: - // lo = b+1, hi = a, adjusted_lim = a-b-1, cond = >=u - // dom_bool = <= (proj = True) or dom_bool = > (proj = False) - // x in ]b, a] on the fail (= False) projection, a+1 > b: - // lo = b+1, hi = a, adjusted_lim = a-b, cond = >=u - // lo = b+1, hi = a, adjusted_lim = a-b-1, cond = >u doesn't work because a = b is possible, then b-a-1 = -1 + // Optimization 2: try to replace the two CmpI with one CmpU + // We can handle the following 4 cases: + // Input: two CmpI Output: one CmpU Assumption + // -------------------- ------------------------- ------------------- + // a) (n > lo && n < hi) -> n - lo - 1 2 && n < 5 ) n - 3 lo && n <= hi) -> n - lo - 1 2 && n <= 5 ) n - 3 = lo && n < hi) -> n - lo = 2 && n < 5 ) n - 2 = lo && n <= hi) -> n - lo <=u hi - lo (assuming lo <= hi) + // (n >= 2 && n <= 5 ) n - 2 <=u 3 + // range: [2, 3, 4, 5] + // + // Note1: the rhs of the CmpU indicates the cardinality of the range, + // allowing n to have exactly that many different values. + // + // Note2: all 4 case have an assumption: lo must be sufficiently smaller + // than hi. Below, and with the use of Lemma1 from below, we will + // prove that this implies that the rhs of the CmpU never + // underflows or overflows, which is critical for correctness. + // + // Below, we will prove and implement each of these cases. But first, + // we must handle the combinations of IfTrue/IfFalse projections for + // middle and succ, and extract which one is the lower bound (lo) and + // which one the upper bound (hi). + // + // <---- lower bound -----> <----------- succ -------------> <---- upper bound -----> + // [min_int .. lo_type->hi] [lo_type->hi+1 .. hi_type->lo-1] [hi_type->lo .. max_int] + // ^ ^ + // n {>/>=} lo n { <------------ unsigned upper bound -------------> + // [0 .. ] [ .. max_uint] + // ^ + // CmpU - swap(lo, hi); - swap(lo_type, hi_type); - swap(lo_test, hi_test); + BoolTest::mask test1 = bool1->_test._test; + BoolTest::mask test2 = bool2->_test._test; + if (middle->Opcode() == Op_IfFalse) { test1 = BoolTest::negate_mask(test1); } + if (succ->Opcode() == Op_IfFalse) { test2 = BoolTest::negate_mask(test2); } - assert((dom_bool->_test.is_less() && proj->_con) || - (dom_bool->_test.is_greater() && !proj->_con), "incorrect test"); - - cond = (hi_test == BoolTest::le || hi_test == BoolTest::gt) ? BoolTest::gt : BoolTest::ge; - - if (lo_test == BoolTest::lt) { - if (hi_test == BoolTest::lt || hi_test == BoolTest::ge) { - cond = BoolTest::ge; - } else if (hi_test == BoolTest::le || hi_test == BoolTest::gt) { - adjusted_lim = igvn->transform(new SubINode(hi, lo)); - adjusted_lim = igvn->transform(new AddINode(adjusted_lim, igvn->intcon(1))); - cond = BoolTest::ge; - } else { - assert(false, "unhandled hi_test: %d", hi_test); - return false; - } - } else if (lo_test == BoolTest::le) { - if (hi_test == BoolTest::lt || hi_test == BoolTest::ge) { - lo = igvn->transform(new AddINode(lo, igvn->intcon(1))); - cond = BoolTest::ge; - } else if (hi_test == BoolTest::le || hi_test == BoolTest::gt) { - adjusted_lim = igvn->transform(new SubINode(hi, lo)); - lo = igvn->transform(new AddINode(lo, igvn->intcon(1))); - cond = BoolTest::ge; - } else { - assert(false, "unhandled hi_test: %d", hi_test); - return false; - } - } else { - assert(igvn->_worklist.member(in(1)) && in(1)->Value(igvn) != igvn->type(in(1)), "unhandled lo_test: %d", lo_test); - return false; - } - // this test was canonicalized - assert(this_bool->_test.is_less() && !fail->_con, "incorrect test"); + Node* lo = nullptr; + Node* hi = nullptr; + const TypeInt* lo_type = nullptr; + const TypeInt* hi_type = nullptr; + BoolTest::mask lo_test = BoolTest::illegal; + BoolTest::mask hi_test = BoolTest::illegal; + if (BoolTest::is_greater(test1) && BoolTest::is_less(test2)) { + lo = v1; + hi = v2; + lo_type = IfNode::filtered_int_type(igvn, n, fail1); + hi_type = IfNode::filtered_int_type(igvn, n, fail2); + lo_test = test1; + hi_test = test2; + } else if (BoolTest::is_less(test1) && BoolTest::is_greater(test2)) { + lo = v2; + hi = v1; + lo_type = IfNode::filtered_int_type(igvn, n, fail2); + hi_type = IfNode::filtered_int_type(igvn, n, fail1); + lo_test = test2; + hi_test = test1; } else { - const TypeInt* failtype = filtered_int_type(igvn, n, proj); - if (failtype != nullptr) { - const TypeInt* type2 = filtered_int_type(igvn, n, fail); - if (type2 != nullptr) { - if (failtype->filter(type2) == Type::TOP) { - // previous if determines the result of this if so - // replace Bool with constant - igvn->replace_input_of(this, 1, igvn->intcon(success->_con)); - progress_guard.commit(); - return true; - } - } - } + // Could not find upper and lower bound. + return false; + } + assert(BoolTest::is_greater(lo_test), "lower bound: n {>/>=} lo"); + assert(BoolTest::is_less(hi_test), "upper bound: n {_hi != max_jint || + lo_type->_lo != min_jint) { + // Upper and lower bounds could not be established. return false; } - assert(lo != nullptr && hi != nullptr, "sanity"); - Node* hook = new Node(lo); // Add a use to lo to prevent him from dying - // Merge the two compares into a single unsigned compare by building (CmpU (n - lo) (hi - lo)) - Node* adjusted_val = igvn->transform(new SubINode(n, lo)); - if (adjusted_lim == nullptr) { - adjusted_lim = igvn->transform(new SubINode(hi, lo)); - } - hook->destruct(igvn); + // ------------------------------------------------------------------- + // In the proofs below, we need some basic Lemmas to deal with integer + // signed and unsigned arithmetic. + // + // Lemma1: + // Let a and b be in [min_int .. max_int]. + // If a >=s b, then: + // U(a - b) = a - b + // + // Proof: + // a >= b + // -> a - b >= 0 + // + // a <= max_int + // b >= min_int + // -> a - b <= max_int - min_int = 2^32-1 + // + // 0 <= a - b <= 2^32-1 + // -> cast to unsigned has no overflow + // -> U(a - b) = a - b + // + // Lemma2: + // Let a and b be in [min_int .. max_int]. + // If a a - b < 0 + // + // a >= min_int + // b <= max_int + // -> a - b >= min_int - max_int = 2^32-1 + // + // 2^32-1 <= a - b < 0 + // -> cast to unsigned leads to exactly one overflow + // -> U(a - b) = a - b + 2^32 + // + // Lemma3: + // Let a and b be in [min_int .. max_int]. + // a + 2^32 > b + // + // Proof: + // Using a >= min_int, and b <= max_int: + // a + 2^32 >= min_int + 2^32 + // = max_int + 1 + // >= b + 1 + // > b + // ------------------------------------------------------------------- - if (adjusted_val->is_top() || adjusted_lim->is_top()) { - return false; + // Handle the 4 cases. + // All produce this form: n - lo + x1 hi - lo + x2 + Node* x1 = nullptr; + Node* x2 = nullptr; + BoolTest::mask cond = BoolTest::illegal; + if (lo_test == BoolTest::gt && hi_test == BoolTest::lt) { + // We perform the the (CHECK) below, which implies (LO-HI), + // as we will show below. + if (lo_type->_hi >= hi_type->_lo) { + return false; // (CHECK) fails, we cannot establish (LO-HI) assumption. + } + // a) (n > lo && n < hi) -> n - lo - 1 _hi] for n <= lo + // -> lo_type->_hi = lo->_hi + // hi_type = [hi->_lo .. max_int] for n >= lo + // -> hi_type->_lo = hi->_lo + // We will need the assumption (LO-HI) below, which we can + // establish with the following (CHECK): + // lo_type->_hi < hi_type->_lo (CHECK) + // -> lo->_hi < hi->_lo + // -> lo < hi (LO-HI) + // + // Case n <= lo: + // (BEFORE) is always false, show (AFTER) is always false. + // Since lo < hi (LO-HI), S(lo+1) = lo+1 (no overflow): + // -> lo+1 <= hi + // -> n < lo+1 + // U(n - (lo + 1)) < U(hi - (lo + 1)) + // -- Lemma2 (n < lo+1) -- -- Lemma1 (lo+1 <= hi) -- + // n - (lo + 1) + 2^32 < hi - (lo + 1) + // n + 2^32 < hi + // Always false by Lemma3. + // + // Case lo < n < hi: + // (BEFORE) is always true, show (AFTER) is always true. + // Since lo < hi (LO-HI), S(lo+1) = lo+1 (no overflow): + // -> lo+1 <= hi + // -> n >= lo+1 + // U(n - (lo + 1)) < U(hi - (lo + 1)) + // -- Lemma1 (n >= lo+1) -- -- Lemma1 (lo+1 <= hi) -- + // n - (lo + 1) < hi - (lo + 1) + // n < hi + // Corresponds to case assumption, so always true. + // + // Case n >= hi: + // (BEFORE) is always false, show (AFTER) is always false. + // Since lo < hi (LO-HI), S(lo+1) = lo+1 (no overflow): + // -> lo+1 <= hi + // U(n - (lo + 1)) < U(hi - (lo + 1)) + // -- Lemma1 (n >= lo+1) -- -- Lemma1 (lo+1 <= hi) -- + // n - (lo + 1) < hi - (lo + 1) + // n < hi + // Contradicts case assumption, so always false. + // QED. + // + // Note: we cannot use anything more relaxed than the assumption + // lo < hi: with lo=hi the rhs of the CmpU would underflow. + // + // Produce form: n - lo + x1 hi - lo + x2 + // n - lo - 1 intcon(-1); + x2 = igvn->intcon(-1); + cond = BoolTest::lt; + } else if (lo_test == BoolTest::gt && hi_test == BoolTest::le) { + // We perform the the (CHECK) below, which implies (LO-HI), + // as we will show below. + if (lo_type->_hi >= hi_type->_lo) { + return false; // (CHECK) fails, we cannot establish (LO-HI) assumption. + } + // b) (n > lo && n <= hi) -> n - lo - 1 _hi] for n <= lo + // -> lo_type->_hi = lo->_hi + // hi_type = [min(hi->_lo+1, max_int) .. max_int] for n > hi + // -> hi_type->_lo <= lo->_lo + 1 + // We will need the assumption (LO-HI) below, which we can + // establish with the following (CHECK): + // lo_type->_hi < hi_type->_lo (CHECK) + // -> lo->_hi < hi->_lo + 1 + // -> lo < hi + 1 + // -> lo <= hi (LO-HI) + // + // Case A: lo = hi + // Let y = lo = hi + // -> n > lo && n <= hi vs n - lo - 1 n > y && n <= y vs n - y - 1 n < lo+1 + // U(n - (lo + 1)) < U(hi - lo) + // -- Lemma2 (n < lo+1) -- -- Lemma1 (lo <= hi, LO-HI) -- + // n - (lo + 1) + 2^32 < hi - lo + // n - 1 + 2^32 < hi + // n + 2^32 <= hi + // Always false by Lemma3. + // Note: To apply Lemma2 above, we must use (Case B), we + // could not have done it with (LO-HI) alone. + // + // Case lo < n <= hi: + // (BEFORE) is always true, show (AFTER) is always true. + // Since lo < hi (Case B), S(lo+1) = lo+1 (no overflow): + // -> n >= lo+1 + // U(n - (lo + 1)) < U(hi - lo) + // -- Lemma1 (n >= lo+1) -- -- Lemma1 (lo <= hi, LO-HI) -- + // n - (lo + 1) < hi - lo + // n - 1 < hi + // n <= hi + // Follows from case assumption, so always true. + // + // Case n > hi: + // (BEFORE) is always false, show (AFTER) is always false. + // Since lo < hi (Case B), S(lo+1) = lo+1 (no overflow): + // -> lo+1 <= hi + // -> n > lo+1 + // U(n - (lo + 1)) < U(hi - lo) + // -- Lemma1 (n > lo+1) -- -- Lemma1 (lo <= hi, LO-HI) -- + // n - (lo + 1) < hi - lo + // n - 1 < hi + // n <= hi + // Contradicts case assumption, so always false. + // QED. + // + // Note: we cannot use anything more relaxed than the assumption + // lo <= hi: with lo=hi+1 the rhs of the CmpU would underflow. + // + // Produce form: n - lo + x1 hi - lo + x2 + // n - lo - 1 intcon(-1); + x2 = igvn->intcon(0); + cond = BoolTest::lt; + } else if (lo_test == BoolTest::ge && hi_test == BoolTest::lt) { + // We perform the the (CHECK) below, which implies (LO-HI), + // as we will show below. + if (lo_type->_hi >= hi_type->_lo) { + return false; // (CHECK) fails, we cannot establish (LO-HI) assumption. + } + // c) (n >= lo && n < hi) -> n - lo _hi - 1)] for n < lo + // -> lo_type->_hi >= lo->_hi - 1 + // hi_type = [b->_lo .. max_int] for n >= hi + // -> hi_type->_lo = hi->_lo + // We will need the assumption (LO-HI) below, which we can + // establish with the following (CHECK): + // lo_type->_hi < hi_type->_lo + // -> lo->_hi - 1 < hi->_lo + // -> lo->_hi <= hi->_lo + // -> lo <= hi (HI-LO) + // + // Case n < lo: + // (BEFORE) is always false, show (AFTER) is always false. + // U(n - lo) < U(hi - lo) + // -- Lemma2 (n < lo) -- -- Lemma1 (lo <= hi, LO-HI) -- + // n - lo + 2^32 < hi - lo + // n + 2^32 < hi + // Always false by Lemma3. + // + // Case lo <=s n = lo) -- -- Lemma1 (lo <= hi, LO-HI) -- + // n - lo < hi - lo + // n < hi + // Follows from case assumption, so always true. + // + // Case n >=s hi: + // (BEFORE) is always false, show (AFTER) is always false. + // U(n - lo) < U(hi - lo) + // -- Lemma1 (n >= lo) -- -- Lemma1 (lo <= hi, LO-HI) -- + // n - lo < hi - lo + // n < hi + // Contradicts case assumption, so always false. + // QED. + // + /// Note: we cannot use anything more relaxed than the assumption + // lo <= hi: with lo=hi+1 the rhs of the CmpU would underflow. + // + // Produce form: n - lo + x1 hi - lo + x2 + // n - lo intcon(0); + x2 = igvn->intcon(0); + cond = BoolTest::lt; + } else { + assert (lo_test == BoolTest::ge && hi_test == BoolTest::le, ""); + // We perform the the (CHECK) below, which implies (LO-HI), + // as we will show below. + jlong lo_type_hi = lo_type->_hi; + jlong hi_type_lo = hi_type->_lo; + if (lo_type_hi >= hi_type_lo - 1) { + return false; // (CHECK) fails, we cannot establish (LO-HI) assumption. + } + // d) (n >= lo && n <= hi) -> n - lo <=u hi - lo (assuming lo <= hi) + // (BEFORE) (AFTER) (LO-HI) + // + // Proof: + // From IfNode::filtered_int_type, we get: + // lo_type = [min_int .. max(min_int, lo->_hi-1)] for n < lo + // -> lo_type->_hi >= lo->_hi - 1 + // hi_type = [min(hi->_lo+1, max_int) .. max_int] for n > hi + // -> hi_type->_lo <= hi->_lo + 1 + // We will need the assumption (LO-HI) below, which we can + // establish with the following (CHECK), which we must compute in + // long to avoid underflow: + // lo_type->_hi < hi_type->_lo - 1 (CHECK) + // -> lo_type->_hi + 1 <= hi_type->_lo - 1 + // -> lo->_hi <= hi->_lo + // -> lo <= hi (LO-HI) + // + // Case n = lo, LO-HI) -- + // n - lo + 2^32 <= hi - lo + // n + 2^32 <= hi + // Always false by Lemma3. + // + // Case lo <=s n <=s hi: + // (BEFORE) is always true, show (AFTER) is always true. + // U(n - lo) <= U(hi - lo) + // -- Lemma1 (n >= lo) -- -- Lemma1 (hi >= lo, LO-HI) -- + // n - lo <= hi - lo + // n <= hi + // Corresponds to case assumption, so always true. + // + // Case n >s hi: + // (BEFORE) is always false, show (AFTER) is always false. + // U(n - lo) <= U(hi - lo) + // -- Lemma1 (n > lo) -- -- Lemma1 (hi >= lo, LO-HI) -- + // n - lo <= hi - lo + // n <= hi + // n <= hi + // Contradicts case assumption, so always false. + // QED. + // + // Note: (CHECK) is stronger in this case than in (a, b, c). We have + // had multiple bugs around this case (d) in the past. For example: + // - Before JDK-8135069: transform into: n - lo <=u hi - lo + // leads to rhs underflow with lo=0 and hi=-1 + // -> we are coming back to this solution, but instead + // of checking lo_type->_hi < hi_type->_lo + // we now check: lo_type->_hi < hi_type->_lo - 1 + // which implies lo <= hi and excludes this bad case. + // - Before JDK-8346420: transform into: n - lo hi - lo + x2 + // n - lo <=u hi - lo + x1 = igvn->intcon(0); + x2 = igvn->intcon(0); + cond = BoolTest::le; } - if (igvn->type(adjusted_lim)->is_int()->_lo < 0 && - !igvn->C->post_loop_opts_phase()) { - // If range check elimination applies to this comparison, it includes code to protect from overflows that may - // cause the main loop to be skipped entirely. Delay this transformation. - // Example: - // for (int i = 0; i < limit; i++) { - // if (i < max_jint && i > min_jint) {... - // } - // Comparisons folded as: - // i - min_jint - 1 outcnt() == 0) { - igvn->remove_dead_node(lo, PhaseIterGVN::NodeOrigin::Speculative); - } - if (adjusted_val->outcnt() == 0) { - igvn->remove_dead_node(adjusted_val, PhaseIterGVN::NodeOrigin::Speculative); - } - if (adjusted_lim->outcnt() == 0) { - igvn->remove_dead_node(adjusted_lim, PhaseIterGVN::NodeOrigin::Speculative); - } - igvn->C->record_for_post_loop_opts_igvn(this); - return false; - } - - Node* newcmp = igvn->transform(new CmpUNode(adjusted_val, adjusted_lim)); + // Construct the new check: n - lo + x1 hi - lo + x2 + Node* lhs = igvn->transform(new SubINode(n, lo)); + lhs = igvn->transform(new AddINode(lhs, x1)); + Node* rhs = igvn->transform(new SubINode(hi, lo)); + rhs = igvn->transform(new AddINode(rhs, x2)); + Node* newcmp = igvn->transform(new CmpUNode(lhs, rhs)); + if (succ->Opcode() == Op_IfFalse) { cond = BoolTest::negate_mask(cond); } Node* newbool = igvn->transform(new BoolNode(newcmp, cond)); - igvn->replace_input_of(dom_iff, 1, igvn->intcon(proj->_con)); - igvn->replace_input_of(this, 1, newbool); + // Fold iff1 towards middle, and replace the iff2 condition: + igvn->replace_input_of(iff1, 1, igvn->intcon(middle->_con)); + igvn->replace_input_of(iff2, 1, newbool); - progress_guard.commit(); - return true; + return true; // Success with CmpU } // Merge the branches that trap for this If and the dominating If into diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp index 29ec25b41f8..358508248d0 100644 --- a/src/hotspot/share/opto/subnode.hpp +++ b/src/hotspot/share/opto/subnode.hpp @@ -334,8 +334,11 @@ struct BoolTest { static mask negate_mask(mask btm) { return mask(btm ^ 4); } static mask unsigned_mask(mask btm); bool is_canonical( ) const { return (_test == BoolTest::ne || _test == BoolTest::lt || _test == BoolTest::le || _test == BoolTest::overflow); } - bool is_less( ) const { return _test == BoolTest::lt || _test == BoolTest::le; } - bool is_greater( ) const { return _test == BoolTest::gt || _test == BoolTest::ge; } + bool is_less( ) const { return is_less(_test); } + bool is_greater( ) const { return is_greater(_test); } + static bool is_less(mask btm) { return btm == BoolTest::lt || btm == BoolTest::le; } + static bool is_greater(mask btm) { return btm == BoolTest::gt || btm == BoolTest::ge; } + void dump_on(outputStream *st) const; mask merge(BoolTest other) const; }; diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestFoldCompares.java b/test/hotspot/jtreg/compiler/rangechecks/TestFoldCompares.java new file mode 100644 index 00000000000..bec3e442403 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestFoldCompares.java @@ -0,0 +1,346 @@ +/* + * 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 id=vanilla + * @bug 8346420 + * @summary Test logic in IfNode::fold_compares, which folds 2 signed comparisons + * into a single comparison. + * @library /test/lib / + * @run main ${test.main.class} + */ + +/* + * @test id=Xcomp + * @bug 8346420 + * @library /test/lib / + * @run main ${test.main.class} -Xcomp -XX:-TieredCompilation -XX:CompileCommand=compileonly,${test.main.class}::test* + */ + +package compiler.rangechecks; + +import compiler.lib.ir_framework.*; + +/** + * This test here is here to cover some basic cases of IfNode::fold_compares. It also contains the + * reproducers for JDK-8346420. We don't do any result verification, other than that we should never + * hit an Exception. For a test with result verification, see TestFoldComparesFuzzer.java + */ +public class TestFoldCompares { + public static boolean FLAG_FALSE = false; + + public static void main(String[] args) { + TestFramework framework = new TestFramework(); + framework.addFlags(args); + framework.start(); + } + + // ------------------------- Failing cases for JDK-8346420 ------------------------------ + + @Test + @Arguments(values = {Argument.NUMBER_42}) + // Reported overflow case with wrong result in JDK-8346420 + public static void test_Case3a_LTLE_overflow(int i) { + int minimum, maximum; + if (FLAG_FALSE) { + minimum = 0; + maximum = 1; + } else { + // Always goes to else-path + minimum = Integer.MIN_VALUE; + maximum = Integer.MAX_VALUE; + } + // i < INT_MIN || i > MAX_INT + // 42 < INT_MIN || 42 > MAX_INT + // false false + // => false + // + // C2 transforms this into: + // i - minimum >=u (maximum - minimum) + 1 + // 42 - INT_MIN >=u (INT_MAX - INT_MIN) + 1 + // 42 + MIN_INT >=u -1 + 1 + // ------ overflow ------- + // 42 + MIN_INT >=u 0 + // => true + if (i < minimum || i > maximum) { + throw new RuntimeException("i can never be outside [min_int, max_int]"); + } + } + + @Test + @Arguments(values = {Argument.NUMBER_42}) + // Same as test_Case3a_LTLE_overflow, just with swapped conditions (JDK-8346420). + public static void test_Case3b_LTLE_overflow(int i) { + int minimum, maximum; + if (FLAG_FALSE) { + minimum = 0; + maximum = 1; + } else { + // Always goes to else-path + minimum = Integer.MIN_VALUE; + maximum = Integer.MAX_VALUE; + } + if (i > maximum || i < minimum) { + throw new RuntimeException("i can never be outside [min_int, max_int]"); + } + } + + @Test + @Arguments(values = {Argument.NUMBER_42}) + // 22 ConI === 0 [[ 25 37 ]] #int:0 + // 35 ConI === 0 [[ 37 ]] #int:minint + // 33 ConI === 0 [[ 38 81 ]] #int:1 + // 37 Phi === 34 35 22 [[ 42 80 81 84 ]] #int:minint..0, 0u..maxint+1 + // 81 AddI === _ 37 33 [[ 82 ]] + // 82 Node === 81 [[ ]] <----- hook + // + // We hit this assert, found while working on JDK-8346420: + // "fatal error: no reachable node should have no use" + // + // Because we compute: + // lo = lo + 1 + // hook = Node(lo) + // adjusted_val = i - lo + // -> gvn transformed to: (i - lo) + -1 + // -> the "lo = lo + 1" AddI now is only used by the hook, + // but once the hook is destroyed, it has no use any more, + // and we hit the assert. + public static void test_Case4a_LELE_assert(int i) { + int minimum, maximum; + if (FLAG_FALSE) { + minimum = 0; + maximum = 1; + } else { + minimum = Integer.MIN_VALUE; + maximum = Integer.MAX_VALUE; + } + if (i <= minimum || i > maximum) { + throw new RuntimeException("should never be reached"); + } + } + + // ------------------- IR tests to check that optimization was performed ------------------------ + + // The following tests with constant bounds are expected to fold to a single CmpU. + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}) + @Arguments(values = {Argument.NUMBER_42}) + public static void test_lohi_ltle(int i) { + if (i < -100_000 || i > 100_000) { + throw new RuntimeException(); + } + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}) + @Arguments(values = {Argument.NUMBER_42}) + public static void test_lohi_lele(int i) { + if (i <= -100_000 || i > 100_000) { + throw new RuntimeException(); + } + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}) + @Arguments(values = {Argument.NUMBER_42}) + public static void test_lohi_ltlt(int i) { + if (i < -100_000 || i >= 100_000) { + throw new RuntimeException(); + } + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}) + @Arguments(values = {Argument.NUMBER_42}) + public static void test_lohi_lelt(int i) { + if (i <= -100_000 || i >= 100_000) { + throw new RuntimeException(); + } + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}) + @Arguments(values = {Argument.NUMBER_42}) + public static void test_hilo_ltle(int i) { + if (i >= 100_000 || i <= -100_000) { + throw new RuntimeException(); + } + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}) + @Arguments(values = {Argument.NUMBER_42}) + public static void test_hilo_lele(int i) { + if (i > 100_000 || i <= -100_000) { + throw new RuntimeException(); + } + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}) + @Arguments(values = {Argument.NUMBER_42}) + public static void test_hilo_lelt(int i) { + if (i > 100_000 || i < -100_000) { + throw new RuntimeException(); + } + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}) + @Arguments(values = {Argument.NUMBER_42}) + public static void test_hilo_ltlt(int i) { + if (i >= 100_000 || i < -100_000) { + throw new RuntimeException(); + } + } + + // The following tests can completely remove the test and branches, we can prove that + // the path cannot be taken. + + @Setup + public static Object[] range256(SetupInfo info) { + return new Object[]{info.invocationCounter() & 255}; + } + + @Setup + public static Object[] rangeM128P127(SetupInfo info) { + return new Object[]{(info.invocationCounter() & 255) - 128}; + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 0"}) + @Arguments(setup = "rangeM128P127") + // Case from JDK-8135069. We used to do the CmpI->CmpU trick, but we can also constant fold + // this directly! + public static void test_empty_0(int i) { + if (i < 0 || i > -1) { + return; // always success + } + throw new RuntimeException("should not be reached"); + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 0"}) + @Arguments(setup = "range256") + public static void test_empty_1(int i) { + if (i < 100 || i > 50) { + return; // always success + } + throw new RuntimeException("should not be reached"); + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 0"}) + @Arguments(setup = "range256") + public static void test_empty_2(int i) { + if (i <= 100 || i >= 101) { + return; // always success + } + throw new RuntimeException("should not be reached"); + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 1", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + // Note: the two CmpI->Bool pairs are already canonicallized and commoned to a single pair. + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 0"}) + @Arguments(setup = "range256") + public static void test_empty_3(int i) { + if (i <= 100 || i > 100) { + return; // always success + } + throw new RuntimeException("should not be reached"); + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 1", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + // Note: the two CmpI->Bool pairs are already canonicallized and commoned to a single pair. + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 0"}) + @Arguments(setup = "range256") + public static void test_empty_4(int i) { + if (i < 101 || i >= 101) { + return; // always success + } + throw new RuntimeException("should not be reached"); + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 0"}) + @Arguments(setup = "range256") + public static void test_empty_5(int i) { + if (i < 101 || i > 100) { + return; // always success + } + throw new RuntimeException("should not be reached"); + } + + // Now test that we can use a.length, which means we do a null-check + // and then a comparison with a LoadRange that has type int[>=0] + + public static int[] ARR = new int[256]; + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING, + applyIf = {"TieredCompilation", "true"}) // proxy for "not Xcomp" + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}, + applyIf = {"TieredCompilation", "true"}) // proxy for "not Xcomp" + @Arguments(setup = "range256") + // Note: cannot get optimized with Xcomp + static int test_array_length_and_null_check_1(int i) { + if (i < 0 || i >= ARR.length) { + return -1; // never happens + } + return i; + } + + @Check(test = "test_array_length_and_null_check_1") + public void check_test_array_length_and_null_check_1(int i) { + if (i < 0) { throw new RuntimeException("Wrong value: " + i); } + } + + @Test + @IR(counts = {IRNode.CMP_I, "= 2", IRNode.CMP_U, "= 0"}, phase = CompilePhase.AFTER_PARSING, + applyIf = {"TieredCompilation", "true"}) // proxy for "not Xcomp" + @IR(counts = {IRNode.CMP_I, "= 0", IRNode.CMP_U, "= 1"}, + applyIf = {"TieredCompilation", "true"}) // proxy for "not Xcomp" + @Arguments(setup = "range256") + // Note: cannot get optimized with Xcomp + static int test_array_length_and_null_check_2(int i) { + if (i < 0 || i >= ARR.length) { + throw new RuntimeException("never go out of bounds"); + } + return i; + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestFoldComparesFuzzer.java b/test/hotspot/jtreg/compiler/rangechecks/TestFoldComparesFuzzer.java new file mode 100644 index 00000000000..0467689eb17 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestFoldComparesFuzzer.java @@ -0,0 +1,728 @@ +/* + * 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 + * @bug 8346420 + * @summary Fuzz patterns for IfNode::fold_compares_helper + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @compile ../lib/ir_framework/TestFramework.java + * @compile ../lib/generators/Generators.java + * @compile ../lib/verify/Verify.java + * @run driver ${test.main.class} + */ + +package compiler.rangechecks; + +import java.util.List; +import java.util.ArrayList; +import java.util.Random; +import java.util.HashSet; +import java.util.Set; + +import jdk.test.lib.Utils; + +import compiler.lib.compile_framework.*; +import compiler.lib.generators.*; +import compiler.lib.template_framework.Template; +import compiler.lib.template_framework.TemplateToken; +import static compiler.lib.template_framework.Template.scope; +import static compiler.lib.template_framework.Template.let; +import static compiler.lib.template_framework.Template.$; + +import compiler.lib.template_framework.library.TestFrameworkClass; + +/** + * For more basic examples, see TestFoldCompares.java + * + * I'm only covering some basic cases to test the fundamental + * logic inside IfNode::fold_compares_helper. + * - TestMethodGeneratorConstIR does extensive result and IR verification + * for the cases a-d) in IfNode::fold_compares_helper, but only with + * constant lo and hi. + * - Other test generators currently don't have IR rules, but check + * correctness in various relevant scenarios I came across during + * the bugfix of JDK-8346420. + * - I'm also mixing signed and unsigned comparisons, just to ensure + * the less often used (and tested) unsigned comparisons don't slip + * through the cracks. + * + * In the future, we could add more cases: + * - Extend to long - though the optimization does not yet cover longs anyway. + * - More IR rules: difficult to make stable. Not all permutations are covered + * by the optimizations, edge-cases could make IR rules brittle. + */ +public class TestFoldComparesFuzzer { + private static final Random RANDOM = Utils.getRandomInstance(); + private static final RestrictableGenerator INT_GEN = Generators.G.ints(); + + public static void main(String[] args) { + // Create a new CompileFramework instance. + CompileFramework comp = new CompileFramework(); + + long t0 = System.nanoTime(); + // Add a java source file. + comp.addJavaSourceCode("compiler.rangecheck.templated.Generated", generate(comp)); + + long t1 = System.nanoTime(); + // Compile the source file. + comp.compile(); + + long t2 = System.nanoTime(); + + // Run the tests without any additional VM flags. + comp.invoke("compiler.rangecheck.templated.Generated", "main", new Object[] {new String[] {}}); + long t3 = System.nanoTime(); + + System.out.println("Code Generation: " + (t1-t0) * 1e-9f); + System.out.println("Code Compilation: " + (t2-t1) * 1e-9f); + System.out.println("Running Tests: " + (t3-t2) * 1e-9f); + } + + public static String generate(CompileFramework comp) { + // Create a list to collect all tests. + List testTemplateTokens = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + testTemplateTokens.add(generateTest(/* no warmup, like -Xcomp */ 0)); + } + for (int i = 0; i < 5; i++) { + testTemplateTokens.add(generateTest(/* with warmup, slower */ 10_000)); + } + + // Create the test class, which runs all testTemplateTokens. + return TestFrameworkClass.render( + // package and class name. + "compiler.rangecheck.templated", "Generated", + // List of imports. + Set.of("compiler.lib.generators.*", + "compiler.lib.verify.*", + "java.util.Random", + "jdk.test.lib.Utils"), + // classpath, so the Test VM has access to the compiled class files. + comp.getEscapedClassPathOfCompiledClasses(), + // The list of tests. + testTemplateTokens); + } + + enum Comparator { + // TODO: enable again after JDK-8385157 + // ULT(" < 0", false), + // ULE(" <= 0", false), + // UGT(" > 0", false), + // UGE(" >= 0", false), + // UEQ(" == 0", false), + // UNE(" != 0", false), + LT(" < ", true), + LE(" <= ", true), + GT(" > ", true), + GE(" >= ", true), + EQ(" == ", true), + NE(" != ", true); + + private final String token; + private final boolean signed; + + Comparator(String token, boolean signed) { + this.token = token; + this.signed = signed; + } + + public String getToken() { + return token; + } + + public boolean isSigned() { + return signed; + } + + public Comparator negate() { + return switch(this) { + // TODO: enable again after JDK-8385157 + // case ULT -> UGE; + // case ULE -> UGT; + // case UGT -> ULE; + // case UGE -> ULT; + // case UEQ -> UNE; + // case UNE -> UEQ; + case LT -> GE; + case LE -> GT; + case GT -> LE; + case GE -> LT; + case EQ -> NE; + case NE -> EQ; + }; + } + + public Comparator flip() { + return switch(this) { + // TODO: enable again after JDK-8385157 + // case ULT -> UGT; + // case ULE -> UGE; + // case UGT -> ULT; + // case UGE -> ULE; + // case UEQ -> UEQ; + // case UNE -> UNE; + case LT -> GT; + case LE -> GE; + case GT -> LT; + case GE -> LE; + case EQ -> EQ; + case NE -> NE; + }; + } + + static Comparator random() { + return values()[RANDOM.nextInt(values().length)]; + } + + static Comparator randomGreater() { + return RANDOM.nextBoolean() ? GE : GT; + } + + static Comparator randomLess() { + return RANDOM.nextBoolean() ? LE : LT; + } + } + + record Comparison(String lhs, Comparator cmp, String rhs, boolean negated) { + public Comparison(String lhs, Comparator cmp, String rhs) { + this(lhs, cmp, rhs, false); + } + + public String toString() { + return cmp.isSigned() + ? ((negated ? "!" : "") + "(" + lhs + " "+ cmp.getToken() + " " + rhs + ")") + : ((negated ? "!" : "") + "(Integer.compareUnsigned(" + lhs + ", " + rhs + ")" + cmp.getToken() + ")"); + } + + // Keep the same semantics of the test, but change its form. + Comparison permuteRandom() { + return flipRandom().complementRandom(); + } + + Comparison flipRandom() { + return RANDOM.nextBoolean() ? this : new Comparison(rhs, cmp.flip(), lhs); + } + + Comparison complementRandom() { + return RANDOM.nextBoolean() ? this : new Comparison(lhs, cmp.negate(), rhs, true); + } + + Comparison negateCmp() { + return new Comparison(lhs, cmp.negate(), rhs, negated); + } + } + + interface TestMethodGenerator { + Template.OneArg getTestTemplate(); + + default Template.ZeroArgs getIRTemplate(boolean withWarmup) { + return Template.make(() -> scope("// No IR rule.\n")); + } + + default Template.ZeroArgs getInputTemplate() { + return Template.make(() -> scope( + """ + RestrictableGenerator gen = Generators.G.ints(); + int n = gen.next(); + int a = gen.next(); + int b = gen.next(); + """ + )); + }; + } + + // Some basic ranges with constant bounds. + // This should test some basic correctness, and also covers the case + // of bug JDK-8135069. + static class TestMethodGeneratorConst implements TestMethodGenerator { + private final int con1 = INT_GEN.next(); + private final int con2 = INT_GEN.next(); + + private final Comparison c1 = new Comparison("n", Comparator.random(), "con1").permuteRandom(); + private final Comparison c2 = new Comparison("n", Comparator.random(), "con2").permuteRandom(); + + private final Template.OneArg testTemplate = Template.make("methodName", (String methodName) -> scope( + let("con1", con1), + let("con2", con2), + let("c1", c1), + let("c2", c2), + """ + static boolean #methodName(int n, int a, int b) { + int con1 = #con1; + int con2 = #con2; + if (#c1 || #c2) { + return true; + } + return false; + } + """ + )); + + public Template.OneArg getTestTemplate() { return testTemplate; } + } + + // Cases where a and b are ranges that touch min_int/max_int. + // Note: if con1=0 and con2=1 then this is like the cases: + // - test_Case3a_LTLE_overflow + // - test_Case3b_LTLE_overflow + // - test_Case4a_LELE_assert + // + // Hence, I think this test gives us quite good coverage for the kinds of bugs + // such as JDK-8346420. + static class TestMethodGeneratorWithIf implements TestMethodGenerator { + private final int con1 = INT_GEN.next(); + private final int con2 = INT_GEN.next(); + private final String m1 = RANDOM.nextBoolean() ? "Integer.MIN_VALUE" : "Integer.MAX_VALUE"; + private final String m2 = RANDOM.nextBoolean() ? "Integer.MIN_VALUE" : "Integer.MAX_VALUE"; + + private final Comparison c1 = new Comparison("n", Comparator.random(), "a").permuteRandom(); + private final Comparison c2 = new Comparison("n", Comparator.random(), "b").permuteRandom(); + + private final Template.OneArg testTemplate = Template.make("methodName", (String methodName) -> scope( + let("con1", con1), + let("con2", con2), + let("m1", m1), + let("m2", m2), + let("c1", c1), + let("c2", c2), + """ + static boolean #methodName(int n, int a, int b) { + if (a < b) { + a = #con1; + b = #con2; + } else { + a = #m1; + b = #m2; + } + if (#c1 || #c2) { + return true; + } + return false; + } + """ + )); + + public Template.OneArg getTestTemplate() { return testTemplate; } + } + + // Just for good practice: add some case where the ranges are more free. + static class TestMethodGeneratorRanges implements TestMethodGenerator { + private final int n_hi = INT_GEN.next(); + private final int n_lo = INT_GEN.next(); + private final int a_hi = INT_GEN.next(); + private final int a_lo = INT_GEN.next(); + private final int b_hi = INT_GEN.next(); + private final int b_lo = INT_GEN.next(); + + private final Comparison c1 = new Comparison("n", Comparator.random(), "a").permuteRandom(); + private final Comparison c2 = new Comparison("n", Comparator.random(), "b").permuteRandom(); + + private final Template.OneArg template = Template.make("methodName", (String methodName) -> scope( + let("n_hi", n_hi), + let("n_lo", n_lo), + let("a_hi", a_hi), + let("a_lo", a_lo), + let("b_hi", b_hi), + let("b_lo", b_lo), + let("c1", c1), + let("c2", c2), + """ + static boolean #methodName(int n, int a, int b) { + n = Math.min(#n_hi, Math.max(#n_lo, n)); + a = Math.min(#a_hi, Math.max(#a_lo, a)); + b = Math.min(#b_hi, Math.max(#b_lo, b)); + if (#c1 || #c2) { + return true; + } + return false; + } + """ + )); + + public Template.OneArg getTestTemplate() { + return template; + } + } + + // Generate some more constrained cases, but with IR rules + static class TestMethodGeneratorConstIR implements TestMethodGenerator { + private final int lo; + private final int hi; + { // instance initializer + // We want to cover all cases for lo and hi combinations. But the + // critical cases happen around int_min and int_max, and when + // lo and hi are close to each other. + switch (RANDOM.nextInt(3)) { + case 0 -> { + // Full freedom, will eventually cover all cases + lo = INT_GEN.next(); + hi = INT_GEN.next(); + } + case 1 -> { + // Pick cases around overflow and underflow + lo = Integer.MAX_VALUE - 5 + RANDOM.nextInt(10); + hi = Integer.MAX_VALUE - 5 + RANDOM.nextInt(10); + } + default -> { + // Pick cases where lo and hi are close to each other + lo = INT_GEN.next(); + hi = lo - 5 + RANDOM.nextInt(10); + } + } + } + + // Since we are using constants for lo and hi, the checks should get canonicalized, + // so that n is always in the lhs. We only create cases that are covered by the + // 4 cases of "2 CmpI -> 1 CmpU" optimization in IfNode::fold_compares_helper. + private final Comparison c_lo = new Comparison("n", Comparator.randomGreater(), "lo"); + private final Comparison c_hi = new Comparison("n", Comparator.randomLess(), "hi"); + private final boolean swap = RANDOM.nextBoolean(); + private final Comparison c1Permuted = (swap ? c_lo : c_hi).permuteRandom(); + private final Comparison c2Permuted = (swap ? c_hi : c_lo).permuteRandom(); + // n > lo && n < hi -> check for inside range + // n <= lo || n >= hi -> chedk for outside range + private final boolean withAnd = RANDOM.nextBoolean(); + private final String operator = withAnd ? "&&" : "||"; + private final Comparison c1 = withAnd ? c1Permuted : c1Permuted.negateCmp(); + private final Comparison c2 = withAnd ? c2Permuted : c2Permuted.negateCmp(); + + private final Template.OneArg testTemplate = Template.make("methodName", (String methodName) -> scope( + let("lo", lo), + let("hi", hi), + let("c1", c1), + let("c2", c2), + let("op", operator), + """ + static boolean #methodName(int n, int a, int b) { + int lo = #lo; + int hi = #hi; + if (#c1 #op #c2) { + return true; + } + return false; + } + """ + )); + + public Template.OneArg getTestTemplate() { return testTemplate; } + + public Template.ZeroArgs getIRTemplate(boolean withWarmup) { + return Template.make(() -> { + String cmpIParse, cmpUParse, cmpIFinal, cmpUFinal; + String comment; + + // If both branches are compiled (in -Xcomp mode, i.e. no warmup), then + // we can know very precisely what happens in each case. + if (c_lo.cmp() == Comparator.GT && c_hi.cmp() == Comparator.LT) { + // a) (n > lo && n < hi) + if (lo == Integer.MAX_VALUE || hi == Integer.MIN_VALUE) { + cmpIParse = "< 2"; cmpUParse = "= 0"; cmpIFinal = "< 2"; cmpUFinal = "= 0"; + comment = "a) one or both checks fold at parse time"; + } else if (lo < hi && lo+2 == hi) { + // Not yet folded at parsing, because lo != hi + // BoolNode::Ideal: x x==0 (signed) + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 1"; cmpUFinal = "= 0"; + comment = "a) replace with CmpU (single element) -> CmpI eq"; + } else if (lo < hi && lo+1 == hi) { + // Not yet folded at parsing, because lo != hi + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 0"; + comment = "a) impossible condition (exact) -> fold away"; + } else if (lo < hi) { + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 1"; + comment = "a) replace with CmpU (non-empty)"; + } else if (lo == hi) { + // same CmpI at parse time + cmpIParse = "= 1"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 0"; + comment = "a) impossible condition -> fold away"; + } else { + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 0"; + comment = "a) impossible condition -> fold away"; + } + } else if (c_lo.cmp() == Comparator.GT && c_hi.cmp() == Comparator.LE) { + // b) (n > lo && n <= hi) + if (lo == Integer.MAX_VALUE || hi == Integer.MAX_VALUE) { + cmpIParse = "< 2"; cmpUParse = "= 0"; cmpIFinal = "< 2"; cmpUFinal = "= 0"; + comment = "b) one or both checks fold at parse time"; + } else if (lo < hi && lo+1 == hi) { + // BoolNode::Ideal: x x==0 (signed) + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 1"; cmpUFinal = "= 0"; + comment = "b) replace with CmpU (single element) -> CmpI eq"; + } else if (lo < hi && lo+1 < hi) { + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 1"; + comment = "b) replace with CmpU (non-empty)"; + } else if (lo == hi) { + cmpIParse = "= 1"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 0"; + comment = "b) impossible condition (exact) -> fold away"; + } else { + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 0"; + comment = "b) impossible condition -> fold away"; + } + } else if (c_lo.cmp() == Comparator.GE && c_hi.cmp() == Comparator.LT) { + // c) (n >= lo && n < hi) + if (lo == Integer.MIN_VALUE || hi == Integer.MIN_VALUE) { + cmpIParse = "< 2"; cmpUParse = "= 0"; cmpIFinal = "< 2"; cmpUFinal = "= 0"; + comment = "c) one or both checks fold at parse time"; + } else if (lo < hi && lo+1 == hi) { + // BoolNode::Ideal: x x==0 (signed) + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 1"; cmpUFinal = "= 0"; + comment = "c) replace with CmpU (single element) -> CmpI eq"; + } else if (lo < hi && lo+1 < hi) { + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 1"; + comment = "c) replace with CmpU (non-empty)"; + } else if (lo == hi) { + // RegionNode::optimize_trichotomy: can fold (n >= x && n < x) -> never + cmpIParse = "< 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 0"; + comment = "c) impossible condition (exact) -> fold away"; + } else { + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 0"; + comment = "c) impossible condition -> fold away"; + } + } else if (c_lo.cmp() == Comparator.GE && c_hi.cmp() == Comparator.LE) { + // d) (n >= lo && n <= hi) + if (lo == Integer.MIN_VALUE || hi == Integer.MAX_VALUE) { + cmpIParse = "< 2"; cmpUParse = "= 0"; cmpIFinal = "< 2"; cmpUFinal = "= 0"; + comment = "d) one or both checks fold at parse time"; + } else if (lo == hi) { + // same CmpI at parse time + // BoolNode::Ideal: x x==0 (signed) + cmpIParse = "= 1"; cmpUParse = "= 0"; cmpIFinal = "= 1"; cmpUFinal = "= 0"; + comment = "d) replace with CmpU (single element) -> CmpI eq"; + } else if (lo < hi) { + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 1"; + comment = "d) replace with CmpU (non-empty)"; + } else { + cmpIParse = "= 2"; cmpUParse = "= 0"; cmpIFinal = "= 0"; cmpUFinal = "= 0"; + comment = "d) impossible condition -> fold away"; + } + } else { + throw new RuntimeException("should not be generated: " + c_lo + " and " + c_hi); + } + + // All the precise counting above assumes that both ifs get compiled, and hence + // both CmpI are generated. Further, it assumes that both of the "or" branches + // (fail1 and fail2) end up "in the same place": either at the same region, or + // both in an uncommon trap. With profiling, the following cases are possible: + // - The first if is constant folded to fail1, and we have no CmpI nor CmpU + // in the graph. + // - The first if always leads to fail1, and away from the second if, and so we + // only have a single CmpI in the graph after parsing. + // - The first if always leads towards the second if, and away from fail1. And + // the second if always points towards fail2 and away from succ. We get an + // uncommon trap for fail1 and succ, and only the fail2 path is compiled. + // Hence, we have two CmpI, but fail1 and fail2 do not end up "in the same place". + // This makes our IR rule quite weak, sadly. We could make the IR rules stronger, + // but we would need to control warmup, and generate corresponding inputs that + // ensure the right paths are compiled or not compiled. + if (withWarmup) { + cmpIParse = "<= 2"; cmpUParse = "= 0"; cmpIFinal = "<= 2"; cmpUFinal = "< 2"; + comment = "with warmup: unstable-if makes precise counting hard."; + } + + return scope( + let("IP", cmpIParse), + let("UP", cmpUParse), + let("IF", cmpIFinal), + let("UF", cmpUFinal), + let("comment", comment), + """ + // #comment + @IR(counts = {IRNode.CMP_I, "#IP", IRNode.CMP_U, "#UP"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.CMP_I, "#IF", IRNode.CMP_U, "#UF"}) + """ + ); + }); + } + + @Override + public Template.ZeroArgs getInputTemplate() { + return Template.make(() -> scope( + let("lo", lo), + let("hi", hi), + """ + Random r = Utils.getRandomInstance(); + RestrictableGenerator gen = Generators.G.ints(); + int a = gen.next(); + int b = gen.next(); + """, + switch (RANDOM.nextInt(9)) { + // Random values + case 0 -> "int n = gen.next();\n"; + // Fuzz around specific values + case 1 -> "int n = r.nextInt(10) - 5 + #lo;\n"; + case 2 -> "int n = r.nextInt(10) - 5 + #hi;\n"; + case 3 -> "int n = r.nextInt(10) - 5 + (r.nextBoolean() ? #lo : #hi);\n"; + case 4 -> "int n = r.nextInt(10) - 5 + Integer.MAX_VALUE;\n"; + // Only very low or very high values, or in the middle + case 5 -> "int n = r.nextInt(10) - 10 + Integer.MAX_VALUE;\n"; + case 6 -> "int n = r.nextInt(10) + Integer.MIN_VALUE;\n"; + case 7 -> "int n = r.nextInt(10) - 5 + #lo/2 + #hi/2;\n"; + // Always the same constant + default -> "int n = " + INT_GEN.next() + ";\n"; + } + )); + }; + } + + // switch cases can also be implemented with range checks using + // constants, and then we can optimize 2 CmpI with a single CmpU, + // at least in some cases. + static class TestMethodGeneratorSwitch implements TestMethodGenerator { + Set cases = new HashSet<>(); + { // instance initializer + int n = RANDOM.nextInt(1, 20); + for (int i = 0; i < n; i++) { + cases.add((short)(int)INT_GEN.next()); + } + } + + private final Template.OneArg testTemplate = Template.make("methodName", (String methodName) -> scope( + """ + static boolean #methodName(int n, int a, int b) { + switch((short)n) { + """, + cases.stream().map(i -> scope( + let("i", i), + """ + case (short)#i: + """ + )).toList(), + """ + return true; + default: + return false; + } + } + """ + )); + + public Template.OneArg getTestTemplate() { return testTemplate; } + } + + // If arr.length is in the second check, the null-check for arr + // is located between the two checks. + // I'm not adding any IR rules here, just checking for correctness. + static class TestMethodGeneratorArrLength implements TestMethodGenerator { + private final int n_hi = INT_GEN.next(); + private final int n_lo = INT_GEN.next(); + private final int a_hi = INT_GEN.next(); + private final int a_lo = INT_GEN.next(); + private final int size = INT_GEN.restricted(0, 100_000).next(); + + // Get checks like: n < a || n >= arr.length + private final Comparison c_lo = new Comparison("n", Comparator.random(), "a").permuteRandom(); + private final Comparison c_hi = new Comparison("n", Comparator.random(), "arr.length").permuteRandom(); + private final boolean swap = RANDOM.nextBoolean(); + private final Comparison c1Permuted = (swap ? c_lo : c_hi).permuteRandom(); + private final Comparison c2Permuted = (swap ? c_hi : c_lo).permuteRandom(); + // n > lo && n < hi -> check for inside range + // n <= lo || n >= hi -> chedk for outside range + private final boolean withAnd = RANDOM.nextBoolean(); + private final String operator = withAnd ? "&&" : "||"; + private final Comparison c1 = withAnd ? c1Permuted : c1Permuted.negateCmp(); + private final Comparison c2 = withAnd ? c2Permuted : c2Permuted.negateCmp(); + + private final Template.OneArg testTemplate = Template.make("methodName", (String methodName) -> scope( + let("n_hi", n_hi), + let("n_lo", n_lo), + let("a_hi", a_hi), + let("a_lo", a_lo), + let("size", size), + let("c1", c1), + let("c2", c2), + let("op", operator), + """ + static boolean #methodName(int n, int a, int b) { + int[] arr = $arr; + n = Math.min(#n_hi, Math.max(#n_lo, n)); + a = Math.min(#a_hi, Math.max(#a_lo, a)); + if (#c1 #op #c2) { + return true; + } + return false; + } + static int[] $arr = new int[#size]; + """ + )); + + public Template.OneArg getTestTemplate() { return testTemplate; } + } + + public static TemplateToken generateTest(int warmup) { + TestMethodGenerator tg = switch(RANDOM.nextInt(6)) { + case 0 -> new TestMethodGeneratorConst(); + case 1 -> new TestMethodGeneratorWithIf(); + case 2 -> new TestMethodGeneratorRanges(); + case 3 -> new TestMethodGeneratorConstIR(); + case 4 -> new TestMethodGeneratorSwitch(); + case 5 -> new TestMethodGeneratorArrLength(); + default -> throw new RuntimeException("not expected"); + }; + Template.ZeroArgs testInputTemplate = tg.getInputTemplate(); + Template.OneArg testMethodTemplate = tg.getTestTemplate(); + Template.ZeroArgs testIRTemplate = tg.getIRTemplate(warmup >= 10_000); + + var testTemplate = Template.make(() -> scope( + let("warmup", warmup / 100), + """ + // --- $test start --- + @Run(test = "$test") + @Warmup(#warmup) + public static void $run() { + for (int i = 0; i < 100; i++) { + // Generate random values for n, a, b. + """, + testInputTemplate.asToken(), + """ + + // Run test and compare with interpreter results. + var result = $test(n, a, b); + var expected = $reference(n, a, b); + if (result != expected) { + throw new RuntimeException("wrong result: " + result + " vs " + expected + + "\\nn: " + n + + "\\na: " + a + + "\\nb: " + b); + } + } + } + + @Test + """, + testIRTemplate.asToken(), + testMethodTemplate.asToken($("test")), + """ + + @DontCompile + """, + testMethodTemplate.asToken($("reference")), + """ + // --- $test end --- + """ + )); + return testTemplate.asToken(); + } +} From 9536b7f6c004e0ad0a8e7db756735946ab0a56a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Sj=C3=B6len?= Date: Mon, 1 Jun 2026 07:17:50 +0000 Subject: [PATCH 05/32] 8384964: Add an entry count to the MallocSiteTable Reviewed-by: coleenp, fbredberg --- src/hotspot/share/nmt/mallocSiteTable.cpp | 8 +++++++ src/hotspot/share/nmt/mallocSiteTable.hpp | 1 + .../jtreg/runtime/NMT/MallocStressTest.java | 23 ++++++++++++++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/nmt/mallocSiteTable.cpp b/src/hotspot/share/nmt/mallocSiteTable.cpp index 0b8dc8e5fa0..d0b4bbe379f 100644 --- a/src/hotspot/share/nmt/mallocSiteTable.cpp +++ b/src/hotspot/share/nmt/mallocSiteTable.cpp @@ -32,6 +32,7 @@ Atomic* MallocSiteTable::_table = nullptr; const NativeCallStack* MallocSiteTable::_hash_entry_allocation_stack = nullptr; const MallocSiteHashtableEntry* MallocSiteTable::_hash_entry_allocation_site = nullptr; +Atomic MallocSiteTable::_entry_count(0); /* * Initialize malloc site table. @@ -82,6 +83,7 @@ bool MallocSiteTable::initialize() { // Add the allocation site to hashtable. int index = hash_to_index(entry.hash()); _table[index].store_relaxed(const_cast(&entry)); + _entry_count.add_then_fetch(1ul, memory_order_relaxed); return true; } @@ -127,6 +129,7 @@ MallocSite* MallocSiteTable::lookup_or_add(const NativeCallStack& key, uint32_t* // swap in the head if (_table[index].compare_set(nullptr, entry)) { + _entry_count.add_then_fetch(1ul, memory_order_relaxed); *marker = build_marker(index, 0); return entry->data(); } @@ -152,6 +155,7 @@ MallocSite* MallocSiteTable::lookup_or_add(const NativeCallStack& key, uint32_t* if (head->atomic_insert(entry)) { pos_idx ++; *marker = build_marker(index, pos_idx); + _entry_count.add_then_fetch(1ul, memory_order_relaxed); return entry->data(); } // contended, other thread won @@ -236,6 +240,10 @@ void MallocSiteTable::print_tuning_statistics(outputStream* st) { } st->print_cr("Malloc allocation site table:"); +#ifdef ASSERT + // This is solely for testing + st->print_cr("\tExpected entry count: %zu", _entry_count.load_relaxed()); +#endif st->print_cr("\tTotal entries: %d", total_entries); st->print_cr("\tEmpty entries (no outstanding mallocs): %d (%2.2f%%)", empty_entries, ((float)empty_entries * 100) / (float)total_entries); diff --git a/src/hotspot/share/nmt/mallocSiteTable.hpp b/src/hotspot/share/nmt/mallocSiteTable.hpp index d78e6ebba6e..22b944301d3 100644 --- a/src/hotspot/share/nmt/mallocSiteTable.hpp +++ b/src/hotspot/share/nmt/mallocSiteTable.hpp @@ -196,6 +196,7 @@ class MallocSiteTable : AllStatic { private: // The callsite hashtable. It has to be a static table, // since malloc call can come from C runtime linker. + static Atomic _entry_count; static Atomic* _table; static const NativeCallStack* _hash_entry_allocation_stack; static const MallocSiteHashtableEntry* _hash_entry_allocation_site; diff --git a/test/hotspot/jtreg/runtime/NMT/MallocStressTest.java b/test/hotspot/jtreg/runtime/NMT/MallocStressTest.java index 6af14268c7e..db65348246b 100644 --- a/test/hotspot/jtreg/runtime/NMT/MallocStressTest.java +++ b/test/hotspot/jtreg/runtime/NMT/MallocStressTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, Red Hat, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -137,6 +137,27 @@ public class MallocStressTest { pb.command(new String[] { JDKToolFinder.getJDKTool("jcmd"), pid, "VM.native_memory", "statistics"}); output = new OutputAnalyzer(pb.start()); output.shouldNotContain("Tracking level has been downgraded due to lack of resources"); + + if (Platform.isDebugBuild()) { + String expectedCountString = output.firstMatch("\\s*Expected entry count: (\\d+)", 1); + String totalEntriesString = output.firstMatch("\\s*Total entries: (\\d+)", 1); + + if (expectedCountString == null || totalEntriesString == null) { + throw new RuntimeException("Missing malloc site table entry counts in NMT statistics output"); + } + + int expectedCount = Integer.parseInt(expectedCountString); + int totalEntries = Integer.parseInt(totalEntriesString); + + // The expected count is loaded after the total entries has accumulated (internal impl detail from hotspot). + // As this is a concurrent hashtable, we might have added more malloc sites. + // So, we're ok with the expectedCount being larger than totalEntries, but if it's *A lot* larger something + // really weird is going on. + if (expectedCount - totalEntries > 5) { + throw new RuntimeException("Malloc site table entry count mismatch: expected " + + expectedCount + ", walked " + totalEntries); + } + } } private static void sleep_wait(int n) { From 3760d75e66cbafe8206394afdabf4a2a9ccfd2b8 Mon Sep 17 00:00:00 2001 From: Anton Seoane Ampudia Date: Mon, 1 Jun 2026 07:20:32 +0000 Subject: [PATCH 06/32] 8384104: Obsolete Xmaxjitcodesize Reviewed-by: kvn, dholmes --- src/hotspot/share/runtime/arguments.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index a607484d02a..dc177bdce6d 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -2195,11 +2195,9 @@ jint Arguments::parse_each_vm_init_arg(const JavaVMInitArgs* args, JVMFlagOrigin if (FLAG_SET_CMDLINE(ThreadStackSize, value) != JVMFlag::SUCCESS) { return JNI_EINVAL; } - } else if (match_option(option, "-Xmaxjitcodesize", &tail) || - match_option(option, "-XX:ReservedCodeCacheSize=", &tail)) { - if (match_option(option, "-Xmaxjitcodesize", &tail)) { - warning("Option -Xmaxjitcodesize was deprecated in JDK 26 and will likely be removed in a future release."); - } + } else if (match_option(option, "-Xmaxjitcodesize", &tail)) { + warning("Ignoring option %s; support was removed in JDK 27", option->optionString); + } else if (match_option(option, "-XX:ReservedCodeCacheSize=", &tail)) { julong long_ReservedCodeCacheSize = 0; ArgsRange errcode = parse_memory_size(tail, &long_ReservedCodeCacheSize, 1); From 2820c789fa6daf625b1f0f6bfe03a7c3eb428d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20H=C3=A4ssig?= Date: Mon, 1 Jun 2026 07:26:30 +0000 Subject: [PATCH 07/32] 8385408: C2: no reachable node should have no use Reviewed-by: dlong, kvn --- src/hotspot/share/opto/mulnode.cpp | 3 +- .../compiler/igvn/TestShiftWorklist.java | 75 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/compiler/igvn/TestShiftWorklist.java diff --git a/src/hotspot/share/opto/mulnode.cpp b/src/hotspot/share/opto/mulnode.cpp index 5e05d6a6e04..e48acd23b87 100644 --- a/src/hotspot/share/opto/mulnode.cpp +++ b/src/hotspot/share/opto/mulnode.cpp @@ -894,7 +894,8 @@ static Node* mask_and_replace_shift_amount(PhaseGVN* phase, Node* shift_node, ui } if (replace) { - shift_node->set_req(2, phase->intcon(masked_shift)); // Replace shift count with masked value. + // Replace shift count with masked value and put potential dead nodes on the worklist. + shift_node->set_req_X(2, phase->intcon(masked_shift), phase); // We need to notify the caller that the graph was reshaped, as Ideal needs // to return the root of the reshaped graph if any change was made. diff --git a/test/hotspot/jtreg/compiler/igvn/TestShiftWorklist.java b/test/hotspot/jtreg/compiler/igvn/TestShiftWorklist.java new file mode 100644 index 00000000000..64c5a5fe658 --- /dev/null +++ b/test/hotspot/jtreg/compiler/igvn/TestShiftWorklist.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, 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 + * @bug 8385408 + * @summary Test that inputs to removed shifts are put on the worklist and cleaned up + * @library /test/lib / + * @run main/othervm -Xbatch -XX:-TieredCompilation -XX:-UseOnStackReplacement + * -XX:CompileCommand=compileonly,${test.main.class}::test + * ${test.main.class} + * @run main ${test.main.class} + */ + +package compiler.igvn; + +import jdk.test.lib.Asserts; + +public class TestShiftWorklist { + int N = 400; + int iArr[] = new int[N]; + + public static void main(String[] args) { + TestShiftWorklist t = new TestShiftWorklist(); + for (int i = 0; i < 2_000; i++) { + int result = t.test(); + Asserts.assertEQ(result, 0); + } + } + + private int test() { + long[] lArr = new long[N]; + long l = 1957; // l % 32 = 5 + int n = 1; + for (int i = 1; i < 30; ++i) { + for (double j = 1; j < 12; j++) { + iArr[i] = 3; + for (long k = 1; k < 2; k++) { + // C2 is able to prove that the effecive shift value for the int n is always 5. + n >>= l; + } + l = 907436423360901L; // l % 32 = 5 + } + } + return (int) checkSum(lArr); + } + + private static long checkSum(long[] a) { + long sum = 0; + for (int j = 0; j < a.length; j++) { + sum += (a[j] / (j + 1) + a[j] % (j + 1)); + } + return sum; + } +} From 05a6580883e4badccf72898f7f27c1f4812e5400 Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Mon, 1 Jun 2026 07:34:49 +0000 Subject: [PATCH 08/32] 8385583: RISC-V: Debugger very slow during stepping Reviewed-by: fyang, dzhang --- src/hotspot/cpu/riscv/interp_masm_riscv.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp index 443f3e3d17f..bb56acb3f38 100644 --- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp +++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp @@ -1221,6 +1221,7 @@ void InterpreterMacroAssembler::notify_method_exit( // track stack depth. If it is possible to enter interp_only_mode we add // the code to check if the event should be sent. if (mode == NotifyJVMTI && (JvmtiExport::can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) { + Label L; // Note: frame::interpreter_frame_result has a dependency on how the // method result is saved across the call to post_method_exit. If this // is changed then the interpreter_frame_result implementation will @@ -1228,8 +1229,18 @@ void InterpreterMacroAssembler::notify_method_exit( // template interpreter will leave the result on the top of the stack. push(state); + + ld(t1, Address(xthread, JavaThread::jvmti_thread_state_offset())); + beqz(t1, L); // if (thread->jvmti_thread_state() == nullptr) exit; + + lwu(t1, Address(t1, JvmtiThreadState::frame_pop_cnt_offset())); + lwu(t0, Address(xthread, JavaThread::interp_only_mode_offset())); + orr(t0, t0, t1); + beqz(t0, L); + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::post_method_exit)); + bind(L); pop(state); } From 3f2002d4880af3f3cbd5344039fd97c073f98486 Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Mon, 1 Jun 2026 08:27:38 +0000 Subject: [PATCH 09/32] 8384507: Incorrect vector reassociation for signed saturating addition Reviewed-by: shade, epeter --- src/hotspot/share/opto/vectornode.cpp | 32 ++- .../TestIncorrectVectorReassociation.java | 218 ++++++++++++++++++ 2 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestIncorrectVectorReassociation.java diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 1059bfe20e5..92777f97d7b 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -1168,6 +1168,32 @@ static bool is_commutative_vector_operation(int opcode) { } } +static bool is_associative_and_commutative_vector_operation(int opcode) { + switch (opcode) { + case Op_AddVB: + case Op_AddVS: + case Op_AddVI: + case Op_AddVL: + case Op_MulVB: + case Op_MulVS: + case Op_MulVI: + case Op_MulVL: + case Op_MaxV: + case Op_MinV: + case Op_UMinV: + case Op_UMaxV: + case Op_XorV: + case Op_OrV: + case Op_AndV: + case Op_AndVMask: + case Op_OrVMask: + case Op_XorVMask: + return true; + default: + return false; + } +} + bool VectorNode::should_swap_inputs_to_help_global_value_numbering() { // Predicated vector operations are sensitive to ordering of inputs. // When the mask corresponding to a vector lane is false then @@ -1299,7 +1325,7 @@ Node* VectorNode::create_reassociated_node(Node* parent, Node* child, Node* cinp return cloned_parent; } -// Try to reassociate commutative vector operations using the following ideal transformation, +// Try to reassociate associative vector operations using the following ideal transformation, // this will facilitate strength reducing a vector operation with all replicated inputs to // a scalar operation. // @@ -1312,8 +1338,8 @@ Node* VectorNode::reassociate_vector_operation(PhaseGVN* phase) { return nullptr; } - // Enable re-association for commutative vector operations. - if (!is_commutative_vector_operation(Opcode())) { + // Enable re-association only for associative and commutative vector operations. + if (!is_associative_and_commutative_vector_operation(Opcode())) { return nullptr; } diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestIncorrectVectorReassociation.java b/test/hotspot/jtreg/compiler/vectorapi/TestIncorrectVectorReassociation.java new file mode 100644 index 00000000000..bc94c0b19cc --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestIncorrectVectorReassociation.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2026, 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 compiler.vectorapi; + +import compiler.lib.ir_framework.*; +import compiler.lib.verify.*; +import java.util.Arrays; +import jdk.incubator.vector.*; + +/** + * @test + * @bug 8384507 8385308 + * @library /test/lib / + * @summary Incorrect vector reassociation for signed saturating addition + * @modules jdk.incubator.vector + * + * @run driver compiler.vectorapi.TestIncorrectVectorReassociation + */ + +public class TestIncorrectVectorReassociation { + + public static void main(String[] args) { + TestFramework testFramework = new TestFramework(); + testFramework.setDefaultWarmup(10000) + .addFlags("--add-modules=jdk.incubator.vector") + .start(); + } + + /* ======================= + * BYTE: a=100, b=100, arr[i]=-50 + * Correct: sat_add(100, sat_add(100, -50)) = sat_add(100, 50) = 127 + * Wrong: sat_add(sat_add(100, 100), -50) = sat_add(127, -50) = 77 + * ======================= */ + + static final VectorSpecies BSP = ByteVector.SPECIES_PREFERRED; + static byte[] byteIn = new byte[BSP.length()]; + static byte[] byteOut = new byte[BSP.length()]; + static final byte BA = 100, BB = 100; + + static { + Arrays.fill(byteIn, (byte) -50); + } + + @Test + @IR(counts = {IRNode.SATURATING_ADD_VB, " 2 "}, + applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"}) + static void test_byte_sadd(int index) { + ByteVector.broadcast(BSP, BA) + .lanewise(VectorOperators.SADD, + ByteVector.broadcast(BSP, BB) + .lanewise(VectorOperators.SADD, + ByteVector.fromArray(BSP, byteIn, index))) + .intoArray(byteOut, index); + } + + @Run(test = "test_byte_sadd") + void run_byte_sadd() { + for (int i = 0; i < BSP.loopBound(byteIn.length); i += BSP.length()) { + test_byte_sadd(i); + } + for (int i = 0; i < BSP.loopBound(byteIn.length); i++) { + Verify.checkEQ(byteOut[i], VectorMath.addSaturating(BA, VectorMath.addSaturating(BB, byteIn[i]))); + } + } + + /* ======================= + * SHORT: a=30000, b=30000, arr[i]=-50 + * Correct: sat_add(30000, sat_add(30000, -50)) = sat_add(30000, 29950) = 32767 + * Wrong: sat_add(sat_add(30000, 30000), -50) = sat_add(32767, -50) = 32717 + * ======================= */ + + static final VectorSpecies SSP = ShortVector.SPECIES_PREFERRED; + static short[] shortIn = new short[SSP.length()]; + static short[] shortOut = new short[SSP.length()]; + static final short SA = 30000, SB = 30000; + + static { + Arrays.fill(shortIn, (short) -50); + } + + @Test + @IR(counts = {IRNode.SATURATING_ADD_VS, " 2 "}, + applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"}) + static void test_short_sadd(int index) { + ShortVector.broadcast(SSP, SA) + .lanewise(VectorOperators.SADD, + ShortVector.broadcast(SSP, SB) + .lanewise(VectorOperators.SADD, + ShortVector.fromArray(SSP, shortIn, index))) + .intoArray(shortOut, index); + } + + @Run(test = "test_short_sadd") + void run_short_sadd() { + for (int i = 0; i < SSP.loopBound(shortIn.length); i += SSP.length()) { + test_short_sadd(i); + } + for (int i = 0; i < SSP.loopBound(shortIn.length); i++) { + Verify.checkEQ(shortOut[i], VectorMath.addSaturating(SA, VectorMath.addSaturating(SB, shortIn[i]))); + } + } + + /* ======================= + * INT: a=2_000_000_000, b=2_000_000_000, arr[i]=-50 + * Correct: sat_add(2B, sat_add(2B, -50)) = sat_add(2B, 1_999_999_950) = MAX + * Wrong: sat_add(sat_add(2B, 2B), -50) = sat_add(MAX, -50) = MAX-50 + * ======================= */ + + static final VectorSpecies ISP = IntVector.SPECIES_PREFERRED; + static int[] intIn = new int[ISP.length()]; + static int[] intOut = new int[ISP.length()]; + static final int IA = 2_000_000_000, IB = 2_000_000_000; + + static { + Arrays.fill(intIn, -50); + } + + @Test + @IR(counts = {IRNode.SATURATING_ADD_VI, " 2 "}, + applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"}) + static void test_int_sadd(int index) { + IntVector.broadcast(ISP, IA) + .lanewise(VectorOperators.SADD, + IntVector.broadcast(ISP, IB) + .lanewise(VectorOperators.SADD, + IntVector.fromArray(ISP, intIn, index))) + .intoArray(intOut, index); + } + + @Run(test = "test_int_sadd") + void run_int_sadd() { + for (int i = 0; i < ISP.loopBound(intIn.length); i += ISP.length()) { + test_int_sadd(i); + } + for (int i = 0; i < ISP.loopBound(intIn.length); i++) { + Verify.checkEQ(intOut[i], VectorMath.addSaturating(IA, VectorMath.addSaturating(IB, intIn[i]))); + } + } + + /* ======================= + * LONG: a=8_000_000_000_000_000_000L, b=8_000_000_000_000_000_000L, arr[i]=-50 + * Correct: sat_add(8e18, sat_add(8e18, -50)) = sat_add(8e18, 7_999_999_999_999_999_950) = MAX + * Wrong: sat_add(sat_add(8e18, 8e18), -50) = sat_add(MAX, -50) = MAX-50 + * ======================= */ + + static final VectorSpecies LSP = LongVector.SPECIES_PREFERRED; + static long[] longIn = new long[LSP.length()]; + static long[] longOut = new long[LSP.length()]; + static final long LA = 8_000_000_000_000_000_000L, LB = 8_000_000_000_000_000_000L; + + static { + Arrays.fill(longIn, -50L); + } + + @Test + @IR(counts = {IRNode.SATURATING_ADD_VL, " 2 "}, + applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"}) + static void test_long_sadd(int index) { + LongVector.broadcast(LSP, LA) + .lanewise(VectorOperators.SADD, + LongVector.broadcast(LSP, LB) + .lanewise(VectorOperators.SADD, + LongVector.fromArray(LSP, longIn, index))) + .intoArray(longOut, index); + } + + @Run(test = "test_long_sadd") + void run_long_sadd() { + for (int i = 0; i < LSP.loopBound(longIn.length); i += LSP.length()) { + test_long_sadd(i); + } + for (int i = 0; i < LSP.loopBound(longIn.length); i++) { + Verify.checkEQ(longOut[i], VectorMath.addSaturating(LA, VectorMath.addSaturating(LB, longIn[i]))); + } + } + + @Test + @IR(counts = {IRNode.SATURATING_ADD_VI, " 2 "}, + applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"}) + static IntVector test_mixed_sadd_suadd() { + IntVector v0 = IntVector.broadcast(ISP, 1); + IntVector v1 = IntVector.broadcast(ISP, 0); + IntVector v2 = v0.lanewise(VectorOperators.SADD, v1); + return v2.lanewise(VectorOperators.SUADD, -1); + } + + @Run(test = "test_mixed_sadd_suadd") + void run_mixed_sadd_suadd() { + IntVector result = test_mixed_sadd_suadd(); + int expected = VectorMath.addSaturatingUnsigned( + VectorMath.addSaturating(1, 0), -1); + int[] res = result.toArray(); + for (int i = 0; i < res.length; i++) { + Verify.checkEQ(res[i], expected); + } + } +} From 9acd9f8790fafbddb7dd5a25360b589efcc5ebf5 Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Mon, 1 Jun 2026 11:17:01 +0000 Subject: [PATCH 10/32] 8381965: NullPointerException in TypeAnnotationPositions Reviewed-by: mcimadamore --- .../sun/tools/javac/comp/DeferredAttr.java | 15 ++ .../TypeAnnotationsOnVariables.java | 188 +++++++++++++++++- 2 files changed, 202 insertions(+), 1 deletion(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java index 89422ac4671..c9c2375fab9 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/DeferredAttr.java @@ -25,6 +25,7 @@ package com.sun.tools.javac.comp; +import com.sun.source.tree.AnnotatedTypeTree; import com.sun.source.tree.LambdaExpressionTree.BodyKind; import com.sun.source.tree.NewClassTree; import com.sun.tools.javac.code.*; @@ -62,6 +63,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.ModifiersTree; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree.JCMemberReference.OverloadKind; @@ -191,6 +193,19 @@ public class DeferredAttr extends JCTree.Visitor { result.pos = t.pos; return result; } + + @Override + public JCTree visitAnnotatedType(AnnotatedTypeTree node, Void p) { + return copy(((JCAnnotatedType) node).underlyingType, p); + } + + @Override + public JCTree visitModifiers(ModifiersTree node, Void p) { + JCModifiers mods = (JCModifiers) super.visitModifiers(node, p); + + mods.annotations = List.nil(); + return mods; + } }; deferredCopier = new TypeMapping () { @Override diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java b/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java index f89e3ff7398..9c61c3f6d4a 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/TypeAnnotationsOnVariables.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8371155 8379550 8384843 + * @bug 8371155 8379550 8381965 8384843 * @summary Verify type annotations on local-like variables are propagated to * their types at an appropriate time. * @library /tools/lib @@ -559,6 +559,192 @@ public class TypeAnnotationsOnVariables { " Test$TypeAnno"); } + @Test + void explicitLambdaHeader3() throws Exception { + Path src = base.resolve("src"); + Path classes = base.resolve("classes"); + tb.writeJavaFiles(src, + """ + import java.lang.annotation.ElementType; + import java.lang.annotation.Target; + import java.util.function.Consumer; + import java.util.List; + + class Test { + @Target(ElementType.TYPE_USE) + @interface TypeAnno { } + + static final Consumer> TEST = + id((List<@TypeAnno String> arg1) -> {}); + + private static Consumer id(Consumer t) { return t;} + private void test() { + Object test = + id((List<@TypeAnno String> arg2) -> {}); + } + } + """); + Files.createDirectories(classes); + List actual = new ArrayList<>(); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(tb.findJavaFiles(src)) + .callback(task -> { + task.addTaskListener(new TaskListener() { + @Override + public void finished(TaskEvent e) { + if (e.getKind() != TaskEvent.Kind.ANALYZE) { + return ; + } + Trees trees = Trees.instance(task); + new TreePathScanner() { + @Override + public Void visitVariable(VariableTree node, Void p) { + actual.add(node.getName() + ": " + typeToString(trees.getTypeMirror(getCurrentPath()))); + return super.visitVariable(node, p); + } + @Override + public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { + actual.add(treeToString(node)+ ": " + typeToString(trees.getTypeMirror(getCurrentPath()))); + return super.visitLambdaExpression(node, p); + } + }.scan(e.getCompilationUnit(), null); + } + }); + }) + .run() + .writeAll(); + + List expected = List.of( + "TEST: java.util.function.Consumer>", + "(List<@TypeAnno String> arg1)->{ }: java.util.function.Consumer>", + "arg1: java.util.List", + "t: java.util.function.Consumer", + "test: java.lang.Object", + "(List<@TypeAnno String> arg2)->{ }: java.util.function.Consumer>", + "arg2: java.util.List" + ); + + actual.forEach(System.out::println); + if (!expected.equals(actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + + Path testClass = classes.resolve("Test.class"); + TestClassDesc testClassDesc = TestClassDesc.create(testClass); + MethodModel clInit = singletonValue(testClassDesc.name2Method().get("")); + assertEmpty(getAnnotationsFromHeader(clInit)); + assertEmpty(getAnnotationsFromCode(clInit)); + MethodModel test = singletonValue(testClassDesc.name2Method().get("test")); + assertEmpty(getAnnotationsFromHeader(test)); + assertEmpty(getAnnotationsFromCode(test)); + + checkTypeAnnotations(testClassDesc, + "lambda$static$0", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]", + " Test$TypeAnno"); + + checkTypeAnnotations(testClassDesc, + "lambda$test$0", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0, location=[TYPE_ARGUMENT(0)]", + " Test$TypeAnno"); + } + + @Test + void explicitLambdaHeader4() throws Exception { + Path src = base.resolve("src"); + Path classes = base.resolve("classes"); + tb.writeJavaFiles(src, + """ + import java.lang.annotation.ElementType; + import java.lang.annotation.Target; + import java.util.function.Consumer; + import java.util.List; + + class Test { + @Target(ElementType.TYPE_USE) + @interface TypeAnno { } + + static final Consumer> TEST = + id((@TypeAnno List arg) -> {}); + + private static Consumer id(Consumer t) { return t;} + private void test() { + Object test = + id((@TypeAnno List arg) -> {}); + } + } + """); + Files.createDirectories(classes); + List actual = new ArrayList<>(); + new JavacTask(tb) + .options("-d", classes.toString()) + .files(tb.findJavaFiles(src)) + .callback(task -> { + task.addTaskListener(new TaskListener() { + @Override + public void finished(TaskEvent e) { + if (e.getKind() != TaskEvent.Kind.ANALYZE) { + return ; + } + Trees trees = Trees.instance(task); + new TreePathScanner() { + @Override + public Void visitVariable(VariableTree node, Void p) { + actual.add(node.getName() + ": " + typeToString(trees.getTypeMirror(getCurrentPath()))); + return super.visitVariable(node, p); + } + @Override + public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { + actual.add(treeToString(node)+ ": " + typeToString(trees.getTypeMirror(getCurrentPath()))); + return super.visitLambdaExpression(node, p); + } + }.scan(e.getCompilationUnit(), null); + } + }); + }) + .run() + .writeAll(); + + List expected = List.of( + "TEST: java.util.function.Consumer>", + "(@TypeAnno List arg)->{ }: java.util.function.Consumer>", + "arg: java.util.@Test.TypeAnno List", + "t: java.util.function.Consumer", + "test: java.lang.Object", + "(@TypeAnno List arg)->{ }: java.util.function.Consumer>", + "arg: java.util.@Test.TypeAnno List" + ); + + actual.forEach(System.out::println); + if (!expected.equals(actual)) { + throw new AssertionError("Expected: " + expected + ", but got: " + actual); + } + + Path testClass = classes.resolve("Test.class"); + TestClassDesc testClassDesc = TestClassDesc.create(testClass); + MethodModel clInit = singletonValue(testClassDesc.name2Method().get("")); + assertEmpty(getAnnotationsFromHeader(clInit)); + assertEmpty(getAnnotationsFromCode(clInit)); + MethodModel test = singletonValue(testClassDesc.name2Method().get("test")); + assertEmpty(getAnnotationsFromHeader(test)); + assertEmpty(getAnnotationsFromCode(test)); + + checkTypeAnnotations(testClassDesc, + "lambda$static$0", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0", + " Test$TypeAnno"); + + checkTypeAnnotations(testClassDesc, + "lambda$test$0", + this::getAnnotationsFromHeader, + " 0: LTest$TypeAnno;(): METHOD_FORMAL_PARAMETER, param_index=0", + " Test$TypeAnno"); + } + private void checkTypeAnnotations(TestClassDesc testClassDesc, String lambdaMethodName, String... expectedEntries) throws IOException { From 64d6cbad08372801b7999810fa14a515954d6d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Mon, 1 Jun 2026 13:53:03 +0000 Subject: [PATCH 11/32] 8385632: ZGC: Incorrect object undo in relocation race for relocation workers Reviewed-by: stefank, aboldtch, tschatzl --- src/hotspot/share/gc/z/zRelocate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index 1c2a4078904..d69475e62a3 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -642,7 +642,7 @@ private: const zaddress to_addr = _forwarding->insert(from_addr, allocated_addr, &cursor); if (to_addr != allocated_addr) { // Already relocated, undo allocation - _allocator->undo_alloc_object(to_page, to_addr, size); + _allocator->undo_alloc_object(to_page, allocated_addr, size); increase_other_forwarded(size); } From 886eb11c645b52ec0af9024d5d2c94fe8d3ceea7 Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Mon, 1 Jun 2026 16:24:26 +0000 Subject: [PATCH 12/32] 8385409: Javap documentation says by default protected and public members are printed but package private members are also printed by default (`-package` behavior, not `-protected`) Reviewed-by: liach --- src/jdk.jdeps/share/man/javap.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/jdk.jdeps/share/man/javap.md b/src/jdk.jdeps/share/man/javap.md index d09ac57f196..0e11176aa10 100644 --- a/src/jdk.jdeps/share/man/javap.md +++ b/src/jdk.jdeps/share/man/javap.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1994, 2026, 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 @@ -54,7 +54,8 @@ javap - disassemble one or more class files The `javap` command disassembles one or more class files. The output depends on the options used. When no options are used, the `javap` command prints the -protected and public fields, and methods of the classes passed to it. +package private, protected, and public fields and methods declared in the classes passed +to it. The `javap` command isn't multirelease JAR aware. Using the class path form of the command results in viewing the base entry in all JAR files, multirelease or From d60fead5cf5679c52069006f474e3437b76a0afb Mon Sep 17 00:00:00 2001 From: Rui Li Date: Mon, 1 Jun 2026 17:11:05 +0000 Subject: [PATCH 13/32] 8384604: Shenandoah: Completed GC count doesn't add up in the log Reviewed-by: wkemper, xpeng, kdnilsen --- .../share/gc/shenandoah/shenandoahCollectorPolicy.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp index cfa79fc055e..e3267517e05 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp @@ -205,8 +205,15 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { out->print_cr("enough regions with no live objects to skip evacuation."); out->cr(); + size_t gc_attempts = 0; + for (int c = 0; c < GCCause::_last_gc_cause; c++) { + gc_attempts += _collection_cause_counts[c]; + } + size_t completed_gcs = _success_full_gcs + _success_degenerated_gcs + _success_concurrent_gcs + _success_old_gcs; - out->print_cr("%5zu Completed GCs", completed_gcs); + size_t cancelled_gcs = gc_attempts - completed_gcs; + out->print_cr("%5zu GC attempts. %zu Completed GCs (%.2f%%).", + gc_attempts, completed_gcs, percent_of(completed_gcs, gc_attempts)); size_t explicit_requests = 0; size_t implicit_requests = 0; @@ -220,7 +227,7 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { implicit_requests += cause_count; } const char* desc = GCCause::to_string(cause); - out->print_cr(" %5zu caused by %s (%.2f%%)", cause_count, desc, percent_of(cause_count, completed_gcs)); + out->print_cr(" %5zu caused by %s (%.2f%%)", cause_count, desc, percent_of(cause_count, gc_attempts)); } } From b2bf91bf1289af4d664261b865dacf0808638995 Mon Sep 17 00:00:00 2001 From: Patricio Chilano Mateo Date: Mon, 1 Jun 2026 17:36:17 +0000 Subject: [PATCH 14/32] 8376621: Should not suspend thread in start_transition if _is_disable_suspend set Reviewed-by: dholmes, sspitsyn --- src/hotspot/share/runtime/continuation.cpp | 3 +- src/hotspot/share/runtime/javaThread.hpp | 2 +- .../share/runtime/mountUnmountDisabler.cpp | 2 +- .../SuspendResume4/SuspendResume4.java | 109 ++++++++++++++++++ .../SuspendResume4/libSuspendResume4.cpp | 74 ++++++++++++ 5 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/SuspendResume4.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/libSuspendResume4.cpp diff --git a/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp index 0b7e64a3ba6..26d865057e6 100644 --- a/src/hotspot/share/runtime/continuation.cpp +++ b/src/hotspot/share/runtime/continuation.cpp @@ -87,7 +87,8 @@ class UnmountBeginMark : public StackObj { } ~UnmountBeginMark() { assert(!_current->is_suspended() - JVMTI_ONLY(|| (_current->is_vthread_transition_disabler() && _result != freeze_ok)), "must be"); + JVMTI_ONLY(|| (_result != freeze_ok && + (_current->is_vthread_transition_disabler() || _current->is_disable_suspend()))), "must be"); assert(_current->is_in_vthread_transition(), "must be"); if (_result != freeze_ok) { diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index 08d3cde8562..9e3c629ad78 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -873,7 +873,7 @@ public: // Atomic version; invoked by a thread other than the owning thread. bool in_critical_atomic() { return AtomicAccess::load(&_jni_active_critical) > 0; } - bool jni_deferred_suspension() { return AtomicAccess::load(&_jni_deferred_suspension_count); } + bool jni_deferred_suspension() const { return AtomicAccess::load(&_jni_deferred_suspension_count); } inline void enter_jni_deferred_suspension(); void exit_jni_deferred_suspension() { precond(Thread::current() == this); diff --git a/src/hotspot/share/runtime/mountUnmountDisabler.cpp b/src/hotspot/share/runtime/mountUnmountDisabler.cpp index 65a82d6c563..277a841a88c 100644 --- a/src/hotspot/share/runtime/mountUnmountDisabler.cpp +++ b/src/hotspot/share/runtime/mountUnmountDisabler.cpp @@ -129,7 +129,7 @@ bool MountUnmountDisabler::is_start_transition_disabled(JavaThread* thread, oop int base_disable_count = notify_jvmti_events() ? 1 : 0; return java_lang_Thread::vthread_transition_disable_count(vthread) > 0 || global_vthread_transition_disable_count() > base_disable_count - JVMTI_ONLY(|| (!thread->is_vthread_transition_disabler() && + JVMTI_ONLY(|| (!thread->is_vthread_transition_disabler() && !thread->is_disable_suspend() && (JvmtiVTSuspender::is_vthread_suspended(java_lang_Thread::thread_id(vthread)) || thread->is_suspended()))); } diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/SuspendResume4.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/SuspendResume4.java new file mode 100644 index 00000000000..db2eff4c1b5 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/SuspendResume4.java @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2026, 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 + * @bug 8376621 + * @summary Suspend virtual thread while it's inside disableSuspendAndPreempt region + * @requires vm.continuations + * @requires vm.jvmti + * @library /test/lib /test/hotspot/jtreg/testlibrary + * @run main/othervm SuspendResume4 + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.io.File; + +import jvmti.JVMTIUtils; + +public class SuspendResume4 { + native static void suspendThread(Thread thread); + native static void resumeThread(Thread thread); + + public static void main(String[] args) throws Exception { + // Run test in child VM where Locale won't be initialized already by jtreg + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + "-Djava.library.path=" + Utils.TEST_NATIVE_PATH, + "-agentpath:" + Utils.TEST_NATIVE_PATH + File.separator + System.mapLibraryName("SuspendResume4"), + "SuspendResume4$Test"); + OutputAnalyzer output = ProcessTools.executeProcess(pb); + System.out.println(output.getStdout()); + output.shouldHaveExitValue(0); + } + + static class Test{ + static String targetName; + + private void runTest() throws Exception { + // start target vthread + Thread target = Thread.ofVirtual().name("target").start(() -> { + // Give time for reader to get suspended in + // disableSuspendAndPreempt region. + spinWaitMillis(100); + // Force unmounting. If reader was suspended inside + // disableSuspendAndPreempt region this will block + // in VirtualThread.unmount. + Thread.yield(); + }); + + // start clinit contender + Thread contender = Thread.ofPlatform().name("contender").start(() -> { + "JAVA".toLowerCase(java.util.Locale.ROOT); + }); + + // start vthread that reads target's state + Thread reader = Thread.ofVirtual().name("reader").start(() -> { + targetName = "name: " + target; + }); + + // start suspend/resumer + Thread suspender = Thread.ofPlatform().name("suspender").start(() -> { + SuspendResume4.suspendThread(reader); + // Give target time for Thread.yield + spinWaitMillis(100); + SuspendResume4.resumeThread(reader); + }); + + target.join(); + contender.join(); + suspender.join(); + reader.join(); + } + + public static void main(String[] args) throws Exception { + Test obj = new Test(); + obj.runTest(); + } + + static void spinWaitMillis(long millis) { + long durationNanos = millis * 1_000_000L; + long start = System.nanoTime(); + while (System.nanoTime() - start < durationNanos) { + Thread.onSpinWait(); + } + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/libSuspendResume4.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/libSuspendResume4.cpp new file mode 100644 index 00000000000..38e90889b7a --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/SuspendResume4/libSuspendResume4.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2026, 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. + */ + +#include +#include +#include +#include +#include "jvmti_common.hpp" + +// set by Agent_OnLoad +static jvmtiEnv* jvmti = nullptr; + +extern "C" { + +JNIEXPORT void JNICALL +Java_SuspendResume4_suspendThread(JNIEnv* jni, jclass klass, jthread thread) { + jvmtiError err = jvmti->SuspendThread(thread); + if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_THREAD_NOT_ALIVE) { + jni->FatalError("error in JVMTI SuspendThread"); + } +} + +JNIEXPORT void JNICALL +Java_SuspendResume4_resumeThread(JNIEnv* jni, jclass klass, jthread thread) { + jvmtiError err = jvmti->ResumeThread(thread); + if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_THREAD_NOT_ALIVE) { + jni->FatalError("error in JVMTI ResumeThread"); + } +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { + jvmtiCapabilities caps; + jvmtiError err; + + printf("Agent_OnLoad: started\n"); + if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION) != JNI_OK) { + LOG("Agent_OnLoad: error in GetEnv"); + return JNI_ERR; + } + + memset(&caps, 0, sizeof(caps)); + caps.can_suspend = 1; + err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("Agent_OnLoad: error in JVMTI AddCapabilities: %d\n", err); + } + + printf("Agent_OnLoad: finished\n"); + + return 0; +} + +} // extern "C" From a23ce9cd30305e7d7339c7f74e1084ea20e345ca Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Mon, 1 Jun 2026 17:48:51 +0000 Subject: [PATCH 15/32] 8379535: test/jdk/java/io/File/ListRoots.java fails with RuntimeException: Does not match FileSystem::getRootDirectories Reviewed-by: alanb --- test/jdk/java/io/File/ListRoots.java | 66 +++++++++++++++++++--------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/test/jdk/java/io/File/ListRoots.java b/test/jdk/java/io/File/ListRoots.java index 7b14b2f3b08..d8717b0246a 100644 --- a/test/jdk/java/io/File/ListRoots.java +++ b/test/jdk/java/io/File/ListRoots.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, 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 @@ -24,9 +24,11 @@ /* @test @bug 4071322 @summary Basic test for File.listRoots method + @run junit ListRoots */ import java.io.File; +import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Path; @@ -35,33 +37,57 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + public class ListRoots { - public static void main(String[] args) throws Exception { + private static Set expectedSet; + private static Set actualSet; + + @BeforeAll + public static void init() throws IOException { File[] rs = File.listRoots(); for (int i = 0; i < rs.length; i++) { - System.out.println(i + ": " + rs[i]); - } - - File f = new File(System.getProperty("test.src", "."), "ListRoots.java"); - String cp = f.getCanonicalPath(); - boolean found = Stream.of(rs) - .map(File::getPath) - .anyMatch(p -> cp.startsWith(p)); - if (!found) { - throw new RuntimeException(cp + " does not have a recognized root"); + System.err.println(i + ": " + rs[i]); } // the list of roots should match FileSystem::getRootDirectories - Set roots1 = Stream.of(rs).collect(Collectors.toSet()); FileSystem fs = FileSystems.getDefault(); - Set roots2 = StreamSupport.stream(fs.getRootDirectories().spliterator(), false) - .map(Path::toFile) - .collect(Collectors.toSet()); - if (!roots1.equals(roots2)) { - System.out.println(roots2); - throw new RuntimeException("Does not match FileSystem::getRootDirectories"); - } + expectedSet = + StreamSupport.stream(fs.getRootDirectories().spliterator(), false) + .map(Path::toFile) + .collect(Collectors.toSet()); + actualSet = Stream.of(rs).collect(Collectors.toSet()); } + @Test + public void checkRoot() throws IOException { + File f = new File(System.getProperty("user.dir")); + String cp = f.getCanonicalPath(); + boolean found = Stream.of(File.listRoots()) + .map(File::getPath) + .anyMatch(p -> cp.startsWith(p)); + assertTrue(found, cp + " does not have a recognized root"); + } + + @Test + @DisabledOnOs(OS.WINDOWS) + public void listRootsUnix() throws IOException { + assertEquals(expectedSet, actualSet, + "Does not equal FileSystem::getRootDirectories"); + } + + @Test + @EnabledOnOs(OS.WINDOWS) + public void listRootsWindows() throws IOException { + assertTrue(expectedSet.stream().anyMatch(actualSet::contains), + "Does not intersect FileSystem::getRootDirectories"); + } } From 045ae965e122a1e867f5aa0e7e9813cb0967693e Mon Sep 17 00:00:00 2001 From: Patrick Fontanilla Date: Mon, 1 Jun 2026 18:04:17 +0000 Subject: [PATCH 16/32] 8384221: GenShen: WhiteBox full GC promotion is not reliable Reviewed-by: wkemper, ysr --- .../shenandoahGenerationalHeuristics.cpp | 12 +++++++- .../shenandoahGenerationalHeuristics.hpp | 2 +- .../heuristics/shenandoahGlobalHeuristics.cpp | 6 ++++ .../gc/shenandoah/shenandoahAgeCensus.cpp | 2 +- .../gc/shenandoah/shenandoahAgeCensus.hpp | 28 ++++++++++--------- .../shenandoahGenerationalControlThread.cpp | 2 +- .../share/gc/shenandoah/shenandoahPLAB.cpp | 2 +- 7 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp index c880af7fd49..ca4dfc71c61 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp @@ -358,7 +358,7 @@ size_t ShenandoahGenerationalHeuristics::select_aged_regions(ShenandoahInPlacePr // Having chosen the collection set, adjust the budgets for generational mode based on its composition. Note // that young_generation->available() now knows about recently discovered immediate garbage. -void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahHeap* const heap, +void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahGenerationalHeap* const heap, ShenandoahCollectionSet* const collection_set) { shenandoah_assert_generational(); // We may find that old_evacuation_reserve and/or loaned_for_young_evacuation are not fully consumed, in which case we may @@ -481,6 +481,16 @@ void ShenandoahGenerationalHeuristics::adjust_evacuation_budgets(ShenandoahHeap* if (add_regions_to_young > 0) { assert(excess_old >= add_regions_to_young * region_size_bytes, "Cannot xfer more than excess old"); + if (heap->age_census()->is_always_tenure()) { + // Cap excess_old at one min-PLAB per worker so this much stays in old's promotion reserve + // instead of being transferred to young. + const size_t min_plab_total = heap->plab_min_size() * HeapWordSize * heap->workers()->max_workers(); + if (excess_old > min_plab_total) { + excess_old = min_plab_total; + // Avoid underflowing excess_old when we subtract below. + add_regions_to_young = 0; + } + } excess_old -= add_regions_to_young * region_size_bytes; log_debug(gc, ergo)("Before start of evacuation, total_promotion reserve is young_advance_promoted_reserve: %zu " "plus excess: old: %zu", young_advance_promoted_reserve_used, excess_old); diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp index a0e4ab78d5c..1860e3d4c0f 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp @@ -92,7 +92,7 @@ private: // Adjust evacuation budgets after choosing collection set. On entry, the instance variable _regions_to_xfer // represents regions to be transferred to old based on decisions made in top_off_collection_set() - void adjust_evacuation_budgets(ShenandoahHeap* const heap, + void adjust_evacuation_budgets(ShenandoahGenerationalHeap* const heap, ShenandoahCollectionSet* const collection_set); protected: diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp index e4ac576aa6f..d9f3bdee828 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp @@ -179,6 +179,12 @@ void ShenandoahGlobalHeuristics::choose_global_collection_set(ShenandoahCollecti size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + original_young_evac_reserve; size_t min_garbage = (free_target > actual_free) ? (free_target - actual_free) : 0; + // Admit every region with any garbage so every live object gets a chance to be promoted. + if (heap->age_census()->is_always_tenure()) { + ignore_threshold = 0; + min_garbage = SIZE_MAX; + } + ShenandoahGlobalCSetBudget budget(region_size_bytes, shared_reserve_regions * region_size_bytes, garbage_threshold, ignore_threshold, min_garbage, diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 4989c929b32..8fa497802fd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -34,7 +34,7 @@ ShenandoahAgeCensus::ShenandoahAgeCensus() } ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers) - : _max_workers(max_workers) + : _max_workers(max_workers), _always_tenure(false) { if (ShenandoahGenerationalMinTenuringAge > ShenandoahGenerationalMaxTenuringAge) { vm_exit_during_initialization( diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index c140f445e21..5636dee3ae2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -121,6 +121,8 @@ class ShenandoahAgeCensus: public CHeapObj { uint _max_workers; // Maximum number of workers for parallel tasks + bool _always_tenure; // When true, every age is tenurable. + // Mortality rate of a cohort, given its population in // previous and current epochs double mortality_rate(size_t prev_pop, size_t cur_pop); @@ -150,9 +152,9 @@ class ShenandoahAgeCensus: public CHeapObj { return _tenuring_threshold[prev]; } - // Override the tenuring threshold for the current epoch. This is used to - // cause everything to be promoted for a whitebox full gc request. - void set_tenuring_threshold(uint threshold) { _tenuring_threshold[_epoch] = threshold; } + // Set always tenure mode. Currently only used by ShenandoahTenuringOverride + // to force is_tenurable() to be true for every age during WB.fullGC tests. + void set_always_tenure(bool always_tenure) { _always_tenure = always_tenure; } #ifndef PRODUCT // Return the sum of size of objects of all ages recorded in the @@ -187,11 +189,13 @@ class ShenandoahAgeCensus: public CHeapObj { // Visible for testing. Use is_tenurable for consistent tenuring comparisons. uint tenuring_threshold() const { return _tenuring_threshold[_epoch]; } - // Return true if this age is at or above the tenuring threshold. + // Return true if this age is at or above the tenuring threshold, or if always tenure is enabled. bool is_tenurable(uint age) const { - return age >= tenuring_threshold(); + return age >= tenuring_threshold() || _always_tenure; } + bool is_always_tenure() const { return _always_tenure; } + // Update the local age table for worker_id by size for // given obj_age, region_age, and region_youth CENSUS_NOISE(void add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id);) @@ -244,24 +248,22 @@ class ShenandoahAgeCensus: public CHeapObj { void print(); }; -// RAII object that temporarily overrides the tenuring threshold for the -// duration of a scope, restoring the original value on destruction. -// Used to force promotion of all young objects during whitebox full GCs. +// RAII object that enables ShenandoahAgeCensus always tenure mode for the +// duration of a scope and disables it on destruction. Used to force promotion +// of all young objects during whitebox full GCs. class ShenandoahTenuringOverride : public StackObj { ShenandoahAgeCensus* _census; - uint _saved_threshold; bool _active; public: ShenandoahTenuringOverride(bool active, ShenandoahAgeCensus* census) : - _census(census), _saved_threshold(0), _active(active) { + _census(census), _active(active) { if (_active) { - _saved_threshold = _census->tenuring_threshold(); - _census->set_tenuring_threshold(0); + _census->set_always_tenure(true); } } ~ShenandoahTenuringOverride() { if (_active) { - _census->set_tenuring_threshold(_saved_threshold); + _census->set_always_tenure(false); } } }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index b3a48f85114..94f3409ac41 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -269,7 +269,7 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest // Cannot uncommit bitmap slices during concurrent reset ShenandoahNoUncommitMark forbid_region_uncommit(_heap); - // When a whitebox full GC is requested, set the tenuring threshold to zero + // When a whitebox full GC is requested, set the age census to always tenure // so that all young objects are promoted to old. This ensures that tests // using WB.fullGC() to promote objects to old gen will not loop forever. ShenandoahTenuringOverride tenuring_override(request.cause == GCCause::_wb_full_gc, diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp index 3d98d431b86..f139f94fc8b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp @@ -39,7 +39,7 @@ ShenandoahPLAB::ShenandoahPLAB() : _promoted(0), _promotion_failure_count(0), _promotion_failure_words(0), - _allows_promotion(false), + _allows_promotion(true), _retries_enabled(false), _heap(ShenandoahGenerationalHeap::heap()) { _plab = new PLAB(PLAB::min_size()); From 70c92d6c2bce373067b6a8df72f194b6f9d08259 Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Mon, 1 Jun 2026 18:37:35 +0000 Subject: [PATCH 17/32] 8382275: Update nsk/stress/jni to use ThreadWrapper Reviewed-by: lmesnik, syan --- .../vmTestbase/nsk/stress/jni/GarbageGenerator.java | 5 +++-- .../vmTestbase/nsk/stress/jni/jnistress001.java | 12 ++++++++---- .../vmTestbase/nsk/stress/jni/jnistress002.java | 12 ++++++++---- .../vmTestbase/nsk/stress/jni/jnistress003.java | 12 ++++++++---- .../vmTestbase/nsk/stress/jni/jnistress004.java | 12 ++++++++---- .../vmTestbase/nsk/stress/jni/jnistress005.java | 12 ++++++++---- .../vmTestbase/nsk/stress/jni/jnistress006.java | 12 ++++++++---- .../vmTestbase/nsk/stress/jni/jnistress007.java | 12 ++++++++---- 8 files changed, 59 insertions(+), 30 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/GarbageGenerator.java b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/GarbageGenerator.java index bb9c9afa394..bc3ada61136 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/GarbageGenerator.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/GarbageGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -22,8 +22,9 @@ */ package nsk.stress.jni; +import jdk.test.lib.thread.ThreadWrapper; -class GarbageGenerator extends Thread { +class GarbageGenerator extends ThreadWrapper { class Garbage { Garbage() { this(1024); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress001.java b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress001.java index 0335942e775..16ccaa310a7 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -61,8 +61,9 @@ package nsk.stress.jni; import nsk.share.Consts; import nsk.share.Debug; import nsk.share.test.StressOptions; +import jdk.test.lib.thread.ThreadWrapper; -public class jnistress001 extends Thread { +public class jnistress001 extends ThreadWrapper { /* Maximum number of iterations. Ignored if <= 0L */ static long numIteration = 0L; @@ -256,8 +257,11 @@ public class jnistress001 extends Thread { garb = new GarbageGenerator[nGarb]; for (i = 0; i < nJNI; i++) jniter[i] = new JNIter001(sync); + Thread[] jniterThreads = new Thread[nJNI]; + for (i = 0; i < nJNI; i++) + jniterThreads[i] = jniter[i].getThread(); for (i = 0; i < nInter; i++) { - irupt[i] = new Interrupter(jniter, sync); + irupt[i] = new Interrupter(jniterThreads, sync); irupt[i].setInterval(iruptInterval); } for (i = 0; i < nGarb; i++) { @@ -372,7 +376,7 @@ public class jnistress001 extends Thread { final private static boolean DEBUG = false; } -class JNIter001 extends Thread { +class JNIter001 extends ThreadWrapper { // The native method for testing JNI UTF-8 calls public native String jnistress(String threadName, int nstr, int printPeriod); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress002.java b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress002.java index 1a9e2999f20..5b6912b623d 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -60,12 +60,13 @@ package nsk.stress.jni; import nsk.share.Consts; import nsk.share.Debug; import nsk.share.test.StressOptions; +import jdk.test.lib.thread.ThreadWrapper; import jdk.test.lib.Utils; import java.lang.reflect.Field; import java.util.Random; -public class jnistress002 extends Thread { +public class jnistress002 extends ThreadWrapper { /* Maximum number of iterations. Ignored if <= 0L */ static long numIteration = 0L; @@ -246,8 +247,11 @@ public class jnistress002 extends Thread { garb = new GarbageGenerator[nGarb]; for (i = 0; i < nJNI; i++) jniter[i] = new JNIter002(sync); + Thread[] jniterThreads = new Thread[nJNI]; + for (i = 0; i < nJNI; i++) + jniterThreads[i] = jniter[i].getThread(); for (i = 0; i < nInter; i++) { - irupt[i] = new Interrupter(jniter, sync); + irupt[i] = new Interrupter(jniterThreads, sync); irupt[i].setInterval(iruptInterval); } for (i = 0; i < nGarb; i++) { @@ -423,7 +427,7 @@ class objectsJNI { } } -class JNIter002 extends Thread { +class JNIter002 extends ThreadWrapper { // The native method for testing JNI Object's calls public native objectsJNI[] jniobjects(String s, int i, long l, diff --git a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress003.java b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress003.java index e06f2e6279f..b1e2702011a 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress003.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress003.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -60,10 +60,11 @@ package nsk.stress.jni; import nsk.share.Consts; import nsk.share.Debug; import nsk.share.test.StressOptions; +import jdk.test.lib.thread.ThreadWrapper; import java.lang.reflect.Array; -public class jnistress003 extends Thread { +public class jnistress003 extends ThreadWrapper { /* Maximum number of iterations. Ignored if <= 0L */ static long numIteration = 0L; @@ -257,8 +258,11 @@ public class jnistress003 extends Thread { garb = new GarbageGenerator[nGarb]; for (i = 0; i < nJNI; i++) jniter[i] = new JNIter003(sync); + Thread[] jniterThreads = new Thread[nJNI]; + for (i = 0; i < nJNI; i++) + jniterThreads[i] = jniter[i].getThread(); for (i = 0; i < nInter; i++) { - irupt[i] = new Interrupter(jniter, sync); + irupt[i] = new Interrupter(jniterThreads, sync); irupt[i].setInterval(iruptInterval); } for (i = 0; i < nGarb; i++) { @@ -379,7 +383,7 @@ public class jnistress003 extends Thread { final private static boolean DEBUG = false; } -class JNIter003 extends Thread { +class JNIter003 extends ThreadWrapper { // The native methods for testing JNI Arrays calls diff --git a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress004.java b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress004.java index 6432b0ccc3c..705ddd8a5f1 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress004.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress004.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -60,8 +60,9 @@ package nsk.stress.jni; import nsk.share.Consts; import nsk.share.Debug; import nsk.share.test.StressOptions; +import jdk.test.lib.thread.ThreadWrapper; -public class jnistress004 extends Thread { +public class jnistress004 extends ThreadWrapper { /* Maximum number of iterations. Ignored if <= 0L */ static long numIteration = 2L; @@ -243,8 +244,11 @@ public class jnistress004 extends Thread { garb = new GarbageGenerator[nGarb]; for (i = 0; i < nJNI; i++) jniter[i] = new JNIter004(sync); + Thread[] jniterThreads = new Thread[nJNI]; + for (i = 0; i < nJNI; i++) + jniterThreads[i] = jniter[i].getThread(); for (i = 0; i < nInter; i++) { - irupt[i] = new Interrupter(jniter, sync); + irupt[i] = new Interrupter(jniterThreads, sync); irupt[i].setInterval(iruptInterval); } for (i = 0; i < nGarb; i++) { @@ -364,7 +368,7 @@ public class jnistress004 extends Thread { final private static boolean DEBUG = false; } -class JNIter004 extends Thread { +class JNIter004 extends ThreadWrapper { // The native methods for testing JNI critical calls public native char[] CheckSum(String str); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress005.java b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress005.java index a841635462b..0144c116f1e 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress005.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress005.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -61,8 +61,9 @@ package nsk.stress.jni; import nsk.share.Consts; import nsk.share.Debug; import nsk.share.test.StressOptions; +import jdk.test.lib.thread.ThreadWrapper; -public class jnistress005 extends Thread { +public class jnistress005 extends ThreadWrapper { /* Maximum number of iterations. Ignored if <= 0L */ static long numIteration = 0L; @@ -244,8 +245,11 @@ public class jnistress005 extends Thread { garb = new GarbageGenerator[nGarb]; for (i = 0; i < nJNI; i++) jniter[i] = new JNIter005(sync); + Thread[] jniterThreads = new Thread[nJNI]; + for (i = 0; i < nJNI; i++) + jniterThreads[i] = jniter[i].getThread(); for (i = 0; i < nInter; i++) { - irupt[i] = new Interrupter(jniter, sync); + irupt[i] = new Interrupter(jniterThreads, sync); irupt[i].setInterval(iruptInterval); } for (i = 0; i < nGarb; i++) { @@ -366,7 +370,7 @@ public class jnistress005 extends Thread { final private static boolean DEBUG = false; } -class JNIter005 extends Thread { +class JNIter005 extends ThreadWrapper { // The native methods for testing JNI exception calls public native void except(Throwable tobj); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress006.java b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress006.java index b6646bb45d4..80178bdef28 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress006.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress006.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -61,8 +61,9 @@ package nsk.stress.jni; import nsk.share.Consts; import nsk.share.Debug; import nsk.share.test.StressOptions; +import jdk.test.lib.thread.ThreadWrapper; -public class jnistress006 extends Thread { +public class jnistress006 extends ThreadWrapper { /* Maximum number of iterations. Ignored if <= 0L */ static long numIteration = 0L; @@ -245,8 +246,11 @@ public class jnistress006 extends Thread { garb = new GarbageGenerator[nGarb]; for (i = 0; i < nJNI; i++) jniter[i] = new JNIter006(sync); + Thread[] jniterThreads = new Thread[nJNI]; + for (i = 0; i < nJNI; i++) + jniterThreads[i] = jniter[i].getThread(); for (i = 0; i < nInter; i++) { - irupt[i] = new Interrupter(jniter, sync); + irupt[i] = new Interrupter(jniterThreads, sync); irupt[i].setInterval(iruptInterval); } for (i = 0; i < nGarb; i++) { @@ -366,7 +370,7 @@ public class jnistress006 extends Thread { final private static boolean DEBUG = false; } -class JNIter006 extends Thread { +class JNIter006 extends ThreadWrapper { // The native methods for testing JNI exception calls public native boolean refs(Object tobj, int jniStringAllocSize); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress007.java b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress007.java index 7497660c9e0..727560fae30 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress007.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/stress/jni/jnistress007.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -61,8 +61,9 @@ package nsk.stress.jni; import nsk.share.Consts; import nsk.share.Debug; import nsk.share.test.StressOptions; +import jdk.test.lib.thread.ThreadWrapper; -public class jnistress007 extends Thread { +public class jnistress007 extends ThreadWrapper { /* Maximum number of iterations. Ignored if <= 0L */ static long numIteration = 0L; @@ -244,8 +245,11 @@ public class jnistress007 extends Thread { garb = new GarbageGenerator[nGarb]; for (i = 0; i < nJNI; i++) jniter[i] = new JNIter007(sync); + Thread[] jniterThreads = new Thread[nJNI]; + for (i = 0; i < nJNI; i++) + jniterThreads[i] = jniter[i].getThread(); for (i = 0; i < nInter; i++) { - irupt[i] = new Interrupter(jniter, sync); + irupt[i] = new Interrupter(jniterThreads, sync); irupt[i].setInterval(iruptInterval); } for (i = 0; i < nGarb; i++) { @@ -364,7 +368,7 @@ public class jnistress007 extends Thread { final private static boolean DEBUG = false; } -class JNIter007 extends Thread { +class JNIter007 extends ThreadWrapper { // The native methods for testing JNI monitors calls public native void incCount(String name); From f640edebf0074f231c1c0a24273536738eb18e28 Mon Sep 17 00:00:00 2001 From: Alexander Zvegintsev Date: Tue, 2 Jun 2026 00:04:01 +0000 Subject: [PATCH 18/32] 8341382: EXCEPTION_ACCESS_VIOLATION in awt.dll after JDK-8185862 Reviewed-by: kizune, prr, vdyakov --- .../windows/native/libawt/windows/Devices.cpp | 144 ++++++++++++------ .../windows/native/libawt/windows/Devices.h | 7 +- .../native/libawt/windows/awt_Toolkit.cpp | 104 +++++++++++-- .../windows/awt_Win32GraphicsDevice.cpp | 70 +++++++-- .../libawt/windows/awt_Win32GraphicsDevice.h | 4 +- .../libawt/windows/awt_Win32GraphicsEnv.cpp | 12 +- 6 files changed, 260 insertions(+), 81 deletions(-) diff --git a/src/java.desktop/windows/native/libawt/windows/Devices.cpp b/src/java.desktop/windows/native/libawt/windows/Devices.cpp index 8096b1084a2..7192f65425b 100644 --- a/src/java.desktop/windows/native/libawt/windows/Devices.cpp +++ b/src/java.desktop/windows/native/libawt/windows/Devices.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -117,47 +117,78 @@ static BOOL IsValidMonitor(HMONITOR hMon) return TRUE; } -// Callback for CountMonitors below -static BOOL WINAPI clb_fCountMonitors(HMONITOR hMon, HDC hDC, LPRECT rRect, LPARAM lpMonitorCounter) -{ - if (IsValidMonitor(hMon)) { - (*((int *)lpMonitorCounter))++; - } - - return TRUE; -} - -int WINAPI CountMonitors(void) -{ - int monitorCounter = 0; - ::EnumDisplayMonitors(NULL, NULL, clb_fCountMonitors, (LPARAM)&monitorCounter); - return monitorCounter; -} // Callback for CollectMonitors below static BOOL WINAPI clb_fCollectMonitors(HMONITOR hMon, HDC hDC, LPRECT rRect, LPARAM lpMonitorData) { MonitorData* pMonitorData = (MonitorData *)lpMonitorData; - if ((pMonitorData->monitorCounter < pMonitorData->monitorLimit) && (IsValidMonitor(hMon))) { - pMonitorData->hmpMonitors[pMonitorData->monitorCounter] = hMon; - pMonitorData->monitorCounter++; + + if (!IsValidMonitor(hMon)) { + return TRUE; } + if (pMonitorData->monitorCounter == pMonitorData->monitorLimit) { + TRY; + + int newMonitorLimit = pMonitorData->monitorLimit * 2; + HMONITOR* newMonitors = + (HMONITOR*)SAFE_SIZE_ARRAY_REALLOC( + safe_Realloc, pMonitorData->hmpMonitors, + newMonitorLimit, sizeof(HMONITOR) + ); + pMonitorData->hmpMonitors = newMonitors; + pMonitorData->monitorLimit = newMonitorLimit; + + CATCH_BAD_ALLOC_RET(FALSE); + } + + pMonitorData->hmpMonitors[pMonitorData->monitorCounter] = hMon; + pMonitorData->monitorCounter++; + return TRUE; } -static int WINAPI CollectMonitors(HMONITOR* hmpMonitors, int nNum) +static HMONITOR* CollectMonitors(int* numScreens) { - if (NULL != hmpMonitors) { - MonitorData monitorData; - monitorData.monitorCounter = 0; - monitorData.monitorLimit = nNum; - monitorData.hmpMonitors = hmpMonitors; - ::EnumDisplayMonitors(NULL, NULL, clb_fCollectMonitors, (LPARAM)&monitorData); - return monitorData.monitorCounter; - } else { - return 0; + const int initialMonitorLimit = 4; + + *numScreens = 0; + + MonitorData data; + data.monitorCounter = 0; + data.monitorLimit = initialMonitorLimit; + + TRY; + + data.hmpMonitors = (HMONITOR*)SAFE_SIZE_ARRAY_ALLOC(safe_Malloc, + initialMonitorLimit, sizeof(HMONITOR)); + CATCH_BAD_ALLOC_RET(NULL); + + if (!::EnumDisplayMonitors(NULL, NULL, clb_fCollectMonitors, (LPARAM)&data)) { + free(data.hmpMonitors); + return NULL; } + + *numScreens = data.monitorCounter; + return data.hmpMonitors; +} + +int WINAPI CountMonitors() +{ + int numScreens = 0; + HMONITOR* monHds = CollectMonitors(&numScreens); + free(monHds); + return numScreens; +} + +static BOOL AreSameMonitorInfo(LPMONITORINFOEX oldInfo, LPMONITORINFOEX newInfo) +{ + if (oldInfo == NULL || newInfo == NULL) { + return FALSE; + } + + return oldInfo->dwFlags == newInfo->dwFlags + && ::lstrcmp(oldInfo->szDevice, newInfo->szDevice) == 0; } BOOL WINAPI MonitorBounds(HMONITOR hmMonitor, RECT* rpBounds) @@ -206,17 +237,26 @@ BOOL Devices::UpdateInstance(JNIEnv *env) { J2dTraceLn(J2D_TRACE_INFO, "Devices::UpdateInstance"); - int numScreens = CountMonitors(); - HMONITOR *monHds = (HMONITOR *)SAFE_SIZE_ARRAY_ALLOC(safe_Malloc, - numScreens, sizeof(HMONITOR)); - if (numScreens != CollectMonitors(monHds, numScreens)) { + int numScreens = 0; + HMONITOR *monHds = CollectMonitors(&numScreens); + if (monHds == NULL) { J2dRlsTraceLn(J2D_TRACE_ERROR, - "Devices::UpdateInstance: Failed to get all "\ + "Devices::UpdateInstance: Failed to get "\ "monitor handles."); free(monHds); return FALSE; } + if (numScreens == 0) { + CriticalSection::Lock l(arrayLock); + if (theInstance != NULL) { + J2dRlsTraceLn(J2D_TRACE_ERROR, + "Devices::UpdateInstance: No valid monitor handles."); + free(monHds); + return FALSE; + } + } + Devices *newDevices = new Devices(numScreens); // This way we know that the array will not be disposed of // at least until we replaced it with a new one. @@ -242,18 +282,26 @@ BOOL Devices::UpdateInstance(JNIEnv *env) theInstance = newDevices; if (oldDevices) { - // Invalidate the devices with indexes out of the new set of - // devices. This doesn't cover all cases when the device - // might should be invalidated (like if it's not the last device - // that was removed), but it will have to do for now. int oldNumScreens = oldDevices->GetNumDevices(); - int newNumScreens = theInstance->GetNumDevices(); - J2dTraceLn(J2D_TRACE_VERBOSE, " Invalidating removed devices"); - for (int i = newNumScreens; i < oldNumScreens; i++) { - // removed device, needs to be invalidated + J2dTraceLn(J2D_TRACE_VERBOSE, " Invalidating changed devices"); + for (int i = 0; i < oldNumScreens; i++) { + AwtWin32GraphicsDevice *oldDevice = + oldDevices->GetDevice(i, FALSE); + AwtWin32GraphicsDevice *newDevice = + theInstance->GetDevice(i, FALSE); + BOOL changed = (newDevice == NULL) + || !AreSameMonitorInfo( + (LPMONITORINFOEX) oldDevice->GetMonitorInfo(), + (LPMONITORINFOEX) newDevice->GetMonitorInfo()); + + if (!changed) { + newDevice->TransferJavaDevice(env, oldDevice); + continue; + } + J2dTraceLn(J2D_TRACE_WARNING, - "Devices::UpdateInstance: device removed: %d", i); - oldDevices->GetDevice(i)->Invalidate(env); + "Devices::UpdateInstance: device changed: %d", i); + oldDevice->Invalidate(env); } // Now that we have a new array in place, remove this (possibly the // last) reference to the old instance. @@ -346,6 +394,12 @@ AwtWin32GraphicsDevice *Devices::GetDevice(int index, BOOL adjust) J2dTraceLn(J2D_TRACE_INFO, "Devices::GetDevice index=%d adjust?=%d", index, adjust); + if (numDevices <= 0) { + J2dTraceLn(J2D_TRACE_WARNING, + "Devices::GetDevice: "\ + "no devices, returning NULL."); + return NULL; + } if (index < 0 || index >= numDevices) { if (!adjust) { J2dTraceLn(J2D_TRACE_WARNING, diff --git a/src/java.desktop/windows/native/libawt/windows/Devices.h b/src/java.desktop/windows/native/libawt/windows/Devices.h index 0972ef1414e..7e7a419453e 100644 --- a/src/java.desktop/windows/native/libawt/windows/Devices.h +++ b/src/java.desktop/windows/native/libawt/windows/Devices.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -47,8 +47,11 @@ static BOOL UpdateInstance(JNIEnv *env); class InstanceAccess { public: INLINE InstanceAccess() { devices = Devices::GetInstance(); } - INLINE ~InstanceAccess() { devices->Release(); } + INLINE ~InstanceAccess() { if (devices != NULL) devices->Release(); } Devices* operator->() { return devices; } + INLINE AwtWin32GraphicsDevice* Device(int index, BOOL adjust = TRUE) { + return devices == NULL ? NULL : devices->GetDevice(index, adjust); + } private: Devices* devices; // prevent bad things like copying or getting address of diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp index b447ad6889a..a94c96c58c5 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, 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 @@ -65,7 +65,7 @@ #include #include -extern void initScreens(JNIEnv *env); +extern BOOL initScreens(JNIEnv *env); extern "C" void awt_dnd_initialize(); extern "C" void awt_dnd_uninitialize(); extern "C" void awt_clipboard_uninitialize(JNIEnv *env); @@ -157,6 +157,78 @@ extern "C" JNIEXPORT jboolean JNICALL AWTIsHeadless() { } #define IDT_AWT_MOUSECHECK 0x101 +#define IDT_AWT_DISPLAYCHANGE 0x102 + +#define AWT_DISPLAYCHANGE_RETRY_DELAY 250 +#define AWT_DISPLAYCHANGE_RETRY_LIMIT 20 + +class DisplayChangeHandler { +public: + static BOOL Handle(JNIEnv *env, HWND hWnd) { + // Reinitialize screens + if (!initScreens(env)) { + OnDisplayChangeFailed(hWnd); + return FALSE; + } + + OnDisplayChangeSucceeded(hWnd); + + // Notify Java side - call WToolkit.displayChanged() + jclass clazz = env->FindClass("sun/awt/windows/WToolkit"); + DASSERT(clazz != NULL); + if (!clazz) throw std::bad_alloc(); + env->CallStaticVoidMethod(clazz, AwtToolkit::displayChangeMID); + + return !env->ExceptionCheck(); + } + + static void Reset(HWND hWnd) { + ::KillTimer(hWnd, IDT_AWT_DISPLAYCHANGE); + retryCount = 0; + } + + static void ScheduleFromSessionChange(HWND hWnd) { + if (!recoveryPending) { + return; + } + Reset(hWnd); + Schedule(hWnd); + } + +private: + static void OnDisplayChangeFailed(HWND hWnd) { + recoveryPending = TRUE; + Schedule(hWnd); + } + + static void OnDisplayChangeSucceeded(HWND hWnd) { + recoveryPending = FALSE; + Reset(hWnd); + } + + static void Schedule(HWND hWnd) { + if (retryCount >= AWT_DISPLAYCHANGE_RETRY_LIMIT) { + Reset(hWnd); + J2dRlsTraceLn(J2D_TRACE_ERROR, + "AwtToolkit: Display change retry limit exceeded."); + return; + } + + retryCount++; + if (::SetTimer(hWnd, IDT_AWT_DISPLAYCHANGE, + AWT_DISPLAYCHANGE_RETRY_DELAY, NULL) == 0) { + Reset(hWnd); + J2dRlsTraceLn(J2D_TRACE_ERROR, + "AwtToolkit: Failed to schedule display change retry."); + } + } + + static int retryCount; + static BOOL recoveryPending; +}; + +int DisplayChangeHandler::retryCount = 0; +BOOL DisplayChangeHandler::recoveryPending = FALSE; static LPCTSTR szAwtToolkitClassName = TEXT("SunAwtToolkit"); @@ -1004,6 +1076,14 @@ LRESULT CALLBACK AwtToolkit::WndProc(HWND hWnd, UINT message, } case WM_TIMER: { + if (wParam == IDT_AWT_DISPLAYCHANGE) { + if (DisplayChangeHandler::Handle(env, hWnd)) { + GetInstance().m_displayChanged = TRUE; + ::PostMessage(HWND_BROADCAST, WM_PALETTEISCHANGING, NULL, NULL); + } + return 0; + } + // 6479820. Should check if a window is in manual resizing process: skip // sending any MouseExit/Enter events while inside resize-loop. // Note that window being in manual moving process could still @@ -1245,18 +1325,11 @@ LRESULT CALLBACK AwtToolkit::WndProc(HWND hWnd, UINT message, return tk.m_inputMethodData; } case WM_DISPLAYCHANGE: { - // Reinitialize screens - initScreens(env); - - // Notify Java side - call WToolkit.displayChanged() - jclass clazz = env->FindClass("sun/awt/windows/WToolkit"); - DASSERT(clazz != NULL); - if (!clazz) throw std::bad_alloc(); - env->CallStaticVoidMethod(clazz, AwtToolkit::displayChangeMID); - - GetInstance().m_displayChanged = TRUE; - - ::PostMessage(HWND_BROADCAST, WM_PALETTEISCHANGING, NULL, NULL); + DisplayChangeHandler::Reset(hWnd); + if (DisplayChangeHandler::Handle(env, hWnd)) { + GetInstance().m_displayChanged = TRUE; + ::PostMessage(HWND_BROADCAST, WM_PALETTEISCHANGING, NULL, NULL); + } break; } /* Session management */ @@ -1341,6 +1414,9 @@ LRESULT CALLBACK AwtToolkit::WndProc(HWND hWnd, UINT message, activate ? JNI_TRUE : JNI_FALSE, reason); + if (activate) { + DisplayChangeHandler::ScheduleFromSessionChange(hWnd); + } } break; } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp index 785f1301516..a43bd9c6bef 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -612,13 +612,52 @@ void AwtWin32GraphicsDevice::Release() } /** - * Links this native object with its java Win32GraphicsDevice. - * Need this link because the colorModel of the java device - * may be updated from native code. + * Links this native object with its java Win32GraphicsDevice peer. + * + * The link is needed for upcalls to the java peer, such as invalidate() + * and dynamic color model updates. + * + * Passing NULL intentionally clears the link. + * Clearing it here prevents stale peer links and releases + * the old JNI weak global ref. + * + * During display changes, the native device array is recreated, + * changed or removed devices invalidate their java peers. + * Unchanged monitors transfer the existing weak ref to + * the new native device by TransferJavaDevice(). */ void AwtWin32GraphicsDevice::SetJavaDevice(JNIEnv *env, jobject objPtr) { - javaDevice = env->NewWeakGlobalRef(objPtr); + jobject newJavaDevice = NULL; + if (objPtr != NULL) { + newJavaDevice = env->NewWeakGlobalRef(objPtr); + if (newJavaDevice == NULL) { + return; + } + } + + if (javaDevice != NULL) { + env->DeleteWeakGlobalRef(javaDevice); + } + javaDevice = newJavaDevice; +} + +/** + * Transfers the java Win32GraphicsDevice's link from a native device that is + * being replaced by a new native device for the same monitor. + */ +void AwtWin32GraphicsDevice::TransferJavaDevice(JNIEnv *env, + AwtWin32GraphicsDevice *device) +{ + if (device == NULL || device == this || device->javaDevice == NULL) { + return; + } + + if (javaDevice != NULL) { + env->DeleteWeakGlobalRef(javaDevice); + } + javaDevice = device->javaDevice; + device->javaDevice = NULL; } /** @@ -1398,7 +1437,10 @@ JNIEXPORT void JNICALL (JNIEnv *env, jobject thisPtr, jint screen) { Devices::InstanceAccess devices; - devices->GetDevice(screen)->SetJavaDevice(env, thisPtr); + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); + if (device != NULL) { + device->SetJavaDevice(env, thisPtr); + } } /* @@ -1411,9 +1453,8 @@ JNIEXPORT void JNICALL (JNIEnv *env, jobject thisPtr, jint screen, jfloat scaleX, jfloat scaleY) { Devices::InstanceAccess devices; - AwtWin32GraphicsDevice *device = devices->GetDevice(screen); - - if (device != NULL ) { + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); + if (device != NULL) { device->DisableScaleAutoRefresh(); device->SetScale(scaleX, scaleY); } @@ -1429,8 +1470,8 @@ JNIEXPORT jfloat JNICALL (JNIEnv *env, jobject thisPtr, jint screen) { Devices::InstanceAccess devices; - AwtWin32GraphicsDevice *device = devices->GetDevice(screen); - return (device == NULL) ? 1 : device->GetScaleX(); + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); + return device == NULL ? 1 : device->GetScaleX(); } /* @@ -1443,8 +1484,8 @@ JNIEXPORT jfloat JNICALL (JNIEnv *env, jobject thisPtr, jint screen) { Devices::InstanceAccess devices; - AwtWin32GraphicsDevice *device = devices->GetDevice(screen); - return (device == NULL) ? 1 : device->GetScaleY(); + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); + return device == NULL ? 1 : device->GetScaleY(); } /* @@ -1457,8 +1498,7 @@ Java_sun_awt_Win32GraphicsDevice_initNativeScale (JNIEnv *env, jobject thisPtr, jint screen) { Devices::InstanceAccess devices; - AwtWin32GraphicsDevice *device = devices->GetDevice(screen); - + AwtWin32GraphicsDevice *device = devices.Device(screen, FALSE); if (device != NULL) { device->InitDesktopScales(); } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.h b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.h index 55f6c1623a8..b3619dcc545 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.h +++ b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsDevice.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -54,6 +54,8 @@ public: unsigned int *GetSystemPaletteEntries(); unsigned char *GetSystemInverseLUT(); void SetJavaDevice(JNIEnv *env, jobject objPtr); + void TransferJavaDevice(JNIEnv *env, + AwtWin32GraphicsDevice *device); HPALETTE SelectPalette(HDC hDC); void RealizePalette(HDC hDC); HPALETTE GetPalette(); diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp index 50591434d4c..cc53f4a3322 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -35,10 +35,12 @@ BOOL DWMIsCompositionEnabled(); -void initScreens(JNIEnv *env) { +BOOL initScreens(JNIEnv *env) { if (!Devices::UpdateInstance(env)) { - JNU_ThrowInternalError(env, "Could not update the devices array."); + J2dRlsTraceLn(J2D_TRACE_ERROR, "initScreens: Could not update the devices array."); + return FALSE; } + return TRUE; } /** @@ -144,7 +146,9 @@ Java_sun_awt_Win32GraphicsEnvironment_initDisplay(JNIEnv *env, DWMIsCompositionEnabled(); - initScreens(env); + if (!initScreens(env)) { + JNU_ThrowInternalError(env, "Could not update the devices array."); + } } /* From 207dc4e82f46d5991421f5a6c66fa82048d3bc74 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Tue, 2 Jun 2026 04:37:09 +0000 Subject: [PATCH 19/32] 8322256: Define and document GZIPInputStream concatenated stream semantics Reviewed-by: lancea, alanb, simonis --- .../java/util/zip/GZIPInputStream.java | 93 ++++--- .../zip/GZIP/BasicGZIPInputStreamTest.java | 36 ++- .../util/zip/GZIP/GZIPInputStreamRead.java | 238 +++++++++++++----- 3 files changed, 272 insertions(+), 95 deletions(-) diff --git a/src/java.base/share/classes/java/util/zip/GZIPInputStream.java b/src/java.base/share/classes/java/util/zip/GZIPInputStream.java index 72fb8036f08..88d08386e8c 100644 --- a/src/java.base/share/classes/java/util/zip/GZIPInputStream.java +++ b/src/java.base/share/classes/java/util/zip/GZIPInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, 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 @@ -34,17 +34,55 @@ import java.io.EOFException; import java.util.Objects; /** - * This class implements a stream filter for reading compressed data in - * the GZIP file format. + * This class implements a stream filter for decompressing GZIP file format data. + * + *

GZIP file format

+ * The GZIP file format is specified by RFC 1952. The format, as specified in section 2.2 of + * the RFC, consists of a series of "members" that appear one after another in the stream with + * no additional information before, between, or after them. Each member consists of a header, + * followed by data that is compressed using the {@code deflate} algorithm, and then a trailer. + *

+ * This class is capable of reading a stream consisting of a series of members. + *

+ * Reading from the stream may read and buffer bytes from the underlying stream. + * This includes bytes that follow a member's trailer. Whether or not any additional bytes + * have been read past a member's trailer, the read methods on this class yield decompressed + * data from at most one member; data from multiple members is not combined in + * a single read operation. + * + *

Thread safety

+ * {@code GZIPInputStream} is not safe for use by multiple concurrent threads. Any multithreaded + * concurrent use must be guarded by appropriate synchronization. + * + * @apiNote + * The {@link #close} method should be called to release resources used by this + * stream, either directly, or with the {@code try}-with-resources statement. + * + * @spec https://www.rfc-editor.org/info/rfc1952 + * RFC 1952: GZIP file format specification version 4.3 + * + * @see InflaterInputStream * - * @see InflaterInputStream - * @author David Connelly * @since 1.1 - * */ public class GZIPInputStream extends InflaterInputStream { /** - * CRC-32 for uncompressed data. + * GZIP header magic number. + */ + public static final int GZIP_MAGIC = 0x8b1f; + + /* + * File header flags. + */ + private static final int FHCRC = 2; // Header CRC + private static final int FEXTRA = 4; // Extra field + private static final int FNAME = 8; // File name + private static final int FCOMMENT = 16; // File comment + + private final byte[] tmpbuf = new byte[128]; + + /** + * CRC-32 for decompressed data. */ protected CRC32 crc = new CRC32(); @@ -66,13 +104,15 @@ public class GZIPInputStream extends InflaterInputStream { /** * Creates a new input stream with the specified buffer size. + * * @param in the input stream * @param size the input buffer size * * @throws ZipException if a GZIP format error has occurred or the * compression method used is unsupported * @throws NullPointerException if {@code in} is null - * @throws IOException if an I/O error has occurred + * @throws IOException if an I/O error occurs when reading the member header + * from the underlying stream * @throws IllegalArgumentException if {@code size <= 0} */ public GZIPInputStream(InputStream in, int size) throws IOException { @@ -103,25 +143,27 @@ public class GZIPInputStream extends InflaterInputStream { /** * Creates a new input stream with a default buffer size. + * * @param in the input stream * * @throws ZipException if a GZIP format error has occurred or the * compression method used is unsupported * @throws NullPointerException if {@code in} is null - * @throws IOException if an I/O error has occurred + * @throws IOException if an I/O error occurs when reading the member header + * from the underlying stream */ public GZIPInputStream(InputStream in) throws IOException { this(in, 512); } /** - * Reads uncompressed data into an array of bytes, returning the number of inflated + * Reads decompressed data into an array of bytes, returning the number of decompressed * bytes. If {@code len} is not zero, the method will block until some input can be * decompressed; otherwise, no bytes are read and {@code 0} is returned. *

* If this method returns a nonzero integer n then {@code buf[off]} - * through {@code buf[off+}n{@code -1]} contain the uncompressed - * data. The content of elements {@code buf[off+}n{@code ]} through + * through {@code buf[off+}n{@code -1]} contain the decompressed + * data. The content of elements {@code buf[off+}n{@code ]} through * {@code buf[off+}len{@code -1]} is undefined, contrary to the * specification of the {@link java.io.InputStream InputStream} superclass, * so an implementation is free to modify these elements during the inflate @@ -131,18 +173,20 @@ public class GZIPInputStream extends InflaterInputStream { * * @param buf the buffer into which the data is read * @param off the start offset in the destination array {@code buf} - * @param len the maximum number of bytes read - * @return the actual number of bytes inflated, or -1 if the end of the - * compressed input stream is reached + * @param len the maximum number of bytes to read into {@code buf} + * @return the actual number of bytes decompressed from a GZIP member, or -1 if the + * end-of-stream is reached * * @throws NullPointerException If {@code buf} is {@code null}. * @throws IndexOutOfBoundsException If {@code off} is negative, * {@code len} is negative, or {@code len} is greater than * {@code buf.length - off} * @throws ZipException if the compressed input data is corrupt. - * @throws IOException if an I/O error has occurred. + * @throws IOException if the stream is closed or an I/O error has occurred. * + * @see ##gzip_file_format GZIP file format */ + @Override public int read(byte[] buf, int off, int len) throws IOException { ensureOpen(); if (eos) { @@ -165,6 +209,7 @@ public class GZIPInputStream extends InflaterInputStream { * with the stream. * @throws IOException if an I/O error has occurred */ + @Override public void close() throws IOException { if (!closed) { super.close(); @@ -173,20 +218,6 @@ public class GZIPInputStream extends InflaterInputStream { } } - /** - * GZIP header magic number. - */ - public static final int GZIP_MAGIC = 0x8b1f; - - /* - * File header flags. - */ - private static final int FTEXT = 1; // Extra text - private static final int FHCRC = 2; // Header CRC - private static final int FEXTRA = 4; // Extra field - private static final int FNAME = 8; // File name - private static final int FCOMMENT = 16; // File comment - /* * Reads GZIP member header and returns the total byte number * of this member header. @@ -309,8 +340,6 @@ public class GZIPInputStream extends InflaterInputStream { return b; } - private byte[] tmpbuf = new byte[128]; - /* * Skips bytes of input data blocking until all bytes are skipped. * Does not assume that the input stream is capable of seeking. diff --git a/test/jdk/java/util/zip/GZIP/BasicGZIPInputStreamTest.java b/test/jdk/java/util/zip/GZIP/BasicGZIPInputStreamTest.java index 1f52d44146e..642b0bafa66 100644 --- a/test/jdk/java/util/zip/GZIP/BasicGZIPInputStreamTest.java +++ b/test/jdk/java/util/zip/GZIP/BasicGZIPInputStreamTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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 @@ -22,15 +22,21 @@ */ import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.stream.Stream; import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /* * @test @@ -51,7 +57,7 @@ public class BasicGZIPInputStreamTest { @ParameterizedTest @MethodSource("npeFromConstructors") public void testNPEFromConstructors(final Executable constructor) { - Assertions.assertThrows(NullPointerException.class, constructor, + assertThrows(NullPointerException.class, constructor, "GZIPInputStream constructor did not throw NullPointerException"); } @@ -71,7 +77,7 @@ public class BasicGZIPInputStreamTest { @ParameterizedTest @MethodSource("iaeFromConstructors") public void testIAEFromConstructors(final Executable constructor) { - Assertions.assertThrows(IllegalArgumentException.class, constructor, + assertThrows(IllegalArgumentException.class, constructor, "GZIPInputStream constructor did not throw IllegalArgumentException"); } @@ -89,7 +95,29 @@ public class BasicGZIPInputStreamTest { @ParameterizedTest @MethodSource("ioeFromConstructors") public void testIOEFromConstructors(final Executable constructor) { - Assertions.assertThrows(IOException.class, constructor, + assertThrows(IOException.class, constructor, "GZIPInputStream constructor did not throw IOException"); } + + /* + * Verifies that GZIPInputStream.read() throws IOException when invoked on a closed + * stream + */ + @Test + void testClosedStreamRead() throws Exception { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) { + gzos.write(new byte[] {0x42, 0x42}); // GZIP compress these input bytes + } + final byte[] gzipCompressed = baos.toByteArray(); + // create the GZIPInputStream to test + final GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(gzipCompressed)); + in.close(); + final IOException ioe = assertThrows(IOException.class, () -> in.read(new byte[1], 0, 1)); + final String exMessage = ioe.getMessage(); + if (exMessage == null || !exMessage.contains("Stream closed")) { + // unexpected exception message, propagate the original exception + throw ioe; + } + } } diff --git a/test/jdk/java/util/zip/GZIP/GZIPInputStreamRead.java b/test/jdk/java/util/zip/GZIP/GZIPInputStreamRead.java index 56bd58e1aaf..9cc13b29f34 100644 --- a/test/jdk/java/util/zip/GZIP/GZIPInputStreamRead.java +++ b/test/jdk/java/util/zip/GZIP/GZIPInputStreamRead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, 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 @@ -21,81 +21,201 @@ * questions. */ -/* @test +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import jdk.test.lib.RandomFactory; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * @test * @bug 4691425 * @summary Test the read and write of GZIPInput/OutputStream, including * concatenated .gz inputstream * @key randomness + * @library /test/lib + * @build jdk.test.lib.RandomFactory + * @run junit ${test.main.class} */ +class GZIPInputStreamRead { -import java.io.*; -import java.util.*; -import java.util.zip.*; + private static final Random random = RandomFactory.getRandom(); -public class GZIPInputStreamRead { - public static void main(String[] args) throws Throwable { - Random rnd = new Random(); - for (int i = 1; i < 100; i++) { - int members = rnd.nextInt(10) + 1; + /* + * Generates GZIP content containing multiple members and then verifies + * that using GZIPInputStream to decompress that content generates the correct + * expected decompressed data. + */ + @Test + void testMultipleMembers() throws Exception { + final int numMembers = random.nextInt(10) + 1; + final ByteArrayOutputStream rawUncompressedBaos = new ByteArrayOutputStream(); + final ByteArrayOutputStream gzipCompressedBaos = new ByteArrayOutputStream(); + // generate GZIP content with multiple members + for (int j = 0; j < numMembers; j++) { + byte[] src = new byte[random.nextInt(8192) + 1]; + random.nextBytes(src); + rawUncompressedBaos.write(src); - ByteArrayOutputStream srcBAOS = new ByteArrayOutputStream(); - ByteArrayOutputStream dstBAOS = new ByteArrayOutputStream(); - for (int j = 0; j < members; j++) { - byte[] src = new byte[rnd.nextInt(8192) + 1]; - rnd.nextBytes(src); - srcBAOS.write(src); - - try (GZIPOutputStream gzos = new GZIPOutputStream(dstBAOS)) { - gzos.write(src); - } - } - byte[] srcBytes = srcBAOS.toByteArray(); - byte[] dstBytes = dstBAOS.toByteArray(); - // try different size of buffer to read the - // GZIPInputStream - /* just for fun when running manually - for (int j = 1; j < 10; j++) { - test(srcBytes, dstBytes, j); - } - */ - for (int j = 0; j < 10; j++) { - int readBufSZ = rnd.nextInt(2048) + 1; - test(srcBytes, - dstBytes, - readBufSZ, - 512); // the defualt buffer size - test(srcBytes, - dstBytes, - readBufSZ, - rnd.nextInt(4096) + 1); + try (GZIPOutputStream gzos = new GZIPOutputStream(gzipCompressedBaos)) { + gzos.write(src); } } + final byte[] uncompressedRawBytes = rawUncompressedBaos.toByteArray(); + final byte[] gzipCompressedBytes = gzipCompressedBaos.toByteArray(); + // decompress using GZIPInputStream and verify the decompressed output. + // use different input buffer size for GZIPInputStream when running the verification. + for (int j = 0; j < 10; j++) { + final int readBufSZ = random.nextInt(2048) + 1; + verifyDecompressed(uncompressedRawBytes, + gzipCompressedBytes, + readBufSZ, + 512); // the default input buffer size + verifyDecompressed(uncompressedRawBytes, + gzipCompressedBytes, + readBufSZ, + random.nextInt(4096) + 1); + } } - private static void test(byte[] src, byte[] dst, - int readBufSize, int gzisBufSize) - throws Throwable - { - try (ByteArrayInputStream bais = new ByteArrayInputStream(dst); - GZIPInputStream gzis = new GZIPInputStream(bais, gzisBufSize)) - { - byte[] result = new byte[src.length + 10]; + /* + * Generates GZIP content containing one member followed by some arbitrary non-member data. + * The test then verifies that using GZIPInputStream to decompress that content generates + * the correct expected decompressed data. + */ + @Test + void testNonMemberAfterTrailer() throws Exception { + final byte[] rawUncompressed = new byte[random.nextInt(1234)]; + random.nextBytes(rawUncompressed); + final ByteArrayOutputStream gzipCompressedPlusExtra = new ByteArrayOutputStream(); + // generate a valid GZIP member + try (GZIPOutputStream gzos = new GZIPOutputStream(gzipCompressedPlusExtra)) { + gzos.write(rawUncompressed); // GZIP compress + } + final int numCompressedBytes = gzipCompressedPlusExtra.size(); + // past the GZIP trailer, write some additional bytes that doesn't represent a GZIP member + final byte[] notGZIPMagic = ByteBuffer.allocate(Integer.BYTES). + putInt(GZIPInputStream.GZIP_MAGIC + 42) + .array(); + gzipCompressedPlusExtra.write(notGZIPMagic); + assertEquals(numCompressedBytes + notGZIPMagic.length, gzipCompressedPlusExtra.size(), + "unexpected number of compressed + extra bytes"); + // now use GZIPInputStream to decompress the compressed plus extra bytes and verify + // that the extra bytes don't cause unexpected decompressed output + final ByteArrayOutputStream decompressedBaos = new ByteArrayOutputStream(); + int n = 0; + try (ByteArrayInputStream bais = new ByteArrayInputStream(gzipCompressedPlusExtra.toByteArray()); + GZIPInputStream gzipIn = new GZIPInputStream(bais)) { + + final byte[] tmpBuf = new byte[42]; + while ((n = gzipIn.read(tmpBuf)) != -1) { + decompressedBaos.write(tmpBuf, 0, n); + } + final byte[] decompressed = decompressedBaos.toByteArray(); + // verify the decompressed content + assertEquals(rawUncompressed.length, decompressed.length, + "unexpected number of decompressed bytes"); + assertArrayEquals(rawUncompressed, decompressed, "unexpected decompressed data"); + // make sure additional calls to read still return EOF + assertEquals(-1, gzipIn.read(), "unexpected return from read(), expected EOF"); + assertEquals(-1, gzipIn.read(new byte[10]), "unexpected return from read(), expected EOF"); + } + } + + /* + * Verifies that the InputStream.available() method is invoked on the underlying InputStream + * to determine presence of additional GZIP members in the stream. + */ + @Test + void testInputStreamAvailableCalled() throws Exception { + final byte[] rawUncompressedMember1 = new byte[random.nextInt(111)]; + random.nextBytes(rawUncompressedMember1); + System.err.println("GZIP member 1 has " + rawUncompressedMember1.length + " bytes"); + + final byte[] rawUncompressedMember2 = new byte[random.nextInt(33)]; + random.nextBytes(rawUncompressedMember2); + System.err.println("GZIP member 2 has " + rawUncompressedMember2.length + " bytes"); + + final ByteArrayOutputStream twoMemberGzipCompressedBaos = new ByteArrayOutputStream(); + // generate GZIP format data with 2 valid GZIP members + try (GZIPOutputStream gzos = new GZIPOutputStream(twoMemberGzipCompressedBaos)) { + gzos.write(rawUncompressedMember1); // GZIP compress + gzos.write(rawUncompressedMember2); // GZIP compress + } + final byte[] gzipCompressed = twoMemberGzipCompressedBaos.toByteArray(); + final AtomicBoolean availableInvoked = new AtomicBoolean(); + // an InputStream which tracks the calls to available() + final ByteArrayInputStream underlying = new ByteArrayInputStream(gzipCompressed) { + @Override + public int available() { + availableInvoked.set(true); + return super.available(); + } + }; + // now use GZIPInputStream to decompress the compressed data and expect the decompressed + // data to be correct and also expect the InputStream.available() to have been invoked + final ByteArrayOutputStream decompressedBaos = new ByteArrayOutputStream(); + int n = 0; + try (GZIPInputStream gzipIn = new GZIPInputStream(underlying)) { + + final byte[] tmpBuf = new byte[1024]; + while ((n = gzipIn.read(tmpBuf)) != -1) { + decompressedBaos.write(tmpBuf, 0, n); + } + assertTrue(availableInvoked.get(), "InputStream.available() wasn't invoked"); + final byte[] decompressed = decompressedBaos.toByteArray(); + // verify the decompressed content, it should represent the two GZIP members + assertEquals(rawUncompressedMember1.length + rawUncompressedMember2.length, + decompressed.length, "unexpected number of decompressed bytes"); + + assertArrayEquals(rawUncompressedMember1, + Arrays.copyOfRange(decompressed, 0, rawUncompressedMember1.length), + "unexpected decompressed data of first member"); + + assertArrayEquals(rawUncompressedMember2, + Arrays.copyOfRange(decompressed, rawUncompressedMember1.length, decompressed.length), + "unexpected decompressed data of second member"); + + // make sure additional calls to read still return EOF + assertEquals(-1, gzipIn.read(), "unexpected return from read(), expected EOF"); + assertEquals(-1, gzipIn.read(new byte[42]), "unexpected return from read(), expected EOF"); + } + } + + // verify that decompressing the gzipCompressed data using GZIPInputStream + // generates the expected output + private static void verifyDecompressed(final byte[] rawUncompressed, + final byte[] gzipCompressed, + final int readBufSize, final int gzisBufSize) + throws IOException { + try (ByteArrayInputStream bais = new ByteArrayInputStream(gzipCompressed); + GZIPInputStream gzis = new GZIPInputStream(bais, gzisBufSize)) { + + byte[] result = new byte[rawUncompressed.length + 10]; byte[] buf = new byte[readBufSize]; int n = 0; - int off = 0; - + int numDecompressed = 0; while ((n = gzis.read(buf, 0, buf.length)) != -1) { - System.arraycopy(buf, 0, result, off, n); - off += n; + System.arraycopy(buf, 0, result, numDecompressed, n); + numDecompressed += n; // no range check, if overflow, let it fail } - if (off != src.length || gzis.available() != 0 || - !Arrays.equals(src, Arrays.copyOf(result, off))) { - throw new RuntimeException( - "GZIPInputStream reading failed! " + - ", src.len=" + src.length + - ", read=" + off); - } + assertEquals(rawUncompressed.length, numDecompressed, + "unexpected number of decompressed bytes"); + assertEquals(0, gzis.available(), + "unexpected additional bytes available in the GZIPInputStream"); + assertArrayEquals(rawUncompressed, Arrays.copyOf(result, numDecompressed), + "unexpected decompressed data"); } } } From 29e37422a5e537eba6de2f05dc30de8dadecc83d Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Tue, 2 Jun 2026 04:55:29 +0000 Subject: [PATCH 20/32] 8385670: Incorrect warning: Unexpected gaps for Klass alignment Reviewed-by: matsaave, kvn --- src/hotspot/share/cds/archiveUtils.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index 7985c62d67b..bfaa1d6644c 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -303,7 +303,8 @@ public: AllocGapNode* node = allocate_node(gap, Empty{}); insert(gap, node); - log_trace(aot, alloc)("adding a gap of %zu bytes @ %p (total = %zu) in %zu blocks", gap_bytes, gap_bottom, _total_gap_bytes, size()); + log_trace(aot, alloc)("adding a gap of %zu bytes @ %p (total = %zu, used = %zu) in %zu blocks", + gap_bytes, gap_bottom, _total_gap_bytes, _total_gap_bytes_used, size()); return gap_bytes; } @@ -325,29 +326,25 @@ public: remove(node); - precond(_total_gap_bytes >= num_bytes); - _total_gap_bytes -= num_bytes; _total_gap_bytes_used += num_bytes; _total_gap_allocs++; DEBUG_ONLY(node = nullptr); // Don't use it anymore! precond(gap_bytes >= num_bytes); if (gap_bytes > num_bytes) { - gap_bytes -= num_bytes; - gap_bottom += num_bytes; - - AllocGap gap(gap_bytes, gap_bottom); // constructor checks alignment + AllocGap gap(gap_bytes - num_bytes, gap_bottom + num_bytes); // constructor checks alignment AllocGapNode* new_node = allocate_node(gap, Empty{}); insert(gap, new_node); } + size_t unfilled_bytes = _total_gap_bytes - _total_gap_bytes_used; log_trace(aot, alloc)("%zu bytes @ %p in a gap of %zu bytes (used gaps %zu times, remain gap = %zu bytes in %zu blocks)", - num_bytes, result, gap_bytes, _total_gap_allocs, _total_gap_bytes, size()); + num_bytes, result, gap_bytes, _total_gap_allocs, unfilled_bytes, size()); return result; } }; -size_t DumpRegion::_total_gap_bytes = 0; -size_t DumpRegion::_total_gap_bytes_used = 0; +size_t DumpRegion::_total_gap_bytes = 0; // All the gaps that have ever been created +size_t DumpRegion::_total_gap_bytes_used = 0; // All the gaps that have been used size_t DumpRegion::_total_gap_allocs = 0; DumpRegion::AllocGapTree DumpRegion::_gap_tree; @@ -418,20 +415,21 @@ void DumpRegion::report_gaps(DumpAllocStats* stats) { }); double unfilled_percent = 0.0; + size_t unfilled_bytes = _total_gap_bytes - _total_gap_bytes_used; if (_gap_tree.size() > 0) { - unfilled_percent = percent_of(_total_gap_bytes, _total_gap_allocs); + unfilled_percent = percent_of(unfilled_bytes, _total_gap_bytes); if (unfilled_percent > 5.0) { // We have a limited number of small objects, so some small gaps may remain // unfilled. If more than 5% of the gaps are unfilled, this likely indicates // a systematic error that should be investigated. Otherwise, do not warn to // avoid noise. - log_warning(aot)("Unexpected %zu gaps (%zu bytes) for Klass alignment", - _gap_tree.size(), _total_gap_bytes); + log_warning(aot)("Unexpected %zu gaps (%zu bytes, %.2f%%) for Klass alignment", + _gap_tree.size(), _total_gap_bytes, unfilled_percent); } } if (_total_gap_allocs > 0) { log_info(aot)("Allocated %zu objects of %zu bytes in gaps (remain = %zu bytes, %.2f%%)", - _total_gap_allocs, _total_gap_bytes_used, _total_gap_bytes, unfilled_percent); + _total_gap_allocs, _total_gap_bytes_used, unfilled_bytes, unfilled_percent); } } From 9c244ec182052f7b2ecaba4088710330e93fea2b Mon Sep 17 00:00:00 2001 From: Eric Fang Date: Tue, 2 Jun 2026 06:37:35 +0000 Subject: [PATCH 21/32] 8382532: C2: VectorAPI: disable VectorMaskCmp-not optimization for predicated compares Reviewed-by: epeter, vlivanov --- src/hotspot/share/opto/vectornode.cpp | 5 +- .../vectorapi/VectorMaskCompareNotTest.java | 839 ++++++++++++++---- 2 files changed, 648 insertions(+), 196 deletions(-) diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 92777f97d7b..dd49d88ce96 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -2727,9 +2727,7 @@ Node* XorVNode::Ideal_XorV_VectorMaskCmp(PhaseGVN* phase, bool can_reshape) { Node* in1 = in(1); Node* in2 = in(2); // Transformations for predicated vectors are not supported for now. - if (is_predicated_vector() || - in1->is_predicated_vector() || - in2->is_predicated_vector()) { + if (is_predicated_vector()) { return nullptr; } @@ -2753,6 +2751,7 @@ Node* XorVNode::Ideal_XorV_VectorMaskCmp(PhaseGVN* phase, bool can_reshape) { } if (in1->Opcode() != Op_VectorMaskCmp || in1->outcnt() != 1 || + in1->is_predicated_vector() || !in1->as_VectorMaskCmp()->predicate_can_be_negated() || !VectorNode::is_all_ones_vector(in2)) { return nullptr; diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCompareNotTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCompareNotTest.java index 09185f63c69..9a2f440ea82 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCompareNotTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCompareNotTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * Copyright (c) 2025, 2026, NVIDIA CORPORATION & 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 @@ -30,7 +30,7 @@ import jdk.test.lib.Asserts; /* * @test - * @bug 8354242 + * @bug 8354242 8382532 * @key randomness * @library /test/lib / * @summary test combining vector not operation with compare @@ -42,6 +42,7 @@ import jdk.test.lib.Asserts; public class VectorMaskCompareNotTest { private static int LENGTH = 128; + private static float TWO_FLOAT = 2.0f; private static final VectorSpecies B_SPECIES = VectorSpecies.ofLargestShape(byte.class); private static final VectorSpecies S_SPECIES = VectorSpecies.ofLargestShape(short.class); @@ -77,6 +78,7 @@ public class VectorMaskCompareNotTest { private static double[] dnan; private static double[] dpinf; private static double[] dninf; + private static boolean[] mi; private static boolean[] mr; static { @@ -99,6 +101,7 @@ public class VectorMaskCompareNotTest { dnan = new double[LENGTH]; dpinf = new double[LENGTH]; dninf = new double[LENGTH]; + mi = new boolean[LENGTH]; mr = new boolean[LENGTH]; Generator iGen = RD.ints(); @@ -125,6 +128,7 @@ public class VectorMaskCompareNotTest { dnan[i] = Double.NaN; dpinf[i] = Double.POSITIVE_INFINITY; dninf[i] = Double.NEGATIVE_INFINITY; + mi[i] = true; } } @@ -271,523 +275,363 @@ public class VectorMaskCompareNotTest { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareEQMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.EQ, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.EQ); testCompareMaskNotByte(B_SPECIES, VectorOperators.EQ, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.EQ); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.EQ, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.EQ); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareNEMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.NE, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.NE); testCompareMaskNotByte(B_SPECIES, VectorOperators.NE, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.NE); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.NE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.NE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareLTMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.LT, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.LT); testCompareMaskNotByte(B_SPECIES, VectorOperators.LT, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.LT); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.LT, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.LT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareGTMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.GT, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.GT); testCompareMaskNotByte(B_SPECIES, VectorOperators.GT, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.GT); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.GT, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.GT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareLEMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.LE, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.LE); testCompareMaskNotByte(B_SPECIES, VectorOperators.LE, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.LE); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.LE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.LE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareGEMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.GE, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.GE); testCompareMaskNotByte(B_SPECIES, VectorOperators.GE, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.GE); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.GE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.GE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareULTMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.ULT, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.ULT); testCompareMaskNotByte(B_SPECIES, VectorOperators.ULT, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.ULT); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.ULT, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.ULT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareUGTMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.UGT, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.UGT); testCompareMaskNotByte(B_SPECIES, VectorOperators.UGT, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.UGT); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.UGT, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.UGT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareULEMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.ULE, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.ULE); testCompareMaskNotByte(B_SPECIES, VectorOperators.ULE, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.ULE); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.ULE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.ULE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 1", - IRNode.VECTOR_MASK_CMP, "= 3" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareUGEMaskNotByte() { testCompareMaskNotByte(B_SPECIES, VectorOperators.UGE, (m) -> { return m.not(); }); verifyResultsByte(B_SPECIES, VectorOperators.UGE); testCompareMaskNotByte(B_SPECIES, VectorOperators.UGE, (m) -> { return B_SPECIES.maskAll(true).xor(m); }); verifyResultsByte(B_SPECIES, VectorOperators.UGE); - - testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.UGE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); - verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.UGE); } // Short tests @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareEQMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.EQ, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.EQ); testCompareMaskNotShort(S_SPECIES, VectorOperators.EQ, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.EQ); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.EQ, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.EQ); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.EQ, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.EQ); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareNEMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.NE, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.NE); testCompareMaskNotShort(S_SPECIES, VectorOperators.NE, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.NE); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.NE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.NE); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.NE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.NE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareLTMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.LT, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.LT); testCompareMaskNotShort(S_SPECIES, VectorOperators.LT, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.LT); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.LT, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.LT); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.LT, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.LT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareGTMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.GT, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.GT); testCompareMaskNotShort(S_SPECIES, VectorOperators.GT, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.GT); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.GT, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.GT); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.GT, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.GT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareLEMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.LE, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.LE); testCompareMaskNotShort(S_SPECIES, VectorOperators.LE, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.LE); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.LE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.LE); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.LE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.LE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareGEMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.GE, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.GE); testCompareMaskNotShort(S_SPECIES, VectorOperators.GE, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.GE); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.GE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.GE); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.GE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.GE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareULTMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.ULT, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.ULT); testCompareMaskNotShort(S_SPECIES, VectorOperators.ULT, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.ULT); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.ULT, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.ULT); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.ULT, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.ULT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareUGTMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.UGT, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.UGT); testCompareMaskNotShort(S_SPECIES, VectorOperators.UGT, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.UGT); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.UGT, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.UGT); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.UGT, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.UGT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareULEMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.ULE, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.ULE); testCompareMaskNotShort(S_SPECIES, VectorOperators.ULE, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.ULE); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.ULE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.ULE); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.ULE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.ULE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareUGEMaskNotShort() { testCompareMaskNotShort(S_SPECIES, VectorOperators.UGE, (m) -> { return m.not(); }); verifyResultsShort(S_SPECIES, VectorOperators.UGE); testCompareMaskNotShort(S_SPECIES, VectorOperators.UGE, (m) -> { return S_SPECIES.maskAll(true).xor(m); }); verifyResultsShort(S_SPECIES, VectorOperators.UGE); - - testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.UGE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); - verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.UGE); - testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.UGE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); - verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.UGE); } // Int tests @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareEQMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.EQ, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.EQ); testCompareMaskNotInt(I_SPECIES, VectorOperators.EQ, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.EQ); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.EQ, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.EQ); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.EQ, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.EQ); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareNEMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.NE, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.NE); testCompareMaskNotInt(I_SPECIES, VectorOperators.NE, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.NE); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.NE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.NE); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.NE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.NE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareLTMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.LT, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.LT); testCompareMaskNotInt(I_SPECIES, VectorOperators.LT, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.LT); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.LT, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.LT); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.LT, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.LT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareGTMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.GT, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.GT); testCompareMaskNotInt(I_SPECIES, VectorOperators.GT, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.GT); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.GT, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.GT); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.GT, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.GT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareLEMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.LE, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.LE); testCompareMaskNotInt(I_SPECIES, VectorOperators.LE, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.LE); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.LE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.LE); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.LE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.LE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareGEMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.GE, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.GE); testCompareMaskNotInt(I_SPECIES, VectorOperators.GE, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.GE); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.GE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.GE); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.GE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.GE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareULTMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.ULT, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.ULT); testCompareMaskNotInt(I_SPECIES, VectorOperators.ULT, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.ULT); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.ULT, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.ULT); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.ULT, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.ULT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareUGTMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.UGT, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.UGT); testCompareMaskNotInt(I_SPECIES, VectorOperators.UGT, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.UGT); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.UGT, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.UGT); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.UGT, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.UGT); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareULEMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.ULE, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.ULE); testCompareMaskNotInt(I_SPECIES, VectorOperators.ULE, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.ULE); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.ULE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.ULE); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.ULE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.ULE); } @Test @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", - IRNode.VECTOR_MASK_CAST, "= 2", - IRNode.VECTOR_MASK_CMP, "= 4" }, + IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareUGEMaskNotInt() { testCompareMaskNotInt(I_SPECIES, VectorOperators.UGE, (m) -> { return m.not(); }); verifyResultsInt(I_SPECIES, VectorOperators.UGE); testCompareMaskNotInt(I_SPECIES, VectorOperators.UGE, (m) -> { return I_SPECIES.maskAll(true).xor(m); }); verifyResultsInt(I_SPECIES, VectorOperators.UGE); - - testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.UGE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); - verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.UGE); - testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.UGE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); - verifyResultsInt(IntVector.SPECIES_128, VectorOperators.UGE); } // Long tests @@ -1291,9 +1135,618 @@ public class VectorMaskCompareNotTest { verifyResultsDouble(D_SPECIES, VectorOperators.LT, da, db); } + // Cast variants of the byte, short and int tests above. These cases use + // mask compares whose result feeds into a VectorMaskCast (and either an + // explicit .not()/xor or an all-true mask xor). + // + // The IR expectation depends on whether the platform requires partial + // vector operations (see Matcher::vector_needs_partial_operations): + // * On SVE, a sub-register vector compare is lowered via + // ideal_partial_operations(), which generates an all-true predicate + // with VectorMaskGen and attaches it to the VectorMaskCmp. The + // compare becomes predicated, so after JDK-8382532 the + // XorV-VectorMaskCmp optimization does not fire and one not node + // (XorVMask) remains. + // * On all other supported platforms (ASIMD-only, AVX2/AVX-512, RVV), + // vector_needs_partial_operations() returns false, the + // VectorMaskCmp stays unpredicated, the optimization fires and the + // not nodes are folded into negated VectorMaskCmps. No XorV/XorVMask + // remains. + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareEQMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.EQ, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.EQ); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareNEMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.NE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.NE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareLTMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.LT, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.LT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareGTMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.GT, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.GT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareLEMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.LE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.LE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareGEMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.GE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.GE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareULTMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.ULT, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.ULT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareUGTMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.UGT, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.UGT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareULEMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.ULE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.ULE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "sve", "true" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareUGEMaskNotByteCast() { + testCompareMaskNotByte(ByteVector.SPECIES_64, VectorOperators.UGE, (m) -> { return m.cast(ShortVector.SPECIES_128).not(); }); + verifyResultsByte(ByteVector.SPECIES_64, VectorOperators.UGE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareEQMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.EQ, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.EQ); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.EQ, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.EQ); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareNEMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.NE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.NE); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.NE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.NE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareLTMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.LT, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.LT); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.LT, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.LT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareGTMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.GT, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.GT); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.GT, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.GT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareLEMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.LE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.LE); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.LE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.LE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareGEMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.GE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.GE); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.GE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.GE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareULTMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.ULT, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.ULT); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.ULT, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.ULT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareUGTMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.UGT, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.UGT); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.UGT, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.UGT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareULEMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.ULE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.ULE); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.ULE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.ULE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareUGEMaskNotShortCast() { + testCompareMaskNotShort(ShortVector.SPECIES_64, VectorOperators.UGE, (m) -> { return IntVector.SPECIES_128.maskAll(true).xor(m.cast(IntVector.SPECIES_128)); }); + verifyResultsShort(ShortVector.SPECIES_64, VectorOperators.UGE); + testCompareMaskNotShort(ShortVector.SPECIES_128, VectorOperators.UGE, (m) -> { return m.cast(ByteVector.SPECIES_64).not(); }); + verifyResultsShort(ShortVector.SPECIES_128, VectorOperators.UGE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareEQMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.EQ, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.EQ); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.EQ, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.EQ); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareNEMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.NE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.NE); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.NE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.NE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareLTMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.LT, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.LT); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.LT, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.LT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareGTMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.GT, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.GT); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.GT, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.GT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareLEMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.LE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.LE); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.LE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.LE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareGEMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.GE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.GE); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.GE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.GE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareULTMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.ULT, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.ULT); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.ULT, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.ULT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareUGTMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.UGT, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.UGT); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.UGT, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.UGT); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareULEMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.ULE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.ULE); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.ULE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.ULE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "sve", "true" }, + applyIf = { "MaxVectorSize", "= 16" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V_MASK, "= 0", + IRNode.XOR_V, "= 0", + IRNode.VECTOR_MASK_CMP, "= 2" }, + applyIfCPUFeatureOr = { "avx2", "true", "rvv", "true" }) + public static void testCompareUGEMaskNotIntCast() { + testCompareMaskNotInt(I_SPECIES_FOR_CAST, VectorOperators.UGE, (m) -> { return L_SPECIES_FOR_CAST.maskAll(true).xor(m.cast(L_SPECIES_FOR_CAST)); }); + verifyResultsInt(I_SPECIES_FOR_CAST, VectorOperators.UGE); + testCompareMaskNotInt(IntVector.SPECIES_128, VectorOperators.UGE, (m) -> { return m.cast(ShortVector.SPECIES_64).not(); }); + verifyResultsInt(IntVector.SPECIES_128, VectorOperators.UGE); + } + + @Test + @IR(counts = { IRNode.XOR_V_MASK, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }) + @IR(counts = { IRNode.XOR_V, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) + @IR(counts = { IRNode.XOR_V, "= 1", + IRNode.VECTOR_MASK_CMP, "= 1" }, + applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) + public static void testMaskedCompareMaskNotNegative() { + int expected = F_SPECIES.length(); + var ones = FloatVector.broadcast(F_SPECIES, 1f); + // All true mask + VectorMask on = VectorMask.fromArray(F_SPECIES, mi, 0); + int got = ones.compare(VectorOperators.NE, TWO_FLOAT, on) + .not() + .firstTrue(); + Asserts.assertEquals(expected, got); + } + public static void main(String[] args) { TestFramework testFramework = new TestFramework(); - testFramework.setDefaultWarmup(5000) + testFramework.setDefaultWarmup(10000) .addFlags("--add-modules=jdk.incubator.vector") .start(); } From e83e79460bcc47a5f6b1c5b9835dae362d688ea2 Mon Sep 17 00:00:00 2001 From: Eric Fang Date: Tue, 2 Jun 2026 07:35:53 +0000 Subject: [PATCH 22/32] 8376285: AArch64: Support fusion optimization for SVE destructive instructions Reviewed-by: aph, dlong, adinn --- src/hotspot/cpu/aarch64/aarch64_vector.ad | 404 +++++++++++++----- src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 | 150 ++++--- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 8 +- .../cpu/aarch64/macroAssembler_aarch64.cpp | 23 + .../cpu/aarch64/macroAssembler_aarch64.hpp | 96 +++++ .../cpu/aarch64/nativeInst_aarch64.hpp | 23 + 6 files changed, 540 insertions(+), 164 deletions(-) diff --git a/src/hotspot/cpu/aarch64/aarch64_vector.ad b/src/hotspot/cpu/aarch64/aarch64_vector.ad index 2ff93c9e288..b9899995531 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector.ad +++ b/src/hotspot/cpu/aarch64/aarch64_vector.ad @@ -1671,24 +1671,42 @@ instruct vnotL(vReg dst, vReg src, immL_M1 m1) %{ // vector not - predicated -instruct vnotI_masked(vReg dst_src, immI_M1 m1, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vnotI_masked(vReg dst, vReg src, immI_M1 m1, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (XorV (Binary dst_src (Replicate m1)) pg)); - format %{ "vnotI_masked $dst_src, $pg, $dst_src" %} + match(Set dst (XorV (Binary src (Replicate m1)) pg)); + format %{ "vnotI_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_not($dst_src$$FloatRegister, get_reg_variant(this), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_not($dst$$FloatRegister, get_reg_variant(this), + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vnotL_masked(vReg dst_src, immL_M1 m1, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vnotL_masked(vReg dst, vReg src, immL_M1 m1, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (XorV (Binary dst_src (Replicate m1)) pg)); - format %{ "vnotL_masked $dst_src, $pg, $dst_src" %} + match(Set dst (XorV (Binary src (Replicate m1)) pg)); + format %{ "vnotL_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_not($dst_src$$FloatRegister, get_reg_variant(this), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_not($dst$$FloatRegister, get_reg_variant(this), + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -1985,62 +2003,116 @@ instruct vabsD(vReg dst, vReg src) %{ // vector abs - predicated -instruct vabsB_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vabsB_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (AbsVB dst_src pg)); - format %{ "vabsB_masked $dst_src, $pg, $dst_src" %} + match(Set dst (AbsVB src pg)); + format %{ "vabsB_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_abs($dst_src$$FloatRegister, __ B, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_abs($dst$$FloatRegister, __ B, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vabsS_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vabsS_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (AbsVS dst_src pg)); - format %{ "vabsS_masked $dst_src, $pg, $dst_src" %} + match(Set dst (AbsVS src pg)); + format %{ "vabsS_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_abs($dst_src$$FloatRegister, __ H, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_abs($dst$$FloatRegister, __ H, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vabsI_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vabsI_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (AbsVI dst_src pg)); - format %{ "vabsI_masked $dst_src, $pg, $dst_src" %} + match(Set dst (AbsVI src pg)); + format %{ "vabsI_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_abs($dst_src$$FloatRegister, __ S, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_abs($dst$$FloatRegister, __ S, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vabsL_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vabsL_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (AbsVL dst_src pg)); - format %{ "vabsL_masked $dst_src, $pg, $dst_src" %} + match(Set dst (AbsVL src pg)); + format %{ "vabsL_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_abs($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_abs($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vabsF_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vabsF_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (AbsVF dst_src pg)); - format %{ "vabsF_masked $dst_src, $pg, $dst_src" %} + match(Set dst (AbsVF src pg)); + format %{ "vabsF_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_fabs($dst_src$$FloatRegister, __ S, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_fabs($dst$$FloatRegister, __ S, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vabsD_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vabsD_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (AbsVD dst_src pg)); - format %{ "vabsD_masked $dst_src, $pg, $dst_src" %} + match(Set dst (AbsVD src pg)); + format %{ "vabsD_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_fabs($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_fabs($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -2158,44 +2230,80 @@ instruct vnegD(vReg dst, vReg src) %{ // vector neg - predicated -instruct vnegI_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vnegI_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (NegVI dst_src pg)); - format %{ "vnegI_masked $dst_src, $pg, $dst_src" %} + match(Set dst (NegVI src pg)); + format %{ "vnegI_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); - __ sve_neg($dst_src$$FloatRegister, __ elemType_to_regVariant(bt), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_neg($dst$$FloatRegister, __ elemType_to_regVariant(bt), + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vnegL_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vnegL_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (NegVL dst_src pg)); - format %{ "vnegL_masked $dst_src, $pg, $dst_src" %} + match(Set dst (NegVL src pg)); + format %{ "vnegL_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_neg($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_neg($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vnegF_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vnegF_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (NegVF dst_src pg)); - format %{ "vnegF_masked $dst_src, $pg, $dst_src" %} + match(Set dst (NegVF src pg)); + format %{ "vnegF_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_fneg($dst_src$$FloatRegister, __ S, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_fneg($dst$$FloatRegister, __ S, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vnegD_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vnegD_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (NegVD dst_src pg)); - format %{ "vnegD_masked $dst_src, $pg, $dst_src" %} + match(Set dst (NegVD src pg)); + format %{ "vnegD_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_fneg($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_fneg($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -2251,22 +2359,40 @@ instruct vsqrtD(vReg dst, vReg src) %{ // vector sqrt - predicated -instruct vsqrtF_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vsqrtF_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (SqrtVF dst_src pg)); - format %{ "vsqrtF_masked $dst_src, $pg, $dst_src" %} + match(Set dst (SqrtVF src pg)); + format %{ "vsqrtF_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_fsqrt($dst_src$$FloatRegister, __ S, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_fsqrt($dst$$FloatRegister, __ S, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vsqrtD_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vsqrtD_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (SqrtVD dst_src pg)); - format %{ "vsqrtD_masked $dst_src, $pg, $dst_src" %} + match(Set dst (SqrtVD src pg)); + format %{ "vsqrtD_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_fsqrt($dst_src$$FloatRegister, __ D, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_fsqrt($dst$$FloatRegister, __ D, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -5331,9 +5457,7 @@ instruct insertI_index_lt32(vReg dst, vReg src, iRegIorL2I val, immI idx, __ sve_index($tmp$$FloatRegister, size, -16, 1); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, size, ptrue, $tmp$$FloatRegister, (int)($idx$$constant) - 16); - if ($dst$$FloatRegister != $src$$FloatRegister) { - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - } + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, size, $pgtmp$$PRegister, $val$$Register); %} ins_pipe(pipe_slow); @@ -5356,9 +5480,7 @@ instruct insertI_index_ge32(vReg dst, vReg src, iRegIorL2I val, immI idx, vReg t __ sve_dup($tmp2$$FloatRegister, size, (int)($idx$$constant)); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, size, ptrue, $tmp1$$FloatRegister, $tmp2$$FloatRegister); - if ($dst$$FloatRegister != $src$$FloatRegister) { - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - } + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, size, $pgtmp$$PRegister, $val$$Register); %} ins_pipe(pipe_slow); @@ -5392,9 +5514,7 @@ instruct insertL_gt128b(vReg dst, vReg src, iRegL val, immI idx, __ sve_index($tmp$$FloatRegister, __ D, -16, 1); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ D, ptrue, $tmp$$FloatRegister, (int)($idx$$constant) - 16); - if ($dst$$FloatRegister != $src$$FloatRegister) { - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - } + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, __ D, $pgtmp$$PRegister, $val$$Register); %} ins_pipe(pipe_slow); @@ -5432,7 +5552,7 @@ instruct insertF_index_lt32(vReg dst, vReg src, vRegF val, immI idx, __ sve_index($dst$$FloatRegister, __ S, -16, 1); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ S, ptrue, $dst$$FloatRegister, (int)($idx$$constant) - 16); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, __ S, $pgtmp$$PRegister, $val$$FloatRegister); %} ins_pipe(pipe_slow); @@ -5451,7 +5571,7 @@ instruct insertF_index_ge32(vReg dst, vReg src, vRegF val, immI idx, vReg tmp, __ sve_dup($dst$$FloatRegister, __ S, (int)($idx$$constant)); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ S, ptrue, $tmp$$FloatRegister, $dst$$FloatRegister); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, __ S, $pgtmp$$PRegister, $val$$FloatRegister); %} ins_pipe(pipe_slow); @@ -5486,7 +5606,7 @@ instruct insertD_gt128b(vReg dst, vReg src, vRegD val, immI idx, __ sve_index($dst$$FloatRegister, __ D, -16, 1); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ D, ptrue, $dst$$FloatRegister, (int)($idx$$constant) - 16); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, __ D, $pgtmp$$PRegister, $val$$FloatRegister); %} ins_pipe(pipe_slow); @@ -5656,8 +5776,12 @@ instruct extractF(vRegF dst, vReg src, immI idx) %{ __ ins($dst$$FloatRegister, __ S, $src$$FloatRegister, 0, index); } else { assert(UseSVE > 0, "must be sve"); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - __ sve_ext($dst$$FloatRegister, $dst$$FloatRegister, index << 2); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the second source of ext. The movprfx destination register + // must not appear in any source operand of the following instruction + // except as the destructive operand. + __ sve_ext($dst$$FloatRegister, $src$$FloatRegister, index << 2); } %} ins_pipe(pipe_slow); @@ -5677,8 +5801,12 @@ instruct extractD(vRegD dst, vReg src, immI idx) %{ __ ins($dst$$FloatRegister, __ D, $src$$FloatRegister, 0, index); } else { assert(UseSVE > 0, "must be sve"); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - __ sve_ext($dst$$FloatRegister, $dst$$FloatRegister, index << 3); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the second source of ext. The movprfx destination register + // must not appear in any source operand of the following instruction + // except as the destructive operand. + __ sve_ext($dst$$FloatRegister, $src$$FloatRegister, index << 3); } %} ins_pipe(pipe_slow); @@ -6855,25 +6983,43 @@ instruct vpopcountL(vReg dst, vReg src) %{ // vector popcount - predicated -instruct vpopcountI_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vpopcountI_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (PopCountVI dst_src pg)); - format %{ "vpopcountI_masked $dst_src, $pg, $dst_src" %} + match(Set dst (PopCountVI src pg)); + format %{ "vpopcountI_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); - __ sve_cnt($dst_src$$FloatRegister, __ elemType_to_regVariant(bt), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_cnt($dst$$FloatRegister, __ elemType_to_regVariant(bt), + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} -instruct vpopcountL_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vpopcountL_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (PopCountVL dst_src pg)); - format %{ "vpopcountL_masked $dst_src, $pg, $dst_src" %} + match(Set dst (PopCountVL src pg)); + format %{ "vpopcountL_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_cnt($dst_src$$FloatRegister, __ D, - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_cnt($dst$$FloatRegister, __ D, + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -7240,14 +7386,23 @@ instruct vcountLeadingZeros(vReg dst, vReg src) %{ // The dst and src should use the same register to make sure the // inactive lanes in dst save the same elements as src. -instruct vcountLeadingZeros_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vcountLeadingZeros_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (CountLeadingZerosV dst_src pg)); - format %{ "vcountLeadingZeros_masked $dst_src, $pg, $dst_src" %} + match(Set dst (CountLeadingZerosV src pg)); + format %{ "vcountLeadingZeros_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); - __ sve_clz($dst_src$$FloatRegister, __ elemType_to_regVariant(bt), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_clz($dst$$FloatRegister, __ elemType_to_regVariant(bt), + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -7296,19 +7451,26 @@ instruct vcountTrailingZeros(vReg dst, vReg src) %{ ins_pipe(pipe_slow); %} -// The dst and src should use the same register to make sure the -// inactive lanes in dst save the same elements as src. -instruct vcountTrailingZeros_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vcountTrailingZeros_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (CountTrailingZerosV dst_src pg)); - format %{ "vcountTrailingZeros_masked $dst_src, $pg, $dst_src" %} + match(Set dst (CountTrailingZerosV src pg)); + format %{ "vcountTrailingZeros_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); Assembler::SIMD_RegVariant size = __ elemType_to_regVariant(bt); - __ sve_rbit($dst_src$$FloatRegister, size, - $pg$$PRegister, $dst_src$$FloatRegister); - __ sve_clz($dst_src$$FloatRegister, size, - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_rbit($dst$$FloatRegister, size, + $pg$$PRegister, $src$$FloatRegister); + __ sve_clz($dst$$FloatRegister, size, + $pg$$PRegister, $dst$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -7347,14 +7509,23 @@ instruct vreverse(vReg dst, vReg src) %{ // The dst and src should use the same register to make sure the // inactive lanes in dst save the same elements as src. -instruct vreverse_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vreverse_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (ReverseV dst_src pg)); - format %{ "vreverse_masked $dst_src, $pg, $dst_src" %} + match(Set dst (ReverseV src pg)); + format %{ "vreverse_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); - __ sve_rbit($dst_src$$FloatRegister, __ elemType_to_regVariant(bt), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_rbit($dst$$FloatRegister, __ elemType_to_regVariant(bt), + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -7393,19 +7564,28 @@ instruct vreverseBytes(vReg dst, vReg src) %{ ins_pipe(pipe_slow); %} -// The dst and src should use the same register to make sure the -// inactive lanes in dst save the same elements as src. -instruct vreverseBytes_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vreverseBytes_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (ReverseBytesV dst_src pg)); - format %{ "vreverseBytes_masked $dst_src, $pg, $dst_src" %} + match(Set dst (ReverseBytesV src pg)); + format %{ "vreverseBytes_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); if (bt == T_BYTE) { - // do nothing + if ($dst$$FloatRegister != $src$$FloatRegister) { + __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); + } } else { - __ sve_revb($dst_src$$FloatRegister, __ elemType_to_regVariant(bt), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_revb($dst$$FloatRegister, __ elemType_to_regVariant(bt), + $pg$$PRegister, $src$$FloatRegister); } %} ins_pipe(pipe_slow); diff --git a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 index c5df949dfb6..a53efd43d5d 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 +++ b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 @@ -899,13 +899,22 @@ dnl dnl VECTOR_NOT_PREDICATE($1 ) dnl VECTOR_NOT_PREDICATE(type) define(`VECTOR_NOT_PREDICATE', ` -instruct vnot$1_masked`'(vReg dst_src, imm$1_M1 m1, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vnot$1_masked`'(vReg dst, vReg src, imm$1_M1 m1, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (XorV (Binary dst_src (Replicate m1)) pg)); - format %{ "vnot$1_masked $dst_src, $pg, $dst_src" %} + match(Set dst (XorV (Binary src (Replicate m1)) pg)); + format %{ "vnot$1_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_not($dst_src$$FloatRegister, get_reg_variant(this), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_not($dst$$FloatRegister, get_reg_variant(this), + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %}')dnl @@ -1042,14 +1051,23 @@ dnl dnl UNARY_OP_PREDICATE($1, $2, $3 ) dnl UNARY_OP_PREDICATE(rule_name, op_name, insn) define(`UNARY_OP_PREDICATE', ` -instruct $1_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct $1_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src ($2 dst_src pg)); - format %{ "$1_masked $dst_src, $pg, $dst_src" %} + match(Set dst ($2 src pg)); + format %{ "$1_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); - __ $3($dst_src$$FloatRegister, __ elemType_to_regVariant(bt), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ $3($dst$$FloatRegister, __ elemType_to_regVariant(bt), + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %}')dnl @@ -1057,12 +1075,21 @@ dnl dnl UNARY_OP_PREDICATE_WITH_SIZE($1, $2, $3, $4 ) dnl UNARY_OP_PREDICATE_WITH_SIZE(rule_name, op_name, insn, size) define(`UNARY_OP_PREDICATE_WITH_SIZE', ` -instruct $1_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct $1_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src ($2 dst_src pg)); - format %{ "$1_masked $dst_src, $pg, $dst_src" %} + match(Set dst ($2 src pg)); + format %{ "$1_masked $dst, $pg, $src" %} ins_encode %{ - __ $3($dst_src$$FloatRegister, __ $4, $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ $3($dst$$FloatRegister, __ $4, $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %}')dnl @@ -3368,9 +3395,7 @@ instruct insertI_index_lt32(vReg dst, vReg src, iRegIorL2I val, immI idx, __ sve_index($tmp$$FloatRegister, size, -16, 1); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, size, ptrue, $tmp$$FloatRegister, (int)($idx$$constant) - 16); - if ($dst$$FloatRegister != $src$$FloatRegister) { - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - } + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, size, $pgtmp$$PRegister, $val$$Register); %} ins_pipe(pipe_slow); @@ -3393,9 +3418,7 @@ instruct insertI_index_ge32(vReg dst, vReg src, iRegIorL2I val, immI idx, vReg t __ sve_dup($tmp2$$FloatRegister, size, (int)($idx$$constant)); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, size, ptrue, $tmp1$$FloatRegister, $tmp2$$FloatRegister); - if ($dst$$FloatRegister != $src$$FloatRegister) { - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - } + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, size, $pgtmp$$PRegister, $val$$Register); %} ins_pipe(pipe_slow); @@ -3429,9 +3452,7 @@ instruct insertL_gt128b(vReg dst, vReg src, iRegL val, immI idx, __ sve_index($tmp$$FloatRegister, __ D, -16, 1); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ D, ptrue, $tmp$$FloatRegister, (int)($idx$$constant) - 16); - if ($dst$$FloatRegister != $src$$FloatRegister) { - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - } + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, __ D, $pgtmp$$PRegister, $val$$Register); %} ins_pipe(pipe_slow); @@ -3469,7 +3490,7 @@ instruct insertF_index_lt32(vReg dst, vReg src, vRegF val, immI idx, __ sve_index($dst$$FloatRegister, __ S, -16, 1); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ S, ptrue, $dst$$FloatRegister, (int)($idx$$constant) - 16); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, __ S, $pgtmp$$PRegister, $val$$FloatRegister); %} ins_pipe(pipe_slow); @@ -3488,7 +3509,7 @@ instruct insertF_index_ge32(vReg dst, vReg src, vRegF val, immI idx, vReg tmp, __ sve_dup($dst$$FloatRegister, __ S, (int)($idx$$constant)); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ S, ptrue, $tmp$$FloatRegister, $dst$$FloatRegister); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, __ S, $pgtmp$$PRegister, $val$$FloatRegister); %} ins_pipe(pipe_slow); @@ -3523,7 +3544,7 @@ instruct insertD_gt128b(vReg dst, vReg src, vRegD val, immI idx, __ sve_index($dst$$FloatRegister, __ D, -16, 1); __ sve_cmp(Assembler::EQ, $pgtmp$$PRegister, __ D, ptrue, $dst$$FloatRegister, (int)($idx$$constant) - 16); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); __ sve_cpy($dst$$FloatRegister, __ D, $pgtmp$$PRegister, $val$$FloatRegister); %} ins_pipe(pipe_slow); @@ -3621,8 +3642,12 @@ instruct extract$1(vReg$1 dst, vReg src, immI idx) %{ __ ins($dst$$FloatRegister, __ $4, $src$$FloatRegister, 0, index); } else { assert(UseSVE > 0, "must be sve"); - __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); - __ sve_ext($dst$$FloatRegister, $dst$$FloatRegister, index << $5); + __ sve_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the second source of ext. The movprfx destination register + // must not appear in any source operand of the following instruction + // except as the destructive operand. + __ sve_ext($dst$$FloatRegister, $src$$FloatRegister, index << $5); } %} ins_pipe(pipe_slow); @@ -4682,13 +4707,22 @@ instruct vpopcountL(vReg dst, vReg src) %{ // vector popcount - predicated UNARY_OP_PREDICATE(vpopcountI, PopCountVI, sve_cnt) -instruct vpopcountL_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vpopcountL_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (PopCountVL dst_src pg)); - format %{ "vpopcountL_masked $dst_src, $pg, $dst_src" %} + match(Set dst (PopCountVL src pg)); + format %{ "vpopcountL_masked $dst, $pg, $src" %} ins_encode %{ - __ sve_cnt($dst_src$$FloatRegister, __ D, - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_cnt($dst$$FloatRegister, __ D, + $pg$$PRegister, $src$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -5100,19 +5134,26 @@ instruct vcountTrailingZeros(vReg dst, vReg src) %{ ins_pipe(pipe_slow); %} -// The dst and src should use the same register to make sure the -// inactive lanes in dst save the same elements as src. -instruct vcountTrailingZeros_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vcountTrailingZeros_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (CountTrailingZerosV dst_src pg)); - format %{ "vcountTrailingZeros_masked $dst_src, $pg, $dst_src" %} + match(Set dst (CountTrailingZerosV src pg)); + format %{ "vcountTrailingZeros_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); Assembler::SIMD_RegVariant size = __ elemType_to_regVariant(bt); - __ sve_rbit($dst_src$$FloatRegister, size, - $pg$$PRegister, $dst_src$$FloatRegister); - __ sve_clz($dst_src$$FloatRegister, size, - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_rbit($dst$$FloatRegister, size, + $pg$$PRegister, $src$$FloatRegister); + __ sve_clz($dst$$FloatRegister, size, + $pg$$PRegister, $dst$$FloatRegister); %} ins_pipe(pipe_slow); %} @@ -5186,19 +5227,28 @@ instruct vreverseBytes(vReg dst, vReg src) %{ ins_pipe(pipe_slow); %} -// The dst and src should use the same register to make sure the -// inactive lanes in dst save the same elements as src. -instruct vreverseBytes_masked(vReg dst_src, pRegGov pg) %{ +// The Java Vector API specification requires that for masked unary operations, +// suppressed lanes are filled from the first vector operand (see "Masked +// Operations" in Vector.java around line 568). So we use movprfx to copy src +// into dst before emitting the predicated instruction. +instruct vreverseBytes_masked(vReg dst, vReg src, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src (ReverseBytesV dst_src pg)); - format %{ "vreverseBytes_masked $dst_src, $pg, $dst_src" %} + match(Set dst (ReverseBytesV src pg)); + format %{ "vreverseBytes_masked $dst, $pg, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); if (bt == T_BYTE) { - // do nothing + if ($dst$$FloatRegister != $src$$FloatRegister) { + __ sve_orr($dst$$FloatRegister, $src$$FloatRegister, $src$$FloatRegister); + } } else { - __ sve_revb($dst_src$$FloatRegister, __ elemType_to_regVariant(bt), - $pg$$PRegister, $dst_src$$FloatRegister); + __ maybe_movprfx($dst$$FloatRegister, $src$$FloatRegister); + // Although dst and src hold the same value after movprfx, we must use src + // (not dst) as the source of the following instruction. The movprfx + // destination register must not appear in any source operand of the + // following instruction except as the destructive operand. + __ sve_revb($dst$$FloatRegister, __ elemType_to_regVariant(bt), + $pg$$PRegister, $src$$FloatRegister); } %} ins_pipe(pipe_slow); diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index 67dc4966d64..cb9e308197e 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -2494,8 +2494,12 @@ void C2_MacroAssembler::sve_extract_integral(Register dst, BasicType bt, FloatRe smov(dst, src, size, idx); } } else { - sve_orr(vtmp, src, src); - sve_ext(vtmp, vtmp, idx << size); + sve_movprfx(vtmp, src); + // Although vtmp and src hold the same value after movprfx, we must use src + // (not vtmp) as the second source of ext. The movprfx destination register + // must not appear in any source operand of the following instruction except + // as the destructive operand. + sve_ext(vtmp, src, idx << size); if (bt == T_INT || bt == T_LONG) { umov(dst, vtmp, size, 0); } else { diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index a52ad112560..ac5bae22384 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -7278,3 +7278,26 @@ void MacroAssembler::neon_vector_rotate(FloatRegister dst, SIMD_Arrangement T, sli(dst, T, src, lshift); } } + +void MacroAssembler::try_to_replace_prev_vector_copy_with_movprfx(FloatRegister dst) { + if (code_section()->is_empty()) { + return; + } + + address prev = pc() - NativeInstruction::instruction_size; + uint32_t insn = nativeInstruction_at(prev)->encoding(); + if (!NativeInstruction::is_neon_vector_mov_alias(insn) && + !NativeInstruction::is_sve_vector_mov_alias(insn)) { + return; + } + + // The destructive instruction must reuse the mov alias destination. + uint32_t rd = Instruction_aarch64::extract(insn, 4, 0); + if (rd != (uint32_t)dst->encoding()) { + return; + } + + uint32_t rn = Instruction_aarch64::extract(insn, 9, 5); + Instruction_aarch64::patch(prev, 31, 0, + NativeInstruction::encode_sve_movprfx(rd, rn)); +} diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index ad8827bd9c0..b1050b45731 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -1734,7 +1734,103 @@ public: private: // Check the current thread doesn't need a cross modify fence. void verify_cross_modify_fence_not_required() PRODUCT_RETURN; + void try_to_replace_prev_vector_copy_with_movprfx(FloatRegister dst); +public: + void maybe_movprfx(FloatRegister dst, FloatRegister src) { + if (dst != src) { + sve_movprfx(dst, src); + } + } + +// Wrappers for SVE explicit destructive instructions, overriding the +// same-signature Assembler entry points to enable movprfx fusion optimization. +// +// Implicit destructive instructions (e.g. predicated unary ops like sve_abs/ +// sve_neg/sve_not, whose ISA encoding allows Zd != Zn but whose use as a Java +// Vector API masked operation requires pass-through of the first source) are +// not covered here. For those, the .ad file is responsible for emitting +// movprfx explicitly via maybe_movprfx() before the destructive op. +#define SVE_DESTRUCTIVE_BINARY_INS(NAME) \ + using Assembler::NAME; \ + void NAME(FloatRegister Zd, SIMD_RegVariant T, PRegister Pg, \ + FloatRegister Zm) { \ + if (Zd != Zm) { \ + try_to_replace_prev_vector_copy_with_movprfx(Zd); \ + } \ + Assembler::NAME(Zd, T, Pg, Zm); \ + } + +#define SVE_DESTRUCTIVE_BINARY_5(I1, I2, I3, I4, I5) \ + SVE_DESTRUCTIVE_BINARY_INS(I1); SVE_DESTRUCTIVE_BINARY_INS(I2); \ + SVE_DESTRUCTIVE_BINARY_INS(I3); SVE_DESTRUCTIVE_BINARY_INS(I4); \ + SVE_DESTRUCTIVE_BINARY_INS(I5); + + SVE_DESTRUCTIVE_BINARY_5(sve_add, sve_and, sve_asr, sve_bic, sve_eor) + SVE_DESTRUCTIVE_BINARY_5(sve_fabd, sve_fadd, sve_fdiv, sve_fmax, sve_fmin) + SVE_DESTRUCTIVE_BINARY_5(sve_fmul, sve_fsub, sve_lsl, sve_lsr, sve_mul) + SVE_DESTRUCTIVE_BINARY_5(sve_orr, sve_smax, sve_smin, sve_sqadd, sve_sqsub) + SVE_DESTRUCTIVE_BINARY_5(sve_sub, sve_uqadd, sve_uqsub, sve_umax, sve_umin) + +#undef SVE_DESTRUCTIVE_BINARY_INS +#undef SVE_DESTRUCTIVE_BINARY_5 + +#define SVE_DESTRUCTIVE_SHIFT_IMM_INS(NAME) \ + void NAME(FloatRegister Zd, SIMD_RegVariant T, PRegister Pg, int shift) { \ + try_to_replace_prev_vector_copy_with_movprfx(Zd); \ + Assembler::NAME(Zd, T, Pg, shift); \ + } + + SVE_DESTRUCTIVE_SHIFT_IMM_INS(sve_asr); + SVE_DESTRUCTIVE_SHIFT_IMM_INS(sve_lsl); + SVE_DESTRUCTIVE_SHIFT_IMM_INS(sve_lsr); + +#undef SVE_DESTRUCTIVE_SHIFT_IMM_INS + +#define SVE_DESTRUCTIVE_UNPRED_IMM_INS(NAME, IMM_TYPE) \ + void NAME(FloatRegister Zd, SIMD_RegVariant T, IMM_TYPE imm) { \ + try_to_replace_prev_vector_copy_with_movprfx(Zd); \ + Assembler::NAME(Zd, T, imm); \ + } + + SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_add, unsigned); + SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_sub, unsigned); + SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_and, uint64_t); + SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_eor, uint64_t); + SVE_DESTRUCTIVE_UNPRED_IMM_INS(sve_orr, uint64_t); + +#undef SVE_DESTRUCTIVE_UNPRED_IMM_INS + +#define SVE_DESTRUCTIVE_TERNARY_INS(NAME) \ + using Assembler::NAME; \ + void NAME(FloatRegister Zd, SIMD_RegVariant T, PRegister Pg, \ + FloatRegister Zn, FloatRegister Zm) { \ + if (Zd != Zn && Zd != Zm) { \ + try_to_replace_prev_vector_copy_with_movprfx(Zd); \ + } \ + Assembler::NAME(Zd, T, Pg, Zn, Zm); \ + } + + SVE_DESTRUCTIVE_TERNARY_INS(sve_fmad); + SVE_DESTRUCTIVE_TERNARY_INS(sve_fmla); + SVE_DESTRUCTIVE_TERNARY_INS(sve_fmls); + SVE_DESTRUCTIVE_TERNARY_INS(sve_fmsb); + SVE_DESTRUCTIVE_TERNARY_INS(sve_fnmad); + SVE_DESTRUCTIVE_TERNARY_INS(sve_fnmla); + SVE_DESTRUCTIVE_TERNARY_INS(sve_fnmls); + SVE_DESTRUCTIVE_TERNARY_INS(sve_fnmsb); + SVE_DESTRUCTIVE_TERNARY_INS(sve_mla); + SVE_DESTRUCTIVE_TERNARY_INS(sve_mls); + +#undef SVE_DESTRUCTIVE_TERNARY_INS + + using Assembler::sve_eor3; + void sve_eor3(FloatRegister Zd, FloatRegister Zm, FloatRegister Zk) { + if (Zd != Zm && Zd != Zk) { + try_to_replace_prev_vector_copy_with_movprfx(Zd); + } + Assembler::sve_eor3(Zd, Zm, Zk); + } }; #ifdef ASSERT diff --git a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp index 4bccbc59582..57bb9a91533 100644 --- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp @@ -140,6 +140,29 @@ public: Instruction_aarch64::extract(insn, 23, 23) == 0b0 && Instruction_aarch64::extract(insn, 26, 25) == 0b00; } + + static bool is_neon_vector_mov_alias(uint32_t insn) { + if (Instruction_aarch64::extract(insn, 31, 31) != 0 || + Instruction_aarch64::extract(insn, 29, 21) != 0b001110101 || + Instruction_aarch64::extract(insn, 15, 10) != 0b000111) { + return false; + } + return Instruction_aarch64::extract(insn, 9, 5) == + Instruction_aarch64::extract(insn, 20, 16); + } + + static bool is_sve_vector_mov_alias(uint32_t insn) { + if (Instruction_aarch64::extract(insn, 31, 21) != 0b00000100011 || + Instruction_aarch64::extract(insn, 15, 10) != 0b001100) { + return false; + } + return Instruction_aarch64::extract(insn, 9, 5) == + Instruction_aarch64::extract(insn, 20, 16); + } + + static uint32_t encode_sve_movprfx(uint32_t dst, uint32_t src) { + return 0x1082f << 10 | (src << 5) | dst; + } }; inline NativeInstruction* nativeInstruction_at(address address) { From 3f8a7213efc383cf8d4cd323a5c3a9f163a311f3 Mon Sep 17 00:00:00 2001 From: Liam Miller-Cushon Date: Tue, 2 Jun 2026 08:47:44 +0000 Subject: [PATCH 23/32] 8385664: Stale comment about JDK-6996415 Reviewed-by: vromero --- .../share/classes/com/sun/tools/javac/comp/TransTypes.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java index 1229939c0bf..86bdc5ac7b7 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransTypes.java @@ -248,9 +248,6 @@ public class TransTypes extends TreeTranslator { meth.name, bridgeType, origin); - /* once JDK-6996415 is solved it should be checked if this approach can - * be applied to method addOverrideBridgesIfNeeded - */ bridge.params = createBridgeParams(impl, bridge, bridgeType); bridge.setAttributes(impl); From 3efa011b968f3a7a8232526e92b85db361b275ce Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Tue, 2 Jun 2026 13:29:16 +0000 Subject: [PATCH 24/32] 8385648: PPC64: Improve receiver type profiling reliability Reviewed-by: shade, dbriemann --- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp | 97 ++---------- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp | 2 +- src/hotspot/cpu/ppc/interp_masm_ppc.hpp | 4 +- src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp | 86 +--------- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 167 ++++++++++++++++++++ src/hotspot/cpu/ppc/macroAssembler_ppc.hpp | 6 + 6 files changed, 192 insertions(+), 170 deletions(-) diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index a9f34b148c6..1270471d150 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -2226,39 +2226,12 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { } +// kills recv void LIR_Assembler::type_profile_helper(Register mdo, int mdo_offset_bias, ciMethodData *md, ciProfileData *data, - Register recv, Register tmp1, Label* update_done) { - uint i; - for (i = 0; i < VirtualCallData::row_limit(); i++) { - Label next_test; - // See if the receiver is receiver[n]. - __ ld(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_offset(i)) - mdo_offset_bias, mdo); - __ verify_klass_ptr(tmp1); - __ cmpd(CR0, recv, tmp1); - __ bne(CR0, next_test); - - __ ld(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_count_offset(i)) - mdo_offset_bias, mdo); - __ addi(tmp1, tmp1, DataLayout::counter_increment); - __ std(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_count_offset(i)) - mdo_offset_bias, mdo); - __ b(*update_done); - - __ bind(next_test); - } - - // Didn't find receiver; find next empty slot and fill it in. - for (i = 0; i < VirtualCallData::row_limit(); i++) { - Label next_test; - __ ld(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_offset(i)) - mdo_offset_bias, mdo); - __ cmpdi(CR0, tmp1, 0); - __ bne(CR0, next_test); - __ li(tmp1, DataLayout::counter_increment); - __ std(recv, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_offset(i)) - mdo_offset_bias, mdo); - __ std(tmp1, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_count_offset(i)) - mdo_offset_bias, mdo); - __ b(*update_done); - - __ bind(next_test); - } + Register recv, Register tmp) { + int mdp_offset = md->byte_offset_of_slot(data, in_ByteSize(0)) - mdo_offset_bias; + __ profile_receiver_type(recv, mdo, mdp_offset, tmp, noreg); } @@ -2320,15 +2293,9 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L __ b(*obj_is_null); __ bind(not_null); - Label update_done; Register recv = klass_RInfo; __ load_klass(recv, obj); - type_profile_helper(mdo, mdo_offset_bias, md, data, recv, Rtmp1, &update_done); - const int slot_offset = md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias; - __ ld(Rtmp1, slot_offset, mdo); - __ addi(Rtmp1, Rtmp1, DataLayout::counter_increment); - __ std(Rtmp1, slot_offset, mdo); - __ bind(update_done); + type_profile_helper(mdo, mdo_offset_bias, md, data, recv, Rtmp1); // kills recv } else { __ cmpdi(CR0, obj, 0); __ beq(CR0, *obj_is_null); @@ -2427,15 +2394,9 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { __ b(done); __ bind(not_null); - Label update_done; Register recv = klass_RInfo; __ load_klass(recv, value); - type_profile_helper(mdo, mdo_offset_bias, md, data, recv, Rtmp1, &update_done); - const int slot_offset = md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias; - __ ld(Rtmp1, slot_offset, mdo); - __ addi(Rtmp1, Rtmp1, DataLayout::counter_increment); - __ std(Rtmp1, slot_offset, mdo); - __ bind(update_done); + type_profile_helper(mdo, mdo_offset_bias, md, data, recv, Rtmp1); // kills recv } else { __ cmpdi(CR0, value, 0); __ beq(CR0, done); @@ -2648,55 +2609,27 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { // We know the type that will be seen at this call site; we can // statically update the MethodData* rather than needing to do // dynamic tests on the receiver type. - - // NOTE: we should probably put a lock around this search to - // avoid collisions by concurrent compilations. ciVirtualCallData* vc_data = (ciVirtualCallData*) data; - uint i; - for (i = 0; i < VirtualCallData::row_limit(); i++) { + for (uint i = 0; i < VirtualCallData::row_limit(); i++) { ciKlass* receiver = vc_data->receiver(i); if (known_klass->equals(receiver)) { - __ ld(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, mdo); - __ addi(tmp1, tmp1, DataLayout::counter_increment); - __ std(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, mdo); + __ increment_mem64(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, + DataLayout::counter_increment, tmp1); return; } } - // Receiver type not found in profile data; select an empty slot. - - // Note that this is less efficient than it should be because it - // always does a write to the receiver part of the - // VirtualCallData rather than just the first time. - for (i = 0; i < VirtualCallData::row_limit(); i++) { - ciKlass* receiver = vc_data->receiver(i); - if (receiver == nullptr) { - metadata2reg(known_klass->constant_encoding(), tmp1); - __ std(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_offset(i)) - mdo_offset_bias, mdo); - - __ ld(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, mdo); - __ addi(tmp1, tmp1, DataLayout::counter_increment); - __ std(tmp1, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i)) - mdo_offset_bias, mdo); - return; - } - } + // Receiver type is not found in profile data. + // Fall back to runtime helper to handle the rest at runtime. + metadata2reg(known_klass->constant_encoding(), recv); } else { __ load_klass(recv, recv); - Label update_done; - type_profile_helper(mdo, mdo_offset_bias, md, data, recv, tmp1, &update_done); - // Receiver did not match any saved receiver and there is no empty row for it. - // Increment total counter to indicate polymorphic case. - __ ld(tmp1, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, mdo); - __ addi(tmp1, tmp1, DataLayout::counter_increment); - __ std(tmp1, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, mdo); - - __ bind(update_done); } + type_profile_helper(mdo, mdo_offset_bias, md, data, recv, tmp1); // kills recv } else { // Static call - __ ld(tmp1, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, mdo); - __ addi(tmp1, tmp1, DataLayout::counter_increment); - __ std(tmp1, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, mdo); + __ increment_mem64(mdo, md->byte_offset_of_slot(data, CounterData::count_offset()) - mdo_offset_bias, + DataLayout::counter_increment, tmp1); } } diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp index 7399a4544e6..5a065d364b2 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.hpp @@ -52,7 +52,7 @@ friend class ArrayCopyStub; // Record the type of the receiver in ReceiverTypeData. void type_profile_helper(Register mdo, int mdo_offset_bias, ciMethodData *md, ciProfileData *data, - Register recv, Register tmp1, Label* update_done); + Register recv, Register tmp); // Setup pointers to MDO, MDO slot, also compute offset bias to access the slot. void setup_md_access(ciMethod* method, int bci, ciMethodData*& md, ciProfileData*& data, int& mdo_offset_bias); diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc.hpp b/src/hotspot/cpu/ppc/interp_masm_ppc.hpp index 275ff92c699..45af9bfc252 100644 --- a/src/hotspot/cpu/ppc/interp_masm_ppc.hpp +++ b/src/hotspot/cpu/ppc/interp_masm_ppc.hpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2025 SAP SE. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. 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 @@ -264,8 +264,6 @@ class InterpreterMacroAssembler: public MacroAssembler { void profile_switch_default(Register scratch1, Register scratch2); void profile_switch_case(Register index, Register scratch1,Register scratch2, Register scratch3); void profile_null_seen(Register Rscratch1, Register Rscratch2); - void record_klass_in_profile(Register receiver, Register scratch1, Register scratch2); - void record_klass_in_profile_helper(Register receiver, Register scratch1, Register scratch2, int start_row, Label& done); // Argument and return type profiling. void profile_obj_type(Register obj, Register mdo_addr_base, RegisterOrConstant mdo_addr_offs, Register tmp, Register tmp2); diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp index a1798289b62..789f8da9574 100644 --- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp @@ -1348,7 +1348,7 @@ void InterpreterMacroAssembler::profile_virtual_call(Register Rreceiver, test_method_data_pointer(profile_continue); // Record the receiver type. - record_klass_in_profile(Rreceiver, Rscratch1, Rscratch2); + profile_receiver_type(Rreceiver, R28_mdx, 0, Rscratch1, Rscratch2); // The method data pointer needs to be updated to reflect the new target. update_mdp_by_constant(in_bytes(VirtualCallData::virtual_call_data_size())); @@ -1367,7 +1367,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register Rklass, Register Rscr mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size()); // Record the object type. - record_klass_in_profile(Rklass, Rscratch1, Rscratch2); + profile_receiver_type(Rklass, R28_mdx, 0, Rscratch1, Rscratch2); } // The method data pointer needs to be updated. @@ -1481,88 +1481,6 @@ void InterpreterMacroAssembler::profile_null_seen(Register Rscratch1, Register R } } -void InterpreterMacroAssembler::record_klass_in_profile(Register Rreceiver, - Register Rscratch1, Register Rscratch2) { - assert(ProfileInterpreter, "must be profiling"); - assert_different_registers(Rreceiver, Rscratch1, Rscratch2); - - Label done; - record_klass_in_profile_helper(Rreceiver, Rscratch1, Rscratch2, 0, done); - bind (done); -} - -void InterpreterMacroAssembler::record_klass_in_profile_helper( - Register receiver, Register scratch1, Register scratch2, - int start_row, Label& done) { - if (TypeProfileWidth == 0) { - increment_mdp_data_at(in_bytes(CounterData::count_offset()), scratch1, scratch2); - return; - } - - int last_row = VirtualCallData::row_limit() - 1; - assert(start_row <= last_row, "must be work left to do"); - // Test this row for both the receiver and for null. - // Take any of three different outcomes: - // 1. found receiver => increment count and goto done - // 2. found null => keep looking for case 1, maybe allocate this cell - // 3. found something else => keep looking for cases 1 and 2 - // Case 3 is handled by a recursive call. - for (int row = start_row; row <= last_row; row++) { - Label next_test; - bool test_for_null_also = (row == start_row); - - // See if the receiver is receiver[n]. - int recvr_offset = in_bytes(VirtualCallData::receiver_offset(row)); - test_mdp_data_at(recvr_offset, receiver, next_test, scratch1); - // delayed()->tst(scratch); - - // The receiver is receiver[n]. Increment count[n]. - int count_offset = in_bytes(VirtualCallData::receiver_count_offset(row)); - increment_mdp_data_at(count_offset, scratch1, scratch2); - b(done); - bind(next_test); - - if (test_for_null_also) { - Label found_null; - // Failed the equality check on receiver[n]... Test for null. - if (start_row == last_row) { - // The only thing left to do is handle the null case. - // Scratch1 contains test_out from test_mdp_data_at. - cmpdi(CR0, scratch1, 0); - beq(CR0, found_null); - // Receiver did not match any saved receiver and there is no empty row for it. - // Increment total counter to indicate polymorphic case. - increment_mdp_data_at(in_bytes(CounterData::count_offset()), scratch1, scratch2); - b(done); - bind(found_null); - break; - } - // Since null is rare, make it be the branch-taken case. - cmpdi(CR0, scratch1, 0); - beq(CR0, found_null); - - // Put all the "Case 3" tests here. - record_klass_in_profile_helper(receiver, scratch1, scratch2, start_row + 1, done); - - // Found a null. Keep searching for a matching receiver, - // but remember that this is an empty (unused) slot. - bind(found_null); - } - } - - // In the fall-through case, we found no matching receiver, but we - // observed the receiver[start_row] is null. - - // Fill in the receiver field and increment the count. - int recvr_offset = in_bytes(VirtualCallData::receiver_offset(start_row)); - set_mdp_data_at(recvr_offset, receiver); - int count_offset = in_bytes(VirtualCallData::receiver_count_offset(start_row)); - li(scratch1, DataLayout::counter_increment); - set_mdp_data_at(count_offset, scratch1); - if (start_row > 0) { - b(done); - } -} // Argument and return type profilig. // kills: tmp, tmp2, R0, CR0, CR1 diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index 95d58d470c8..0d6c272decb 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -4329,6 +4329,173 @@ void MacroAssembler::multiply_to_len(Register x, Register xlen, bind(L_done); } // multiply_to_len +void MacroAssembler::increment_mem64(Register base, RegisterOrConstant ind_or_offs, int val, Register tmp) { + ld(tmp, ind_or_offs, base); + addi(tmp, tmp, val); + std(tmp, ind_or_offs, base); +} + +// Handle the receiver type profile update given the "recv" klass. +// +// Normally updates the ReceiverData (RD) that starts at "mdp" + "mdp_offset". +// If there are no matching or claimable receiver entries in RD, updates +// the polymorphic counter. +// +// This code expected to run by either the interpreter or JIT-ed code, without +// extra synchronization. For safety, receiver cells are claimed atomically, which +// avoids grossly misrepresenting the profiles under concurrent updates. For speed, +// counter updates are not atomic. +// +void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_offset, Register tmp1, Register tmp2) { + assert_different_registers(recv, mdp, tmp1, tmp2); + + int base_receiver_offset = in_bytes(ReceiverTypeData::receiver_offset(0)); + int poly_count_offset = in_bytes(CounterData::count_offset()); + int receiver_step = in_bytes(ReceiverTypeData::receiver_offset(1)) - base_receiver_offset; + int receiver_to_count_step = in_bytes(ReceiverTypeData::receiver_count_offset(0)) - base_receiver_offset; + + // Adjust for MDP offsets. + base_receiver_offset += mdp_offset; + poly_count_offset += mdp_offset; + +#ifdef ASSERT + // We are about to walk the MDO slots without asking for offsets. + // Check that our math hits all the right spots. + for (uint c = 0; c < ReceiverTypeData::row_limit(); c++) { + int real_recv_offset = mdp_offset + in_bytes(ReceiverTypeData::receiver_offset(c)); + int real_count_offset = mdp_offset + in_bytes(ReceiverTypeData::receiver_count_offset(c)); + int offset = base_receiver_offset + receiver_step*c; + int count_offset = offset + receiver_to_count_step; + assert(offset == real_recv_offset, "receiver slot math"); + assert(count_offset == real_count_offset, "receiver count math"); + } + int real_poly_count_offset = mdp_offset + in_bytes(CounterData::count_offset()); + assert(poly_count_offset == real_poly_count_offset, "poly counter math"); +#endif + + // Corner case: no profile table. Increment poly counter and exit. + if (ReceiverTypeData::row_limit() == 0) { + increment_mem64(mdp, poly_count_offset, DataLayout::counter_increment, tmp1); + return; + } + + Label L_loop_search_receiver, L_loop_search_empty; + Label L_restart, L_found_recv, L_found_empty, L_count_update; + Register offset = tmp1, count = tmp2; + + // The code here recognizes three major cases: + // A. Fastest: receiver found in the table + // B. Fast: no receiver in the table, and the table is full + // C. Slow: no receiver in the table, free slots in the table + // + // The case A performance is most important, as perfectly-behaved code would end up + // there, especially with larger TypeProfileWidth. The case B performance is + // important as well, this is where bulk of code would land for normally megamorphic + // cases. The case C performance is not essential, its job is to deal with installation + // races, we optimize for code density instead. Case C needs to make sure that receiver + // rows are only claimed once. This makes sure we never overwrite a row for another + // receiver and never duplicate the receivers in the list, making profile type-accurate. + // + // It is very tempting to handle these cases in a single loop, and claim the first slot + // without checking the rest of the table. But, profiling code should tolerate free slots + // in the table, as class unloading can clear them. After such cleanup, the receiver + // we need might be _after_ the free slot. Therefore, we need to let at least full scan + // to complete, before trying to install new slots. Splitting the code in several tight + // loops also helpfully optimizes for cases A and B. + // + // This code is effectively: + // + // restart: + // // Fastest: receiver is already installed + // for (i = 0; i < receiver_count(); i++) { + // if (receiver(i) == recv) goto found_recv(i); + // } + // + // // Fast: no receiver, but profile is full + // for (i = 0; i < receiver_count(); i++) { + // if (receiver(i) == null) goto found_null(i); + // } + // goto polymorphic + // + // // Slow: try to install receiver + // found_null(i): + // CAS(&receiver(i), null, recv); + // goto restart + // + // polymorphic: + // count++; + // return + // + // found_recv(i): + // *receiver_count(i)++ + // + + if (count != noreg) { + li(count, ReceiverTypeData::row_limit()); + } + + bind(L_restart); + + // Fastest: receiver is already installed + if (count != noreg) { + mtctr(count); + } else { + li(R0, ReceiverTypeData::row_limit()); + mtctr(R0); + } + li(offset, base_receiver_offset); + bind(L_loop_search_receiver); + ldx(R0, offset, mdp); + cmpd(CR0, R0, recv); + beq(CR0, L_found_recv); + addi(offset, offset, receiver_step); + bdnz(L_loop_search_receiver); + + // Fast: no receiver, but profile is full + if (count != noreg) { + mtctr(count); + } else { + li(R0, ReceiverTypeData::row_limit()); + mtctr(R0); + } + li(offset, base_receiver_offset); + bind(L_loop_search_empty); + ldx(R0, offset, mdp); + cmpdi(CR0, R0, 0); + beq(CR0, L_found_empty); + addi(offset, offset, receiver_step); + bdnz(L_loop_search_empty); + + // Polymorphic: Increment polymorphic counter instead of receiver slot. + li(offset, poly_count_offset); + b(L_count_update); + + // Slow: try to install receiver + bind(L_found_empty); + + // Atomically swing receiver slot: null -> recv. + { + Register receiver_addr = offset; + add(receiver_addr, mdp, offset); // kills offset + cmpxchgd(CR0, R0, RegisterOrConstant(0), recv, receiver_addr, MemBarNone, cmpxchgx_hint_atomic_update(), + noreg, nullptr, /* check without ldarx first */ false, /* weak */ true); + } + + // CAS success means the slot now has the receiver we want. CAS failure means + // something had claimed the slot concurrently: it can be the same receiver we want, + // or something else. Since this is a slow path, we can optimize for code density, + // and just restart the search from the beginning. + b(L_restart); + + // Found a receiver, convert its slot offset to corresponding count offset. + bind(L_found_recv); + addi(offset, offset, receiver_to_count_step); + + // Counter update + bind(L_count_update); + increment_mem64(mdp, offset, DataLayout::counter_increment, /* temp */ (count != noreg) ? count : recv); +} + #ifdef ASSERT void MacroAssembler::asm_assert(AsmAssertCond cond, const char *msg) { Label ok; diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp index 21ab192373f..bbfa75f5151 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp @@ -870,6 +870,12 @@ class MacroAssembler: public Assembler { Register tmp6, Register tmp7, Register tmp8, Register tmp9, Register tmp10, Register tmp11, Register tmp12, Register tmp13); + // non-atomic 64-bit memory increment by simm16 + void increment_mem64(Register base, RegisterOrConstant ind_or_offs, int val, Register tmp); + + // Bytecode profiling (tmp2 = noreg is allowed, but then recv is killed) + void profile_receiver_type(Register recv, Register mdp, int mdp_offset, Register tmp1, Register tmp2); + // Emitters for CRC32 calculation. // A note on invertCRC: // Unfortunately, internal representation of crc differs between CRC32 and CRC32C. From e5d8e332050854da0c1b635df57e13bb4d920c06 Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Tue, 2 Jun 2026 13:33:09 +0000 Subject: [PATCH 25/32] 8385658: Debug code in frame::patch_pc() doesn't initialize on_heap info Reviewed-by: dlong, mbaesken --- src/hotspot/cpu/ppc/frame_ppc.cpp | 6 +++--- src/hotspot/cpu/ppc/frame_ppc.inline.hpp | 2 +- src/hotspot/cpu/x86/frame_x86.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hotspot/cpu/ppc/frame_ppc.cpp b/src/hotspot/cpu/ppc/frame_ppc.cpp index 6b6a792117d..7d2e22b5965 100644 --- a/src/hotspot/cpu/ppc/frame_ppc.cpp +++ b/src/hotspot/cpu/ppc/frame_ppc.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2025 SAP SE. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. 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 @@ -319,7 +319,7 @@ void frame::patch_pc(Thread* thread, address pc) { #ifdef ASSERT { - frame f(this->sp(), pc, this->unextended_sp()); + frame f(sp(), unextended_sp(), fp(), pc, cb(), oop_map(), is_heap_frame()); assert(f.is_deoptimized_frame() == this->is_deoptimized_frame() && f.pc() == this->pc() && f.raw_pc() == this->raw_pc(), "must be (f.is_deoptimized_frame(): %d this->is_deoptimized_frame(): %d " "f.pc(): " INTPTR_FORMAT " this->pc(): " INTPTR_FORMAT " f.raw_pc(): " INTPTR_FORMAT " this->raw_pc(): " INTPTR_FORMAT ")", diff --git a/src/hotspot/cpu/ppc/frame_ppc.inline.hpp b/src/hotspot/cpu/ppc/frame_ppc.inline.hpp index cedcb399a83..123e6d8a0b1 100644 --- a/src/hotspot/cpu/ppc/frame_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/frame_ppc.inline.hpp @@ -81,7 +81,7 @@ inline void frame::setup(kind knd) { // Continuation frames on the java heap are not aligned. // When thawing interpreted frames the sp can be unaligned (see new_stack_frame()). assert(_on_heap || - ((is_aligned(_sp, alignment_in_bytes) || is_interpreted_frame() || is_deoptimized_frame()) && + ((is_aligned(_sp, alignment_in_bytes) || is_interpreted_frame()) && (is_aligned(_fp, alignment_in_bytes) || !is_fully_initialized())), "invalid alignment sp:" PTR_FORMAT " unextended_sp:" PTR_FORMAT " fp:" PTR_FORMAT, p2i(_sp), p2i(_unextended_sp), p2i(_fp)); } diff --git a/src/hotspot/cpu/x86/frame_x86.cpp b/src/hotspot/cpu/x86/frame_x86.cpp index 27741ee3bb1..2b06f9ee80c 100644 --- a/src/hotspot/cpu/x86/frame_x86.cpp +++ b/src/hotspot/cpu/x86/frame_x86.cpp @@ -298,7 +298,7 @@ void frame::patch_pc(Thread* thread, address pc) { #ifdef ASSERT { - frame f(this->sp(), this->unextended_sp(), this->fp(), pc); + frame f(sp(), unextended_sp(), fp(), pc, cb(), oop_map(), is_heap_frame()); assert(f.is_deoptimized_frame() == this->is_deoptimized_frame() && f.pc() == this->pc() && f.raw_pc() == this->raw_pc(), "must be (f.is_deoptimized_frame(): %d this->is_deoptimized_frame(): %d " "f.pc(): " INTPTR_FORMAT " this->pc(): " INTPTR_FORMAT " f.raw_pc(): " INTPTR_FORMAT " this->raw_pc(): " INTPTR_FORMAT ")", From cb502e7993cc0a9d89a69901db0b22a236962137 Mon Sep 17 00:00:00 2001 From: Stefan Karlsson Date: Tue, 2 Jun 2026 14:46:25 +0000 Subject: [PATCH 26/32] 8385747: Test gc/g1/TestGCLogMessages.java failed: 'Update Derived Pointers' found in stdout Reviewed-by: dholmes, tschatzl --- src/hotspot/share/prims/whitebox.cpp | 9 +++++++++ test/hotspot/jtreg/gc/g1/TestGCLogMessages.java | 2 +- test/lib/jdk/test/whitebox/WhiteBox.java | 2 ++ test/lib/jdk/test/whitebox/code/Compiler.java | 9 +++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index b7e12fc1c92..2c389d37fec 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -747,6 +747,14 @@ WB_ENTRY(void, WB_NMTArenaMalloc(JNIEnv* env, jobject o, jlong arena, jlong size a->Amalloc(size_t(size)); WB_END +WB_ENTRY(jboolean, WB_isC2Included(JNIEnv* env)) +#ifdef COMPILER2 + return true; +#else + return false; +#endif +WB_END + static jmethodID reflected_method_to_jmid(JavaThread* thread, JNIEnv* env, jobject method) { assert(method != nullptr, "method should not be null"); ThreadToNativeFromVM ttn(thread); @@ -2843,6 +2851,7 @@ static JNINativeMethod methods[] = { {CC"NMTNewArena", CC"(J)J", (void*)&WB_NMTNewArena }, {CC"NMTFreeArena", CC"(J)V", (void*)&WB_NMTFreeArena }, {CC"NMTArenaMalloc", CC"(JJ)V", (void*)&WB_NMTArenaMalloc }, + {CC"isC2Included", CC"()Z", (void*)&WB_isC2Included }, {CC"deoptimizeFrames", CC"(Z)I", (void*)&WB_DeoptimizeFrames }, {CC"isFrameDeoptimized", CC"(I)Z", (void*)&WB_IsFrameDeoptimized}, {CC"deoptimizeAll", CC"()V", (void*)&WB_DeoptimizeAll }, diff --git a/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java b/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java index 36e38e6e2fb..68391893a32 100644 --- a/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java +++ b/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java @@ -86,7 +86,7 @@ public class TestGCLogMessages { } public boolean isAvailable() { - return Compiler.isC2Enabled(); + return Compiler.isC2Included(); } } diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index 311502a16e2..c4b2da9beb0 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -336,6 +336,8 @@ public class WhiteBox { // Compiler + public native boolean isC2Included(); + public native int matchesMethod(Executable method, String pattern); public native int matchesInline(Executable method, String pattern); public native boolean shouldPrintAssembly(Executable method, int comp_level); diff --git a/test/lib/jdk/test/whitebox/code/Compiler.java b/test/lib/jdk/test/whitebox/code/Compiler.java index 333ca42782c..2450fa81ee6 100644 --- a/test/lib/jdk/test/whitebox/code/Compiler.java +++ b/test/lib/jdk/test/whitebox/code/Compiler.java @@ -34,6 +34,15 @@ public class Compiler { private static final WhiteBox WB = WhiteBox.getWhiteBox(); + /** + * Check if C2 was included in the VM build + * + * @return true if C2 was included in the VM build. + */ + public static boolean isC2Included() { + return WB.isC2Included(); + } + /** * Check if C2 is used as JIT compiler. * From c469bb2cd71bb1bd4f635767ac5902c46a9137a7 Mon Sep 17 00:00:00 2001 From: Roman Marchenko Date: Tue, 2 Jun 2026 14:54:16 +0000 Subject: [PATCH 27/32] 8383867: File.getCanonicalPath drops backslash from UNC path with directory junctions Reviewed-by: alanb --- .../windows/native/libjava/canonicalize_md.c | 6 ++- test/jdk/java/io/File/GetCanonicalPath.java | 38 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/java.base/windows/native/libjava/canonicalize_md.c b/src/java.base/windows/native/libjava/canonicalize_md.c index 8596521509c..bc17531e4a5 100644 --- a/src/java.base/windows/native/libjava/canonicalize_md.c +++ b/src/java.base/windows/native/libjava/canonicalize_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, 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 @@ -181,10 +181,12 @@ WCHAR* getFinalPath(WCHAR* path, WCHAR* finalPath, DWORD size) int isUnc = (finalPath[4] == L'U' && finalPath[5] == L'N' && finalPath[6] == L'C'); + // keep leading double backslashes in case of UNC + const int startIdx = (isUnc) ? 1 : 0; int prefixLen = (isUnc) ? 7 : 4; // the amount to copy includes terminator int amountToCopy = len - prefixLen + 1; - wmemmove(finalPath, finalPath + prefixLen, amountToCopy); + wmemmove(finalPath + startIdx, finalPath + prefixLen, amountToCopy); } return finalPath; diff --git a/test/jdk/java/io/File/GetCanonicalPath.java b/test/jdk/java/io/File/GetCanonicalPath.java index 14ef90260fc..f1d517c2202 100644 --- a/test/jdk/java/io/File/GetCanonicalPath.java +++ b/test/jdk/java/io/File/GetCanonicalPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, 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 @@ -22,7 +22,7 @@ */ /* @test - * @bug 4899022 8003887 8355342 + * @bug 4899022 8003887 8355342 8383867 * @summary Look for erroneous representation of drive letter * @run junit GetCanonicalPath */ @@ -148,7 +148,14 @@ public class GetCanonicalPath { Runtime rt = Runtime.getRuntime(); String share = "\\\\localhost\\" + cwd.charAt(0) + "$" + cwd.substring(2); + String junctionName = "tmpDir"; try { + // create directory junction + Path tmpDir = Files.createTempDirectory(junctionName); + String tmpDirLink = cwd + "\\" + junctionName; + Process pmklink = rt.exec(new String[] {"cmd", "/c", "mklink", "/J", tmpDirLink, tmpDir.toString()}); + assertEquals(0, pmklink.waitFor()); + Process p = rt.exec(new String[] {"net", "use", drive + ":", share}); assertEquals(0, p.waitFor()); } catch (InterruptedException x) { @@ -157,13 +164,26 @@ public class GetCanonicalPath { // check that the canonical path name and its content are as expected try { - final String filename = "file.txt"; - final String text = "This is some text"; - Files.writeString(Path.of(share, filename), text); - File file = new File(drive + ":\\" + filename); - String canonicalPath = file.getCanonicalPath(); - assertEquals(drive + ":\\" + filename, canonicalPath); - assertEquals(text, Files.readString(Path.of(canonicalPath))); + // use drive letter + { + final String filename = "file.txt"; + final String text = "This is some text"; + Files.writeString(Path.of(share, filename), text); + File file = new File(drive + ":\\" + filename); + String canonicalPath = file.getCanonicalPath(); + assertEquals(drive + ":\\" + filename, canonicalPath); + assertEquals(text, Files.readString(Path.of(canonicalPath))); + } + // use reparse point (directory junction) + { + final String filename = junctionName + "\\file.txt"; + final String text = "This is some text"; + Files.writeString(Path.of(share, filename), text); + File file = new File(drive + ":\\" + filename); + String canonicalPath = file.getCanonicalPath(); + assertTrue(canonicalPath.startsWith("\\\\localhost\\")); + assertEquals(text, Files.readString(Path.of(canonicalPath))); + } } finally { try { Process p = rt.exec(new String[] {"net", "use", drive + ":", "/Delete"}); From 1e67c84b66617668da2e9323c8e0b27ad5cd3a19 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Tue, 2 Jun 2026 15:26:28 +0000 Subject: [PATCH 28/32] 8382936: C2: Load folding must not drop Type information Reviewed-by: kvn, vlivanov --- src/hotspot/share/opto/graphKit.cpp | 29 ++--- src/hotspot/share/opto/memnode.cpp | 22 +++- src/hotspot/share/opto/type.cpp | 28 +++-- src/hotspot/share/opto/type.hpp | 1 + .../compiler/parsing/TestAastoreTypeSafe.java | 113 ++++++++++++++++++ 5 files changed, 163 insertions(+), 30 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/parsing/TestAastoreTypeSafe.java diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 78bfc1ab5b8..3112bb6b169 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -3401,7 +3401,7 @@ Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, // Null check; get casted pointer; set region slot 3 Node* null_ctl = top(); - Node* not_null_obj = null_check_oop(obj, &null_ctl, never_see_null, safe_for_replace, speculative_not_null); + Node* not_null_obj = null_check_oop(obj, &null_ctl, never_see_null, false /*safe_for_replace*/, speculative_not_null); // If not_null_obj is dead, only null-path is taken if (stopped()) { // Doing instance-of on a null? @@ -3429,7 +3429,7 @@ Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, // a speculative type use it to perform an exact cast. ciKlass* spec_obj_type = obj_type->speculative_type(); if (spec_obj_type != nullptr || data != nullptr) { - cast_obj = maybe_cast_profiled_receiver(not_null_obj, improved_klass_ptr_type, spec_obj_type, safe_for_replace); + cast_obj = maybe_cast_profiled_receiver(not_null_obj, improved_klass_ptr_type, spec_obj_type, false /*safe_for_replace*/); if (cast_obj != nullptr) { if (failure_control != nullptr) // failure is now impossible (*failure_control) = top(); @@ -3467,24 +3467,17 @@ Node* GraphKit::gen_checkcast(Node *obj, Node* superklass, region->init_req(_obj_path, control()); phi ->init_req(_obj_path, cast_obj); - // A merge of null or Casted-NotNull obj - Node* res = _gvn.transform(phi); - - // Note I do NOT always 'replace_in_map(obj,result)' here. - // if( tk->klass()->can_be_primary_super() ) - // This means that if I successfully store an Object into an array-of-String - // I 'forget' that the Object is really now known to be a String. I have to - // do this because we don't have true union types for interfaces - if I store - // a Baz into an array-of-Interface and then tell the optimizer it's an - // Interface, I forget that it's also a Baz and cannot do Baz-like field - // references to it. FIX THIS WHEN UNION TYPES APPEAR! - // replace_in_map( obj, res ); - // Return final merged results set_control( _gvn.transform(region) ); record_for_igvn(region); - return record_profiled_receiver_for_speculation(res); + // A merge of null or Casted-NotNull obj + Node* res = _gvn.transform(phi); + res = record_profiled_receiver_for_speculation(res); + if (safe_for_replace) { + replace_in_map(obj, res); + } + return res; } //------------------------------next_monitor----------------------------------- @@ -4208,13 +4201,13 @@ Node* GraphKit::load_String_value(Node* str, bool set_ctrl) { const TypeInstPtr* string_type = TypeInstPtr::make(TypePtr::NotNull, C->env()->String_klass(), false, nullptr, 0); const TypePtr* value_field_type = string_type->add_offset(value_offset); - const TypeAryPtr* value_type = TypeAryPtr::make(TypePtr::NotNull, + const TypeAryPtr* value_type = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInt::BYTE, TypeInt::POS), ciTypeArrayKlass::make(T_BYTE), true, 0); Node* p = basic_plus_adr(str, str, value_offset); Node* load = access_load_at(str, p, value_field_type, value_type, T_OBJECT, IN_HEAP | (set_ctrl ? C2_CONTROL_DEPENDENT_LOAD : 0) | MO_UNORDERED); - return load; + return must_be_not_null(load, true); } Node* GraphKit::load_String_coder(Node* str, bool set_ctrl) { diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index d5737166bb6..4f68ff281a0 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -53,6 +53,7 @@ #include "opto/vectornode.hpp" #include "utilities/align.hpp" #include "utilities/copy.hpp" +#include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/vmError.hpp" @@ -1217,7 +1218,9 @@ Node* LoadNode::can_see_stored_value_through_membars(Node* st, PhaseValues* phas } } - return can_see_stored_value(st, phase); + Node* res = can_see_stored_value(st, phase); + assert(res == nullptr || is_java_primitive(value_basic_type()) || res->bottom_type()->higher_equal(type()), "the fold is unsafe"); + return res; } // If st is a store to the same location as this, return the stored value @@ -1273,7 +1276,22 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const { return nullptr; } } - return st->in(MemNode::ValueIn); + + // Even if we can see the store, we cannot fold the load if the store is not type safe (e.g. + // store a j.l.Object into an array of j.l.String) because folding makes the compiler lose the + // type information that the uses of this node may need. This is only necessary for pointers, we + // can see the stored value of a LoadS even if it is an int because LoadSNode::Ideal will do the + // necessary truncation. + // The same phenomenon is not an issue for StoreNodes because they don't use res. + Node* res = st->in(MemNode::ValueIn); + if (is_Store() || is_java_primitive(value_basic_type()) || res->bottom_type()->higher_equal(bottom_type())) { + return res; + } + + // Type-unsafe stores must be due to array polymorphism + const TypePtr* adr_type = this->adr_type(); + assert(adr_type == nullptr || adr_type->isa_aryptr() != nullptr, "unexpected type-unsafe store"); + return nullptr; } // A load from a freshly-created object always returns zero. diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index 6c48ad470c9..fcdf9b09fc3 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -22,6 +22,7 @@ * */ +#include "ci/ciInstanceKlass.hpp" #include "ci/ciMethodData.hpp" #include "ci/ciTypeFlow.hpp" #include "classfile/javaClasses.hpp" @@ -3280,6 +3281,17 @@ bool TypeInterfaces::eq(ciInstanceKlass* k) const { return true; } +// Check whether an instance of type k will satisfy this +bool TypeInterfaces::is_subset(ciInstanceKlass* k) const { + assert(k->is_loaded(), "should be loaded"); + GrowableArray* k_interfaces = k->transitive_interfaces(); + for (int i = 0; i < _interfaces.length(); i++) { + if (!k_interfaces->contains(_interfaces.at(i))) { + return false; + } + } + return true; +} uint TypeInterfaces::hash() const { assert(_initialized, "must be"); @@ -5958,20 +5970,16 @@ const TypeKlassPtr* TypeInstKlassPtr::try_improve() const { Compile* C = Compile::current(); Dependencies* deps = C->dependencies(); assert((deps != nullptr) == (C->method() != nullptr && C->method()->code_size() > 0), "sanity"); - const TypeInterfaces* interfaces = _interfaces; if (k->is_loaded()) { ciInstanceKlass* ik = k->as_instance_klass(); bool klass_is_exact = ik->is_final(); - if (!klass_is_exact && - deps != nullptr) { + if (!klass_is_exact && deps != nullptr) { ciInstanceKlass* sub = ik->unique_concrete_subklass(); - if (sub != nullptr) { - if (_interfaces->eq(sub)) { - deps->assert_abstract_with_unique_concrete_subtype(ik, sub); - k = ik = sub; - klass_is_exact = sub->is_final(); - return TypeKlassPtr::make(klass_is_exact ? Constant : _ptr, k, _offset); - } + if (sub != nullptr && _interfaces->is_subset(sub)) { + deps->assert_abstract_with_unique_concrete_subtype(ik, sub); + k = ik = sub; + klass_is_exact = sub->is_final(); + return TypeKlassPtr::make(klass_is_exact ? Constant : _ptr, k, _offset); } } } diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 3e029c387b1..6eaa497e86d 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -1137,6 +1137,7 @@ public: static const TypeInterfaces* make(GrowableArray* interfaces = nullptr); bool eq(const Type* other) const; bool eq(ciInstanceKlass* k) const; + bool is_subset(ciInstanceKlass* k) const; uint hash() const; const Type *xdual() const; void dump(outputStream* st) const; diff --git a/test/hotspot/jtreg/compiler/parsing/TestAastoreTypeSafe.java b/test/hotspot/jtreg/compiler/parsing/TestAastoreTypeSafe.java new file mode 100644 index 00000000000..f33c0fc0b87 --- /dev/null +++ b/test/hotspot/jtreg/compiler/parsing/TestAastoreTypeSafe.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2026, 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 compiler.parsing; + +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8382936 + * @library /test/lib + * @summary Test that aastore generates a type safe graph + * @run main ${test.main.class} + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xcomp -XX:-TieredCompilation + * -XX:CompileOnly=${test.main.class}::test* -XX:-MonomorphicArrayCheck + * -XX:+AlwaysIncrementalInline -XX:+UseCompressedOops ${test.main.class} + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xcomp -XX:-TieredCompilation + * -XX:CompileOnly=${test.main.class}::test* -XX:-MonomorphicArrayCheck + * -XX:+AlwaysIncrementalInline -XX:-UseCompressedOops ${test.main.class} + */ +public class TestAastoreTypeSafe { + private static final class A { + int v; + } + + private static class B { + } + + private static final class C extends B { + int v; + } + + public static void main(String[] args) { + { + A[] array = new A[1]; + A element = new A(); + for (int i = 0; i < 10; i++) { + test1(true, array, element, element); + Asserts.assertEQ(0, element.v); + } + } + + { + C[] array = new C[1]; + C element = new C(); + for (int i = 0; i < 10; i++) { + test2(true, array, element, element); + Asserts.assertEQ(0, element.v); + } + } + } + + // The array load will be folded, but the element type is lost in the graph. This leads to the + // scheduler missing the dependency between the load from the return value of aaload and the + // store into alias.v. The load will then be put late just before the second store to alias.v, + // which is an invalid schedule. + private static void test1(boolean b, A[] array, Object element, A alias) { + aastoreA(array, element); + int v = aaloadA(array).v; + alias.v = 1; + if (b) { + alias.v = v; + } + } + + private static void aastoreA(A[] array, Object element) { + // This forces the compiler to try storing an Object into an A[]. Otherwise, doing + // array[0] = (A)element will make it so that the stored value being an A already. + ((Object[]) array)[0] = element; + } + + private static A aaloadA(A[] array) { + return array[0]; + } + + // Similar to above, but the store cannot be truly type safe because the exact array type is + // unknown + private static void test2(boolean b, B[] array, Object element, C alias) { + aastoreB(array, element); + int v = aaloadC(array).v; + alias.v = 1; + if (b) { + alias.v = v; + } + } + + private static void aastoreB(B[] array, Object element) { + ((Object[]) array)[0] = element; + } + + private static C aaloadC(B[] array) { + return ((C[]) array)[0]; + } +} From 6253a03af30f11f684a17ff7874c6e91a0e160f7 Mon Sep 17 00:00:00 2001 From: Ferenc Rakoczi Date: Tue, 2 Jun 2026 16:43:57 +0000 Subject: [PATCH 29/32] 8383635: Improve performance of ML-KEM's implKyberNttMultJava() method Reviewed-by: semery, weijun --- .../cpu/aarch64/stubGenerator_aarch64.cpp | 18 ++++ .../cpu/x86/stubGenerator_x86_64_kyber.cpp | 21 ++++ .../com/sun/crypto/provider/ML_KEM.java | 98 +++++++++---------- 3 files changed, 84 insertions(+), 53 deletions(-) diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 46849b329cb..8e9af2b7b8a 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -6276,6 +6276,24 @@ class StubGenerator: public StubCodeGenerator { // static int implKyberNttMult( // short[] result, short[] ntta, short[] nttb, short[] zetas) {} // + // The actual algorithm that is used here differs from the one in the Java + // implementation, it uses Montgomery multiplications instead of Barrett + // reduction, but the end result modulo MLKEM_Q is the same. This is the + // Java equivalent of this intrinsic implementation: + // static void implKyberNttMultJava(short[] result, short[] ntta, short[] nttb) { + // for (int m = 0; m < ML_KEM_N / 2; m++) { + // int a0 = ntta[2 * m]; + // int a1 = ntta[2 * m + 1]; + // int b0 = nttb[2 * m]; + // int b1 = nttb[2 * m + 1]; + // int r = montMul(a0, b0) + + // montMul(montMul(a1, b1), MONT_ZETAS_FOR_NTT_MULT[m]); + // result[2 * m] = (short) montMul(r, MONT_R_SQUARE_MOD_Q); + // result[2 * m + 1] = (short) montMul( + // (montMul(a0, b1) + montMul(a1, b0)), MONT_R_SQUARE_MOD_Q); + // } + // } + // // result (short[256]) = c_rarg0 // ntta (short[256]) = c_rarg1 // nttb (short[256]) = c_rarg2 diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp index 13b1c942213..c35a2a1bba6 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp @@ -631,6 +631,27 @@ address generate_kyberInverseNtt_avx512(StubGenerator *stubgen, } // Kyber multiply polynomials in the NTT domain. +// Implements +// static int implKyberNttMult( +// short[] result, short[] ntta, short[] nttb, short[] zetas) {} +// +// The actual algorithm that is used here differs from the one in the Java +// implementation, it uses Montgomery multiplications instead of Barrett +// reduction, but the end result modulo MLKEM_Q is the same. This is the +// Java equivalent of this intrinsic implementation: +// static void implKyberNttMultJava(short[] result, short[] ntta, short[] nttb) { +// for (int m = 0; m < ML_KEM_N / 2; m++) { +// int a0 = ntta[2 * m]; +// int a1 = ntta[2 * m + 1]; +// int b0 = nttb[2 * m]; +// int b1 = nttb[2 * m + 1]; +// int r = montMul(a0, b0) + +// montMul(montMul(a1, b1), MONT_ZETAS_FOR_NTT_MULT[m]); +// result[2 * m] = (short) montMul(r, MONT_R_SQUARE_MOD_Q); +// result[2 * m + 1] = (short) montMul( +// (montMul(a0, b1) + montMul(a1, b0)), MONT_R_SQUARE_MOD_Q); +// } +// } // // result (short[256]) = c_rarg0 // ntta (short[256]) = c_rarg1 diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index 6bd70c3cdd6..96a1eb686cc 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -46,14 +46,17 @@ public final class ML_KEM { private static final int XOF_PAD = 24; private static final int MONT_R_BITS = 20; private static final int MONT_Q = 3329; - private static final int MONT_R_SQUARE_MOD_Q = 152; private static final int MONT_Q_INV_MOD_R = 586497; // toMont((ML_KEM_N / 2)^-1 mod ML_KEM_Q) using R = 2^MONT_R_BITS private static final int MONT_DIM_HALF_INVERSE = 1534; private static final int BARRETT_MULTIPLIER = 20159; + private static final int BARRETT_ADDEND = 1665; private static final int BARRETT_SHIFT = 26; - private static final int[] MONT_ZETAS_FOR_NTT = new int[]{ + + // The values from Appendix A of the FIPS 203 standard converted to the + // Montgomery domain, i.e. toMont(zeta^ (bitrev_7(i)) for i = 0..127 + private static final int[] MONT_ZETAS_FOR_NTT = new int[] { 1188, 914, -969, 585, -551, 1263, -97, 593, -35, -1400, -417, -1253, 742, -281, 185, -819, -1226, 895, -530, 52, 25, 1000, 1249, -909, @@ -72,7 +75,7 @@ public final class ML_KEM { -1599, -709, -789, -1317, -57, 1049, -584 }; - private static final short[] montZetasForVectorNttArr = new short[]{ + private static final short[] montZetasForVectorNttArr = new short[] { // level 0 -758, -758, -758, -758, -758, -758, -758, -758, -758, -758, -758, -758, -758, -758, -758, -758, @@ -193,26 +196,8 @@ public final class ML_KEM { -108, -108, -308, -308, 996, 996, 991, 991, 958, 958, -1460, -1460, 1522, 1522, 1628, 1628 }; - private static final int[] MONT_ZETAS_FOR_INVERSE_NTT = new int[]{ - 584, -1049, 57, 1317, 789, 709, 1599, -1601, - -990, 604, 348, 857, 612, 474, 1177, -1014, - -88, -982, -191, 668, 1386, 486, -1153, -534, - 514, 137, 586, -1178, 227, 339, -907, 244, - 1200, -833, 1394, -30, 1074, 636, -317, -1192, - -1259, -355, -425, -884, -977, 1430, 868, 607, - 184, 1448, 702, 1327, 431, 497, 595, -94, - 1649, -1497, -620, 42, -172, 1107, -222, 1003, - 426, -845, 395, -510, 1613, 825, 1269, -290, - -1429, 623, -567, 1617, 36, 1007, 1440, 332, - -201, 1313, -1382, -744, 669, -1538, 128, -1598, - 1401, 1183, -553, 714, 405, -1155, -445, 406, - -1496, -49, 82, 1369, 259, 1604, 373, 909, - -1249, -1000, -25, -52, 530, -895, 1226, 819, - -185, 281, -742, 1253, 417, 1400, 35, -593, - 97, -1263, 551, -585, 969, -914, -1188 - }; - private static final short[] montZetasForVectorInverseNttArr = new short[]{ + private static final short[] montZetasForVectorInverseNttArr = new short[] { // level 0 -1628, -1628, -1522, -1522, 1460, 1460, -958, -958, -991, -991, -996, -996, 308, 308, 108, 108, @@ -334,25 +319,28 @@ public final class ML_KEM { 758, 758, 758, 758, 758, 758, 758, 758 }; - private static final int[] MONT_ZETAS_FOR_NTT_MULT = new int[]{ - -1003, 1003, 222, -222, -1107, 1107, 172, -172, - -42, 42, 620, -620, 1497, -1497, -1649, 1649, - 94, -94, -595, 595, -497, 497, -431, 431, - -1327, 1327, -702, 702, -1448, 1448, -184, 184, - -607, 607, -868, 868, -1430, 1430, 977, -977, - 884, -884, 425, -425, 355, -355, 1259, -1259, - 1192, -1192, 317, -317, -636, 636, -1074, 1074, - 30, -30, -1394, 1394, 833, -833, -1200, 1200, - -244, 244, 907, -907, -339, 339, -227, 227, - 1178, -1178, -586, 586, -137, 137, -514, 514, - 534, -534, 1153, -1153, -486, 486, -1386, 1386, - -668, 668, 191, -191, 982, -982, 88, -88, - 1014, -1014, -1177, 1177, -474, 474, -612, 612, - -857, 857, -348, 348, -604, 604, 990, -990, - 1601, -1601, -1599, 1599, -709, 709, -789, 789, - -1317, 1317, -57, 57, 1049, -1049, -584, 584 + // modulo MLKEM_Q positive equivalents of the values listed for + // the MultiplyNTTs algorithm in the FIPS 203 standard + private static final int[] ZETAS_FOR_NTT_MULT = new int[] { + 17, 3312, 2761, 568, 583, 2746, 2649, 680, + 1637, 1692, 723, 2606, 2288, 1041, 1100, 2229, + 1409, 1920, 2662, 667, 3281, 48, 233, 3096, + 756, 2573, 2156, 1173, 3015, 314, 3050, 279, + 1703, 1626, 1651, 1678, 2789, 540, 1789, 1540, + 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, + 939, 2390, 2308, 1021, 2437, 892, 2388, 941, + 733, 2596, 2337, 992, 268, 3061, 641, 2688, + 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, + 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, + 1063, 2266, 319, 3010, 2773, 556, 757, 2572, + 2099, 1230, 561, 2768, 2466, 863, 2594, 735, + 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, + 1143, 2186, 2150, 1179, 2775, 554, 886, 2443, + 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, + 2110, 1219, 2935, 394, 885, 2444, 2154, 1175 }; - private static final short[] montZetasForVectorNttMultArr = new short[]{ + + private static final short[] montZetasForVectorNttMultArr = new short[] { -1103, 1103, 430, -430, 555, -555, 843, -843, -1251, 1251, 871, -871, 1550, -1550, 105, -105, 422, -422, 587, -587, 177, -177, -235, 235, @@ -1174,17 +1162,20 @@ public final class ML_KEM { } static void implKyberNttMultJava(short[] result, short[] ntta, short[] nttb) { - for (int m = 0; m < ML_KEM_N / 2; m++) { - - int a0 = ntta[2 * m]; - int a1 = ntta[2 * m + 1]; - int b0 = nttb[2 * m]; - int b1 = nttb[2 * m + 1]; - int r = montMul(a0, b0) + - montMul(montMul(a1, b1), MONT_ZETAS_FOR_NTT_MULT[m]); - result[2 * m] = (short) montMul(r, MONT_R_SQUARE_MOD_Q); - result[2 * m + 1] = (short) montMul( - (montMul(a0, b1) + montMul(a1, b0)), MONT_R_SQUARE_MOD_Q); + for (int m = 0; m < ML_KEM_N; m += 2) { + int a0 = ntta[m]; + int a1 = ntta[m + 1]; + int b0 = nttb[m]; + int b1 = nttb[m + 1]; + long r = a1 * b1; + r -= ((r * BARRETT_MULTIPLIER) >> BARRETT_SHIFT) * ML_KEM_Q; + r *= ZETAS_FOR_NTT_MULT[m >> 1]; + r += a0 * b0; + result[m] = (short) (r - (((r + BARRETT_ADDEND) * + BARRETT_MULTIPLIER) >> BARRETT_SHIFT) * ML_KEM_Q); + long r1 = a0 * b1 + a1 * b0; + result[m + 1] = (short) (r1 - (((r1 + BARRETT_ADDEND) * + BARRETT_MULTIPLIER) >> BARRETT_SHIFT) * ML_KEM_Q); } } @@ -1552,9 +1543,10 @@ public final class ML_KEM { } static void implKyberBarrettReduceJava(short[] poly) { + int tmp = 0; for (int m = 0; m < ML_KEM_N; m++) { - int tmp = ((int) poly[m] * BARRETT_MULTIPLIER) >> BARRETT_SHIFT; - poly[m] = (short) (poly[m] - tmp * ML_KEM_Q); + tmp = poly[m]; + poly[m] = (short) (tmp - ((tmp * BARRETT_MULTIPLIER) >> BARRETT_SHIFT) * ML_KEM_Q); } } From 4192f9bf2edb14e6449e922a8bb27ec14c1003d4 Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Tue, 2 Jun 2026 20:27:12 +0000 Subject: [PATCH 30/32] 8385736: Optimize ListFormat custom pattern parsing Reviewed-by: jlu --- .../share/classes/java/text/ListFormat.java | 162 ++++++++++++------ .../Format/ListFormat/TestListFormat.java | 58 ++++++- 2 files changed, 163 insertions(+), 57 deletions(-) diff --git a/src/java.base/share/classes/java/text/ListFormat.java b/src/java.base/share/classes/java/text/ListFormat.java index 5cd0e9e3651..3f320bbcc9b 100644 --- a/src/java.base/share/classes/java/text/ListFormat.java +++ b/src/java.base/share/classes/java/text/ListFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -32,7 +32,6 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Objects; -import java.util.regex.Pattern; import java.util.stream.IntStream; import sun.util.locale.provider.LocaleProviderAdapter; @@ -112,6 +111,7 @@ public final class ListFormat extends Format { private static final int TWO = 3; private static final int THREE = 4; private static final int PATTERN_ARRAY_LENGTH = THREE + 1; + private static final int PLACEHOLDER_LENGTH = 3; // i.e., "{i}".length() /** * The locale to use for formatting list patterns. @@ -126,14 +126,11 @@ public final class ListFormat extends Format { */ private final String[] patterns; - private static final Pattern PARSE_START = Pattern.compile("(.*?)\\{0}(.*?)\\{1}"); - private static final Pattern PARSE_MIDDLE = Pattern.compile("\\{0}(.*?)\\{1}"); - private static final Pattern PARSE_END = Pattern.compile("\\{0}(.*?)\\{1}(.*?)"); - private static final Pattern PARSE_TWO = Pattern.compile("(.*?)\\{0}(.*?)\\{1}(.*?)"); - private static final Pattern PARSE_THREE = Pattern.compile("(.*?)\\{0}(.*?)\\{1}(.*?)\\{2}(.*?)"); - private transient Pattern startPattern; + private transient String startBefore; + private transient String startBetween; private transient String middleBetween; - private transient Pattern endPattern; + private transient String endBetween; + private transient String endAfter; private ListFormat(Locale l, String[] patterns) { locale = l; @@ -149,50 +146,60 @@ public final class ListFormat extends Format { } } - // get pattern strings - var m = PARSE_START.matcher(patterns[START]); - String startBefore; - String startBetween; - if (m.matches()) { - startBefore = m.group(1); - startBetween = m.group(2); + // Get pattern strings. Pattern conditions from LDML are: + // - it contains the placeholders {0}, {1}, and {2} ("3"-pattern only) in order + // - "start" and "middle" patterns end with the {1} placeholder + // - "middle" and "end" patterns begin with the {0} placeholder + var pattern = patterns[START]; + var placeholderPositions = findPlaceholders(pattern); + if (placeholderPositions != null && + placeholderPositions[1] + PLACEHOLDER_LENGTH == pattern.length()) { + startBefore = pattern.substring(0, placeholderPositions[0]); + startBetween = pattern.substring(placeholderPositions[0] + PLACEHOLDER_LENGTH, + placeholderPositions[1]); } else { - throw new IllegalArgumentException("start pattern is incorrect: " + patterns[START]); + throw new IllegalArgumentException("start pattern is incorrect: " + pattern); } - m = PARSE_MIDDLE.matcher(patterns[MIDDLE]); - if (m.matches()) { - middleBetween = m.group(1); + + pattern = patterns[MIDDLE]; + placeholderPositions = findPlaceholders(pattern); + if (placeholderPositions != null && + placeholderPositions[0] == 0 && + placeholderPositions[1] + PLACEHOLDER_LENGTH == pattern.length()) { + middleBetween = pattern.substring(placeholderPositions[0] + PLACEHOLDER_LENGTH, + placeholderPositions[1]); } else { - throw new IllegalArgumentException("middle pattern is incorrect: " + patterns[MIDDLE]); + throw new IllegalArgumentException("middle pattern is incorrect: " + pattern); } - m = PARSE_END.matcher(patterns[END]); - String endBetween; - String endAfter; - if (m.matches()) { - endBetween = m.group(1); - endAfter = m.group(2); + + pattern = patterns[END]; + placeholderPositions = findPlaceholders(pattern); + if (placeholderPositions != null && placeholderPositions[0] == 0) { + endBetween = pattern.substring(placeholderPositions[0] + PLACEHOLDER_LENGTH, + placeholderPositions[1]); + endAfter = pattern.substring(placeholderPositions[1] + PLACEHOLDER_LENGTH); } else { - throw new IllegalArgumentException("end pattern is incorrect: " + patterns[END]); + throw new IllegalArgumentException("end pattern is incorrect: " + pattern); } // Validate two/three patterns, if given. Otherwise, generate them - if (!patterns[TWO].isEmpty()) { - if (!PARSE_TWO.matcher(patterns[TWO]).matches()) { - throw new IllegalArgumentException("pattern for two is incorrect: " + patterns[TWO]); + pattern = patterns[TWO]; + if (!pattern.isEmpty()) { + if (findPlaceholders(pattern) == null) { + throw new IllegalArgumentException("pattern for two is incorrect: " + pattern); } } else { patterns[TWO] = startBefore + "{0}" + endBetween + "{1}" + endAfter; } - if (!patterns[THREE].isEmpty()) { - if (!PARSE_THREE.matcher(patterns[THREE]).matches()) { - throw new IllegalArgumentException("pattern for three is incorrect: " + patterns[THREE]); + pattern = patterns[THREE]; + if (!pattern.isEmpty()) { + placeholderPositions = findPlaceholders(pattern); + if (placeholderPositions == null || placeholderPositions[2] == -1) { + throw new IllegalArgumentException("pattern for three is incorrect: " + pattern); } } else { patterns[THREE] = startBefore + "{0}" + startBetween + "{1}" + endBetween + "{2}" + endAfter; } - - startPattern = Pattern.compile(startBefore + "(.+?)" + startBetween); - endPattern = Pattern.compile(endBetween + "(.+?)" + endAfter); } /** @@ -455,21 +462,29 @@ public final class ListFormat extends Format { public Object parseObject(String source, ParsePosition parsePos) { Objects.requireNonNull(source); Objects.requireNonNull(parsePos); - var sm = startPattern.matcher(source); - var em = endPattern.matcher(source); + var startPattern = findPattern(source, parsePos.getIndex(), startBefore, startBetween); + var endPattern = findPattern(source, parsePos.getIndex(), endBetween, endAfter); Object parsed = null; - if (sm.find(parsePos.getIndex()) && em.find(parsePos.getIndex())) { - // get em to the last - var c = em.start(); - while (em.find()) { - c = em.start(); + if (startPattern != null && endPattern != null) { + // get endPattern to the last + var ep = endPattern; + while ((ep = findPattern(source, ep[1], endBetween, endAfter)) != null) { + endPattern = ep; } - em.find(c); - var startEnd = sm.end(); - var endStart = em.start(); + + var startEnd = startPattern[1]; + var endStart = endPattern[0]; if (startEnd <= endStart) { var mid = source.substring(startEnd, endStart); - var count = mid.split(middleBetween).length + 2; + var count = 3; + var mbLength = middleBetween.length(); + if (mbLength > 0) { + var midIndex = 0; + while ((midIndex = mid.indexOf(middleBetween, midIndex)) >= 0) { + count++; + midIndex += mbLength; + } + } parsed = new MessageFormat(createMessageFormatString(count), locale).parseObject(source, parsePos); } } @@ -565,7 +580,9 @@ public final class ListFormat extends Format { private String createMessageFormatString(int count) { var sb = new StringBuilder(256).append(patterns[START]); IntStream.range(2, count - 1).forEach(i -> sb.append(middleBetween).append("{").append(i).append("}")); - sb.append(patterns[END].replaceFirst("\\{0}", "").replaceFirst("\\{1}", "\\{" + (count - 1) + "\\}")); + sb.append(endBetween) + .append("{").append(count - 1).append("}") + .append(endAfter); return sb.toString(); } @@ -643,4 +660,51 @@ public final class ListFormat extends Format { */ NARROW } + + /** + * {@return the positions of the "{0}", "{1}", and "{2}" placeholders in the + * given pattern string, or null if the pattern is invalid} + * + * The returned array contains -1 for "{2}" if that placeholder is absent. + * + * @param pattern pattern string to parse + */ + private static int[] findPlaceholders(String pattern) { + var positions = new int[3]; + for (int i = 0; i < positions.length; i++) { + positions[i] = pattern.indexOf("{" + i + "}"); + } + + // Check the existence and order of the placeholders + if (positions[0] == -1 || + positions[1] == -1 || + positions[0] + PLACEHOLDER_LENGTH > positions[1] || + positions[2] != -1 && positions[1] + PLACEHOLDER_LENGTH > positions[2]) { + return null; + } + + return positions; + } + + /** + * {@return the start and end positions of the first pattern found in + * the given {@code source} starting at {@code pos}, or null if no such + * pattern exists} + * + * The pattern must contain at least one character between the + * {@code prefix} and {@code suffix} strings. The returned end position is + * exclusive. + * + * @param source string to search + * @param pos position at which to start the search + * @param prefix starting string within the pattern + * @param suffix ending string within the pattern + */ + private static int[] findPattern(String source, int pos, String prefix, String suffix) { + var prefixPos = source.indexOf(prefix, pos); + var suffixPos = prefixPos != -1 ? source.indexOf(suffix, prefixPos + prefix.length() + 1) : -1; + + return prefixPos < suffixPos ? + new int[] {prefixPos, suffixPos + suffix.length()} : null; + } } diff --git a/test/jdk/java/text/Format/ListFormat/TestListFormat.java b/test/jdk/java/text/Format/ListFormat/TestListFormat.java index bcccc2873f5..1500a1b0818 100644 --- a/test/jdk/java/text/Format/ListFormat/TestListFormat.java +++ b/test/jdk/java/text/Format/ListFormat/TestListFormat.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8041488 8316974 8318569 8306116 + * @bug 8041488 8316974 8318569 8306116 8385736 * @summary Tests for ListFormat class * @run junit TestListFormat */ @@ -66,6 +66,14 @@ public class TestListFormat { "", "", }; + // Ensures regex metacharacters in custom patterns are treated as literals. + private static final String[] CUSTOM_PATTERNS_METACHAR = { + ". {0} * {1}", + "{0} + {1}", + "{0} | {1} [", + "", + "", + }; private static final String[] CUSTOM_PATTERNS_IAE_START = { "{0}", "{0} mid {1}", @@ -120,7 +128,7 @@ public class TestListFormat { assertEquals(ListFormat.getInstance(), ListFormat.getInstance(Locale.getDefault(Locale.Category.FORMAT), ListFormat.Type.STANDARD, ListFormat.Style.FULL)); } - static Arguments[] getInstance_1Arg() { + private static Arguments[] getInstance_1Arg() { return new Arguments[] { arguments(CUSTOM_PATTERNS_FULL, SAMPLE1, "foo"), arguments(CUSTOM_PATTERNS_FULL, SAMPLE2, "twobef foo two bar twoaft"), @@ -130,10 +138,14 @@ public class TestListFormat { arguments(CUSTOM_PATTERNS_MINIMAL, SAMPLE2, "sbef foo ebet bar eaft"), arguments(CUSTOM_PATTERNS_MINIMAL, SAMPLE3, "sbef foo sbet bar ebet baz eaft"), arguments(CUSTOM_PATTERNS_MINIMAL, SAMPLE4, "sbef foo sbet bar mid baz ebet qux eaft"), + arguments(CUSTOM_PATTERNS_METACHAR, SAMPLE1, "foo"), + arguments(CUSTOM_PATTERNS_METACHAR, SAMPLE2, ". foo | bar ["), + arguments(CUSTOM_PATTERNS_METACHAR, SAMPLE3, ". foo * bar | baz ["), + arguments(CUSTOM_PATTERNS_METACHAR, SAMPLE4, ". foo * bar + baz | qux ["), }; } - static Arguments[] getInstance_1Arg_IAE() { + private static Arguments[] getInstance_1Arg_IAE() { return new Arguments[] { arguments(new String[1], "Pattern array length should be 5"), arguments(new String[6], "Pattern array length should be 5"), @@ -146,7 +158,7 @@ public class TestListFormat { }; } - static Arguments[] getInstance_3Arg() { + private static Arguments[] getInstance_3Arg() { return new Arguments[] { arguments(Locale.US, ListFormat.Type.STANDARD, ListFormat.Style.FULL, "foo, bar, and baz", true), @@ -188,7 +200,7 @@ public class TestListFormat { }; } - static Arguments[] parseObject_parsePos() { + private static Arguments[] parseObject_parsePos() { return new Arguments[] { arguments(CUSTOM_PATTERNS_FULL, SAMPLE1), arguments(CUSTOM_PATTERNS_FULL, SAMPLE2), @@ -201,7 +213,7 @@ public class TestListFormat { }; } - static Arguments[] getInstance_3Arg_InheritPatterns() { + private static Arguments[] getInstance_3Arg_InheritPatterns() { return new Arguments[] { arguments(ListFormat.Type.STANDARD, ListFormat.Style.FULL), arguments(ListFormat.Type.STANDARD, ListFormat.Style.SHORT), @@ -215,7 +227,7 @@ public class TestListFormat { }; } - static Arguments[] getLocale_localeDependent() { + private static Arguments[] getLocale_localeDependent() { return new Arguments[] { arguments(Locale.ROOT), arguments(Locale.US), @@ -225,6 +237,16 @@ public class TestListFormat { }; } + private static Arguments[] getInstance_1Arg_InvalidLongPattern() { + return new Arguments[] { + arguments(0, "start pattern is incorrect:"), + arguments(1, "middle pattern is incorrect:"), + arguments(2, "end pattern is incorrect:"), + arguments(3, "pattern for two is incorrect:"), + arguments(4, "pattern for three is incorrect:"), + }; + } + @ParameterizedTest @MethodSource void getInstance_1Arg(String[] patterns, List input, String expected) throws ParseException { @@ -240,6 +262,25 @@ public class TestListFormat { assertEquals(errorMsg, ex.getMessage()); } + @ParameterizedTest + @MethodSource + void getInstance_1Arg_InvalidLongPattern(int index, String expected) { + var patterns = new String[]{ + "{0}, {1}", + "{0}, {1}", + "{0}, and {1}", + "{0} and {1}", + "{0} {1} {2}" + }; + patterns[index] = "{0}".repeat(100_000); + + // Ensures validation of invalid long patterns completes without timing out + var msg = assertThrows(IllegalArgumentException.class, + () -> ListFormat.getInstance(patterns)) + .getMessage(); + assertEquals(expected, msg.substring(0, Math.min(msg.length(), expected.length()))); + } + @ParameterizedTest @MethodSource void getInstance_3Arg(Locale l, ListFormat.Type type, ListFormat.Style style, String expected, boolean roundTrip) throws ParseException { @@ -348,6 +389,7 @@ public class TestListFormat { // should be inherited from parent locales. Locale.availableLocales().forEach(l -> ListFormat.getInstance(l, type, style)); } + @Test void getInstance_3Arg_InheritanceValidation() { // Tests if inheritance works as expected. From 43d80fd19233cc6691db9fb47963a4a227996eb6 Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Tue, 2 Jun 2026 21:42:24 +0000 Subject: [PATCH 31/32] 8329548: Change KeyUpdate messages from TLS 1.3 Reviewed-by: jnimeh --- .../classes/sun/security/ssl/SSLCipher.java | 18 +- .../ssl/SSLEngineImpl/KeyUpdateOnce.java | 546 ++++++++++++++++++ 2 files changed, 560 insertions(+), 4 deletions(-) create mode 100644 test/jdk/sun/security/ssl/SSLEngineImpl/KeyUpdateOnce.java diff --git a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java index 9d1d6dabaec..a0fc6c6e207 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLCipher.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLCipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -26,6 +26,7 @@ package sun.security.ssl; import sun.security.ssl.Authenticator.MAC; +import sun.security.util.Debug; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -371,15 +372,15 @@ enum SSLCipher { ProtocolVersion[]>[] writeCipherGenerators; // Map of Ciphers listed in jdk.tls.keyLimits - private static final HashMap cipherLimits = new HashMap<>(); + static final HashMap cipherLimits = new HashMap<>(); // Keywords found on the jdk.tls.keyLimits security property. static final String[] tag = {"KEYUPDATE"}; + static final long COUNTDOWNWARN = 20000; // Print debug warning under limit static { final long max = 4611686018427387904L; // 2^62 String prop = Security.getProperty("jdk.tls.keyLimits"); - if (prop != null) { String[] propvalue = prop.split(","); @@ -617,12 +618,21 @@ enum SSLCipher { /** * Check if processed bytes have reached the key usage limit. - * If key usage limit is not be monitored, return false. + * If key usage limits are not be monitored, return false. */ public boolean atKeyLimit() { + if (keyLimitCountdown < COUNTDOWNWARN && SSLLogger.isOn()) { + SSLLogger.fine("keyLimitCountdown: " + keyLimitCountdown); + } if (keyLimitCountdown >= 0) { return false; } + if (keyLimitEnabled == false) { + if (SSLLogger.isOn()) { + SSLLogger.fine("KeyUpdate already sent, skipping"); + } + return false; + } // Turn off limit checking as KeyUpdate will be occurring keyLimitEnabled = false; diff --git a/test/jdk/sun/security/ssl/SSLEngineImpl/KeyUpdateOnce.java b/test/jdk/sun/security/ssl/SSLEngineImpl/KeyUpdateOnce.java new file mode 100644 index 00000000000..298ba86250a --- /dev/null +++ b/test/jdk/sun/security/ssl/SSLEngineImpl/KeyUpdateOnce.java @@ -0,0 +1,546 @@ +/* + * Copyright (c) 2026, 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 + * @bug 8329548 + * @library ../../ + * /test/lib + * /javax/net/ssl/templates + * @summary Verify KeyUpdate messages skipped after first one sent. + * + * @run main KeyUpdateOnce server TLS_AES_256_GCM_SHA384 200000 + * @run main KeyUpdateOnce client TLS_AES_256_GCM_SHA384 200000 + */ + +/* + * This test runs in another process so we can monitor the debug + * results. The OutputAnalyzer must see correct debug output to return a + * success. + */ + +import jdk.test.lib.Utils; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; + +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +/** + * This server/client TLS test will force side A to stop reading as it + * continuously writes out. These write ops will trigger the side B to + * request a KeyUpdate. With side A not reading, side B must skip + * sending more KeyUpdate messages. Only one KeyUpdate message will be + * sent by side B. + * + * This test depends on debug messages string match. Changing the KeyUpdate- + * related messages may cause a failure. + */ + +public class KeyUpdateOnce extends SSLContextTemplate { + + private static final int DATALEN = 10240; + private static final int BUF_DATALEN = 4 * DATALEN; + private static final int MAXLOOPS = 150; + private static final int COUNTDOWNLIMIT = 5; + + private static final boolean DEBUG = true; + + private static ByteBuffer cTos; + private static ByteBuffer sToc; + private static ByteBuffer outData; + private final ByteBuffer inData; + + // thread flags + private static boolean ready = false; + private static boolean sc = true; + private static boolean readDone = false; + private static boolean serverWrites = true; + + private static long newLimit; + + // Reflection handle captured on read side once handshake completes + private static Object readSideInputRecord = null; + + protected SSLEngine engine; + private final int delay = 1; + private int totalDataLen = 0; + + KeyUpdateOnce() { + this.inData = ByteBuffer.allocate(BUF_DATALEN); + } + + /** + * args should have: + * server|client, cipherSuite, + * + * Prepending 'p' is for internal use only (test harness relaunch). + */ + public static void main(String[] args) throws Exception { + for (String arg : args) { + System.out.print(" " + arg); + } + System.out.println(); + + // Harness mode: relaunch self with 'p' to force add-opens + debugging flags + if (!"p".equals(args[0])) { + // args[]: 0 = client/server, 1 = cipher suite, 2 = newLimit + System.setProperty("test.java.opts", + System.getProperty("test.java.opts") + + " -Dtest.src=" + System.getProperty("test.src") + + " -Dtest.jdk=" + System.getProperty("test.jdk") + + " -Djavax.net.debug=ssl,handshake" + + " -Djavatest.maxOutputSize=99999999" + + " --add-opens java.base/sun.security.ssl=ALL-UNNAMED"); + + System.out.println("test.java.opts: " + + System.getProperty("test.java.opts")); + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + Utils.addTestJavaOpts("KeyUpdateOnce", "p", args[0], + args[1], args[2])); + + OutputAnalyzer output = ProcessTools.executeProcess(pb); + try { + output.shouldContain(String.format( + "\"cipher suite\" : \"%s", args[1])); + System.err.println("Output logs should show KeyUpdate has" + + " been sent and skipped"); + List producedList = output.asLines().stream() + .filter(s -> s.contains("Produced KeyUpdate")) + .toList(); + List skippingList = output.asLines().stream() + .filter(s -> s.contains("KeyUpdate already sent, skipping")) + .toList(); + producedList.forEach(System.err::println); + skippingList.forEach(System.err::println); + System.err.println("\"Produced KeyUpdate\" count = " + producedList.size()); + System.err.println("\"KeyUpdate already send, skipping\" count = " + skippingList.size()); + + /* + * Sometimes debug messages may not be consistent. The below + * checks verify that at least 1 of each message were received. + */ + // Ideally there should be 2 "Produced KeyUpdate" + if (producedList.isEmpty()) { + throw new AssertionError("No \"Produced KeyUpdate\""); + } + // Ideally there should be 5 "KeyUpdate already send, skipping" + if (skippingList.isEmpty()) { + throw new AssertionError("No \"KeyUpdate already send, skipping\""); + } + + } finally { + System.out.println("-- BEGIN Stdout:"); + System.out.println(output.getStdout()); + System.out.println("-- END Stdout"); + System.out.println("-- BEGIN Stderr:"); + System.out.println(output.getStderr()); + System.out.println("-- END Stderr"); + } + return; + } + + // Worker mode: + // args[]: 0 = p, 1 = client/server, 2 = cipher suite, 3 = newLimit + serverWrites = !"client".equals(args[1]); + newLimit = Long.parseLong(args[3]); + + cTos = ByteBuffer.allocateDirect(BUF_DATALEN); + sToc = ByteBuffer.allocateDirect(BUF_DATALEN); + outData = ByteBuffer.allocateDirect(DATALEN); + + byte[] data = new byte[DATALEN]; + Arrays.fill(data, (byte) 0x0A); + outData.put(data).flip(); + + cTos.clear(); + sToc.clear(); + + Thread peer = new Thread(serverWrites ? new Client() : + new Server(args[2])); + peer.start(); + + (serverWrites ? new Server(args[2]) : new Client()).run(); + + peer.interrupt(); + peer.join(); + } + + private static void doTask(SSLEngineResult result, SSLEngine engine) + throws Exception { + if (result.getHandshakeStatus() == + SSLEngineResult.HandshakeStatus.NEED_TASK) { + Runnable runnable; + while ((runnable = engine.getDelegatedTask()) != null) { + print("\trunning delegated task..."); + runnable.run(); + } + SSLEngineResult.HandshakeStatus hsStatus = + engine.getHandshakeStatus(); + if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) { + throw new Exception("handshake shouldn't need additional tasks"); + } + print("\tnew HandshakeStatus: " + hsStatus); + } + } + + private static void print(String s) { + if (DEBUG) { + System.err.println(s); + } + } + + private static void log(String s, SSLEngineResult r) { + if (DEBUG) { + System.err.println(s + ": " + + r.getStatus() + "/" + r.getHandshakeStatus() + " " + + r.bytesConsumed() + "/" + r.bytesProduced()); + } + } + + private static void dumpBuffers(String aName, ByteBuffer a) { + if (DEBUG) { + System.err.println(aName + " pos=" + a.position() + + " rem=" + a.remaining() + + " lim=" + a.limit() + " cap=" + a.capacity()); + } + } + + void writeLoop() throws Exception { + int i = 0; + SSLEngineResult r; + int countdown = COUNTDOWNLIMIT; + + while (!ready) { + Thread.sleep(delay); + } + + print("Write-side begins"); + + while (i++ < MAXLOOPS) { + while (sc) { + if (readDone) { + return; + } + Thread.sleep(delay); + } + + outData.rewind(); + + while (true) { + r = engine.wrap(outData, getWriteBuf()); + log("write wrap", r); + + if (DEBUG && r.getStatus() != SSLEngineResult.Status.OK) { + dumpBuffers("outData", outData); + dumpBuffers("writeBuf", getWriteBuf()); + } + + if (r.getStatus() == SSLEngineResult.Status.OK && + r.getHandshakeStatus() == + SSLEngineResult.HandshakeStatus.NEED_WRAP) { + continue; + } + break; + } + + doTask(r, engine); + + getWriteBuf().flip(); + sc = true; + + while (sc) { + if (readDone) { + return; + } + Thread.sleep(delay); + } + + long rlimit = Long.MAX_VALUE; + if (readSideInputRecord != null) { + rlimit = getReadLimit(readSideInputRecord); + } + if (rlimit <= 0) { + countdown--; + } + System.err.println("Write side readLimit = " + rlimit); + + if (countdown == COUNTDOWNLIMIT || countdown <= 0) { + inData.clear(); + r = engine.unwrap(getReadBuf(), inData); + log("write unwrap", r); + + if (DEBUG && r.getStatus() != SSLEngineResult.Status.OK) { + dumpBuffers("inData", inData); + dumpBuffers("readBuf", getReadBuf()); + } + } else { + print("write side unwrap skipped"); + } + + doTask(r, engine); + getReadBuf().compact(); + dumpBuffers("compacted getReadBuf()", getReadBuf()); + sc = true; + } + } + + void readLoop() throws Exception { + byte b = 0x0B; + ByteBuffer buf = ByteBuffer.allocateDirect(DATALEN); + + SSLEngineResult r = null; + boolean again = true; + boolean firstNotHandshake = false; + + while (engine == null) { + Thread.sleep(delay); + } + + try { + System.out.println("connected"); + print("entering read loop"); + ready = true; + + while (true) { + while (!sc) { + Thread.sleep(delay); + } + + boolean exit = false; + while (!exit) { + buf.put(b); + buf.flip(); + + r = engine.wrap(buf, getWriteBuf()); + log("read wrap", r); + + if (DEBUG) { + dumpBuffers("buf", buf); + dumpBuffers( "writeBuf", getWriteBuf()); + } + + if (again && r.getStatus() == SSLEngineResult.Status.OK && + r.getHandshakeStatus() == + SSLEngineResult.HandshakeStatus.NEED_WRAP) { + buf.compact(); + again = false; + continue; + } + exit = true; + } + + doTask(r, engine); + + buf.clear(); + getWriteBuf().flip(); + sc = false; + + while (!sc) { + Thread.sleep(delay); + } + + while (true) { + inData.clear(); + r = engine.unwrap(getReadBuf(), inData); + log("read unwrap", r); + + if (DEBUG && r.getStatus() != SSLEngineResult.Status.OK) { + dumpBuffers("inData", inData); + dumpBuffers("readBuf", getReadBuf()); + + doTask(r, engine); + } + + if (again && r.getStatus() == SSLEngineResult.Status.OK && + r.getHandshakeStatus() == + SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { + inData.clear(); + print("again"); + again = false; + continue; + } + break; + } + + inData.clear(); + getReadBuf().compact(); + + totalDataLen += r.bytesProduced(); + sc = false; + + if (!firstNotHandshake && + r.getHandshakeStatus() == + SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { + + try { + readSideInputRecord = getInputRecord(engine); + setReadLimit(readSideInputRecord, newLimit); + } catch (Exception e) { + throw new RuntimeException(e); + } + System.err.println("Resetting readside"); + firstNotHandshake = true; + } + } + } catch (Exception e) { + sc = false; + readDone = true; + + System.out.println(e.getMessage()); + e.printStackTrace(); + System.out.println("Total data read = " + totalDataLen); + } + } + + // Overridden in Server/Client + ByteBuffer getReadBuf() { + return null; + } + ByteBuffer getWriteBuf() { + return null; + } + + SSLContext initContext() throws Exception { + return createServerSSLContext(); + } + + @Override + protected SSLContextTemplate.ContextParameters getServerContextParameters() { + return new SSLContextTemplate.ContextParameters("TLSv1.3", "PKIX", "NewSunX509"); + } + + static Object getInputRecord(SSLEngine eng) throws Exception { + Class engineImplCls = Class.forName("sun.security.ssl.SSLEngineImpl"); + Object conContext = getPrivate(eng, engineImplCls, "conContext"); + + Class transportCtxCls = Class.forName("sun.security.ssl.TransportContext"); + return getPrivate(conContext, transportCtxCls, "inputRecord"); + } + + static void setReadLimit(Object inputRecord, long newCountdown) throws Exception { + Class inputRecordCls = Class.forName("sun.security.ssl.InputRecord"); + Object readCipher = getPrivate(inputRecord, inputRecordCls, "readCipher"); + Class sslReadCipher = readCipher.getClass().getSuperclass(); + + Field f = getField(sslReadCipher, "keyLimitCountdown"); + f.setLong(readCipher, newCountdown); + } + + static long getReadLimit(Object inputRecord) throws Exception { + Class inputRecordCls = Class.forName("sun.security.ssl.InputRecord"); + Object readCipher = getPrivate(inputRecord, inputRecordCls, "readCipher"); + Class sslReadCipher = readCipher.getClass().getSuperclass(); + + Field f = getField(sslReadCipher, "keyLimitCountdown"); + return f.getLong(readCipher); + } + + private static Field getField(Class type, String name) throws Exception { + Field f = type.getDeclaredField(name); + f.setAccessible(true); // requires --add-opens for sun.security.ssl + return f; + } + + private static Object getPrivate(Object target, Class owner, String name) throws Exception { + return getField(owner, name).get(target); + } + + static class Server extends KeyUpdateOnce implements Runnable { + Server(String cipherSuite) throws Exception { + super(); + engine = initContext().createSSLEngine(); + engine.setUseClientMode(false); + engine.setNeedClientAuth(true); + + if (cipherSuite != null && !cipherSuite.isEmpty()) { + engine.setEnabledCipherSuites(new String[] { cipherSuite }); + } + } + + @Override + public void run() { + try { + if (serverWrites) { + writeLoop(); + } else { + readLoop(); + } + } catch (Exception e) { + System.out.println("server: " + e.getMessage()); + e.printStackTrace(); + } + System.out.println("Server closed"); + } + + @Override + ByteBuffer getWriteBuf() { + return sToc; + } + + @Override + ByteBuffer getReadBuf() { + return cTos; + } + } + + static class Client extends KeyUpdateOnce implements Runnable { + Client() throws Exception { + super(); + engine = initContext().createSSLEngine(); + engine.setUseClientMode(true); + } + + @Override + public void run() { + try { + if (!serverWrites) { + writeLoop(); + } else { + readLoop(); + } + } catch (Exception e) { + System.out.println("client: " + e.getMessage()); + e.printStackTrace(); + } + System.out.println("Client closed"); + } + + @Override + ByteBuffer getWriteBuf() { + return cTos; + } + + @Override + ByteBuffer getReadBuf() { + return sToc; + } + } +} From f630ab0e5fc1c1b9d598ae80f0fe99262049eabe Mon Sep 17 00:00:00 2001 From: Alexander Matveev Date: Tue, 2 Jun 2026 22:35:48 +0000 Subject: [PATCH 32/32] 8384250: [macos] Test tools/jpackage/share/AsyncTest.java failed Reviewed-by: asemenyuk --- test/jdk/tools/jpackage/share/AsyncTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/jdk/tools/jpackage/share/AsyncTest.java b/test/jdk/tools/jpackage/share/AsyncTest.java index b7dd13f91fa..ae90e6315e8 100644 --- a/test/jdk/tools/jpackage/share/AsyncTest.java +++ b/test/jdk/tools/jpackage/share/AsyncTest.java @@ -113,7 +113,10 @@ public class AsyncTest { @Test @ParameterSupplier("ids") public void testNativeBundle(int id) throws Exception { - new PackageTest().addInitializer(AsyncTest::init).run(Action.CREATE_AND_UNPACK); + new PackageTest() + .excludeTypes(PackageType.MAC_DMG) // See JDK-8384250 + .addInitializer(AsyncTest::init) + .run(Action.CREATE_AND_UNPACK); } public static Collection ids() {