From acc953f38f62a05b4be64343384d701b8e76c61b Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Thu, 8 Oct 2026 09:22:40 +0000 Subject: [PATCH 001/101] 8380089: C2: Elide stores in loops by peeling Reviewed-by: roland, chagedorn, aseoane --- src/hotspot/share/opto/loopTransform.cpp | 230 +++++++++++++-- src/hotspot/share/opto/loopnode.hpp | 14 +- .../TestRemoveStoresAfterPeeling.java | 261 ++++++++++++++++++ 3 files changed, 476 insertions(+), 29 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/loopopts/TestRemoveStoresAfterPeeling.java diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 09ea9740ce5..209e4bd6bea 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -26,6 +26,7 @@ #include "gc/shared/barrierSet.hpp" #include "gc/shared/c2/barrierSetC2.hpp" #include "memory/allocation.inline.hpp" +#include "memory/resourceArea.hpp" #include "opto/addnode.hpp" #include "opto/callnode.hpp" #include "opto/castnode.hpp" @@ -33,12 +34,14 @@ #include "opto/convertnode.hpp" #include "opto/divnode.hpp" #include "opto/loopnode.hpp" +#include "opto/memnode.hpp" #include "opto/movenode.hpp" #include "opto/mulnode.hpp" #include "opto/node.hpp" #include "opto/opaquenode.hpp" #include "opto/opcodes.hpp" #include "opto/phase.hpp" +#include "opto/phasetype.hpp" #include "opto/predicates.hpp" #include "opto/rootnode.hpp" #include "opto/runtime.hpp" @@ -564,11 +567,9 @@ void IdealLoopTree::reassociate_invariants(PhaseIdealLoop *phase) { } } -//------------------------------policy_peeling--------------------------------- -// Return TRUE if the loop should be peeled, otherwise return FALSE. Peeling -// is applicable if we can make a loop-invariant test (usually a null-check) -// execute before we enter the loop. When TRUE, the estimated node budget is -// also requested. +// Return TRUE if the loop should be peeled, otherwise return FALSE. Peeling should be performed if +// it allows us to remove something in the main body (see elide_redundancies_after_peeling) and the +// esimated increase in graph size is within the budget. bool IdealLoopTree::policy_peeling(PhaseIdealLoop *phase) { uint estimate = estimate_peeling(phase); @@ -622,9 +623,10 @@ uint IdealLoopTree::estimate_peeling(PhaseIdealLoop *phase) { // ...otherwise, let's apply our heuristic. #endif - Node* test = tail(); - - while (test != _head) { // Scan till run off top of loop + // Walk up dominators to loop _head looking for an exit test which is executed on every path + // through the loop. If the tested value is a loop-invariant, then the IfNode can be folded after + // peeling, giving us a reason to peel. + for (Node* test = tail(); test != _head; test = phase->idom(test)) { // Scan till run off top of loop if (test->is_If()) { // Test? Node *ctrl = phase->get_ctrl(test->in(1)); if (ctrl->is_top()) { @@ -642,18 +644,34 @@ uint IdealLoopTree::estimate_peeling(PhaseIdealLoop *phase) { return estimate; // Found reason to peel! } } - // Walk up dominators to loop _head looking for test which is executed on - // every path through the loop. - test = phase->idom(test); } + + // Whether peeling allows a store to be elided from the remaining iterations + for (uint i = 0; i < _body.size(); i++) { + Node* n = _body.at(i); + if (n->is_Store() && can_elide_store_if_peeled(n->as_Store())) { + return estimate; + } + } + return 0; } -//------------------------------peeled_dom_test_elim--------------------------- -// If we got the effect of peeling, either by actually peeling or by making -// a pre-loop which must execute at least once, we can remove all -// loop-invariant dominated tests in the main body. -void PhaseIdealLoop::peeled_dom_test_elim(IdealLoopTree* loop, Node_List& old_new) { +// After peeling the loop or creating a pre-loop that executes at least once, the first iteration +// dominates the remaining iterations. This allows us to: +// - Remove all loop-invariant dominated tests in the main body. +// - Remove all loop-invariant dominated stores in the main body. +void PhaseIdealLoop::elide_redundancies_after_peeling(IdealLoopTree* loop, const Node_List& old_new) { + elide_redundant_tests_after_peeling(loop, old_new); + elide_redundant_stores_after_peeling(loop, old_new); +} + +// If an IfProj 'prev' dominates the tail of 'loop', its clone 'old_new[prev->_idx]' in the peeled +// iteration or pre-loop will dominate the remaining iterations, which includes the input 'test' of +// 'prev'. As a result, if the condition of 'test' is a loop invariant, 'test' will be dominated by +// the IfProj of 'old_new[test->_idx]', and both 'test' and 'old_new[test->_idx]' will have the +// same Bool input. This allows 'test' to be removed using PhaseIdealLoop::dominated_by. +void PhaseIdealLoop::elide_redundant_tests_after_peeling(IdealLoopTree* loop, const Node_List& old_new) { bool progress = true; while (progress) { progress = false; // Reset for next iteration @@ -688,6 +706,174 @@ void PhaseIdealLoop::peeled_dom_test_elim(IdealLoopTree* loop, Node_List& old_ne } // End of while (progress) } +// If a store 'n' dominates the tail of 'loop', its clone 'old_new[n->_idx]' will dominate the +// remaining iterations. As a result, if the pointer and value inputs of 'n' are loop invariants, +// we can remove 'n' if we can prove that there is no interfering store between 'n' and +// 'old_new[n->_idx]'. This is because in those cases we know that the value that is already at +// the memory location stored into by 'n' is the same as the value that 'n' stores, or in other +// words, the store 'n' is useless. +// +// This complements try_move_store_before_loop. Eliding a store by peeling the loop is a strictly +// more general transformation. In other words, for all stores that can be hoisted above a loop, +// they can also be elided from the remaining iterations after peeling. The downside is that +// peeling a loop increases the graph size much more compared to just hoisting the store. As a +// result, we always invoke try_move_store_before_loop first, and only after it gives up, we resort +// to this transformation. +// +// To be more specific, conceptually, hoisting a store above a loop can be understood as composed +// of several steps: +// +// 0. +// loop (0..n) { +// Store(p1, v1); +// Store(p, v); +// StoreLoadFence(); +// } +// +// 1. Peel the first iteration +// Store(p1, v1); +// Store(p, v); +// StoreLoadFence(); +// loop (1..n) { +// Store(p1, v1); +// Store(p, v); +// StoreLoadFence(); +// } +// +// 2. Remove Store(p, v) inside the loop because there is a similar store dominating it +// Store(p1, v1); +// Store(p, v); +// StoreLoadFence(); +// loop (1..n) { +// Store(p1, v1); +// StoreLoadFence(); +// } +// +// 3. Move Store(p, v) above the first iteration +// Store(p, v); +// Store(p1, v1); +// StoreLoadFence(); +// loop (1..n) { +// Store(p1, v1); +// StoreLoadFence(); +// } +// +// 4. Unpeel the first iteration (fold the first iteration back into the loop) +// Store(p, v); +// loop (0..n) { +// Store(p1, v1); +// StoreLoadFence(); +// } +// +// As a result, for any store that can be hoisted above a loop, peeling the loop also allows us to +// remove it from the main body. Note that this is only an analysis of the transformation as a +// conceptual explanation. In practice, in try_move_store_before_loop, we don't actually peel and +// unpeel the loop but we hoist the store directly. +void PhaseIdealLoop::elide_redundant_stores_after_peeling(IdealLoopTree* loop, const Node_List& old_new) { + for (uint i = 0; i < loop->_body.size(); i++) { + Node* n = loop->_body.at(i); + if (n->is_Store() && loop->can_elide_store_after_peeling(n->as_Store(), old_new[n->_idx]->as_Store())) { + // This does not yet handle the case where 'n' has accompanying MemBars. We do not elide + // those stores yet. In the future, when the logic to walk past MemBars is implemented, we + // should also remove the accompanying MemBars of the elided StoreNode, if any. + assert(n->as_Store()->trailing_membar() == nullptr, "should not be elided"); + loop->_body.yank(n); + _loop_or_ctrl.map(n->_idx, nullptr); + _igvn.replace_node(n, n->in(MemNode::Memory)); + i--; + } + } +} + +// Try to analyze whether peeling 'this' can help elide 'store' from the remaining iterations. This +// is the case if 'store' writes the same value into the same location in every iteration, the +// 'store' in the first iteration dominates itself in the remaining iterations, and there is no +// other access in the loop that can write into the memory location written to by 'store'. In other +// words, if the execution of 'store' is useless after the first iteration because we know the +// value being written is the same as the value that is there, then we can elide 'store' after +// peeling. +bool IdealLoopTree::can_elide_store_if_peeled(StoreNode* store) { + return can_elide_store_after_peeling_impl(store, nullptr); +} + +// After the loop has been peeled, try to see whether 'store' can be elided because it is dominated +// by 'dominating_store', given that 'dominating_store' is the clone of 'store' in the pre-loop/ +// peeled iteration. +bool IdealLoopTree::can_elide_store_after_peeling(StoreNode* store, StoreNode* dominating_store) { + assert(dominating_store != nullptr, "must have a dominating_store"); + return can_elide_store_after_peeling_impl(store, dominating_store); +} + +// Common implementation of can_elide_store_if_peeled, which calls this method with +// dominating_store = nullptr, and can_elide_store_after_peeling, which calls this method with +// dominating_store = old_new[store->_idx]. +bool IdealLoopTree::can_elide_store_after_peeling_impl(StoreNode* store, StoreNode* dominating_store) { + if (store->req() > MemNode::ValueIn + 1) { + // It is generally incorrect to analyze StoreVectorMasked, StoreVectorScatter, and + // StoreVectorScatterMasked the same as other StoreNodes because their access patterns are + // different. For example, they may not access a certain number of bytes starting from their + // pointer input. + return false; + } else if (!_phase->is_dominator(_phase->get_ctrl(store), tail())) { + // We can only remove the store after peeling if the store in later iterations is dominated by + // the one in the first iteration. + return false; + } else if (!is_invariant(store->in(MemNode::Address)) || !is_invariant(store->in(MemNode::ValueIn))) { + // We can only remove the store after peeling if it stores the same value at the same place + // in every iteration. + return false; + } + + // If we are checking whether 'store' would be elided if 'this' is peeled. Otherwise, we are + // checking whether 'store' can be elided now. + bool is_profitability_check = dominating_store == nullptr; + + // Walk the memory graph. The check only succeeds if we encounter no store which can interfere + // with 'store'. If is_profitability_check is true, walk all the relevant nodes inside 'this'. + // Otherwise, walk all the relevant nodes between 'store' and 'dominating_store'. + ResourceMark rm; + Unique_Node_List worklist; + worklist.push(store->in(MemNode::Memory)); + AccessAnalyzer analyzer(&_phase->_igvn, store); + for (uint worklist_idx = 0; worklist_idx < worklist.size(); worklist_idx++) { + Node* mem = worklist.at(worklist_idx); + assert(mem != nullptr, "cannot be a nullptr"); + if (mem == store || mem == dominating_store) { + continue; + } + + if (is_profitability_check) { + // We should only be able to step outside the loop from the loop phi, so this means the graph + // is broken + assert(!is_invariant(mem), "broken memory graph"); + } else { + // The graph is broken, or some transformation incorrectly moves dominating_store + assert(mem->in(0) != _phase->C->start(), "dominating_store must dominate store"); + } + + if (mem->is_Phi()) { + if (is_profitability_check && mem->in(0) == _head) { + // Don't walk out of the loop when checking for profitability + worklist.push(mem->in(LoopNode::LoopBackControl)); + } else { + for (uint i = 1; i < mem->req(); i++) { + worklist.push(mem->in(i)); + } + } + continue; + } + + AccessAnalyzer::AccessIndependence independence = analyzer.detect_access_independence(mem); + if (!independence.independent) { + return false; + } + + worklist.push(independence.mem); + } + + return true; +} + //------------------------------do_peeling------------------------------------- // Peel the first iteration of the given loop. // Step 1: Clone the loop body. The clone becomes the peeled iteration. @@ -934,9 +1120,7 @@ void PhaseIdealLoop::do_peeling(IdealLoopTree *loop, Node_List &old_new) { cast_incr_before_loop(init, init_ctrl, cl); } - // Now force out all loop-invariant dominating tests. The optimizer - // finds some, but we _know_ they are all useless. - peeled_dom_test_elim(loop,old_new); + elide_redundancies_after_peeling(loop, old_new); loop->record_for_igvn(); @@ -1652,9 +1836,7 @@ void PhaseIdealLoop::insert_pre_post_loops(IdealLoopTree *loop, Node_List &old_n post_head->set_profile_trip_cnt(4.0); pre_head->set_profile_trip_cnt(4.0); - // Now force out all loop-invariant dominating tests. The optimizer - // finds some, but we _know_ they are all useless. - peeled_dom_test_elim(loop,old_new); + elide_redundancies_after_peeling(loop, old_new); loop->record_for_igvn(); C->print_method(PHASE_AFTER_PRE_MAIN_POST, 4, main_head); @@ -1719,9 +1901,7 @@ void PhaseIdealLoop::insert_vector_post_loop(IdealLoopTree *loop, Node_List &old // so guess that unit vector trips is a reasonable value. post_head->set_profile_trip_cnt(cur_unroll); - // Now force out all loop-invariant dominating tests. The optimizer - // finds some, but we _know_ they are all useless. - peeled_dom_test_elim(loop, old_new); + elide_redundancies_after_peeling(loop, old_new); loop->record_for_igvn(); } diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 54ea9c75f3f..a6f0ddd8b6b 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -27,6 +27,7 @@ #include "opto/cfgnode.hpp" #include "opto/multnode.hpp" +#include "opto/node.hpp" #include "opto/phaseX.hpp" #include "opto/predicates.hpp" #include "opto/subnode.hpp" @@ -756,6 +757,9 @@ class IdealLoopTree : public ResourceObj { uint estimate_peeling(PhaseIdealLoop *phase); + bool can_elide_store_if_peeled(StoreNode* store); + bool can_elide_store_after_peeling(StoreNode* store, StoreNode* dominating_store); + // Return TRUE or FALSE if the loop should be maximally unrolled. Stash any // known trip count in the counted loop node. bool policy_maximally_unroll(PhaseIdealLoop *phase) const; @@ -871,6 +875,8 @@ class IdealLoopTree : public ResourceObj { bool empty_loop_candidate(PhaseIdealLoop* phase) const; bool empty_loop_with_extra_nodes_candidate(PhaseIdealLoop* phase) const; + + bool can_elide_store_after_peeling_impl(StoreNode* store, StoreNode* dominating_store); }; // -----------------------------PhaseIdealLoop--------------------------------- @@ -1281,6 +1287,9 @@ class PhaseIdealLoop : public PhaseTransform { return n; } + void elide_redundant_tests_after_peeling(IdealLoopTree* loop, const Node_List& old_new); + void elide_redundant_stores_after_peeling(IdealLoopTree* loop, const Node_List& old_new); + public: Node* idom(Node* n) const { return idom(n->_idx); @@ -1526,10 +1535,7 @@ class PhaseIdealLoop : public PhaseTransform { IdealLoopTree* outer_loop, int dd, Node_List &old_new, Node_List& extra_data_nodes); - // If we got the effect of peeling, either by actually peeling or by - // making a pre-loop which must execute at least once, we can remove - // all loop-invariant dominated tests in the main body. - void peeled_dom_test_elim( IdealLoopTree *loop, Node_List &old_new ); + void elide_redundancies_after_peeling(IdealLoopTree* loop, const Node_List& old_new); // Generate code to do a loop peel for the given loop (and body). // old_new is a temp array. diff --git a/test/hotspot/jtreg/compiler/loopopts/TestRemoveStoresAfterPeeling.java b/test/hotspot/jtreg/compiler/loopopts/TestRemoveStoresAfterPeeling.java new file mode 100644 index 00000000000..0a3c69ca464 --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestRemoveStoresAfterPeeling.java @@ -0,0 +1,261 @@ +/* + * 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.loopopts; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; +import jdk.test.lib.Platform; + +/* + * @test + * @bug 8380089 + * @summary Test that stores can be elided by peeling the loop. + * @library /test/lib / + * @run driver ${test.main.class} + */ +public class TestRemoveStoresAfterPeeling { + private static class A { + int v; + } + + public static void main(String[] args) { + var framework = new TestFramework(); + framework.addScenarios(new Scenario(0)); + if (Platform.isDebugBuild()) { + // When a store is not removed from a loop after that loop is peeled, there will be + // multiple stores in the graph + framework.addScenarios(new Scenario(1, "-XX:+StressLoopPeeling")); + } + framework.start(); + } + + @Test + @IR(counts = {IRNode.STORE_I, "1"}) + private void testStoreHoisting(A a, int v) { + for (int i = 1; i < 100; i *= 2) { + // Simple case, the store can be hoisted above the loop + a.v = v; + } + } + + @Run(test = "testStoreHoisting") + public void runStoreHoisting() { + A a = new A(); + testStoreHoisting(a, 1); + Asserts.assertEQ(1, a.v); + } + + @Test + @IR(counts = {IRNode.STORE_I, "1"}) + private void testStorePeeling1(int limit, A a, int v) { + for (int i = 1; i < 100; i *= 2) { + // The store does not post-dominate the loop head, so it cannot be hoisted + if (i >= limit) { + break; + } + + a.v = v; + } + } + + @Run(test = "testStorePeeling1") + public void runStorePeeling1() { + A a = new A(); + testStorePeeling1(0, a, 1); + Asserts.assertEQ(0, a.v); + testStorePeeling1(200, a, 1); + Asserts.assertEQ(1, a.v); + } + + @Test + @IR(counts = {IRNode.STORE_I, "1"}, phase = CompilePhase.BEFORE_MACRO_EXPANSION) + private A testStorePeeling2(A a1, int v1, float v2) { + A a2 = new A(); + for (int i = 1; i < 100; i *= 2) { + // There are 2 independent stores in the loop + a2.v = Float.floatToRawIntBits(i * v2); + a1.v = v1; + } + return a2; + } + + @Run(test = "testStorePeeling2") + public void runStorePeeling2() { + A a1 = new A(); + A a2 = testStorePeeling2(a1, 1, 1); + Asserts.assertEQ(1, a1.v); + Asserts.assertEQ(Float.floatToRawIntBits(64), a2.v); + } + + @Test + @IR(counts = {IRNode.STORE_I, "1"}, phase = CompilePhase.BEFORE_MACRO_EXPANSION) + private A testStorePeeling3(int limit, A a1, int v1, float v2) { + A a2 = new A(); + for (int i = 1; i < 100; i *= 2) { + // Combine the previous 2 cases + a2.v = Float.floatToRawIntBits(i * v2); + if (i >= limit) { + break; + } + a1.v = v1; + } + return a2; + } + + @Run(test = "testStorePeeling3") + public void runStorePeeling3() { + A a1 = new A(); + A a2 = testStorePeeling3(0, a1, 1, 1); + Asserts.assertEQ(0, a1.v); + Asserts.assertEQ(Float.floatToRawIntBits(1), a2.v); + a2 = testStorePeeling3(200, a1, 1, 1); + Asserts.assertEQ(1, a1.v); + Asserts.assertEQ(Float.floatToRawIntBits(64), a2.v); + } + + @Test + @IR(counts = {IRNode.STORE_I, "1"}) + private int testStorePeeling4(A[] array, A a, int v) { + int sum = 0; + for (int i = 1; i < 100; i *= 2) { + // The store to a.v cannot be directly hoisted because the preceding load array[i].v + // may alias it. Peeling preserves the first iteration load-before-store ordering. + // Later identical stores are redundant. + sum += array[i].v; + a.v = v; + } + return sum; + } + + @Run(test = "testStorePeeling4") + public void runStorePeeling4() { + A[] array = new A[100]; + for (int i = 0; i < 100; i++) { + array[i] = new A(); + } + + A unrelated = new A(); + Asserts.assertEQ(0, testStorePeeling4(array, unrelated, 1)); + Asserts.assertEQ(1, testStorePeeling4(array, array[2], 1)); + array[2].v = 0; + Asserts.assertEQ(0, testStorePeeling4(array, array[1], 1)); + } + + @Test + @IR(counts = {IRNode.STORE_I, "1"}) + private int testStorePeeling5(boolean b, A[] array, A a, int v) { + int sum = 0; + for (int i = 1; i < 100; i *= 2) { + // Similar to testStorePeeling4, and this test triggers peeling without + // StressLoopPeeling + if (b) { + break; + } + + sum += array[i].v; + a.v = v; + } + return sum; + } + + @Run(test = "testStorePeeling5") + private void runStorePeeling5() { + A[] array = new A[100]; + for (int i = 0; i < 100; i++) { + array[i] = new A(); + } + Asserts.assertEQ(0, testStorePeeling5(true, array, array[2], 1)); + + A unrelated = new A(); + Asserts.assertEQ(0, testStorePeeling5(true, array, unrelated, 1)); + Asserts.assertEQ(0, testStorePeeling5(false, array, unrelated, 1)); + Asserts.assertEQ(1, testStorePeeling5(false, array, array[2], 1)); + array[2].v = 0; + Asserts.assertEQ(0, testStorePeeling5(false, array, array[1], 1)); + } + + @Test + private void testStoreNotElided1(int limit, A a, int v) { + for (int i = 1; i < 100; i *= 2) { + // Cannot elide the store after peeling because it does not dominate the back edge + if (i >= limit) { + a.v = v; + } + } + } + + @Run(test = "testStoreNotElided1") + public void runStoreNotElided1() { + A a = new A(); + testStoreNotElided1(-1, a, 1); + Asserts.assertEQ(1, a.v); + a.v = 0; + testStoreNotElided1(50, a, 1); + Asserts.assertEQ(1, a.v); + a.v = 0; + testStoreNotElided1(200, a, 1); + Asserts.assertEQ(0, a.v); + } + + @Test + private void testStoreNotElided2(A a1, A a2, int v1, int v2) { + for (int i = 1; i < 100; i *= 2) { + // Cannot elide the store after peeling because there is an interfering store + a2.v = Float.floatToRawIntBits(i * v2); + a1.v = v1; + } + } + + @Run(test = "testStoreNotElided2") + public void runStoreNotElided2() { + A a1 = new A(); + A a2 = new A(); + testStoreNotElided2(a1, a2, 1, 1); + Asserts.assertEQ(1, a1.v); + Asserts.assertEQ(Float.floatToRawIntBits(64), a2.v); + a1.v = 0; + testStoreNotElided2(a1, a1, 1, 1); + Asserts.assertEQ(1, a1.v); + } + + @Test + @IR(counts = {IRNode.STORE_I, "2"}, phase = CompilePhase.AFTER_PRE_MAIN_POST) + private int testPreMainPost(int[] array1, int[] array2, int v) { + int sum = 0; + for (int i = 0; i < array1.length; i++) { + // Iteration split also removes the store from the main-loop. It does not elide the + // store in the post-loop, though. + sum += array1[i]; + array2[1] = v; + } + return sum; + } + + @Run(test = "testPreMainPost") + public void runPreMainPost() { + int[] array1 = new int[100]; + int[] array2 = new int[100]; + Asserts.assertEQ(0, testPreMainPost(array1, array2, 1)); + Asserts.assertEQ(1, testPreMainPost(array1, array1, 1)); + } +} From a675ed259f508e99d8b6e10f76b6bbdbdfa8ce9c Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Thu, 8 Oct 2026 09:58:23 +0000 Subject: [PATCH 002/101] 8393657: G1: Fix num_region naming for heap expansion and shrinking Reviewed-by: aboldtch --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 28 +++---- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 2 +- .../share/gc/g1/g1CommittedRegionMap.hpp | 6 +- .../share/gc/g1/g1HeapRegionManager.cpp | 74 +++++++++---------- .../share/gc/g1/g1HeapRegionManager.hpp | 10 +-- src/hotspot/share/gc/g1/g1HeapRegionSet.hpp | 4 +- 6 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 8577e9e0659..ed7dfa4e3e4 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -552,18 +552,18 @@ HeapWord* G1CollectedHeap::alloc_archive_region(size_t word_size) { // when mmap'ing archived heap data in, so pre-touching is wasted. FlagSetting fs(AlwaysPreTouch, false); - size_t commits = 0; + size_t num_newly_activated_regions = 0; // Attempt to allocate towards the end of the heap. HeapWord* start_addr = reserved.end() - align_up(word_size, G1HeapRegion::GrainWords); MemRegion range = MemRegion(start_addr, word_size); HeapWord* last_address = range.last(); - if (!_hrm.allocate_containing_regions(range, &commits, workers())) { + if (!_hrm.allocate_containing_regions(range, &num_newly_activated_regions, workers())) { return nullptr; } increase_used(word_size * HeapWordSize); - if (commits != 0) { + if (num_newly_activated_regions != 0) { log_debug(gc, ergo, heap)("Attempt heap expansion (allocate archive regions). Total size: %zuB", - G1HeapRegion::GrainWords * HeapWordSize * commits); + G1HeapRegion::GrainWords * HeapWordSize * num_newly_activated_regions); } // Mark each G1 region touched by the range as old, add it to @@ -607,22 +607,22 @@ void G1CollectedHeap::dealloc_archive_regions(MemRegion range) { p2i(start_address), p2i(last_address)); size_used += range.byte_size(); - uint max_shrink_count = 0; + uint max_num_regions_to_shrink = 0; if (capacity() > MinHeapSize) { size_t max_shrink_bytes = capacity() - MinHeapSize; - max_shrink_count = (uint)(max_shrink_bytes / G1HeapRegion::GrainBytes); + max_num_regions_to_shrink = (uint)(max_shrink_bytes / G1HeapRegion::GrainBytes); } - uint shrink_count = 0; + uint num_shrunk_regions = 0; // Free, empty and uncommit regions with CDS archive content. auto dealloc_archive_region = [&] (G1HeapRegion* r, bool is_last) { guarantee(r->is_old(), "Expected old region at index %u", r->hrm_index()); _old_set.remove(r); r->set_free(); r->set_top(r->bottom()); - if (shrink_count < max_shrink_count) { + if (num_shrunk_regions < max_num_regions_to_shrink) { _hrm.shrink_at(r->hrm_index(), 1); - shrink_count++; + num_shrunk_regions++; } else { _hrm.insert_into_free_list(r); } @@ -630,11 +630,11 @@ void G1CollectedHeap::dealloc_archive_regions(MemRegion range) { iterate_regions_in_range(range, dealloc_archive_region); - if (shrink_count != 0) { + if (num_shrunk_regions != 0) { log_debug(gc, ergo, heap)("Attempt heap shrinking (CDS archive regions). Total size: %zuB (%u Regions)", - G1HeapRegion::GrainWords * HeapWordSize * shrink_count, shrink_count); + G1HeapRegion::GrainWords * HeapWordSize * num_shrunk_regions, num_shrunk_regions); // Explicit uncommit. - uncommit_regions(shrink_count); + uncommit_regions(num_shrunk_regions); } decrease_used(size_used); } @@ -2512,8 +2512,8 @@ void G1CollectedHeap::gc_epilogue(bool full) { _refinement_epoch++; } -uint G1CollectedHeap::uncommit_regions(uint region_limit) { - return _hrm.uncommit_inactive_regions(region_limit); +uint G1CollectedHeap::uncommit_regions(uint max_num_regions_to_uncommit) { + return _hrm.uncommit_inactive_regions(max_num_regions_to_uncommit); } bool G1CollectedHeap::has_uncommittable_regions() { diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index cd8ea2124dc..ce57d371506 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -584,7 +584,7 @@ class G1CollectedHeap : public CollectedHeap { // Check if there is memory to uncommit and if so schedule a task to do it. void uncommit_regions_if_necessary(); // Immediately uncommit uncommittable regions. - uint uncommit_regions(uint region_limit); + uint uncommit_regions(uint max_num_regions_to_uncommit); bool has_uncommittable_regions(); G1NUMA* numa() const { return _numa; } diff --git a/src/hotspot/share/gc/g1/g1CommittedRegionMap.hpp b/src/hotspot/share/gc/g1/g1CommittedRegionMap.hpp index 7df88616007..5129f35d903 100644 --- a/src/hotspot/share/gc/g1/g1CommittedRegionMap.hpp +++ b/src/hotspot/share/gc/g1/g1CommittedRegionMap.hpp @@ -43,9 +43,9 @@ class G1HeapRegionRange : public StackObj { uint num_regions() const { return _end - _start; } }; -// The G1CommittedRegionMap keeps track of which regions are currently committed. -// It tracks both regions ready for use and if there are any regions ready for -// uncommit. We basically have three states. Uncommitted, Active, Inactive. All +// The G1CommittedRegionMap keeps track of the state of regions. +// It tracks both regions ready for use (active) and if there are any regions ready for +// uncommit (inactive). We basically have three states. Uncommitted, Active, Inactive. All // regions that are either Active or Inactive are committed. // // State transitions: diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp index b637e7e83b4..ff86b003e75 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp @@ -245,7 +245,7 @@ void G1HeapRegionManager::reactivate_regions(uint start, uint num_regions) { } void G1HeapRegionManager::deactivate_regions(uint start, uint num_regions) { - assert(num_regions > 0, "Need to specify at least one region to uncommit, tried to uncommit zero regions at %u", start); + assert(num_regions > 0, "Need to specify at least one region to deactivate, tried to deactivate zero regions at %u", start); assert(num_committed_regions() >= num_regions, "pre-condition"); // Reset NUMA index to and print state change. @@ -290,10 +290,10 @@ bool G1HeapRegionManager::has_inactive_regions() const { return _committed_map.num_inactive_regions() > 0; } -uint G1HeapRegionManager::uncommit_inactive_regions(uint limit) { - assert(limit > 0, "Need to specify at least one region to uncommit"); +uint G1HeapRegionManager::uncommit_inactive_regions(uint max_num_regions_to_uncommit) { + assert(max_num_regions_to_uncommit > 0, "Need to specify at least one region to uncommit"); - uint uncommitted = 0; + uint num_uncommitted_regions = 0; uint offset = 0; do { MutexLocker uc(G1Uncommit_lock, Mutex::_no_safepoint_check_flag); @@ -301,22 +301,22 @@ uint G1HeapRegionManager::uncommit_inactive_regions(uint limit) { // No more regions available for uncommit. Return the number of regions // already uncommitted or 0 if there were no longer any inactive regions. if (range.num_regions() == 0) { - return uncommitted; + return num_uncommitted_regions; } uint start = range.start(); - uint num_regions = MIN2(range.num_regions(), limit - uncommitted); - uncommitted += num_regions; + uint num_regions = MIN2(range.num_regions(), max_num_regions_to_uncommit - num_uncommitted_regions); + num_uncommitted_regions += num_regions; uncommit_regions(start, num_regions); - } while (uncommitted < limit); + } while (num_uncommitted_regions < max_num_regions_to_uncommit); - assert(uncommitted == limit, "Invariant"); - return uncommitted; + assert(num_uncommitted_regions == max_num_regions_to_uncommit, "Invariant"); + return num_uncommitted_regions; } uint G1HeapRegionManager::expand_inactive(uint num_regions) { uint offset = 0; - uint expanded = 0; + uint num_expanded_regions = 0; do { G1HeapRegionRange regions = _committed_map.next_inactive_range(offset); @@ -325,20 +325,20 @@ uint G1HeapRegionManager::expand_inactive(uint num_regions) { break; } - uint to_expand = MIN2(num_regions - expanded, regions.num_regions()); - reactivate_regions(regions.start(), to_expand); - expanded += to_expand; + uint num_regions_to_expand = MIN2(num_regions - num_expanded_regions, regions.num_regions()); + reactivate_regions(regions.start(), num_regions_to_expand); + num_expanded_regions += num_regions_to_expand; offset = regions.end(); - } while (expanded < num_regions); + } while (num_expanded_regions < num_regions); - return expanded; + return num_expanded_regions; } uint G1HeapRegionManager::expand_any(uint num_regions, WorkerThreads* pretouch_workers) { assert(num_regions > 0, "Must expand at least 1 region"); uint offset = 0; - uint expanded = 0; + uint num_expanded_regions = 0; do { G1HeapRegionRange regions = _committed_map.next_committable_range(offset); @@ -347,13 +347,13 @@ uint G1HeapRegionManager::expand_any(uint num_regions, WorkerThreads* pretouch_w break; } - uint to_expand = MIN2(num_regions - expanded, regions.num_regions()); - expand(regions.start(), to_expand, pretouch_workers); - expanded += to_expand; + uint num_regions_to_expand = MIN2(num_regions - num_expanded_regions, regions.num_regions()); + expand(regions.start(), num_regions_to_expand, pretouch_workers); + num_expanded_regions += num_regions_to_expand; offset = regions.end(); - } while (expanded < num_regions); + } while (num_expanded_regions < num_regions); - return expanded; + return num_expanded_regions; } uint G1HeapRegionManager::expand_by(uint num_regions, WorkerThreads* pretouch_workers) { @@ -361,15 +361,15 @@ uint G1HeapRegionManager::expand_by(uint num_regions, WorkerThreads* pretouch_wo // First "undo" any requests to uncommit memory concurrently by // reverting such regions to being available. - uint expanded = expand_inactive(num_regions); + uint num_expanded_regions = expand_inactive(num_regions); // Commit more regions if needed. - if (expanded < num_regions) { - expanded += expand_any(num_regions - expanded, pretouch_workers); + if (num_expanded_regions < num_regions) { + num_expanded_regions += expand_any(num_regions - num_expanded_regions, pretouch_workers); } verify_optional(); - return expanded; + return num_expanded_regions; } void G1HeapRegionManager::expand_exact(uint start, uint num_regions, WorkerThreads* pretouch_workers) { @@ -526,16 +526,16 @@ void G1HeapRegionManager::iterate(G1HeapRegionIndexClosure* blk) const { } } -bool G1HeapRegionManager::allocate_containing_regions(MemRegion range, size_t* num_regions_committed, WorkerThreads* pretouch_workers) { - size_t commits = 0; +bool G1HeapRegionManager::allocate_containing_regions(MemRegion range, size_t* num_newly_activated_regions, WorkerThreads* pretouch_workers) { + size_t num_activated_regions = 0; uint start_index = (uint)_regions.get_index_by_address(range.start()); uint last_index = (uint)_regions.get_index_by_address(range.last()); // Ensure that each G1 region in the range is free, returning false if not. - // Commit those that are not yet available, and keep count. + // Activates inactive regions and commits others (making them Active) if necessary. for (uint curr_index = start_index; curr_index <= last_index; curr_index++) { if (!is_available(curr_index)) { - commits++; + num_activated_regions++; expand_exact(curr_index, 1, pretouch_workers); } G1HeapRegion* curr_region = _regions.get_by_index(curr_index); @@ -545,7 +545,7 @@ bool G1HeapRegionManager::allocate_containing_regions(MemRegion range, size_t* n } allocate_free_regions_starting_at(start_index, (last_index - start_index) + 1); - *num_regions_committed = commits; + *num_newly_activated_regions = num_activated_regions; return true; } @@ -588,24 +588,24 @@ uint G1HeapRegionManager::shrink_by(uint num_regions_to_remove) { return 0; } - uint removed = 0; + uint num_removed_regions = 0; uint cur = _next_highest_used_hrm_index; uint idx_last_found = 0; uint num_last_found = 0; - while ((removed < num_regions_to_remove) && + while ((num_removed_regions < num_regions_to_remove) && (num_last_found = find_empty_from_idx_reverse(cur, &idx_last_found)) > 0) { - uint to_remove = MIN2(num_regions_to_remove - removed, num_last_found); + uint num_regions_to_remove_from_range = MIN2(num_regions_to_remove - num_removed_regions, num_last_found); - shrink_at(idx_last_found + num_last_found - to_remove, to_remove); + shrink_at(idx_last_found + num_last_found - num_regions_to_remove_from_range, num_regions_to_remove_from_range); cur = idx_last_found; - removed += to_remove; + num_removed_regions += num_regions_to_remove_from_range; } verify_optional(); - return removed; + return num_removed_regions; } void G1HeapRegionManager::shrink_at(uint index, size_t num_regions) { diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp index 0bef96ceba4..69c708717b4 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp @@ -259,8 +259,8 @@ class G1HeapRegionManager: public CHeapObj { // Allocate the regions that contain the address range specified, committing the // regions if necessary. Return false if any of the regions is already committed - // and not free, and return the number of regions newly committed in num_regions_committed. - bool allocate_containing_regions(MemRegion range, size_t* num_regions_committed, WorkerThreads* pretouch_workers); + // and not free, and return the number of regions newly activated in num_newly_activated_regions. + bool allocate_containing_regions(MemRegion range, size_t* num_newly_activated_regions, WorkerThreads* pretouch_workers); // Apply blk->do_heap_region() on all committed regions in address order, // terminating the iteration early if do_heap_region() returns true. @@ -269,8 +269,8 @@ class G1HeapRegionManager: public CHeapObj { void par_iterate(G1HeapRegionClosure* blk, G1HeapRegionClaimer* hrclaimer, const uint start_index) const; - // Uncommit up to num_regions_to_remove regions that are completely free. - // Return the actual number of uncommitted regions. + // Deactivate up to num_regions_to_remove regions that are completely free for later uncommit. + // Return the actual number of deactivated regions. uint shrink_by(uint num_regions_to_remove); // Remove a number of regions starting at the specified index, which must be available, @@ -282,7 +282,7 @@ class G1HeapRegionManager: public CHeapObj { // Uncommit inactive regions. Limit the number of regions to uncommit and return // actual number uncommitted. - uint uncommit_inactive_regions(uint limit); + uint uncommit_inactive_regions(uint max_num_regions_to_uncommit); void verify(); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp index 32646ea8423..6db3001b117 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp @@ -124,8 +124,8 @@ class G1HeapRegionSet : public G1HeapRegionSetBase { G1HeapRegionSetBase(name, checker) { } - void bulk_remove(const uint removed) { - _num_regions -= removed; + void bulk_remove(const uint num_removed_regions) { + _num_regions -= num_removed_regions; } }; From ee4d42c8fae5cd57e484d19d06e26c013f04486c Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Thu, 8 Oct 2026 14:45:14 +0000 Subject: [PATCH 003/101] 8385488: Compiler allows "case _ when" in switches, but JLS doesn't Reviewed-by: abimpoudis, vromero --- .../classes/com/sun/tools/javac/parser/JavacParser.java | 3 +++ test/langtools/tools/javac/patterns/UnnamedErrors.java | 9 ++++++++- test/langtools/tools/javac/patterns/UnnamedErrors.out | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index 19daed61aa7..2d17523ed4f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -982,6 +982,9 @@ public JCPattern parsePattern(int pos, JCModifiers mods, JCExpression parsedType mods = mods != null ? mods : optFinal(0); JCExpression e; if (token.kind == UNDERSCORE && parsedType == null) { + if (!allowVar && Feature.UNNAMED_VARIABLES.allowedInSource(source)){ + log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.UseOfUnderscoreNotAllowedNonVariable); + } nextToken(); checkSourceLevel(Feature.UNNAMED_VARIABLES); pattern = toP(F.at(token.pos).AnyPattern()); diff --git a/test/langtools/tools/javac/patterns/UnnamedErrors.java b/test/langtools/tools/javac/patterns/UnnamedErrors.java index ed345343cd6..f3c13cfee09 100644 --- a/test/langtools/tools/javac/patterns/UnnamedErrors.java +++ b/test/langtools/tools/javac/patterns/UnnamedErrors.java @@ -1,6 +1,6 @@ /* * @test /nodynamiccopyright/ - * @bug 8304246 8309093 + * @bug 8304246 8309093 8385488 * @summary Compiler Implementation for Unnamed patterns and variables * @compile/fail/ref=UnnamedErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW UnnamedErrors.java */ @@ -116,6 +116,13 @@ void testUnderscoreInExpression() { } } + int guardedPattern(String s) { + return switch (s) { + case _ when s.isEmpty() -> 1; + default -> 0; + }; + } + class Lock implements AutoCloseable { @Override public void close() {} diff --git a/test/langtools/tools/javac/patterns/UnnamedErrors.out b/test/langtools/tools/javac/patterns/UnnamedErrors.out index d0bc7514737..1bcd37e8f5b 100644 --- a/test/langtools/tools/javac/patterns/UnnamedErrors.out +++ b/test/langtools/tools/javac/patterns/UnnamedErrors.out @@ -24,6 +24,7 @@ UnnamedErrors.java:103:26: compiler.err.expected: = UnnamedErrors.java:109:13: compiler.err.use.of.underscore.not.allowed.with.brackets UnnamedErrors.java:110:18: compiler.err.use.of.underscore.not.allowed.with.brackets UnnamedErrors.java:114:24: compiler.err.use.of.underscore.not.allowed.non.variable +UnnamedErrors.java:121:18: compiler.err.use.of.underscore.not.allowed.non.variable UnnamedErrors.java:10:17: compiler.err.already.defined: kindname.variable, x, kindname.class, UnnamedErrors UnnamedErrors.java:35:13: compiler.err.unconditional.pattern.and.default UnnamedErrors.java:44:18: compiler.err.pattern.dominated @@ -33,4 +34,4 @@ UnnamedErrors.java:67:32: compiler.err.flows.through.from.pattern UnnamedErrors.java:81:56: compiler.err.already.defined: kindname.variable, x2, kindname.method, guardErrors(java.lang.Object,int,int) UnnamedErrors.java:82:13: compiler.err.switch.mixing.case.types UnnamedErrors.java:89:30: compiler.err.pattern.dominated -35 errors \ No newline at end of file +36 errors From fd9cf2b9df52f3e1ea40e69eed7e6b67f260d04d Mon Sep 17 00:00:00 2001 From: Axel Boldt-Christmas Date: Thu, 8 Oct 2026 15:29:14 +0000 Subject: [PATCH 004/101] 8386128: ZGC: Race when retiring shared medium relocation target page 8386706: [REDO] ZGC: Incorrect object undo in relocation race for relocation workers Co-authored-by: Stefan Karlsson Reviewed-by: stefank, eosterlund --- src/hotspot/share/gc/z/zPage.cpp | 9 +- src/hotspot/share/gc/z/zPage.hpp | 8 +- src/hotspot/share/gc/z/zPage.inline.hpp | 17 +- src/hotspot/share/gc/z/zRelocate.cpp | 463 ++++++++++++++++-------- 4 files changed, 352 insertions(+), 145 deletions(-) diff --git a/src/hotspot/share/gc/z/zPage.cpp b/src/hotspot/share/gc/z/zPage.cpp index b6e0d162ef0..da41375a78e 100644 --- a/src/hotspot/share/gc/z/zPage.cpp +++ b/src/hotspot/share/gc/z/zPage.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -42,7 +42,8 @@ ZPage::ZPage(ZPageType type, ZPageAge age, const ZVirtualMemory& vmem, ZMultiPar _livemap(object_max_count()), _remembered_set(), _multi_partition_tracker(multi_partition_tracker), - _relocate_promoted(false) { + _relocate_promoted(false), + _relocation_target_ref_count(0) { assert(!_virtual.is_null(), "Should not be null"); assert((_type == ZPageType::small && size() == ZPageSizeSmall) || (_type == ZPageType::medium && ZPageSizeMediumMin <= size() && size() <= ZPageSizeMediumMax) || @@ -63,6 +64,8 @@ ZPage::ZPage(ZPageType type, ZPageAge age, const ZVirtualMemory& vmem, ZMultiPar ZPage* ZPage::clone_for_promotion() const { assert(_age != ZPageAge::old, "must be used for promotion"); + assert(_relocation_target_ref_count == 0, "Invalid ref count"); + // Only copy type and memory layouts, and also update _top. Let the rest be // lazily reconstructed when needed. ZPage* const page = new ZPage(_type, ZPageAge::old, _virtual, _multi_partition_tracker, _single_partition_id); @@ -101,6 +104,8 @@ void ZPage::remset_alloc() { } ZPage* ZPage::reset(ZPageAge age) { + assert(_relocation_target_ref_count == 0, "Invalid ref count"); + _age = age; _generation_id = age == ZPageAge::old diff --git a/src/hotspot/share/gc/z/zPage.hpp b/src/hotspot/share/gc/z/zPage.hpp index 0fc134e170a..b119992964b 100644 --- a/src/hotspot/share/gc/z/zPage.hpp +++ b/src/hotspot/share/gc/z/zPage.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -53,6 +53,7 @@ class ZPage : public CHeapObj { ZRememberedSet _remembered_set; ZMultiPartitionTracker* const _multi_partition_tracker; volatile bool _relocate_promoted; + uint32_t _relocation_target_ref_count; const char* type_to_string() const; @@ -107,6 +108,11 @@ class ZPage : public CHeapObj { bool allows_raw_null() const; void set_is_relocate_promoted(); + // References owned by relocation target slots. These are used to keep medium + // relocation target pages alive while shared and worker-local slots overlap. + void inc_relocation_target_ref_count(); + bool dec_relocation_target_ref_count(); + uint32_t seqnum() const; bool is_allocating() const; bool is_relocatable() const; diff --git a/src/hotspot/share/gc/z/zPage.inline.hpp b/src/hotspot/share/gc/z/zPage.inline.hpp index 5ff6278ea09..89f490d889f 100644 --- a/src/hotspot/share/gc/z/zPage.inline.hpp +++ b/src/hotspot/share/gc/z/zPage.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ #include "gc/z/zPage.hpp" +#include "cppstdlib/limits.hpp" #include "gc/z/zAddress.inline.hpp" #include "gc/z/zGeneration.inline.hpp" #include "gc/z/zGlobals.hpp" @@ -173,6 +174,20 @@ inline ZPageAge ZPage::age() const { return _age; } +inline void ZPage::inc_relocation_target_ref_count() { + assert(_relocation_target_ref_count < std::numeric_limits::max(), "Relocation target ref count overflow"); + + _relocation_target_ref_count++; +} + +inline bool ZPage::dec_relocation_target_ref_count() { + assert(_relocation_target_ref_count > 0, "Invalid ref count"); + + _relocation_target_ref_count--; + + return _relocation_target_ref_count == 0; +} + inline uint32_t ZPage::seqnum() const { return _seqnum; } diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index 1c2a4078904..6cfcb40ccaf 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -48,6 +48,7 @@ #include "gc/z/zVerify.hpp" #include "gc/z/zWorkers.hpp" #include "prims/jvmtiTagMap.hpp" +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" @@ -415,113 +416,215 @@ zaddress ZRelocate::forward_object(ZForwarding* forwarding, zaddress_unsafe from return to_addr; } -static ZPage* alloc_page(ZForwarding* forwarding) { - if (ZStressRelocateInPlace) { - // Simulate failure to allocate a new page. This will - // cause the page being relocated to be relocated in-place. - return nullptr; - } +// +// Base class that handles the low-level allocation and retiring of pages. +// +class ZRelocateAllocatorShared { +private: + ZGeneration* const _generation; - const ZPageType type = forwarding->type(); - const size_t size = forwarding->size(); - const ZPageAge age = forwarding->to_age(); - const uint32_t preferred_partition = forwarding->partition_id(); +protected: + Atomic _in_place_count; - ZAllocationFlags flags; - flags.set_non_blocking(); - flags.set_gc_relocation(); +public: + ZRelocateAllocatorShared(ZGeneration* generation) + : _generation(generation), + _in_place_count() {} - return ZHeap::heap()->alloc_page(type, size, flags, age, preferred_partition); -} + ZPage* alloc_page(ZForwarding* forwarding) { + if (ZStressRelocateInPlace) { + // Simulate failure to allocate a new page. This will + // cause the page being relocated to be relocated in-place. + return nullptr; + } + + const ZPageType type = forwarding->type(); + const size_t size = forwarding->size(); + const ZPageAge age = forwarding->to_age(); + const uint32_t preferred_partition = forwarding->partition_id(); -static void retire_target_page(ZGeneration* generation, ZPage* page) { - if (generation->is_young() && page->is_old()) { - generation->increase_promoted(page->used()); - } else { - generation->increase_compacted(page->used()); + ZAllocationFlags flags; + flags.set_non_blocking(); + flags.set_gc_relocation(); + + return ZHeap::heap()->alloc_page(type, size, flags, age, preferred_partition); } - // Free target page if it is empty. We can end up with an empty target - // page if we allocated a new target page, and then lost the race to - // relocate the remaining objects, leaving the target page empty when - // relocation completed. - if (page->used() == 0) { - ZHeap::heap()->free_page(page); + void retire_target_page(ZPage* page) const { + if (_generation->is_young() && page->is_old()) { + _generation->increase_promoted(page->used()); + } else { + _generation->increase_compacted(page->used()); + } } -} -class ZRelocateSmallAllocator { -private: - ZGeneration* const _generation; - Atomic _in_place_count; + size_t in_place_count() const { + return _in_place_count.load_relaxed(); + } +}; + +// +// Base class for the handling of the per-worker target pages. +// +class ZRelocateAllocatorPerWorker { +protected: + ZRelocationTargets* const _targets; public: - ZRelocateSmallAllocator(ZGeneration* generation) - : _generation(generation), - _in_place_count(0) {} + ZRelocateAllocatorPerWorker(ZRelocationTargets* targets) + : _targets(targets) {} - ZPage* alloc_and_retire_target_page(ZForwarding* forwarding, ZPage* target) { - ZPage* const page = alloc_page(forwarding); - if (page == nullptr) { + ZPage* target(ZForwarding* forwarding) const { + return _targets->get(forwarding->partition_id(), forwarding->to_age()); + } + + void set_target(ZForwarding* forwarding, ZPage* target) { + _targets->set(forwarding->partition_id(), forwarding->to_age(), target); + } +}; + +// +// Allocator class to handle life cycle of small pages and related statistics. +// +class ZRelocateAllocatorSharedSmall : public ZRelocateAllocatorShared { +public: + ZRelocateAllocatorSharedSmall(ZGeneration* generation) + : ZRelocateAllocatorShared(generation) {} + + ZPage* alloc_and_retire_target_page(ZForwarding* forwarding, ZPage* previous_target) { + ZPage* const new_target = alloc_page(forwarding); + + if (new_target == nullptr) { _in_place_count.add_then_fetch(1u); } - if (target != nullptr) { - // Retire the old target page - retire_target_page(_generation, target); + if (previous_target != nullptr) { + // Retire the previous target page + retire_target_page(previous_target); } - return page; + return new_target; } - void share_target_page(ZPage* page, uint32_t partition_id) { - // Does nothing - } - - void free_target_page(ZPage* page) { + void retire_local_target_page(ZPage* page) { if (page != nullptr) { - retire_target_page(_generation, page); + retire_target_page(page); } } +}; + +// +// The allocator class for the per-worker operations on small pages. +// +class ZRelocateAllocatorPerWorkerSmall : public ZRelocateAllocatorPerWorker { +private: + ZRelocateAllocatorSharedSmall* const _shared_allocator; + +public: + ZRelocateAllocatorPerWorkerSmall(ZRelocationTargets* targets, + ZRelocateAllocatorSharedSmall* shared_allocator) + : ZRelocateAllocatorPerWorker(targets), + _shared_allocator(shared_allocator) {} + + ~ZRelocateAllocatorPerWorkerSmall() { + // Retire all local target pages + _targets->apply_and_clear_targets([&](ZPage* page) { + _shared_allocator->retire_local_target_page(page); + }); + } - zaddress alloc_object(ZPage* page, size_t size) const { - return (page != nullptr) ? page->alloc_object(size) : zaddress::null; + zaddress alloc_object(ZForwarding* forwarding, size_t size) const { + ZPage* const page = target(forwarding); + if (page == nullptr) { + return zaddress::null; + } + + return page->alloc_object(size); } - void undo_alloc_object(ZPage* page, zaddress addr, size_t size) const { + void undo_alloc_object(ZForwarding* forwarding, zaddress addr, size_t size) const { + ZPage* const page = target(forwarding); + assert(page != nullptr, "Invalid state"); + page->undo_alloc_object(addr, size); } - size_t in_place_count() const { - return _in_place_count.load_relaxed(); + bool alloc_and_set_target_page(ZForwarding* forwarding) { + ZPage* const previous_target = target(forwarding); + + // Allocate the new target + ZPage* const new_target = _shared_allocator->alloc_and_retire_target_page(forwarding, previous_target); + + // Set the new target page + set_target(forwarding, new_target); + + return new_target != nullptr; + } + + void set_target_for_in_place_relocation(ZForwarding* forwarding, ZPage* new_target) { + precond(target(forwarding) == nullptr); + + set_target(forwarding, new_target); + } + + void reuse_in_place_target_page(ZForwarding* forwarding) { + // The page is already installed in the target. + precond(target(forwarding) != nullptr); + + // Small pages are not shared. } }; -class ZRelocateMediumAllocator { +// +// Allocator class to handle life cycle of medium pages and related statistics. +// +class ZRelocateAllocatorSharedMedium : public ZRelocateAllocatorShared { private: - ZGeneration* const _generation; - ZConditionLock _lock; - ZRelocationTargets* _shared_targets; - bool _in_place; - Atomic _in_place_count; + ZRelocationTargets* const _shared_targets; + ZConditionLock _lock; + bool _in_place; public: - ZRelocateMediumAllocator(ZGeneration* generation, ZRelocationTargets* shared_targets) - : _generation(generation), - _lock(), + ZRelocateAllocatorSharedMedium(ZGeneration* generation, ZRelocationTargets* shared_targets) + : ZRelocateAllocatorShared(generation), _shared_targets(shared_targets), - _in_place(false), - _in_place_count(0) {} + _lock(), + _in_place(false) {} + + ~ZRelocateAllocatorSharedMedium() { + ZLocker locker(&_lock); - ~ZRelocateMediumAllocator() { + // Release and clear all shared targets _shared_targets->apply_and_clear_targets([&](ZPage* page) { - if (page != nullptr) { - retire_target_page(_generation, page); - } + release_target_page(page); }); } - ZPage* alloc_and_retire_target_page(ZForwarding* forwarding, ZPage* target) { +private: + ZPage* shared_target(ZForwarding* forwarding) const { + return _shared_targets->get(forwarding->partition_id(), forwarding->to_age()); + } + + void set_shared_target(ZForwarding* forwarding, ZPage* target) { + _shared_targets->set(forwarding->partition_id(), forwarding->to_age(), target); + } + + // Target references are owned by target slots, not by transient local + // variables. These helpers must be called with _lock held. + void retain_target_page(ZPage* page) const { + if (page != nullptr) { + page->inc_relocation_target_ref_count(); + } + } + + void release_target_page(ZPage* page) const { + if (page != nullptr && page->dec_relocation_target_ref_count()) { + retire_target_page(page); + } + } + +public: + ZPage* alloc_and_release_target_page(ZForwarding* forwarding, ZPage* previous_target) { ZLocker locker(&_lock); // Wait for any ongoing in-place relocation to complete @@ -529,57 +632,144 @@ class ZRelocateMediumAllocator { _lock.wait(); } - // Allocate a new page only if the shared page is the same as the - // current target page. The shared page will be different from the - // current target page if another thread shared a page, or allocated - // a new page. - const ZPageAge to_age = forwarding->to_age(); - const uint32_t partition_id = forwarding->partition_id(); - if (_shared_targets->get(partition_id, to_age) == target) { - ZPage* const to_page = alloc_page(forwarding); - _shared_targets->set(partition_id, to_age, to_page); - if (to_page == nullptr) { - _in_place_count.add_then_fetch(1u); - _in_place = true; - } + ZPage* const previous_shared_target = shared_target(forwarding); - // This thread is responsible for retiring the shared target page - if (target != nullptr) { - retire_target_page(_generation, target); - } + // Check if another thread already installed a new shared target page + if (previous_target != previous_shared_target) { + // Someone else beat us to installing a new shared target page + + // Release from local usage + release_target_page(previous_target); + + // Retain for local usage + retain_target_page(previous_shared_target); + + return previous_shared_target; + } + + // Allocate new target page + ZPage* const new_target = alloc_page(forwarding); + + if (new_target == nullptr) { + // No memory available, fallback to in-place relocation + _in_place_count.add_then_fetch(1u); + _in_place = true; } - return _shared_targets->get(partition_id, to_age); + // Release from shared usage + release_target_page(previous_shared_target); + + // Retain for shared usage + retain_target_page(new_target); + + // Replace the previous shared target page + set_shared_target(forwarding, new_target); + + // Release from local usage + release_target_page(previous_target); + + // Retain for local usage + retain_target_page(new_target); + + return new_target; } - void share_target_page(ZPage* page, uint32_t partition_id) { - const ZPageAge age = page->age(); + void share_in_place_target_page(ZForwarding* forwarding, ZPage* new_target) { + ZLocker locker(&_lock); + + // Retain for shared usage + retain_target_page(new_target); + + // Set the new, shared target page + assert(shared_target(forwarding) == nullptr, "Invalid state"); + set_shared_target(forwarding, new_target); + // Retain for local usage + retain_target_page(new_target); + } + + void signal_in_place_done(ZForwarding* forwarding) { ZLocker locker(&_lock); - assert(_in_place, "Invalid state"); - assert(_shared_targets->get(partition_id, age) == nullptr, "Invalid state"); - assert(page != nullptr, "Invalid page"); - _shared_targets->set(partition_id, age, page); + // The page is already installed as the shared target. + precond(shared_target(forwarding) != nullptr); + precond(_in_place); + _in_place = false; _lock.notify_all(); } - void free_target_page(ZPage* page) { - // Does nothing + void release_local_target_page(ZPage* page) { + if (page != nullptr) { + ZLocker locker(&_lock); + release_target_page(page); + } + } +}; + +// +// The allocator class for the per-worker operations on medium pages. +// +class ZRelocateAllocatorPerWorkerMedium : public ZRelocateAllocatorPerWorker { +private: + ZRelocateAllocatorSharedMedium* const _shared_allocator; + +public: + ZRelocateAllocatorPerWorkerMedium(ZRelocationTargets* targets, + ZRelocateAllocatorSharedMedium* shared_allocator) + : ZRelocateAllocatorPerWorker(targets), + _shared_allocator(shared_allocator) {} + + ~ZRelocateAllocatorPerWorkerMedium() { + // Release the local target pages + _targets->apply_and_clear_targets([&](ZPage* page) { + _shared_allocator->release_local_target_page(page); + }); + } + + bool alloc_and_set_target_page(ZForwarding* forwarding) { + ZPage* const previous_target = target(forwarding); + + // Allocate and update shared target + ZPage* const new_target = _shared_allocator->alloc_and_release_target_page(forwarding, previous_target); + + // Update the local target + set_target(forwarding, new_target); + + return new_target != nullptr; } - zaddress alloc_object(ZPage* page, size_t size) const { - return (page != nullptr) ? page->alloc_object_atomic(size) : zaddress::null; + void set_target_for_in_place_relocation(ZForwarding* forwarding, ZPage* new_target) { + precond(target(forwarding) == nullptr); + + _shared_allocator->share_in_place_target_page(forwarding, new_target); + + set_target(forwarding, new_target); } - void undo_alloc_object(ZPage* page, zaddress addr, size_t size) const { - page->undo_alloc_object_atomic(addr, size); + void reuse_in_place_target_page(ZForwarding* forwarding) { + // The page is already installed in the target. + precond(target(forwarding) != nullptr); + + // Medium pages are shared. + _shared_allocator->signal_in_place_done(forwarding); } - size_t in_place_count() const { - return _in_place_count.load_relaxed(); + zaddress alloc_object(ZForwarding* forwarding, size_t size) const { + ZPage* const page = target(forwarding); + if (page == nullptr) { + return zaddress::null; + } + + return page->alloc_object_atomic(size); + } + + void undo_alloc_object(ZForwarding* forwarding, zaddress addr, size_t size) const { + ZPage* const page = target(forwarding); + assert(page != nullptr, "Invalid state"); + + page->undo_alloc_object_atomic(addr, size); } }; @@ -588,7 +778,6 @@ class ZRelocateWork : public StackObj { private: Allocator* const _allocator; ZForwarding* _forwarding; - ZRelocationTargets* _targets; ZGeneration* const _generation; size_t _other_promoted; size_t _other_compacted; @@ -607,11 +796,10 @@ class ZRelocateWork : public StackObj { } } - zaddress try_relocate_object_inner(zaddress from_addr, uint32_t partition_id) { + zaddress try_relocate_object_inner(zaddress from_addr) { ZForwardingCursor cursor; const size_t size = ZUtils::object_size(from_addr); - ZPage* const to_page = _targets->get(partition_id, _forwarding->to_age()); // Lookup forwarding { @@ -624,8 +812,9 @@ class ZRelocateWork : public StackObj { } // Allocate object - const zaddress allocated_addr = _allocator->alloc_object(to_page, size); + const zaddress allocated_addr = _allocator->alloc_object(_forwarding, size); if (is_null(allocated_addr)) { + assert(!_forwarding->in_place_relocation(), "In-place object allocations should never fail"); // Allocation failed return zaddress::null; } @@ -642,7 +831,7 @@ class ZRelocateWork : public StackObj { 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(_forwarding, allocated_addr, size); increase_other_forwarded(size); } @@ -821,8 +1010,8 @@ class ZRelocateWork : public StackObj { } } - bool try_relocate_object(zaddress from_addr, uint32_t partition_id) { - const zaddress to_addr = try_relocate_object_inner(from_addr, partition_id); + bool try_relocate_object(zaddress from_addr) { + const zaddress to_addr = try_relocate_object_inner(from_addr); if (is_null(to_addr)) { return false; @@ -859,7 +1048,7 @@ class ZRelocateWork : public StackObj { } } - ZPage* start_in_place_relocation(zoffset relocated_watermark) { + void start_in_place_relocation(zoffset relocated_watermark) { _forwarding->in_place_relocation_claim_page(); _forwarding->in_place_relocation_start(relocated_watermark); @@ -896,51 +1085,38 @@ class ZRelocateWork : public StackObj { ZGeneration::young()->register_in_place_relocate_promoted(from_page); } - return to_page; + _allocator->set_target_for_in_place_relocation(_forwarding, to_page); } void relocate_object(oop obj) { const zaddress addr = to_zaddress(obj); assert(ZHeap::heap()->is_object_live(addr), "Should be live"); - const ZPageAge to_age = _forwarding->to_age(); - const uint32_t partition_id = _forwarding->partition_id(); - - while (!try_relocate_object(addr, partition_id)) { + while (!try_relocate_object(addr)) { // Failed to relocate object, try to allocate a new target page, // or if that fails, use the page being relocated as the new target, // which will cause it to be relocated in-place. - ZPage* const target_page = _targets->get(partition_id, to_age); - ZPage* to_page = _allocator->alloc_and_retire_target_page(_forwarding, target_page); - _targets->set(partition_id, to_age, to_page); - - // We got a new page, retry relocation - if (to_page != nullptr) { + if (_allocator->alloc_and_set_target_page(_forwarding)) { + // We got a new page, retry relocation continue; } // Start in-place relocation to block other threads from accessing // the page, or its forwarding table, until it has been released // (relocation completed). - to_page = start_in_place_relocation(ZAddress::offset(addr)); - _targets->set(partition_id, to_age, to_page); + start_in_place_relocation(ZAddress::offset(addr)); } } public: - ZRelocateWork(Allocator* allocator, ZRelocationTargets* targets, ZGeneration* generation) + ZRelocateWork(Allocator* allocator, ZGeneration* generation) : _allocator(allocator), _forwarding(nullptr), - _targets(targets), _generation(generation), _other_promoted(0), _other_compacted(0) {} ~ZRelocateWork() { - _targets->apply_and_clear_targets([&](ZPage* page) { - _allocator->free_target_page(page); - }); - // Report statistics on-behalf of non-worker threads _generation->increase_promoted(_other_promoted); _generation->increase_compacted(_other_compacted); @@ -1025,17 +1201,19 @@ class ZRelocateWork : public StackObj { if (in_place) { // Wait for all other threads to call release_page - ZPage* const page = _forwarding->detach_page(); + ZPage* const from_page = _forwarding->detach_page(); // Ensure that previous remset bits are cleared - clear_remset_before_in_place_reuse(page); + clear_remset_before_in_place_reuse(from_page); - page->log_msg(" (relocate page done in-place)"); + from_page->log_msg(" (relocate page done in-place)"); - // Different pages when promoting - const uint32_t target_partition = _forwarding->partition_id(); - ZPage* const target_page = _targets->get(target_partition, _forwarding->to_age()); - _allocator->share_target_page(target_page, target_partition); + // ZPage instances are different when pages have been promoted. + // The to_space in-place copy resides in the allocator's targets array. + _allocator->reuse_in_place_target_page(forwarding); + + // Note that we don't delete the from_page of promoted pages here. + // The promoted pages have been registered and will be deleted later on. } else { // Wait for all other threads to call release_page @@ -1084,8 +1262,8 @@ class ZRelocateTask : public ZRestartableTask { ZPerNUMA* _iters; ZPerWorker* _small_targets; ZPerWorker* _medium_targets; - ZRelocateSmallAllocator _small_allocator; - ZRelocateMediumAllocator _medium_allocator; + ZRelocateAllocatorSharedSmall _shared_small_allocator; + ZRelocateAllocatorSharedMedium _shared_medium_allocator; const size_t _total_forwardings; public: @@ -1101,8 +1279,8 @@ class ZRelocateTask : public ZRestartableTask { _iters(iters), _small_targets(small_targets), _medium_targets(medium_targets), - _small_allocator(_generation), - _medium_allocator(_generation, shared_medium_targets), + _shared_small_allocator(_generation), + _shared_medium_allocator(_generation, shared_medium_targets), _total_forwardings(relocation_set->nforwardings()) { for (uint32_t i = 0; i < ZNUMA::count(); i++) { @@ -1118,15 +1296,18 @@ class ZRelocateTask : public ZRestartableTask { } ~ZRelocateTask() { - _generation->stat_relocation()->at_relocate_end(_small_allocator.in_place_count(), _medium_allocator.in_place_count()); + _generation->stat_relocation()->at_relocate_end(_shared_small_allocator.in_place_count(), _shared_medium_allocator.in_place_count()); // Signal that we're not using the queue anymore. Used mostly for asserts. _queue->deactivate(); } virtual void work() { - ZRelocateWork small(&_small_allocator, _small_targets->addr(), _generation); - ZRelocateWork medium(&_medium_allocator, _medium_targets->addr(), _generation); + ZRelocateAllocatorPerWorkerSmall small_allocator(_small_targets->addr(), &_shared_small_allocator); + ZRelocateAllocatorPerWorkerMedium medium_allocator(_medium_targets->addr(), &_shared_medium_allocator); + + ZRelocateWork small(&small_allocator, _generation); + ZRelocateWork medium(&medium_allocator, _generation); const uint32_t num_nodes = ZNUMA::count(); const uint32_t start_node = ZNUMA::id(); From 6edf757bc4152dc62c449ad69f30f7c85a1b4f00 Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Thu, 8 Oct 2026 15:36:46 +0000 Subject: [PATCH 005/101] 8393335: Update Pandoc to version 3.6.4 for Oracle builds Reviewed-by: erikj, jsjolen --- doc/building.html | 117 +++++++++++++++--------------- doc/building.md | 4 +- doc/hotspot-style.html | 8 +- doc/hotspot-unit-tests.html | 8 +- doc/ide.html | 8 +- doc/starting-next-release.html | 8 +- doc/testing.html | 29 ++++---- doc/value-class-preview.html | 8 +- make/autoconf/basic_tools.m4 | 2 +- make/conf/jib-profiles.js | 2 +- make/devkit/createPandocBundle.sh | 11 ++- 11 files changed, 104 insertions(+), 101 deletions(-) diff --git a/doc/building.html b/doc/building.html index 53defa0c61c..fb18b1589c3 100644 --- a/doc/building.html +++ b/doc/building.html @@ -11,8 +11,11 @@ div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - ul.task-list{list-style: none;} + /* The extra [class] is a hack that increases specificity enough to + override a similar rule in reveal.js */ + ul.task-list[class]{list-style: none;} ul.task-list li input[type="checkbox"] { + font-size: inherit; width: 0.8em; margin: 0 0.8em 0.2em -1.6em; vertical-align: middle; @@ -21,9 +24,6 @@ -
@@ -302,8 +302,8 @@

Special Considerations

in one of the columns of the ouput. If a directory is missing a short name, the safest way to get one is to enable short names for that particular volume with -fsutil 8dot3name set <drive letter>: 0 (note that -you need to run as administrator for this), and then re-create the +fsutil 8dot3name set <drive letter>: 0 (note that you +need to run as administrator for this), and then re-create the particular directory. A short name should be generated automatically then. Another option is to manually assign a short name to the directory using @@ -366,13 +366,14 @@

Building on aarch64

also possible to use cross-compiling.

Branch Protection

In order to use Branch Protection features in the VM, ---enable-branch-protection must be used. This option requires C++ -compiler support for -mbranch-protection=standard in GCC 9.1.0+ or -Clang 10+ on Linux/AArch64 or for /guard:signret in Visual Studio -2019+ on Windows/ARM64. The resulting build can be run on both machines with and -without support for branch protection in hardware. Branch Protection is fully -supported for the Linux/AArch64 target and only partially supported for the -Windows/ARM64 targets.

+--enable-branch-protection must be used. This option +requires C++ compiler support for +-mbranch-protection=standard in GCC 9.1.0+ or Clang 10+ on +Linux/AArch64 or for /guard:signret in Visual Studio 2019+ +on Windows/ARM64. The resulting build can be run on both machines with +and without support for branch protection in hardware. Branch Protection +is fully supported for the Linux/AArch64 target and only partially +supported for the Windows/ARM64 target.

Building on 32-bit ARM

This is not recommended. Instead, see the section on Cross-compiling.

@@ -389,25 +390,25 @@

Operating System to date at the time of writing.

- + - + - + - + - + @@ -580,25 +581,25 @@

Native Compiler system and toolchain.

Operating system Vendor/version used
Linux/x64 Oracle Linux 6.4 / 8.x
Linux/aarch64 Oracle Linux 7.6 / 8.x
macOS macOS 14.x
Windows Windows Server 2016
- + - + - + - + - + @@ -612,21 +613,21 @@

Native Compiler to compile successfully without issues.

Operating system Supported toolchain
Linux gcc, clang
macOS Apple Xcode (using clang)
AIX IBM Open XL C/C++
Windows Microsoft Visual Studio
- + - + - + - + @@ -902,7 +903,7 @@

Graphviz and Pandoc

href="https://pandoc.org">Pandoc is required. For full docs also Graphviz is required. Any recent versions should work. For reference, and subject to change, Oracle -builds use Graphviz 9.0.0 and Pandoc 2.19.2.

+builds use Graphviz 9.0.0 and Pandoc 3.6.4.

Running Configure

To build the JDK, you need a "configuration", which consists of a directory where to store the build output, coupled with information @@ -1473,27 +1474,27 @@

Using OpenJDK Devkits

following targets are known to work:

Operating system Toolchain version
Linux gcc 14.2.0
macOS Apple Xcode 15.4 (using clang 15.0.0)
Windows Microsoft Visual Studio 2022 version 17.13.2
- + - + - + - + - + - + - + @@ -1589,7 +1590,7 @@

Using Debian debootstrap

- + @@ -1598,98 +1599,98 @@

Using Debian debootstrap

- + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1818,13 +1819,13 @@

Ccache

recommend evaluating it for yourself. To enable it, make sure it's on the path and configure with --enable-ccache.

Sccache

-

The JDK build supports building with sccache when using gcc, clang, or -Microsoft toolchains. To enable it, make sure the sccache binary is on the path -(or specify the path to the binary using the SCCACHE argument to -the configure script) and configure with --enable-sccache. To -optionally specify where sccache stores its cache files, use ---with-sccache-dir. Precompiled headers are disabled when sccache -is enabled.

+

The JDK build supports building with sccache when using gcc, clang, +or Microsoft toolchains. To enable it, make sure the sccache binary is +on the path (or specify the path to the binary using the +SCCACHE argument to the configure script) and configure +with --enable-sccache. To optionally specify where sccache +stores its cache files, use --with-sccache-dir. Precompiled +headers are disabled when sccache is enabled.

Precompiled Headers

By default, the Hotspot build uses pre-compiled headers (PCH) on the toolchains were it is properly supported (clang, gcc, and Visual @@ -1961,7 +1962,7 @@

Running "make doctor"

* Checking for warnings from configure... --- The following warnings were produced. Repeated here for convenience: -WARNING: pandoc is version 3.1.9, not the recommended version 2.19.2 +WARNING: pandoc is version 3.1.9, not the recommended version 3.6.4 --- ! Inspect the warnings, fix any problems, and re-run configure diff --git a/doc/building.md b/doc/building.md index 17eaf760b06..d273010ddc1 100644 --- a/doc/building.md +++ b/doc/building.md @@ -713,7 +713,7 @@ In order to build man pages and the full docs (see the `--enable-full-docs` configure option) [Pandoc](https://pandoc.org) is required. For full docs also [Graphviz](https://www.graphviz.org) is required. Any recent versions should work. For reference, and subject to change, Oracle builds use Graphviz -9.0.0 and Pandoc 2.19.2. +9.0.0 and Pandoc 3.6.4. ## Running Configure @@ -1720,7 +1720,7 @@ certain well-known problems, but it can never find all possible errors. * Checking for warnings from configure... --- The following warnings were produced. Repeated here for convenience: -WARNING: pandoc is version 3.1.9, not the recommended version 2.19.2 +WARNING: pandoc is version 3.1.9, not the recommended version 3.6.4 --- ! Inspect the warnings, fix any problems, and re-run configure diff --git a/doc/hotspot-style.html b/doc/hotspot-style.html index c7126622c7d..f0e07630a21 100644 --- a/doc/hotspot-style.html +++ b/doc/hotspot-style.html @@ -11,8 +11,11 @@ div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - ul.task-list{list-style: none;} + /* The extra [class] is a hack that increases specificity enough to + override a similar rule in reveal.js */ + ul.task-list[class]{list-style: none;} ul.task-list li input[type="checkbox"] { + font-size: inherit; width: 0.8em; margin: 0 0.8em 0.2em -1.6em; vertical-align: middle; @@ -20,9 +23,6 @@ .display.math{display: block; text-align: center; margin: 0.5rem auto;} -
diff --git a/doc/hotspot-unit-tests.html b/doc/hotspot-unit-tests.html index 5cc424322f9..a4a01b47051 100644 --- a/doc/hotspot-unit-tests.html +++ b/doc/hotspot-unit-tests.html @@ -11,8 +11,11 @@ div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - ul.task-list{list-style: none;} + /* The extra [class] is a hack that increases specificity enough to + override a similar rule in reveal.js */ + ul.task-list[class]{list-style: none;} ul.task-list li input[type="checkbox"] { + font-size: inherit; width: 0.8em; margin: 0 0.8em 0.2em -1.6em; vertical-align: middle; @@ -20,9 +23,6 @@ .display.math{display: block; text-align: center; margin: 0.5rem auto;} -
diff --git a/doc/ide.html b/doc/ide.html index ef6f434013e..2407becf94d 100644 --- a/doc/ide.html +++ b/doc/ide.html @@ -11,8 +11,11 @@ div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - ul.task-list{list-style: none;} + /* The extra [class] is a hack that increases specificity enough to + override a similar rule in reveal.js */ + ul.task-list[class]{list-style: none;} ul.task-list li input[type="checkbox"] { + font-size: inherit; width: 0.8em; margin: 0 0.8em 0.2em -1.6em; vertical-align: middle; @@ -20,9 +23,6 @@ .display.math{display: block; text-align: center; margin: 0.5rem auto;} -
diff --git a/doc/starting-next-release.html b/doc/starting-next-release.html index bbb48e243ad..a03044097e7 100644 --- a/doc/starting-next-release.html +++ b/doc/starting-next-release.html @@ -11,8 +11,11 @@ div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - ul.task-list{list-style: none;} + /* The extra [class] is a hack that increases specificity enough to + override a similar rule in reveal.js */ + ul.task-list[class]{list-style: none;} ul.task-list li input[type="checkbox"] { + font-size: inherit; width: 0.8em; margin: 0 0.8em 0.2em -1.6em; vertical-align: middle; @@ -20,9 +23,6 @@ .display.math{display: block; text-align: center; margin: 0.5rem auto;} -
diff --git a/doc/testing.html b/doc/testing.html index f52f72ac2f7..bb6af964c29 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -11,8 +11,11 @@ div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - ul.task-list{list-style: none;} + /* The extra [class] is a hack that increases specificity enough to + override a similar rule in reveal.js */ + ul.task-list[class]{list-style: none;} ul.task-list li input[type="checkbox"] { + font-size: inherit; width: 0.8em; margin: 0 0.8em 0.2em -1.6em; vertical-align: middle; @@ -21,9 +24,6 @@ -
@@ -494,8 +494,8 @@

VALUE_CLASS_PLUGIN

In the meantime, this mode allows test sources to compile and run as either value classes or regular identity classes without source-level changes.

-

When set to any non-empty value, the following options are appended to -every JTReg invocation:

+

When set to any non-empty value, the following options are appended +to every JTReg invocation:

  • -cpa:<valueClassPlugin.jar> — appends the plugin JAR to the compile-time classpath (only when the JAR is present in the @@ -511,16 +511,15 @@

    VALUE_CLASS_PLUGIN

    plugin.

The plugin scans each compilation unit after parsing and converts any -class annotated with -@jdk.test.lib.valueclass.AsValueClass into a value class by -setting the internal VALUE_CLASS modifier flag and clearing -the IDENTITY_TYPE flag. This transformation only takes -effect when --enable-preview is active; without it the -annotation is a no-op and the class compiles as an ordinary identity -class, so the same test source can exercise both code paths.

+class annotated with @jdk.test.lib.valueclass.AsValueClass +into a value class by setting the internal VALUE_CLASS +modifier flag and clearing the IDENTITY_TYPE flag. This +transformation only takes effect when --enable-preview is +active; without it the annotation is a no-op and the class compiles as +an ordinary identity class, so the same test source can exercise both +code paths.

Example:

-
$ make test TEST=jdk_lang JTREG="VALUE_CLASS_PLUGIN=true"
-
+
$ make test TEST=jdk_lang JTREG="VALUE_CLASS_PLUGIN=true"

JVMTI_STRESS_AGENT

Executes JTReg tests with JVM TI stress agent. The stress agent is the part of test library and located in diff --git a/doc/value-class-preview.html b/doc/value-class-preview.html index 04b8ffb0f9b..b6c84f2386d 100644 --- a/doc/value-class-preview.html +++ b/doc/value-class-preview.html @@ -11,8 +11,11 @@ div.columns{display: flex; gap: min(4vw, 1.5em);} div.column{flex: auto; overflow-x: auto;} div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} - ul.task-list{list-style: none;} + /* The extra [class] is a hack that increases specificity enough to + override a similar rule in reveal.js */ + ul.task-list[class]{list-style: none;} ul.task-list li input[type="checkbox"] { + font-size: inherit; width: 0.8em; margin: 0 0.8em 0.2em -1.6em; vertical-align: middle; @@ -21,9 +24,6 @@ -

diff --git a/make/autoconf/basic_tools.m4 b/make/autoconf/basic_tools.m4 index db6c893c8b3..b8b260ab499 100644 --- a/make/autoconf/basic_tools.m4 +++ b/make/autoconf/basic_tools.m4 @@ -26,7 +26,7 @@ ################################################################################ # It is recommended to use exactly this version of pandoc, especially for # re-generating checked in html files -RECOMMENDED_PANDOC_VERSION=2.19.2 +RECOMMENDED_PANDOC_VERSION=3.6.4 ################################################################################ # Setup the most fundamental tools, used for setting up build platform and diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index f64e2ec2270..b757fff55e6 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1233,7 +1233,7 @@ var getJibProfilesDependencies = function (input, common) { pandoc: { organization: common.organization, ext: "tar.gz", - revision: "2.19.2+1.0", + revision: "3.6.4+1.0", module: "pandoc-" + input.build_platform, configure_args: "PANDOC=" + input.get("pandoc", "install_path") + "/pandoc/pandoc", environment_path: input.get("pandoc", "install_path") + "/pandoc" diff --git a/make/devkit/createPandocBundle.sh b/make/devkit/createPandocBundle.sh index f582b2e9b1b..e5d0d5f1ac7 100644 --- a/make/devkit/createPandocBundle.sh +++ b/make/devkit/createPandocBundle.sh @@ -1,6 +1,6 @@ #!/bin/bash -e # -# Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2017, 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 @@ -31,7 +31,7 @@ trap "rm -rf \"$TMPDIR\"" EXIT ORIG_DIR=`pwd` cd "$TMPDIR" -PANDOC_VERSION=2.19.2 +PANDOC_VERSION=3.6.4 PACKAGE_VERSION=1.0 TARGET_PLATFORM=linux_x64 if [ $# = 1 ]; then @@ -47,7 +47,10 @@ elif [[ $TARGET_PLATFORM == linux_aarch64 ]] ; then PANDOC_PLATFORM=linux-arm64 PANDOC_SUFFIX=tar.gz elif [[ $TARGET_PLATFORM == macosx_x64 ]] ; then - PANDOC_PLATFORM=macOS + PANDOC_PLATFORM=x86_64-macOS + PANDOC_SUFFIX=zip +elif [[ $TARGET_PLATFORM == macosx_aarch64 ]] ; then + PANDOC_PLATFORM=arm64-macOS PANDOC_SUFFIX=zip elif [[ $TARGET_PLATFORM == windows_x64 ]] ; then PANDOC_PLATFORM=windows-x86_64 @@ -72,7 +75,7 @@ fi cd .. mkdir pandoc -cp tmp/pandoc-$PANDOC_VERSION/$PANDOC_PATH pandoc +cp tmp/pandoc-"$PANDOC_VERSION"*/"$PANDOC_PATH" pandoc chmod +x pandoc/$PANDOC_EXE tar -cvzf ../$BUNDLE_NAME pandoc From 20c6982976b2e2cdbac636f4d90bb77a64a0a98b Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Thu, 8 Oct 2026 16:18:38 +0000 Subject: [PATCH 006/101] 8393848: Align the JDK with Microsoft's new timezone name for Manitoba Reviewed-by: jlu --- src/java.base/share/data/tzdata/tzmappings.override | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/java.base/share/data/tzdata/tzmappings.override b/src/java.base/share/data/tzdata/tzmappings.override index 8466e3585c3..0ccc52caf9a 100644 --- a/src/java.base/share/data/tzdata/tzmappings.override +++ b/src/java.base/share/data/tzdata/tzmappings.override @@ -38,3 +38,7 @@ British Columbia Standard Time:001:America/Vancouver: # tz2026c: Alberta # https://techcommunity.microsoft.com/blog/dstblog/interim-guidance-for-alberta-time-zone-changes-2026/4545015 Alberta Standard Time:001:America/Edmonton: + +# tz2026e: Manitoba +# https://techcommunity.microsoft.com/blog/dstblog/interim-guidance-for-manitoba-permanent-dst-2026/4562488 +Manitoba Standard Time:001:America/Winnipeg: From 36683b7796ce80cad58b0fdb86bf6a4e514845f0 Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Thu, 8 Oct 2026 18:54:32 +0000 Subject: [PATCH 007/101] 8393864: Improve equality-related discussion in BigDecimal Reviewed-by: psandoz --- .../share/classes/java/math/BigDecimal.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index 3cfe18e84c3..a99ccd2fc99 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -100,7 +100,7 @@ * used in the result's representation. * * The different representations of the same numerical value are - * called members of the same cohort. The {@linkplain + * called members of the same {@index cohort}. The {@linkplain * compareTo(BigDecimal) natural order} of {@code BigDecimal} * considers members of the same cohort to be equal to each other. In * contrast, the {@link equals equals} method requires both the @@ -109,6 +109,12 @@ * #unscaledValue()} will differ for numerically equal values with * different representations. * + *

In the terminology {@linkplain Double##fpNumericalEq discussed + * for binary floating-point}, {@code BigDecimal.equals} uses + * representation equivalence while {@code BigDecimal.compareTo} + * uses numerical equality to compare members of the same + * cohort. + * *

In general the rounding modes and precision setting determine * how operations return results with a limited number of digits when * the exact result has more digits (perhaps infinitely many in the @@ -3228,7 +3234,9 @@ else if (ys != INFLATED) /** * Compares this {@code BigDecimal} with the specified {@code - * Object} for equality. Unlike {@link #compareTo(BigDecimal) + * Object} for equality using {@linkplain Double##fpNumericalEq + * representation equivalence}. + * Unlike {@link #compareTo(BigDecimal) * compareTo}, this method considers two {@code BigDecimal} * objects equal only if they are equal in value and * scale. Therefore 2.0 is not equal to 2.00 when compared by this @@ -3244,9 +3252,6 @@ else if (ys != INFLATED) * HALF_UP)} which evaluates to 0.7 and
* {@code new BigDecimal("2.00").divide(BigDecimal.valueOf(3), * HALF_UP)} which evaluates to 0.67. - * The behavior of this method is analogous to checking the - * {@linkplain Double##repEquivalence representation equivalence} - * of {@code double} values. * * @param x {@code Object} to which this {@code BigDecimal} is * to be compared. From c5a311116ab2d360923f4717c9995053279856b7 Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Thu, 8 Oct 2026 20:39:02 +0000 Subject: [PATCH 008/101] 8392122: Test jdk/internal/platform/docker/TestDockerMemoryMetrics.java fails with OutOfMemoryError Reviewed-by: cnorrbin, epavlova --- .../platform/docker/MetricsMemoryTester.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/jdk/jdk/internal/platform/docker/MetricsMemoryTester.java b/test/jdk/jdk/internal/platform/docker/MetricsMemoryTester.java index 53924b6abad..7306b721937 100644 --- a/test/jdk/jdk/internal/platform/docker/MetricsMemoryTester.java +++ b/test/jdk/jdk/internal/platform/docker/MetricsMemoryTester.java @@ -87,10 +87,10 @@ private static void testMemoryFailCount() { System.out.println("Initial memory fail count: " + initialFailCount); - // Allocate 512M of data in 1M chunks per iteration - byte[][] bytes = new byte[512][]; + // Allocate up to 256M of data in 1M chunks, two G1 regions per chunk at this heap size + byte[][] bytes = new byte[256][]; - for (int i = 0; i < 512; i++) { + for (int i = 0; i < bytes.length; i++) { if (i % 8 == 0) { System.out.printf("Allocated: %3dM, Memory usage: %3dM, Memory and swap: %3dM\n", i, @@ -113,6 +113,13 @@ private static void testMemoryFailCount() { System.out.println("Final memory fail count: " + newCount); if (newCount <= initialFailCount) { + // A limiting ancestor may account for events not visible in this cgroup. + if (initialFailCount >= 0 && newCount == initialFailCount + && memLimit > 0 && metrics.getMemoryAndSwapUsage() > memLimit) { + System.out.println("Memory and swap usage exceeds the memory limit, " + + "but the fail count is unchanged. Ignoring test!"); + return; + } throw new RuntimeException("Memory fail count did not increase: initial=" + initialFailCount + ", final=" + newCount); } From e976cefbd5864f1eddce2fae0e0318360586ad00 Mon Sep 17 00:00:00 2001 From: Matias Saavedra Silva Date: Thu, 8 Oct 2026 21:08:25 +0000 Subject: [PATCH 009/101] 8393431: Strict static field read-write state incorrectly preserved between AOT runs Reviewed-by: iklam, heidinga, fparain --- src/hotspot/share/oops/cpCache.cpp | 12 ++++++++++++ src/hotspot/share/oops/instanceKlass.cpp | 13 +++++++++++++ src/hotspot/share/oops/instanceKlass.hpp | 1 + 3 files changed, 26 insertions(+) diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 73879b7656c..1dc1fa57d08 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -436,6 +436,18 @@ void ConstantPoolCache::iterate_resolved_field_entries_with_archivability_check( bool archivable = (resolved && !CDSConfig::is_dumping_preimage_static_archive() && AOTConstantPoolResolver::is_resolution_deterministic(src_cp, cp_index)); + + // The state of strict static field accesses is reset in InstanceKlass::remove_unshareable_info + // when the class does not have an AOT initialized mirror. Since will be called in + // the subsequent runtime, the resolved field entries associated with strict static fields are + // not archiveable. + if (rfi->is_resolved(Bytecodes::_getstatic) || rfi->is_resolved(Bytecodes::_putstatic)) { + InstanceKlass* holder = rfi->field_holder(); + if (!holder->has_aot_initialized_mirror() && holder->field_is_strict(rfi->field_index())) { + archivable = false; + } + } + f(cp, src_cp, cp_index, rfi, resolved, archivable); } } diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 705e04c4c56..44208c22320 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -3124,6 +3124,19 @@ void InstanceKlass::remove_unshareable_info() { // being added to class hierarchy (see InstanceKlass:::add_to_hierarchy()). _init_state = allocated; + // Classes without an AOT-initialized mirror will run again. Restore + // the initial status of their strict static fields for that execution. + if (has_strict_static_fields() && !has_aot_initialized_mirror()) { + for (AllFieldStream fs(this); !fs.done(); fs.next()) { + FieldInfo fi = fs.to_FieldInfo(); + if (fi.access_flags().is_strict() && fi.access_flags().is_static() && fi.initializer_index() == 0) { + FieldStatus& fs = *fields_status()->adr_at(fi.index()); + fs.update_strict_static_unset(true); + fs.update_strict_static_unread(true); + } + } + } + { // Otherwise this needs to take out the Compile_lock. assert(SafepointSynchronize::is_at_safepoint(), "only called at safepoint"); init_implementor(); diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 5f915ea87d4..1c9ddf84b76 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -455,6 +455,7 @@ class InstanceKlass: public Klass { bool field_is_flat(int index) const { return field_flags(index).is_flat(); } bool field_has_null_marker(int index) const { return field_flags(index).has_null_marker(); } bool field_is_null_free_value_type(int index) const; + bool field_is_strict(int index) const { return field(index).access_flags().is_strict(); } bool is_class_in_loadable_descriptors_attribute(Symbol* name) const; int field_null_marker_offset(int index) const; From f6fa65381d0c32e22120159415f8c472e990cdc7 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Thu, 8 Oct 2026 21:31:21 +0000 Subject: [PATCH 010/101] 8380476: Implement JEP 544: Ahead-of-Time Code Compilation Co-authored-by: John R Rose Co-authored-by: Andrew Dinn Co-authored-by: Ashutosh Mehra Co-authored-by: Dan Heidinga Co-authored-by: Ioi Lam Co-authored-by: Vladimir Ivanov Co-authored-by: Ekaterina Pavlova Co-authored-by: Aleksey Shipilev Co-authored-by: Igor Veresov Co-authored-by: Dingli Zhang Reviewed-by: adinn, iveresov, iklam, jrose, asmehra, mhaessig --- make/hotspot/lib/JvmFeatures.gmk | 1 + .../cpu/aarch64/c1_LIRGenerator_aarch64.cpp | 9 - .../cpu/aarch64/c1_MacroAssembler_aarch64.cpp | 2 +- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 4 +- .../cpu/aarch64/c2_MacroAssembler_aarch64.hpp | 3 + .../cpu/aarch64/compiledIC_aarch64.cpp | 11 +- .../gc/g1/g1BarrierSetAssembler_aarch64.cpp | 4 +- .../gc/z/zBarrierSetAssembler_aarch64.cpp | 2 +- .../cpu/aarch64/macroAssembler_aarch64.cpp | 26 +- src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp | 14 +- src/hotspot/cpu/arm/c1_LIRGenerator_arm.cpp | 8 - src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp | 8 - .../cpu/riscv/c1_LIRGenerator_riscv.cpp | 7 - .../cpu/riscv/c2_MacroAssembler_riscv.cpp | 6 +- .../gc/g1/g1BarrierSetAssembler_riscv.cpp | 3 +- .../riscv/gc/z/zBarrierSetAssembler_riscv.cpp | 3 +- .../cpu/riscv/globalDefinitions_riscv.hpp | 8 + .../cpu/riscv/macroAssembler_riscv.cpp | 27 +- .../cpu/riscv/macroAssembler_riscv.hpp | 7 +- src/hotspot/cpu/riscv/relocInfo_riscv.cpp | 13 +- src/hotspot/cpu/riscv/riscv.ad | 15 +- src/hotspot/cpu/s390/c1_LIRGenerator_s390.cpp | 7 - .../cpu/s390/c1_MacroAssembler_s390.cpp | 2 +- src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp | 8 - src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp | 2 +- src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp | 8 +- src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp | 3 + .../x86/stubGenerator_x86_64_arraycopy.cpp | 1 + src/hotspot/cpu/x86/x86.ad | 10 +- src/hotspot/share/asm/codeBuffer.cpp | 15 +- src/hotspot/share/asm/codeBuffer.hpp | 6 + src/hotspot/share/c1/c1_Compilation.cpp | 8 +- src/hotspot/share/c1/c1_Compiler.cpp | 40 +- src/hotspot/share/c1/c1_Compiler.hpp | 3 +- src/hotspot/share/c1/c1_GraphBuilder.cpp | 7 +- src/hotspot/share/c1/c1_LIRGenerator.cpp | 23 +- src/hotspot/share/c1/c1_LIRGenerator.hpp | 1 - src/hotspot/share/c1/c1_Runtime1.cpp | 70 +- src/hotspot/share/c1/c1_Runtime1.hpp | 12 +- src/hotspot/share/cds/aotCacheAccess.cpp | 112 +- src/hotspot/share/cds/aotCacheAccess.hpp | 86 +- src/hotspot/share/cds/aotClassLinker.cpp | 4 + .../share/cds/aotCompressedPointers.hpp | 2 + .../share/cds/aotConstantPoolResolver.cpp | 25 + src/hotspot/share/cds/aotMetaspace.cpp | 99 +- src/hotspot/share/cds/aotMetaspace.hpp | 3 +- .../share/cds/aotStreamedHeapLoader.cpp | 2 + src/hotspot/share/cds/archiveBuilder.cpp | 13 +- src/hotspot/share/cds/archiveBuilder.hpp | 7 +- src/hotspot/share/cds/archiveUtils.cpp | 22 +- src/hotspot/share/cds/archiveUtils.hpp | 8 +- src/hotspot/share/cds/cdsConfig.cpp | 43 +- src/hotspot/share/cds/cdsConfig.hpp | 5 + src/hotspot/share/cds/cds_globals.hpp | 9 +- src/hotspot/share/cds/cppVtables.cpp | 37 +- src/hotspot/share/cds/cppVtables.hpp | 2 + src/hotspot/share/cds/filemap.cpp | 82 +- src/hotspot/share/cds/filemap.hpp | 8 +- src/hotspot/share/cds/heapShared.cpp | 51 +- src/hotspot/share/cds/heapShared.hpp | 1 + src/hotspot/share/ci/ciConstant.cpp | 13 + src/hotspot/share/ci/ciConstant.hpp | 2 + src/hotspot/share/ci/ciEnv.cpp | 530 ++- src/hotspot/share/ci/ciEnv.hpp | 39 +- src/hotspot/share/ci/ciField.cpp | 6 + src/hotspot/share/ci/ciInstanceKlass.cpp | 30 +- src/hotspot/share/ci/ciInstanceKlass.hpp | 2 + src/hotspot/share/ci/ciMethod.cpp | 82 +- src/hotspot/share/ci/ciMethod.hpp | 9 +- src/hotspot/share/ci/ciObject.cpp | 28 +- src/hotspot/share/ci/ciObjectFactory.cpp | 26 +- src/hotspot/share/ci/ciObjectFactory.hpp | 2 +- src/hotspot/share/ci/ciReplay.cpp | 2 +- .../share/classfile/classLoaderDataShared.cpp | 4 +- src/hotspot/share/classfile/moduleEntry.cpp | 10 +- .../classfile/systemDictionaryShared.cpp | 2 +- src/hotspot/share/code/aotCodeCache.cpp | 4106 ++++++++++++++--- src/hotspot/share/code/aotCodeCache.hpp | 768 ++- src/hotspot/share/code/codeBlob.cpp | 12 +- src/hotspot/share/code/codeBlob.hpp | 4 +- src/hotspot/share/code/codeCache.cpp | 48 +- src/hotspot/share/code/debugInfoRec.cpp | 2 + src/hotspot/share/code/debugInfoRec.hpp | 4 + src/hotspot/share/code/dependencies.cpp | 10 +- src/hotspot/share/code/dependencies.hpp | 6 + src/hotspot/share/code/nmethod.cpp | 263 +- src/hotspot/share/code/nmethod.hpp | 78 +- src/hotspot/share/code/relocInfo.cpp | 22 +- src/hotspot/share/code/relocInfo.hpp | 27 +- .../share/compiler/abstractCompiler.cpp | 18 +- .../share/compiler/abstractCompiler.hpp | 24 +- .../share/compiler/aotCompileBroker.cpp | 242 + .../share/compiler/aotCompileBroker.hpp | 38 + src/hotspot/share/compiler/compilationLog.cpp | 6 +- .../share/compiler/compilationPolicy.cpp | 199 +- .../share/compiler/compilationPolicy.hpp | 9 +- src/hotspot/share/compiler/compileBroker.cpp | 610 ++- src/hotspot/share/compiler/compileBroker.hpp | 44 +- src/hotspot/share/compiler/compileTask.cpp | 53 +- src/hotspot/share/compiler/compileTask.hpp | 49 +- .../share/compiler/compilerDefinitions.hpp | 3 + .../share/compiler/compilerDirectives.cpp | 10 +- .../share/compiler/compilerDirectives.hpp | 5 +- src/hotspot/share/compiler/compilerOracle.hpp | 3 + src/hotspot/share/compiler/compilerThread.cpp | 2 +- src/hotspot/share/compiler/compilerThread.hpp | 5 + .../share/compiler/compiler_globals.hpp | 35 + src/hotspot/share/compiler/oopMap.cpp | 1 - .../shenandoah/c1/shenandoahBarrierSetC1.hpp | 26 + src/hotspot/share/gc/z/c1/zBarrierSetC1.hpp | 1 + src/hotspot/share/include/cds.h | 2 +- src/hotspot/share/oops/instanceKlass.cpp | 7 +- src/hotspot/share/oops/instanceKlass.hpp | 2 + src/hotspot/share/oops/klass.cpp | 18 + src/hotspot/share/oops/klass.hpp | 5 + src/hotspot/share/oops/method.cpp | 12 +- src/hotspot/share/oops/methodCounters.cpp | 24 + src/hotspot/share/oops/methodCounters.hpp | 17 +- src/hotspot/share/oops/methodData.cpp | 2 +- src/hotspot/share/oops/trainingData.hpp | 84 +- src/hotspot/share/opto/c2_globals.hpp | 17 + src/hotspot/share/opto/c2compiler.cpp | 13 +- src/hotspot/share/opto/compile.cpp | 44 +- src/hotspot/share/opto/compile.hpp | 11 + src/hotspot/share/opto/doCall.cpp | 27 + src/hotspot/share/opto/graphKit.cpp | 46 +- src/hotspot/share/opto/graphKit.hpp | 1 + src/hotspot/share/opto/library_call.cpp | 5 +- src/hotspot/share/opto/library_call.hpp | 2 +- src/hotspot/share/opto/memnode.cpp | 17 +- src/hotspot/share/opto/output.cpp | 31 +- src/hotspot/share/opto/parse.hpp | 7 + src/hotspot/share/opto/parse1.cpp | 158 +- src/hotspot/share/opto/parse2.cpp | 2 +- src/hotspot/share/opto/rootnode.cpp | 2 + src/hotspot/share/opto/runtime.cpp | 22 + src/hotspot/share/opto/runtime.hpp | 12 +- src/hotspot/share/prims/whitebox.cpp | 2 +- src/hotspot/share/runtime/deoptimization.cpp | 10 +- .../flags/jvmFlagConstraintsCompiler.cpp | 21 + .../flags/jvmFlagConstraintsCompiler.hpp | 1 + src/hotspot/share/runtime/frame.cpp | 5 +- src/hotspot/share/runtime/globals.hpp | 9 +- src/hotspot/share/runtime/init.cpp | 2 + src/hotspot/share/runtime/java.cpp | 11 +- src/hotspot/share/runtime/java.hpp | 2 + src/hotspot/share/runtime/sharedRuntime.cpp | 4 +- .../share/runtime/stubDeclarations.hpp | 1 + src/hotspot/share/runtime/threads.cpp | 4 + src/hotspot/share/runtime/vmStructs.cpp | 5 + .../share/utilities/nativeStackPrinter.cpp | 2 +- src/hotspot/share/utilities/vmError.cpp | 2 +- test/hotspot/jtreg/ProblemList-AotJdk.txt | 11 + test/hotspot/jtreg/TEST.groups | 7 +- .../commands/CompileLevelPrintTest.java | 4 +- .../compiler/inlining/LateInlinePrinting.java | 25 +- .../lib/ir_framework/TestFramework.java | 1 + .../spectrapredefineclass/Launcher.java | 4 +- .../Launcher.java | 4 +- .../TestMachTempsAcrossSafepoints.java | 2 + .../jtreg/runtime/cds/MetaspaceAllocGaps.java | 3 + .../aotCache/AOTCompatibleOopCompression.java | 2 +- .../appcds/aotCache/AOTCompileEagerly.java | 4 +- .../aotClassLinking/TestSetupAOTTest.java | 2 +- .../AOTCodeCPUFeatureIncompatibilityTest.java | 8 +- .../aotCode/AOTCodeCompressedOopsTest.java | 6 +- .../cds/appcds/aotCode/AOTCodeFlags.java | 113 +- .../cds/appcds/aotCode/AOTCodeTest.java | 6 +- .../AOTCodeTestUnknownExternAddress.java | 156 + .../appcds/aotCode/TestAOTCodeCounters.java | 124 + .../appcds/aotCode/TestAOTCodeFeatures.java | 110 + .../appcds/aotCode/TestDisableAOTCode.java | 219 + .../cds/appcds/aotCode/TestVerifyAOTCode.java | 120 + .../runtime/cds/appcds/aotFlags/AOTFlags.java | 19 +- .../appcds/aotProfile/AOTProfileFlags.java | 4 + test/jdk/ProblemList-AotJdk.txt | 10 + test/jtreg-ext/requires/VMProps.java | 1 + test/lib/jdk/test/lib/cds/CDSAppTester.java | 8 +- test/setup_aot/TestSetupAOT.java | 2 +- 179 files changed, 8641 insertions(+), 1553 deletions(-) create mode 100644 src/hotspot/share/compiler/aotCompileBroker.cpp create mode 100644 src/hotspot/share/compiler/aotCompileBroker.hpp create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestAOTCodeCounters.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestAOTCodeFeatures.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestDisableAOTCode.java create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestVerifyAOTCode.java diff --git a/make/hotspot/lib/JvmFeatures.gmk b/make/hotspot/lib/JvmFeatures.gmk index ced4f1f6350..7097bce89e1 100644 --- a/make/hotspot/lib/JvmFeatures.gmk +++ b/make/hotspot/lib/JvmFeatures.gmk @@ -123,6 +123,7 @@ ifneq ($(call check-jvm-feature, cds), true) aotCodeCache.cpp \ classLoaderDataShared.cpp \ classLoaderExt.cpp \ + aotCompileBroker.cpp \ systemDictionaryShared.cpp \ trainingData.cpp JVM_EXCLUDE_PATTERNS += cds/ diff --git a/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp index eba058885b2..674f79afbb3 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRGenerator_aarch64.cpp @@ -236,15 +236,6 @@ LIR_Opr LIRGenerator::load_immediate(jlong x, BasicType type) { } - -void LIRGenerator::increment_counter(address counter, BasicType type, int step) { - LIR_Opr pointer = new_pointer_register(); - __ move(LIR_OprFact::intptrConst(counter), pointer); - LIR_Address* addr = new LIR_Address(pointer, type); - increment_counter(addr, step); -} - - void LIRGenerator::increment_counter(LIR_Address* addr, int step) { LIR_Opr imm; switch(addr->type()) { diff --git a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp index 60f8cf99560..72b8c84e237 100644 --- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp @@ -315,7 +315,7 @@ int C1_MacroAssembler::scalarized_entry(const CompiledEntrySignature* ces, int f // C1 code is not hot enough to micro optimize the nmethod entry barrier with an out-of-line stub bs->nmethod_entry_barrier(this, nullptr /* slow_path */, nullptr /* continuation */, nullptr /* guard */); - mov(r19, (intptr_t) ces->method()); + mov_metadata(r19, ces->method()); if (is_value_ro_entry) { far_call(RuntimeAddress(Runtime1::entry_for(StubId::c1_buffer_value_args_no_receiver_id))); } else { diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index a59e4ed6afe..22b1b807fa6 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -2674,7 +2674,7 @@ bool C2_MacroAssembler::in_scratch_emit_size() { return MacroAssembler::in_scratch_emit_size(); } -static void abort_verify_int_in_range(uint idx, jint val, jint lo, jint hi) { +void C2_MacroAssembler::abort_verify_int_in_range(uint idx, jint val, jint lo, jint hi) { fatal("Invalid CastII, idx: %u, val: %d, lo: %d, hi: %d", idx, val, lo, hi); } @@ -2713,7 +2713,7 @@ void C2_MacroAssembler::verify_int_in_range(uint idx, const TypeInt* t, Register BLOCK_COMMENT("} verify_int_in_range"); } -static void abort_verify_long_in_range(uint idx, jlong val, jlong lo, jlong hi) { +void C2_MacroAssembler::abort_verify_long_in_range(uint idx, jlong val, jlong lo, jlong hi) { fatal("Invalid CastLL, idx: %u, val: " JLONG_FORMAT ", lo: " JLONG_FORMAT ", hi: " JLONG_FORMAT, idx, val, lo, hi); } diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp index 082cdbe09c1..83739021185 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp @@ -239,6 +239,9 @@ void vector_signum_sve(FloatRegister dst, FloatRegister src, FloatRegister zero, FloatRegister one, FloatRegister vtmp, PRegister pgtmp, SIMD_RegVariant T); + static void abort_verify_int_in_range(uint idx, jint val, jint lo, jint hi); + static void abort_verify_long_in_range(uint idx, jlong val, jlong lo, jlong hi); + void verify_int_in_range(uint idx, const TypeInt* t, Register val, Register tmp); void verify_long_in_range(uint idx, const TypeLong* t, Register val, Register tmp); diff --git a/src/hotspot/cpu/aarch64/compiledIC_aarch64.cpp b/src/hotspot/cpu/aarch64/compiledIC_aarch64.cpp index 640cd495383..571cb3e949a 100644 --- a/src/hotspot/cpu/aarch64/compiledIC_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/compiledIC_aarch64.cpp @@ -114,12 +114,19 @@ void CompiledDirectCall::set_stub_to_clean(static_stub_Relocation* static_stub) address stub = static_stub->addr(); assert(stub != nullptr, "stub not found"); assert(CompiledICLocker::is_safe(stub), "mt unsafe call"); + // Creation also verifies the object. NativeMovConstReg* method_holder = nativeMovConstReg_at(stub + NativeInstruction::instruction_size); method_holder->set_data(0); - NativeJump* jump = nativeJump_at(method_holder->next_instruction_address()); - jump->set_jump_destination((address)-1); + + // See comment about AOT in set_to_interpreted(). + address next_instr = method_holder->next_instruction_address(); + if (nativeInstruction_at(next_instr)->is_general_jump()) { + nativeGeneralJump_at(next_instr)->set_jump_destination((address)-1); + } else { + nativeJump_at(next_instr)->set_jump_destination((address)-1); + } } //----------------------------------------------------------------------------- diff --git a/src/hotspot/cpu/aarch64/gc/g1/g1BarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/g1/g1BarrierSetAssembler_aarch64.cpp index 2a1987bffd2..98ae760ca89 100644 --- a/src/hotspot/cpu/aarch64/gc/g1/g1BarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/g1/g1BarrierSetAssembler_aarch64.cpp @@ -246,7 +246,7 @@ static void generate_post_barrier(MacroAssembler* masm, assert_different_registers(store_addr, new_val, thread, tmp1, tmp2, noreg, rscratch1); // Does store cross heap regions? - #if INCLUDE_CDS +#if INCLUDE_CDS // AOT code needs to load the barrier grain shift from the aot // runtime constants area in the code cache otherwise we can compile // it as an immediate operand @@ -303,7 +303,7 @@ static void generate_c2_barrier_runtime_call(MacroAssembler* masm, G1BarrierStub __ mov(c_rarg0, arg); } __ mov(c_rarg1, rthread); - __ mov(rscratch1, runtime_path); + __ lea(rscratch1, RuntimeAddress(runtime_path)); __ blr(rscratch1); } diff --git a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp index ced01ec3e6a..69a04f0fac5 100644 --- a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp @@ -1150,7 +1150,7 @@ void ZBarrierSetAssembler::generate_c2_load_barrier_stub(MacroAssembler* masm, Z { SaveLiveRegisters save_live_registers(masm, stub); ZSetupArguments setup_arguments(masm, stub); - __ mov(rscratch1, stub->slow_path()); + __ lea(rscratch1, RuntimeAddress(stub->slow_path())); __ blr(rscratch1); } // Stub exit diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index e3731a13609..dd9215c55bd 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -330,6 +330,16 @@ class Patcher : public AllStatic { return 2; } static int immediate(address insn_addr, address &target) { + // Metadata pointers are either narrow (32 bits) or wide (48 bits). + // We encode narrow ones by setting the upper 16 bits in the first + // instruction. + if (Instruction_aarch64::extract(insn_at(insn_addr, 0), 31, 21) == 0b11010010101) { + assert(nativeInstruction_at(insn_addr+4)->is_movk(), "wrong insns in patch"); + narrowKlass nk = CompressedKlassPointers::encode((Klass*)target); + Instruction_aarch64::patch(insn_addr, 20, 5, nk >> 16); + Instruction_aarch64::patch(insn_addr+4, 20, 5, nk & 0xffff); + return 2; + } assert(Instruction_aarch64::extract(insn_at(insn_addr, 0), 31, 21) == 0b11010010100, "must be"); uint64_t dest = (uint64_t)target; // Move wide constant @@ -457,6 +467,16 @@ class AArch64Decoder : public AllStatic { } static int immediate(address insn_addr, address &target) { uint32_t *insns = (uint32_t *)insn_addr; + // Metadata pointers are either narrow (32 bits) or wide (48 bits). + // We encode narrow ones by setting the upper 16 bits in the first + // instruction. + if (Instruction_aarch64::extract(insns[0], 31, 21) == 0b11010010101) { + assert(nativeInstruction_at(insn_addr+4)->is_movk(), "wrong insns in patch"); + narrowKlass nk = (narrowKlass)((uint32_t(Instruction_aarch64::extract(insns[0], 20, 5)) << 16) + + uint32_t(Instruction_aarch64::extract(insns[1], 20, 5))); + target = (address)CompressedKlassPointers::decode(nk); + return 2; + } assert(Instruction_aarch64::extract(insns[0], 31, 21) == 0b11010010100, "must be"); // Move wide constant: movz, movk, movk. See movptr(). assert(nativeInstruction_at(insns+1)->is_movk(), "wrong insns in patch"); @@ -2468,7 +2488,7 @@ void MacroAssembler::mov(Register r, Address dest) { // reach anywhere. void MacroAssembler::movptr(Register r, uintptr_t imm64) { #ifndef PRODUCT - { + if (!AOTCodeCache::is_on_for_dump()) { char buffer[64]; os::snprintf_checked(buffer, sizeof(buffer), "0x%" PRIX64, (uint64_t)imm64); block_comment(buffer); @@ -2526,7 +2546,7 @@ void MacroAssembler::mov(FloatRegister Vd, SIMD_Arrangement T, uint64_t imm64) { void MacroAssembler::mov_immediate64(Register dst, uint64_t imm64) { #ifndef PRODUCT - { + if (!AOTCodeCache::is_on_for_dump()) { char buffer[64]; os::snprintf_checked(buffer, sizeof(buffer), "0x%" PRIX64, imm64); block_comment(buffer); @@ -2639,7 +2659,7 @@ void MacroAssembler::mov_immediate64(Register dst, uint64_t imm64) void MacroAssembler::mov_immediate32(Register dst, uint32_t imm32) { #ifndef PRODUCT - { + if (!AOTCodeCache::is_on_for_dump()) { char buffer[64]; os::snprintf_checked(buffer, sizeof(buffer), "0x%" PRIX32, imm32); block_comment(buffer); diff --git a/src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp b/src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp index 5d6bcb45613..38be53ac6b6 100644 --- a/src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/relocInfo_aarch64.cpp @@ -24,6 +24,7 @@ */ #include "asm/macroAssembler.hpp" +#include "code/aotCodeCache.hpp" #include "code/nmethod.hpp" #include "code/relocInfo.hpp" #include "nativeInst_aarch64.hpp" @@ -97,7 +98,10 @@ void trampoline_stub_Relocation::pd_fix_owner_after_move() { NativeCall* call = nativeCall_at(owner()); address trampoline = addr(); address dest = nativeCallTrampolineStub_at(trampoline)->destination(); - if (!Assembler::reachable_from_branch_at(owner(), dest)) { + // Force trampoline as call's destination in assembly phase. + // The call will be fixed when AOT code is loaded in production run + // depending on distance at that time. + if (AOTCodeCache::is_on_for_dump() || !Assembler::reachable_from_branch_at(owner(), dest)) { dest = trampoline; } call->set_destination(dest); @@ -122,3 +126,11 @@ void poll_Relocation::fix_relocation_after_move(const CodeBuffer* src, CodeBuffe void metadata_Relocation::pd_fix_value(address x) { } + +address trampoline_stub_Relocation::pd_destination() { + return nativeCallTrampolineStub_at(addr())->destination(); +} + +void trampoline_stub_Relocation::pd_set_destination(address x) { + nativeCallTrampolineStub_at(addr())->set_destination(x); +} diff --git a/src/hotspot/cpu/arm/c1_LIRGenerator_arm.cpp b/src/hotspot/cpu/arm/c1_LIRGenerator_arm.cpp index 7da13da1bf1..fd6f2979333 100644 --- a/src/hotspot/cpu/arm/c1_LIRGenerator_arm.cpp +++ b/src/hotspot/cpu/arm/c1_LIRGenerator_arm.cpp @@ -297,14 +297,6 @@ LIR_Opr LIRGenerator::load_immediate(jlong x, BasicType type) { } -void LIRGenerator::increment_counter(address counter, BasicType type, int step) { - LIR_Opr pointer = new_pointer_register(); - __ move(LIR_OprFact::intptrConst(counter), pointer); - LIR_Address* addr = new LIR_Address(pointer, type); - increment_counter(addr, step); -} - - void LIRGenerator::increment_counter(LIR_Address* addr, int step) { LIR_Opr temp = new_register(addr->type()); __ move(addr, temp); diff --git a/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp index 0dcc19004cc..3c87869879a 100644 --- a/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp @@ -259,14 +259,6 @@ LIR_Opr LIRGenerator::load_immediate(jlong x, BasicType type) { } -void LIRGenerator::increment_counter(address counter, BasicType type, int step) { - LIR_Opr pointer = new_pointer_register(); - __ move(LIR_OprFact::intptrConst(counter), pointer); - LIR_Address* addr = new LIR_Address(pointer, type); - increment_counter(addr, step); -} - - void LIRGenerator::increment_counter(LIR_Address* addr, int step) { LIR_Opr temp = new_register(addr->type()); __ move(addr, temp); diff --git a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp index 174b93ce06e..49cdb7c86e5 100644 --- a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp @@ -208,13 +208,6 @@ LIR_Opr LIRGenerator::load_immediate(jlong x, BasicType type) { return r; } -void LIRGenerator::increment_counter(address counter, BasicType type, int step) { - LIR_Opr pointer = new_pointer_register(); - __ move(LIR_OprFact::intptrConst(counter), pointer); - LIR_Address* addr = new LIR_Address(pointer, type); - increment_counter(addr, step); -} - void LIRGenerator::increment_counter(LIR_Address* addr, int step) { LIR_Opr reg = new_register(addr->type()); __ load(addr, reg); diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index bdcf60f41c7..b49a112606f 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -1861,8 +1861,7 @@ void C2_MacroAssembler::arrays_hashcode_v(Register ary, Register cnt, Register r const VectorRegister v_coeffs = v6; const VectorRegister v_tmp = v8; - const address adr_pows31 = StubRoutines::riscv::arrays_hashcode_powers_of_31() - + sizeof(jint); + const address adr_pows31 = StubRoutines::riscv::arrays_hashcode_powers_of_31(); Label VEC_LOOP, DONE, SCALAR_TAIL, SCALAR_TAIL_LOOP; // NB: at this point (a) 'result' already has some value, @@ -1872,6 +1871,9 @@ void C2_MacroAssembler::arrays_hashcode_v(Register ary, Register cnt, Register r beqz(t0, SCALAR_TAIL); la(t1, ExternalAddress(adr_pows31)); + // Keep the relocation target at the registered stub entry, then skip the + // first element when loading the vector coefficients. + addi(t1, t1, sizeof(jint)); lw(pow31_highest, Address(t1, -1 * sizeof(jint))); vsetvli(consumed, cnt, Assembler::e32, Assembler::m2); diff --git a/src/hotspot/cpu/riscv/gc/g1/g1BarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/g1/g1BarrierSetAssembler_riscv.cpp index 19b744cf976..7bba2bdaf0b 100644 --- a/src/hotspot/cpu/riscv/gc/g1/g1BarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/g1/g1BarrierSetAssembler_riscv.cpp @@ -324,8 +324,7 @@ static void generate_c2_barrier_runtime_call(MacroAssembler* masm, G1BarrierStub __ mv(c_rarg0, arg); } __ mv(c_rarg1, xthread); - __ mv(t1, runtime_path); - __ jalr(t1); + __ rt_call(runtime_path); } void G1BarrierSetAssembler::g1_write_barrier_pre_c2(MacroAssembler* masm, diff --git a/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp index 9fbc59fe5ce..11f2585dbe8 100644 --- a/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp @@ -749,8 +749,7 @@ void ZBarrierSetAssembler::generate_c2_load_barrier_stub(MacroAssembler* masm, Z { SaveLiveRegisters save_live_registers(masm, stub); ZSetupArguments setup_arguments(masm, stub); - __ mv(t1, stub->slow_path()); - __ jalr(t1); + __ rt_call(stub->slow_path()); } // Stub exit diff --git a/src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp b/src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp index 57223cf4390..3d3590c6047 100644 --- a/src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp +++ b/src/hotspot/cpu/riscv/globalDefinitions_riscv.hpp @@ -48,6 +48,14 @@ const bool CCallingConventionRequiresIntsAsLongs = false; #define USE_POINTERS_TO_REGISTER_IMPL_ARRAY +// A reloc call keeps its destination in an address stub, so the trampoline +// relocation has to be able to read and write it. Note that we deliberately do +// not define USE_TRAMPOLINE_STUB_FIX_OWNER: the call site reaches its stub with +// an auipc pair inside the same blob and so stays in range after a move, and +// NativeCall::set_destination() already picks jal or jalr through +// optimize_call() according to the distance to the final destination. +#define USE_TRAMPOLINE_STUB_DESTINATION + // auipc useable for all cc -> cc calls and jumps #define CODE_CACHE_SIZE_LIMIT ((2*G)-(2*K)) diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index 9c383b6ddd5..6238ea4d3c3 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -3070,6 +3070,10 @@ static int patch_offset_in_conditional_branch(address branch, int64_t offset) { static int patch_offset_in_pc_relative(address branch, int64_t offset) { const int PC_RELATIVE_INSTRUCTION_NUM = 2; // auipc, addi/jalr/load + // The auipc pair can only encode a 32-bit pc-relative offset. Check the same + // range the emitters check, otherwise the high bits would be silently dropped. + guarantee(MacroAssembler::is_valid_32bit_offset(offset), + "offset " INT64_FORMAT " out of range for auipc", offset); Assembler::patch(branch, 31, 12, ((offset + 0x800) >> 12) & 0xfffff); // Auipc. offset[31:12] ==> branch[31:12] Assembler::patch(branch + 4, 31, 20, offset & 0xfff); // Addi/Jalr/Load. offset[11:0] ==> branch[31:20] return PC_RELATIVE_INSTRUCTION_NUM * MacroAssembler::instruction_size; @@ -3254,6 +3258,24 @@ int MacroAssembler::patch_oop(address insn_addr, address o) { return -1; } +int MacroAssembler::patch_metadata(address insn_addr, address metadata) { + // Metadata is either a narrow klass (32 bits, materialized by set_narrow_klass() + // with li32) or a full 48-bit pointer (materialized by mov_metadata() with movptr). + if (MacroAssembler::is_li32_at(insn_addr)) { + // Move narrow klass + Metadata* value = reinterpret_cast(metadata); + assert(value != nullptr && value->is_klass(), "narrow metadata must be a klass"); + narrowKlass nk = CompressedKlassPointers::encode(static_cast(value)); + return patch_imm_in_li32(insn_addr, static_cast(nk)); + } else if (MacroAssembler::is_movptr1_at(insn_addr) || + MacroAssembler::is_movptr2_at(insn_addr)) { + // Move wide metadata + return pd_patch_instruction_size(insn_addr, metadata); + } + ShouldNotReachHere(); + return -1; +} + void MacroAssembler::reinit_heapbase() { if (UseCompressedOops) { if (Universe::is_fully_initialized() && !AOTCodeCache::is_on_for_dump()) { @@ -3280,7 +3302,10 @@ void MacroAssembler::movptr(Register Rd, address addr, Register temp) { void MacroAssembler::movptr(Register Rd, address addr, int32_t &offset, Register temp) { uint64_t uimm64 = (uint64_t)addr; #ifndef PRODUCT - { + // Skip the block comment when dumping the AOT code cache: the comment text is + // the build-time address, which is meaningless in a future JVM instance and + // would flood the AOT C string table with unique entries. + if (!AOTCodeCache::is_on_for_dump()) { char buffer[64]; os::snprintf_checked(buffer, sizeof(buffer), "0x%" PRIx64, uimm64); block_comment(buffer); diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index bd736a985fd..cbb56091aae 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -514,6 +514,7 @@ class MacroAssembler: public Assembler { } static int patch_oop(address insn_addr, address o); + static int patch_metadata(address insn_addr, address metadata); static address get_target_of_li32(address insn_addr); static int patch_imm_in_li32(address branch, int32_t target); @@ -847,15 +848,17 @@ class MacroAssembler: public Assembler { void double_blt(FloatRegister Rs1, FloatRegister Rs2, Label &l, bool is_far = false, bool is_unordered = false); void double_bgt(FloatRegister Rs1, FloatRegister Rs2, Label &l, bool is_far = false, bool is_unordered = false); -private: +public: // The signed 20-bit upper imm can materialize at most negative 0xF...F80000000, two G. // The following signed 12-bit imm can at max subtract 0x800, two K, from that previously loaded two G. - bool is_valid_32bit_offset(int64_t x) { + // Also used when patching an auipc pair, so that emit and patch agree on the reachable range. + static bool is_valid_32bit_offset(int64_t x) { constexpr int64_t twoG = (2 * G); constexpr int64_t twoK = (2 * K); return x < (twoG - twoK) && x >= (-twoG - twoK); } +private: // Ensure that the auipc can reach the destination at x from anywhere within // the code cache so that if it is relocated we know it will still reach. bool is_32bit_offset_from_codecache(int64_t x) { diff --git a/src/hotspot/cpu/riscv/relocInfo_riscv.cpp b/src/hotspot/cpu/riscv/relocInfo_riscv.cpp index f7793ce81e3..3632fc35f90 100644 --- a/src/hotspot/cpu/riscv/relocInfo_riscv.cpp +++ b/src/hotspot/cpu/riscv/relocInfo_riscv.cpp @@ -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. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -50,6 +50,9 @@ void Relocation::pd_set_data_value(address x, bool verify_only) { } break; } + case relocInfo::metadata_type: + bytes = MacroAssembler::patch_metadata(addr(), x); + break; default: bytes = MacroAssembler::pd_patch_instruction_size(addr(), x); break; @@ -128,3 +131,11 @@ void poll_Relocation::fix_relocation_after_move(const CodeBuffer* src, CodeBuffe void metadata_Relocation::pd_fix_value(address x) { } + +address trampoline_stub_Relocation::pd_destination() { + return reinterpret_cast

(Bytes::get_native_u8(addr())); +} + +void trampoline_stub_Relocation::pd_set_destination(address x) { + Bytes::put_native_u8(addr(), reinterpret_cast(x)); +} diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 9e974a277e9..ed528012ff3 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -2456,7 +2456,20 @@ encode %{ __ mov_metadata(dst_reg, (Metadata*)con, t0); } else { assert(rtype == relocInfo::none || rtype == relocInfo::external_word_type, "unexpected reloc type"); - __ mv(dst_reg, $src$$constant); + // Load fake address constants as a plain immediate. They are not real + // addresses (a bci may be used as a jsr return address, and compiling + // an unreachable unsafe access can produce a bad base) so they are of + // no interest to the AOT code cache, and movptr() only materializes + // the low 48 bits. + if (rtype == relocInfo::none || + (uint64_t)con >= (1ull << 48) || + con < (address)(uintptr_t)os::vm_page_size()) { + __ mv(dst_reg, $src$$constant); + } else { + // Use a relocation so that the address can be fixed up when this code + // is loaded from the AOT code cache in a new JVM instance. + __ la(dst_reg, Address(con, rtype)); + } } } %} diff --git a/src/hotspot/cpu/s390/c1_LIRGenerator_s390.cpp b/src/hotspot/cpu/s390/c1_LIRGenerator_s390.cpp index 4f3c9f860bc..07f5953fc9d 100644 --- a/src/hotspot/cpu/s390/c1_LIRGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/c1_LIRGenerator_s390.cpp @@ -205,13 +205,6 @@ LIR_Opr LIRGenerator::load_immediate(jlong x, BasicType type) { return r; } -void LIRGenerator::increment_counter(address counter, BasicType type, int step) { - LIR_Opr pointer = new_pointer_register(); - __ move(LIR_OprFact::intptrConst(counter), pointer); - LIR_Address* addr = new LIR_Address(pointer, type); - increment_counter(addr, step); -} - void LIRGenerator::increment_counter(LIR_Address* addr, int step) { __ add((LIR_Opr)addr, LIR_OprFact::intConst(step), (LIR_Opr)addr); } diff --git a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp index 2ee98f22c3f..c2db1fc6e2c 100644 --- a/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_MacroAssembler_s390.cpp @@ -124,7 +124,7 @@ int C1_MacroAssembler::scalarized_entry(const CompiledEntrySignature* ces, int f // Z_R13 is the method register expected by c1_buffer_value_args (see // c1_Runtime1_s390.cpp, StubId::c1_buffer_value_args_id handler). - load_const_optimized(Z_R13, (intptr_t)(ces->method())); + load_const(Z_R13, constant_metadata_address(ces->method())); if (is_value_ro_entry) { call_c_opt(Runtime1::entry_for(StubId::c1_buffer_value_args_no_receiver_id)); } else { diff --git a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp index b2efdfd17ff..94009af28b8 100644 --- a/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRGenerator_x86.cpp @@ -217,14 +217,6 @@ LIR_Opr LIRGenerator::load_immediate(jlong x, BasicType type) { return r; } -void LIRGenerator::increment_counter(address counter, BasicType type, int step) { - LIR_Opr pointer = new_pointer_register(); - __ move(LIR_OprFact::intptrConst(counter), pointer); - LIR_Address* addr = new LIR_Address(pointer, type); - increment_counter(addr, step); -} - - void LIRGenerator::increment_counter(LIR_Address* addr, int step) { __ add((LIR_Opr)addr, LIR_OprFact::intConst(step), (LIR_Opr)addr); } diff --git a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp index 777e9587918..e64fcfb111e 100644 --- a/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_MacroAssembler_x86.cpp @@ -299,7 +299,7 @@ int C1_MacroAssembler::scalarized_entry(const CompiledEntrySignature* ces, int f // C1 code is not hot enough to micro optimize the nmethod entry barrier with an out-of-line stub bs->nmethod_entry_barrier(this, nullptr /* slow_path */, nullptr /* continuation */); - movptr(rbx, (intptr_t)(ces->method())); + mov_metadata(rbx, ces->method()); if (is_value_ro_entry) { call(RuntimeAddress(Runtime1::entry_for(StubId::c1_buffer_value_args_no_receiver_id))); } else { diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index 1285b742649..dcce0aa9c00 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -588,7 +588,7 @@ void C2_MacroAssembler::fast_unlock(Register obj, Register reg_rax, Register t, // C2 uses the value of ZF to determine the continuation. } -static void abort_verify_int_in_range(uint idx, jint val, jint lo, jint hi) { +void C2_MacroAssembler::abort_verify_int_in_range(uint idx, jint val, jint lo, jint hi) { fatal("Invalid CastII, idx: %u, val: %d, lo: %d, hi: %d", idx, val, lo, hi); } @@ -651,7 +651,7 @@ void C2_MacroAssembler::verify_int_in_range(uint idx, const TypeInt* t, Register BLOCK_COMMENT("} // CastII"); } -static void abort_verify_long_in_range(uint idx, jlong val, jlong lo, jlong hi) { +void C2_MacroAssembler::abort_verify_long_in_range(uint idx, jlong val, jlong lo, jlong hi) { fatal("Invalid CastLL, idx: %u, val: " JLONG_FORMAT ", lo: " JLONG_FORMAT ", hi: " JLONG_FORMAT, idx, val, lo, hi); } @@ -3572,9 +3572,9 @@ void C2_MacroAssembler::arrays_hashcode(Register ary1, Register cnt1, Register r // release bound // vresult *= IntVector.fromArray(I256, power_of_31_backwards, 1); + lea(tmp2, ExternalAddress(StubRoutines::x86::arrays_hashcode_powers_of_31() + (0 * sizeof(jint)))); for (int idx = 0; idx < 4; idx++) { - lea(tmp2, ExternalAddress(StubRoutines::x86::arrays_hashcode_powers_of_31() + ((8 * idx + 1) * sizeof(jint)))); - arrays_hashcode_elvload(vcoef[idx], Address(tmp2, 0), T_INT); + arrays_hashcode_elvload(vcoef[idx], Address(tmp2, (int)((8 * idx + 1) * sizeof(jint))), T_INT); vpmulld(vresult[idx], vresult[idx], vcoef[idx], Assembler::AVX_256bit); } // result += vresult.reduceLanes(ADD); diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index 926f9390ae0..a6471cbd3fd 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -40,6 +40,9 @@ Register t, Register thread); void fast_unlock(Register obj, Register reg_rax, Register t, Register thread); + static void abort_verify_int_in_range(uint idx, jint val, jint lo, jint hi); + static void abort_verify_long_in_range(uint idx, jlong val, jlong lo, jlong hi); + void verify_int_in_range(uint idx, const TypeInt* t, Register val); void verify_long_in_range(uint idx, const TypeLong* t, Register val, Register tmp); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp index 3e84023ef0d..2575d0e5007 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp @@ -25,6 +25,7 @@ #include "asm/macroAssembler.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/barrierSetAssembler.hpp" +#include "gc/shared/gc_globals.hpp" #include "oops/objArrayKlass.hpp" #include "runtime/arguments.hpp" #include "runtime/sharedRuntime.hpp" diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index cbbbc3e5e72..62c30a668d7 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -7836,7 +7836,15 @@ instruct loadConP(rRegP dst, immP con) %{ format %{ "movq $dst, $con\t# ptr" %} ins_encode %{ - __ mov64($dst$$Register, $con$$constant, $con->constant_reloc(), RELOC_IMM64); + address con = (address)$con$$constant; + relocInfo::relocType rtype = $con->constant_reloc(); + if (rtype == relocInfo::external_word_type && + (con == (address)-1 || + con < (address)(uintptr_t)os::vm_page_size())) { + // Don't use relocation for fake address + rtype = relocInfo::none; + } + __ mov64($dst$$Register, $con$$constant, rtype, RELOC_IMM64); %} ins_pipe(ialu_reg_fat); // XXX %} diff --git a/src/hotspot/share/asm/codeBuffer.cpp b/src/hotspot/share/asm/codeBuffer.cpp index 55ba27fcbe1..84f158b978e 100644 --- a/src/hotspot/share/asm/codeBuffer.cpp +++ b/src/hotspot/share/asm/codeBuffer.cpp @@ -1013,8 +1013,8 @@ void CodeBuffer::verify_section_allocation() { } guarantee(other->disjoint(sect), "sanity"); } - guarantee(sect->end() <= tend, "sanity"); - guarantee(sect->end() <= sect->limit(), "sanity"); + guarantee(sect->end() <= tend, "sanity, sect_end: " PTR_FORMAT " tend: " PTR_FORMAT " size: %d", p2i(sect->end()), p2i(tend), (int)_total_size); + guarantee(sect->end() <= sect->limit(), "sanity, sect_end: " PTR_FORMAT " sect_limit: " PTR_FORMAT, p2i(sect->end()), p2i(sect->limit())); } } @@ -1115,7 +1115,10 @@ AsmRemarks::AsmRemarks() { } AsmRemarks::~AsmRemarks() { - assert(_remarks == nullptr, "Must 'clear()' before deleting!"); + if (_remarks != nullptr) { + clear(); + } + postcond(_remarks == nullptr); } void AsmRemarks::init() { @@ -1172,7 +1175,10 @@ DbgStrings::DbgStrings() { } DbgStrings::~DbgStrings() { - assert(_strings == nullptr, "Must 'clear()' before deleting!"); + if (_strings != nullptr) { + clear(); + } + postcond(_strings == nullptr); } void DbgStrings::init() { @@ -1213,6 +1219,7 @@ const char* AsmRemarkCollection::insert(uint offset, const char* remstr) { } else { _remarks->push_back(cell); } + log_trace(codestrings)("AsmRemark::insert: offset=%u '%s'.", offset, remstr); return cell->string(); } diff --git a/src/hotspot/share/asm/codeBuffer.hpp b/src/hotspot/share/asm/codeBuffer.hpp index ff79b391fd1..15da890e008 100644 --- a/src/hotspot/share/asm/codeBuffer.hpp +++ b/src/hotspot/share/asm/codeBuffer.hpp @@ -440,6 +440,9 @@ class AsmRemarks { void share(const AsmRemarks &src); void clear(); + // Clear Collection reference when storing AOT code, + // new one will be created during AOT code load. + void clear_ref() { _remarks = nullptr; } uint print(uint offset, outputStream* strm = tty) const; // For testing purposes only. @@ -468,6 +471,9 @@ class DbgStrings { void share(const DbgStrings &src); void clear(); + // Clear Collection reference when storing AOT code, + // new one will be created during AOT code load. + void clear_ref() { _strings = nullptr; } // For testing purposes only. const DbgStringCollection* ref() const { return _strings; } diff --git a/src/hotspot/share/c1/c1_Compilation.cpp b/src/hotspot/share/c1/c1_Compilation.cpp index e6a5c004df6..10f506f5526 100644 --- a/src/hotspot/share/c1/c1_Compilation.cpp +++ b/src/hotspot/share/c1/c1_Compilation.cpp @@ -426,11 +426,14 @@ void Compilation::install_code(int frame_size) { exception_handler_table(), implicit_exception_table(), compiler(), + false, // has_clinit_barriers + false, // for_preload has_unsafe_access(), SharedRuntime::is_wide_vector(max_vector_size()), has_monitors(), has_scoped_access(), - _immediate_oops_patched + _immediate_oops_patched, + should_install_code() ); } @@ -475,8 +478,7 @@ void Compilation::compile_method() { // Note: make sure we mark the method as not compilable! CHECK_BAILOUT(); - if (should_install_code()) { - // install code + { // install code PhaseTraceTime timeit(_t_codeinstall); install_code(frame_size); } diff --git a/src/hotspot/share/c1/c1_Compiler.cpp b/src/hotspot/share/c1/c1_Compiler.cpp index 254c9ef81f9..945772d1fe4 100644 --- a/src/hotspot/share/c1/c1_Compiler.cpp +++ b/src/hotspot/share/c1/c1_Compiler.cpp @@ -30,6 +30,8 @@ #include "c1/c1_MacroAssembler.hpp" #include "c1/c1_Runtime1.hpp" #include "c1/c1_ValueType.hpp" +#include "code/aotCodeCache.hpp" +#include "compiler/compilationPolicy.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compilerDirectives.hpp" #include "interpreter/linkResolver.hpp" @@ -64,15 +66,32 @@ bool Compiler::init_c1_runtime() { void Compiler::initialize() { - // Buffer blob must be allocated per C1 compiler thread at startup - BufferBlob* buffer_blob = init_buffer_blob(); - + CompilerThread* thread = CompilerThread::current(); + bool is_aot_comp_thread = thread->is_aot_thread(); + // AOT code loading does not use scratch buffer but it needs + // to wait when normal C1 compiler thread initializes runtime. + // Except when C1 JIT compilation is disabled and we need to + // initialize runtime by AOT thread. + if (is_aot_comp_thread && CompilationPolicy::c1_count() > 0) { + wait_for_initialization(); + return; + } + // Buffer blob must be allocated per C1 compiler thread at startup. + BufferBlob* buffer_blob = is_aot_comp_thread ? nullptr : init_buffer_blob(thread); if (should_perform_init()) { + if (is_aot_comp_thread) { + buffer_blob = init_buffer_blob(thread); // for runtime initialization + } if (buffer_blob == nullptr || !init_c1_runtime()) { // When we come here we are in state 'initializing'; entire C1 compilation // can be shut down. set_state(failed); } else { + // AOT code loading does not use scratch buffer. + if (is_aot_comp_thread) { + CompilerThread::current()->set_buffer_blob(nullptr); + BufferBlob::free(buffer_blob); + } set_state(initialized); } } @@ -82,15 +101,15 @@ uint Compiler::code_buffer_size() { return Compilation::desired_max_code_buffer_size + Compilation::desired_max_constant_size; } -BufferBlob* Compiler::init_buffer_blob() { +BufferBlob* Compiler::init_buffer_blob(CompilerThread* thread) { // Allocate buffer blob once at startup since allocation for each // compilation seems to be too expensive (at least on Intel win32). - assert (CompilerThread::current()->get_buffer_blob() == nullptr, "Should initialize only once"); + assert (thread->get_buffer_blob() == nullptr, "Should initialize only once"); // Setup CodeBuffer. BufferBlob* buffer_blob = BufferBlob::create("C1 temporary CodeBuffer", code_buffer_size()); if (buffer_blob != nullptr) { - CompilerThread::current()->set_buffer_blob(buffer_blob); + thread->set_buffer_blob(buffer_blob); } return buffer_blob; @@ -252,6 +271,15 @@ bool Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { } void Compiler::compile_method(ciEnv* env, ciMethod* method, int entry_bci, bool install_code, DirectiveSet* directive) { + CompileTask* task = env->task(); + if (task->is_aot_load()) { + assert(install_code, "AOT code loading requires install_code"); + assert(!task->is_aot_preload(), "Pre-loading AOT code is not implemented for C1 code"); + AOTCodeCache::load_nmethod(env, method, entry_bci, this); + // We want to go quickly through AOT code load requests + // instead of spending time on normal compilation. + return; + } BufferBlob* buffer_blob = CompilerThread::current()->get_buffer_blob(); assert(buffer_blob != nullptr, "Must exist"); // invoke compilation diff --git a/src/hotspot/share/c1/c1_Compiler.hpp b/src/hotspot/share/c1/c1_Compiler.hpp index fde7c8e7932..39a74f2ea5d 100644 --- a/src/hotspot/share/c1/c1_Compiler.hpp +++ b/src/hotspot/share/c1/c1_Compiler.hpp @@ -28,13 +28,14 @@ #include "compiler/abstractCompiler.hpp" class DirectiveSet; +class CompilerThread; // There is one instance of the Compiler per CompilerThread. class Compiler: public AbstractCompiler { private: static bool init_c1_runtime(); - BufferBlob* init_buffer_blob(); + BufferBlob* init_buffer_blob(CompilerThread* thread); public: // Creation diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index bfa04d36825..50bad1daef2 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -1839,7 +1839,7 @@ void GraphBuilder::access_field(Bytecodes::Code code) { (!field->is_flat() && PatchALot); ValueStack* state_before = nullptr; - if (!holder->is_initialized() || needs_patching) { + if (!holder->is_initialized() || needs_patching || compilation()->env()->is_aot_compile()) { // save state before instruction for debug info when // deoptimization happens during patching state_before = copy_state_before(); @@ -2430,7 +2430,8 @@ void GraphBuilder::invoke(Bytecodes::Code code) { callee_holder->is_loaded()) { // the effect of symbolic reference resolution // callee is known => check if we have static binding - if ((code == Bytecodes::_invokestatic && klass->is_initialized()) || // invokestatic involves an initialization barrier on declaring class + if ((code == Bytecodes::_invokestatic && klass->is_initialized() && + !compilation()->env()->is_aot_compile()) || // invokestatic involves an initialization barrier on declaring class code == Bytecodes::_invokespecial || (code == Bytecodes::_invokevirtual && target->is_final_method()) || code == Bytecodes::_invokedynamic) { @@ -4199,6 +4200,8 @@ bool GraphBuilder::try_inline_full(ciMethod* callee, bool holder_known, bool ign if (!callee->holder()->is_linked()) INLINE_BAILOUT("callee's klass not linked yet"); if (bc == Bytecodes::_invokestatic && !callee->holder()->is_initialized()) INLINE_BAILOUT("callee's klass not initialized yet"); + if (bc == Bytecodes::_invokestatic && + compilation()->env()->is_aot_compile()) INLINE_BAILOUT("AOT compilation"); if (!callee->has_balanced_monitors()) INLINE_BAILOUT("callee's monitors do not match"); // Proper inlining of methods with jsrs requires a little more work. diff --git a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp index 0e13a181e91..045e8cdbbc7 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.cpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp @@ -36,6 +36,7 @@ #include "ci/ciObjArrayKlass.hpp" #include "ci/ciUtilities.hpp" #include "ci/ciValueKlass.hpp" +#include "code/aotCodeCache.hpp" #include "compiler/compilerDefinitions.inline.hpp" #include "compiler/compilerOracle.hpp" #include "gc/shared/barrierSet.hpp" @@ -668,7 +669,8 @@ void LIRGenerator::new_instance(LIR_Opr dst, ciInstanceKlass* klass, bool is_unr if (UseFastNewInstance && klass->is_loaded() && (allow_value || !klass->is_value_klass()) && !Klass::layout_helper_needs_slow_path(klass->layout_helper())) { - StubId stub_id = klass->is_initialized() ? StubId::c1_fast_new_instance_id : StubId::c1_fast_new_instance_init_check_id; + bool known_initialized = klass->is_initialized() && !compilation()->env()->is_aot_compile(); + StubId stub_id = known_initialized ? StubId::c1_fast_new_instance_id : StubId::c1_fast_new_instance_init_check_id; CodeStub* slow_path = new NewInstanceStub(klass_reg, dst, klass, info, stub_id); @@ -677,7 +679,7 @@ void LIRGenerator::new_instance(LIR_Opr dst, ciInstanceKlass* klass, bool is_unr assert(klass->size_helper() > 0, "illegal instance size"); const int instance_size = align_object_size(klass->size_helper()); __ allocate_object(dst, scratch1, scratch2, scratch3, scratch4, - oopDesc::header_size(), instance_size, klass_reg, !klass->is_initialized(), slow_path); + oopDesc::header_size(), instance_size, klass_reg, !known_initialized, slow_path); } else { CodeStub* slow_path = new NewInstanceStub(klass_reg, dst, klass, info, StubId::c1_new_instance_id); __ jump(slow_path); @@ -2500,7 +2502,11 @@ void LIRGenerator::do_Throw(Throw* x) { #ifndef PRODUCT if (PrintC1Statistics) { - increment_counter(Runtime1::throw_count_address(), T_INT); + BasicTypeList signature; + LIR_OprList* args = new LIR_OprList(); + call_runtime(&signature, args, + CAST_FROM_FN_PTR(address, Runtime1::increment_throw_count), + voidType, nullptr); } #endif @@ -3849,13 +3855,18 @@ void LIRGenerator::increment_event_counter_impl(CodeEmitInfo* info, int offset = -1; LIR_Opr counter_holder; if (level == CompLevel_limited_profile) { - MethodCounters* counters_adr = method->ensure_method_counters(); + ciMetadata* counters_adr = method->ensure_method_counters(); if (counters_adr == nullptr) { bailout("method counters allocation failed"); return; } - counter_holder = new_pointer_register(); - __ move(LIR_OprFact::intptrConst(counters_adr), counter_holder); + if (AOTCodeCache::is_dumping_code()) { + counter_holder = new_register(T_METADATA); + __ metadata2reg(counters_adr->constant_encoding(), counter_holder); + } else { + counter_holder = new_pointer_register(); + __ move(LIR_OprFact::intptrConst(counters_adr->constant_encoding()), counter_holder); + } offset = in_bytes(backedge ? MethodCounters::backedge_counter_offset() : MethodCounters::invocation_counter_offset()); } else if (level == CompLevel_full_profile) { diff --git a/src/hotspot/share/c1/c1_LIRGenerator.hpp b/src/hotspot/share/c1/c1_LIRGenerator.hpp index 3ce7e6f4d38..2706384c6f2 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.hpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.hpp @@ -354,7 +354,6 @@ class LIRGenerator: public InstructionVisitor, public BlockClosure { void arithmetic_call_op (Bytecodes::Code code, LIR_Opr result, LIR_OprList* args); - void increment_counter(address counter, BasicType type, int step = 1); void increment_counter(LIR_Address* addr, int step = 1); void arithmetic_op(Bytecodes::Code code, LIR_Opr result, LIR_Opr left, LIR_Opr right, LIR_Opr tmp, CodeEmitInfo* info = nullptr); diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index 1df3a033b7a..40c3f6a99b9 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -165,8 +165,55 @@ address Runtime1::arraycopy_count_address(BasicType type) { } } +JRT_LEAF(void, Runtime1::increment_throw_count()) + _throw_count++; +JRT_END -#endif +#endif // not PRODUCT + +#if INCLUDE_CDS +// publish external addresses defined in this file +void Runtime1::init_AOTAddressTable(GrowableArray
& external_addresses) { +#define ADD(count) external_addresses.append((address)(&count)); +#ifndef PRODUCT + ADD(_generic_arraycopystub_cnt); + ADD(_arraycopy_slowcase_cnt); + ADD(_arraycopy_checkcast_cnt); + ADD(_arraycopy_checkcast_attempt_cnt); + ADD(_new_type_array_slowcase_cnt); + ADD(_new_object_array_slowcase_cnt); + ADD(_new_null_free_array_slowcase_cnt); + ADD(_new_instance_slowcase_cnt); + ADD(_new_multi_array_slowcase_cnt); + ADD(_load_flat_array_slowcase_cnt); + ADD(_store_flat_array_slowcase_cnt); + ADD(_substitutability_check_slowcase_cnt); + ADD(_buffer_value_args_slowcase_cnt); + ADD(_buffer_value_args_no_receiver_slowcase_cnt); + ADD(_monitorenter_slowcase_cnt); + ADD(_monitorexit_slowcase_cnt); + ADD(_patch_code_slowcase_cnt); + ADD(_throw_range_check_exception_count); + ADD(_throw_index_exception_count); + ADD(_throw_div0_exception_count); + ADD(_throw_null_pointer_exception_count); + ADD(_throw_class_cast_exception_count); + ADD(_throw_incompatible_class_change_error_count); + ADD(_throw_illegal_monitor_state_exception_count); + ADD(_throw_identity_exception_count); + ADD(_throw_count); + + ADD(_byte_arraycopy_stub_cnt); + ADD(_short_arraycopy_stub_cnt); + ADD(_int_arraycopy_stub_cnt); + ADD(_long_arraycopy_stub_cnt); + ADD(_oop_arraycopy_stub_cnt); + + ADD(increment_throw_count); // function +#endif // not PRODUCT +#undef ADD +} +#endif // INCLUDE_CDS // Simple helper to see if the caller of a runtime stub which // entered the VM has been deoptimized @@ -293,8 +340,12 @@ bool Runtime1::initialize(BufferBlob* blob) { return false; } } - // disallow any further c1 stub generation - AOTCodeCache::set_c1_stubs_complete(); + BarrierSetC1* bs = BarrierSet::barrier_set()->barrier_set_c1(); + bool success = bs->generate_c1_runtime_stubs(blob); + if (success) { + // disallow any further c1 AOT stub generation + AOTCodeCache::set_c1_stubs_complete(); + } // printing #ifndef PRODUCT if (PrintSimpleStubs) { @@ -309,8 +360,7 @@ bool Runtime1::initialize(BufferBlob* blob) { } } #endif - BarrierSetC1* bs = BarrierSet::barrier_set()->barrier_set_c1(); - return bs->generate_c1_runtime_stubs(blob); + return success; } CodeBlob* Runtime1::blob_for(StubId id) { @@ -378,6 +428,10 @@ const char* Runtime1::name_for_address(address entry) { FUNCTION_CASE(entry, StubRoutines::dtanh()); FUNCTION_CASE(entry, StubRoutines::dcbrt()); +#ifndef PRODUCT + FUNCTION_CASE(entry, increment_throw_count); +#endif + #undef FUNCTION_CASE // Soft float adds more runtime names. @@ -988,7 +1042,8 @@ static Klass* resolve_field_return_klass(const methodHandle& caller, int bci, TR // We must load class, initialize class and resolve the field fieldDescriptor result; // initialize class if needed constantPoolHandle constants(THREAD, caller->constants()); - LinkResolver::resolve_field_access(result, constants, field_access.index(), caller, Bytecodes::java_code(code), CHECK_NULL); + LinkResolver::resolve_field_access(result, constants, field_access.index(), caller, + Bytecodes::java_code(code), ClassInitMode::init, CHECK_NULL); return result.field_holder(); } @@ -1139,7 +1194,8 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, StubId stub_id )) fieldDescriptor result; // initialize class if needed Bytecodes::Code code = field_access.code(); constantPoolHandle constants(current, caller_method->constants()); - LinkResolver::resolve_field_access(result, constants, field_access.index(), caller_method, Bytecodes::java_code(code), CHECK); + LinkResolver::resolve_field_access(result, constants, field_access.index(), caller_method, + Bytecodes::java_code(code), ClassInitMode::init, CHECK); patch_field_offset = result.offset(); patch_field_type = result.field_type(); diff --git a/src/hotspot/share/c1/c1_Runtime1.hpp b/src/hotspot/share/c1/c1_Runtime1.hpp index ed2e167cb1f..6dbef7675e2 100644 --- a/src/hotspot/share/c1/c1_Runtime1.hpp +++ b/src/hotspot/share/c1/c1_Runtime1.hpp @@ -76,6 +76,13 @@ class Runtime1: public AllStatic { static uint _throw_illegal_monitor_state_exception_count; static uint _throw_identity_exception_count; static uint _throw_count; + + static address arraycopy_count_address(BasicType type); + static void increment_throw_count(); +#endif + +#if INCLUDE_CDS +static void init_AOTAddressTable(GrowableArray
& external_addresses); #endif private: @@ -156,11 +163,6 @@ class Runtime1: public AllStatic { // method tracing static void trace_block_entry(jint block_id); -#ifndef PRODUCT - static address throw_count_address() { return (address)&_throw_count; } - static address arraycopy_count_address(BasicType type); -#endif - // directly accessible leaf routine static int is_instance_of(oopDesc* mirror, oopDesc* obj); diff --git a/src/hotspot/share/cds/aotCacheAccess.cpp b/src/hotspot/share/cds/aotCacheAccess.cpp index 117979f41b5..b8d853792dc 100644 --- a/src/hotspot/share/cds/aotCacheAccess.cpp +++ b/src/hotspot/share/cds/aotCacheAccess.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, 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,33 +23,133 @@ */ #include "cds/aotCacheAccess.hpp" -#include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" #include "cds/heapShared.hpp" #include "classfile/stringTable.hpp" +#include "compiler/compiler_globals.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "memory/virtualspace.hpp" #include "oops/instanceKlass.hpp" +#include "oops/method.hpp" + +bool AOTCacheAccess::can_generate_aot_code(address addr) { + assert(CDSConfig::is_dumping_final_static_archive(), "must be"); + return ArchiveBuilder::is_active() && ArchiveBuilder::current()->has_been_archived(addr); +} + +bool AOTCacheAccess::can_generate_aot_code_for(InstanceKlass* ik) { + assert(CDSConfig::is_dumping_final_static_archive(), "must be"); + if (!ArchiveBuilder::is_active()) { + return false; + } + ArchiveBuilder* builder = ArchiveBuilder::current(); + if (!builder->has_been_archived((address)ik)) { + return false; + } + if (ik->defined_by_other_loaders()) { + return false; + } + return true; +} + +Klass* AOTCacheAccess::try_narrow_ptr_to_klass(narrowPtr narrowp) { + Metadata* metadata = try_narrow_ptr_to_metadata(narrowp, sizeof(Klass)); + if (metadata == nullptr) { + return nullptr; + } + Klass* k = (Klass*)metadata; + return Klass::is_valid_aot_klass(k) ? k : nullptr; +} + +Method* AOTCacheAccess::try_narrow_ptr_to_method(narrowPtr narrowp) { + Metadata* metadata = try_narrow_ptr_to_metadata(narrowp, sizeof(Method)); + if (metadata == nullptr) { + return nullptr; + } + Method* m = (Method*)metadata; + return Method::is_valid_method(m) ? m : nullptr; +} + +Metadata* AOTCacheAccess::try_narrow_ptr_to_metadata(narrowPtr narrowp, size_t size) { + if (narrowp == AOTCompressedPointers::null()) { + return nullptr; + } + uintptr_t base = SharedBaseAddress; + uintptr_t low_bound = p2u(MetaspaceObj::aot_metaspace_base()); + uintptr_t high_bound = p2u(MetaspaceObj::aot_metaspace_top()); + size_t low_offset = low_bound - base; + size_t high_offset = high_bound - base; + + size_t offset = AOTCompressedPointers::get_byte_offset(narrowp); + if (offset > AOTCompressedPointers::MaxMetadataOffsetBytes || + offset < low_offset || offset >= high_offset || + size > (high_offset - offset)) { + return nullptr; + } + + Metadata* meta = reinterpret_cast(base + offset); + assert(Metaspace::in_aot_cache(meta), "must be"); + return meta; +} + +#if INCLUDE_CDS_JAVA_HEAP +int AOTCacheAccess::get_archived_object_permanent_index(oop obj) { + return HeapShared::get_root_index(obj); // -1 if obj is not a root. +} + +oop AOTCacheAccess::get_archived_object(int permanent_index) { + oop o = HeapShared::get_root(permanent_index); + assert(oopDesc::is_oop_or_null(o), "sanity"); + return o; +} + +#endif // INCLUDE_CDS_JAVA_HEAP void* AOTCacheAccess::allocate_aot_code_region(size_t size) { assert(CDSConfig::is_dumping_final_static_archive(), "must be"); return (void*)ArchiveBuilder::ac_region_alloc(size); } +bool AOTCacheAccess::verify_narrow_ptr = false; + +static size_t _aot_code_region_size = 0; + size_t AOTCacheAccess::get_aot_code_region_size() { - assert(CDSConfig::is_using_archive(), "must be"); - FileMapInfo* mapinfo = FileMapInfo::current_info(); - assert(mapinfo != nullptr, "must be"); - return mapinfo->region_at(AOTMetaspace::ac)->used_aligned(); + return _aot_code_region_size; +} + +void AOTCacheAccess::set_aot_code_region_size(size_t sz) { + _aot_code_region_size = sz; } bool AOTCacheAccess::map_aot_code_region(ReservedSpace rs) { FileMapInfo* static_mapinfo = FileMapInfo::current_info(); assert(UseSharedSpaces && static_mapinfo != nullptr, "must be"); + verify_narrow_ptr = VerifyAOTCode; return static_mapinfo->map_aot_code_region(rs); } + +void AOTCacheAccess::unmap_aot_code_region() { + FileMapInfo* static_mapinfo = FileMapInfo::current_info(); + assert(UseSharedSpaces && static_mapinfo != nullptr, "must be"); + return static_mapinfo->unmap_aot_code_region(); +} + +bool AOTCacheAccess::is_aot_code_region_empty() { + assert(CDSConfig::is_dumping_final_static_archive(), "must be"); + return ArchiveBuilder::current()->ac_region()->is_empty(); +} + +void AOTCacheAccess::set_pointer(address* ptr, address value) { + ArchiveBuilder* builder = ArchiveBuilder::current(); + if (value != nullptr && !builder->is_in_buffer_space(value)) { + value = builder->get_buffered_addr(value); + } + *ptr = value; + ArchivePtrMarker::mark_pointer(ptr); +} diff --git a/src/hotspot/share/cds/aotCacheAccess.hpp b/src/hotspot/share/cds/aotCacheAccess.hpp index 31fb9da534d..1b26a81cd1e 100644 --- a/src/hotspot/share/cds/aotCacheAccess.hpp +++ b/src/hotspot/share/cds/aotCacheAccess.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, 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 @@ -25,23 +25,107 @@ #ifndef SHARE_CDS_AOTCACHEACCESS_HPP #define SHARE_CDS_AOTCACHEACCESS_HPP +#include "cds/aotCompressedPointers.hpp" +#include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" +#include "cds/cdsConfig.hpp" #include "memory/allStatic.hpp" #include "oops/oopsHierarchy.hpp" #include "utilities/globalDefinitions.hpp" +class InstanceKlass; +class Klass; +class Method; class ReservedSpace; // AOT Cache API for AOT compiler class AOTCacheAccess : AllStatic { + using narrowPtr = AOTCompressedPointers::narrowPtr; +private: + static bool can_generate_aot_code(address addr) NOT_CDS_RETURN_(false); + static bool verify_narrow_ptr; public: + static bool can_generate_aot_code(Method* m) { + return can_generate_aot_code((address)m); + } + static bool can_generate_aot_code(Klass* k) { + assert(!k->is_instance_klass(), "other method should be called"); + return can_generate_aot_code((address)k); + } + static bool can_generate_aot_code_for(InstanceKlass* ik) NOT_CDS_RETURN_(false); + + /* + * Used during an assembly run to encode metadata object pointer present in the AOT Cache. + * The input argument is the address of a metadata object (Method/Klass) loaded by the assembly JVM. + */ + template + static narrowPtr to_narrow_ptr(T addr) { + assert(CDSConfig::is_dumping_final_static_archive(), "must be"); + assert(ArchiveBuilder::is_active(), "must be"); + return AOTCompressedPointers::encode_not_null(addr); + } + + // Get metadata with narrowp verification + static Metadata* try_narrow_ptr_to_metadata(narrowPtr narrowp, size_t size); + + /* + * Used during a production run to materialize a real pointer to a Klass from the encoded pointer located in a loaded AOT Cache. + * The encoded pointer is normally obtained by reading a value embedded in some other AOT-ed entry, like an AOT compiled code. + */ + static Klass* narrow_ptr_to_klass(narrowPtr narrowp) { + if (verify_narrow_ptr) { + return try_narrow_ptr_to_klass(narrowp); + } + Metadata* metadata = AOTCompressedPointers::decode_not_null(narrowp); + assert(metadata->is_klass(), "sanity check"); + return (Klass*)metadata; + } + // Version with narrowp verification + static Klass* try_narrow_ptr_to_klass(narrowPtr narrowp); + + /* + * Used during a production run to materialize a real pointer to a Method from the encoded pointer located in a loaded AOT Cache. + * The encoded pointer is normally obtained by reading a value embedded in some other AOT-ed entry, like an AOT compiled code. + */ + static Method* narrow_ptr_to_method(narrowPtr narrowp) { + if (verify_narrow_ptr) { + return try_narrow_ptr_to_method(narrowp); + } + Metadata* metadata = AOTCompressedPointers::decode_not_null(narrowp); + assert(metadata->is_method(), "sanity check"); + return (Method*)metadata; + } + // Version with narrowp verification + static Method* try_narrow_ptr_to_method(narrowPtr narrowp); + + // Used during production run to convert a Method in AOTCache to encoded pointer + static narrowPtr method_to_narrow_ptr(Method* method) { + assert(CDSConfig::is_using_archive() && !CDSConfig::is_dumping_final_static_archive(), "must be"); + assert(AOTMetaspace::in_aot_cache(method), "method %p is not in AOTCache", method); + return AOTCompressedPointers::encode_address_in_cache(method); + } + + static int get_archived_object_permanent_index(oop obj) NOT_CDS_JAVA_HEAP_RETURN_(-1); + static oop get_archived_object(int permanent_index) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + static void* allocate_aot_code_region(size_t size) NOT_CDS_RETURN_(nullptr); static size_t get_aot_code_region_size() NOT_CDS_RETURN_(0); + static void set_aot_code_region_size(size_t sz) NOT_CDS_RETURN; static bool map_aot_code_region(ReservedSpace rs) NOT_CDS_RETURN_(false); + static void unmap_aot_code_region() NOT_CDS_RETURN; + + + static bool is_aot_code_region_empty() NOT_CDS_RETURN_(true); + + template + static void set_pointer(T** ptr, T* value) { + set_pointer((address*)ptr, (address)value); + } + static void set_pointer(address* ptr, address value); }; #endif // SHARE_CDS_AOTCACHEACCESS_HPP diff --git a/src/hotspot/share/cds/aotClassLinker.cpp b/src/hotspot/share/cds/aotClassLinker.cpp index c66435b03bb..0ed17dd6746 100644 --- a/src/hotspot/share/cds/aotClassLinker.cpp +++ b/src/hotspot/share/cds/aotClassLinker.cpp @@ -136,6 +136,10 @@ bool AOTClassLinker::try_add_candidate(InstanceKlass* ik) { return true; } + if (!ik->is_linked() && SystemDictionaryShared::has_class_failed_verification(ik)) { + return false; + } + if (ik->is_hidden()) { assert(!ik->defined_by_other_loaders(), "hidden classes are archived only for builtin loaders"); if (!CDSConfig::is_dumping_method_handles()) { diff --git a/src/hotspot/share/cds/aotCompressedPointers.hpp b/src/hotspot/share/cds/aotCompressedPointers.hpp index a3919fb95a9..5e783cd5e2d 100644 --- a/src/hotspot/share/cds/aotCompressedPointers.hpp +++ b/src/hotspot/share/cds/aotCompressedPointers.hpp @@ -141,4 +141,6 @@ inline u4 cast_to_u4(AOTCompressedPointers::narrowPtr narrowp) { return checked_cast(narrowp); } +using narrowPtr = AOTCompressedPointers::narrowPtr; + #endif // SHARE_CDS_AOTCOMPRESSEDPOINTERS_HPP diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index f1a704d4bee..4add165e143 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -26,6 +26,8 @@ #include "cds/aotConstantPoolResolver.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" +#include "cds/lambdaFormInvokers.inline.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" @@ -149,6 +151,29 @@ void AOTConstantPoolResolver::preresolve_string_cp_entries(InstanceKlass* ik, TR break; } } + + // Normally, we don't want to archive any CP entries that were not resolved + // in the training run. Otherwise the AOT/JIT may inline too much code that has not + // been executed. + // + // However, we want to aggressively resolve all klass/field/method constants for + // LambdaForm Invoker Holder classes, Lambda Proxy classes, and LambdaForm classes, + // so that the compiler can inline through them. + if (SystemDictionaryShared::is_builtin_loader(ik->class_loader_data())) { + bool eager_resolve = false; + + if (LambdaFormInvokers::may_be_regenerated_class(ik->name())) { + eager_resolve = true; + } + if (ik->is_hidden() && HeapShared::is_archivable_hidden_klass(ik)) { + eager_resolve = true; + } + + if (eager_resolve) { + preresolve_class_cp_entries(THREAD, ik, nullptr); + preresolve_field_and_method_cp_entries(THREAD, ik, nullptr); + } + } } // This works only for the boot/platform/app loaders diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index 8733c794ea7..f198f14da13 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -23,6 +23,7 @@ */ #include "cds/aotArtifactFinder.hpp" +#include "cds/aotCacheAccess.hpp" #include "cds/aotClassInitializer.hpp" #include "cds/aotClassLinker.hpp" #include "cds/aotClassLocation.hpp" @@ -62,6 +63,7 @@ #include "classfile/vmSymbols.hpp" #include "code/aotCodeCache.hpp" #include "code/codeCache.hpp" +#include "compiler/aotCompileBroker.hpp" #include "gc/shared/gcVMOperations.hpp" #include "interpreter/bytecodes.hpp" #include "interpreter/bytecodeStream.hpp" @@ -81,6 +83,7 @@ #include "oops/flatArrayKlass.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/klass.inline.hpp" +#include "oops/method.inline.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" #include "oops/oopHandle.hpp" @@ -360,7 +363,8 @@ void AOTMetaspace::post_initialize(TRAPS) { // Close any open file descriptors. However, mmap'ed pages will remain in memory. static_mapinfo->close(); - if (HeapShared::is_loading() && HeapShared::is_loading_mapping_mode()) { + if (!(HeapShared::is_loading() && HeapShared::is_loading_streaming_mode())) { + // Streaming mode will unmap the bm region later in AOTStreamedHeapLoader::cleanup() static_mapinfo->unmap_region(AOTMetaspace::bm); } @@ -963,17 +967,16 @@ void AOTMetaspace::dump_static_archive(TRAPS) { ResourceMark rm(THREAD); HandleMark hm(THREAD); - if (CDSConfig::is_dumping_final_static_archive()) { - if (AOTPrintTrainingInfo) { - tty->print_cr("==================== archived_training_data ** before dumping ===================="); - TrainingData::print_archived_training_data_on(tty); - } - LogStreamHandle(Info, aot, training, data) log; - if (log.is_enabled()) { - TrainingData::print_archived_training_data_on(&log); - } - } - + if (CDSConfig::is_dumping_final_static_archive()) { + if (AOTPrintTrainingInfo) { + tty->print_cr("==================== archived_training_data ** before dumping ===================="); + TrainingData::print_archived_training_data_on(tty); + } + LogStreamHandle(Info, aot, training, data) log; + if (log.is_enabled()) { + TrainingData::print_archived_training_data_on(&log); + } + } StaticArchiveBuilder builder; dump_static_archive_impl(builder, THREAD); @@ -997,6 +1000,8 @@ void AOTMetaspace::dump_static_archive(TRAPS) { } else { tty->print_cr("AOTCache creation is complete: %s " INT64_FORMAT " bytes", AOTCache, (int64_t)(st.st_size)); } + print_statistics_before_exit(); + ostream_exit(); // finalize VM log vm_direct_exit(0); } } @@ -1011,15 +1016,32 @@ void AOTMetaspace::init_heap_settings() { // We don't need it -- always disable for better jitted code. FLAG_SET_ERGO(AOTCompatibleOopCompression, false); } else if (CDSConfig::is_dumping_final_static_archive()) { - // Obey the command-line switch. Do not override + // Enable by default: use the CompressedOops::HeapBasedNarrowOop mode + // so that AOT code can always work regardless of runtime heap range. + // + // If you are *absolutely sure* that the CompressedOops::mode() will be the same + // between training and production runs (e.g., if you specify -Xmx128m for + // both training and production runs, and you know the OS will always reserve + // the heap under 4GB), you can explicitly disable this with: + // java -XX:+UnlockDiagnosticVMOptions -XX:-AOTCompatibleOopCompression ... + // However, this is risky and there's a chance that the startup in production run + // will be slower than expected because it is unable to load the AOT code cache + // due to a different compressed oops encoding. + // + FLAG_SET_ERGO_IF_DEFAULT(AOTCompatibleOopCompression, true); } else if (CDSConfig::is_using_archive()) { - precond(FileMapInfo::current_info() == nullptr); - FileMapInfo* static_mapinfo = open_static_archive(); + FileMapInfo* static_mapinfo = FileMapInfo::current_info(); + // may have been opened by get_aot_code_region_size() + if (static_mapinfo == nullptr) { + static_mapinfo = open_static_archive(); + } if (static_mapinfo != nullptr && static_mapinfo->header()->compatible_oop_compression()) { // Use the same setting as recorded in the archive. FLAG_SET_ERGO(AOTCompatibleOopCompression, true); } } + } else { + FLAG_SET_ERGO(AOTCompatibleOopCompression, false); } if (!CDSConfig::is_dumping_heap() || UseCompressedOops) { @@ -1182,15 +1204,22 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS VM_PopulateDumpSharedSpace op(builder, _output_mapinfo); VMThread::execute(&op); - if (AOTCodeCache::is_on_for_dump() && CDSConfig::is_dumping_final_static_archive()) { - CDSConfig::enable_dumping_aot_code(); - { - builder.start_ac_region(); - // Write the contents to AOT code region before packing the region - AOTCodeCache::dump(); - builder.end_ac_region(); + if (AOTCodeCache::is_caching_enabled() && CDSConfig::is_dumping_final_static_archive()) { + // We have just created the final image. Now dump AOT adapters, stubs and compiled code. + builder.start_ac_region(); + if (AOTCodeCache::is_dumping_code()) { + // Let's run the AOT compiler. + CDSConfig::enable_dumping_aot_code(); + log_info(aot)("Compiling AOT code with %d compiler threads", (int)CICompilerCount); + AOTCompileBroker::compile_aot_code(&builder, CHECK); + log_info(aot)("Finished compiling AOT code"); + CDSConfig::disable_dumping_aot_code(); + } + // Write the contents to AOT code region before packing the region + if (AOTCodeCache::dump()) { + log_info(aot)("Dumped AOT code Cache"); } - CDSConfig::disable_dumping_aot_code(); + builder.end_ac_region(); } bool status = write_static_archive(&builder, _output_mapinfo, op.mapped_heap_info(), op.streamed_heap_info()); @@ -1409,7 +1438,7 @@ bool AOTMetaspace::try_link_class(JavaThread* current, InstanceKlass* ik) { void VM_PopulateDumpSharedSpace::dump_java_heap_objects() { if (CDSConfig::is_dumping_heap()) { HeapShared::write_heap(&_mapped_heap_info, &_streamed_heap_info); - } else { + } else if (!CDSConfig::is_dumping_preimage_static_archive()) { CDSConfig::log_reasons_for_not_dumping_heap(); } } @@ -1554,6 +1583,8 @@ void AOTMetaspace::initialize_runtime_shared_and_meta_spaces() { // Register CDS memory region with LSan. LSAN_REGISTER_ROOT_REGION(cds_base, cds_end - cds_base); set_aot_metaspace_range(cds_base, static_mapinfo->mapped_end(), cds_end); + guarantee(SharedBaseAddress == p2u(MetaspaceObj::aot_metaspace_base()), "must be"); + guarantee(p2u(MetaspaceObj::aot_metaspace_base()) <= p2u(MetaspaceObj::aot_metaspace_top()), "must be"); _relocation_delta = static_mapinfo->relocation_delta(); _requested_base_address = static_mapinfo->requested_base_address(); if (dynamic_mapped) { @@ -1591,7 +1622,25 @@ void AOTMetaspace::initialize_runtime_shared_and_meta_spaces() { delete dynamic_mapinfo; } if (RequireSharedSpaces && has_failed) { - AOTMetaspace::unrecoverable_loading_error("Unable to map shared spaces"); + // static archive mapped but dynamic archive failed + AOTMetaspace::unrecoverable_loading_error("Unable to map shared spaces"); + } +} + +// This is called very early at VM start up to get AOT cache header +// and the size of the AOT code region during production run. +// We need the size to reserve space to map code from AOT code region. +void AOTMetaspace::get_aot_code_region_size() { + if (!AOTCodeCache::is_caching_enabled() || CDSConfig::is_dumping_final_static_archive()) { + return; + } else if (CDSConfig::is_using_archive()) { // production run with AOT code cache + precond(FileMapInfo::current_info() == nullptr); + precond(CodeCache::max_capacity() == 0); + FileMapInfo* static_mapinfo = open_static_archive(); + if (static_mapinfo != nullptr) { + FileMapRegion* aot_code_region = static_mapinfo->region_at(AOTMetaspace::ac); + AOTCacheAccess::set_aot_code_region_size(aot_code_region->used_aligned()); + } } } diff --git a/src/hotspot/share/cds/aotMetaspace.hpp b/src/hotspot/share/cds/aotMetaspace.hpp index cc24bce52e0..40181b96390 100644 --- a/src/hotspot/share/cds/aotMetaspace.hpp +++ b/src/hotspot/share/cds/aotMetaspace.hpp @@ -76,7 +76,7 @@ class AOTMetaspace : AllStatic { static void dump_static_archive(TRAPS) NOT_CDS_RETURN; #ifdef _LP64 - static void init_heap_settings() NOT_CDS_JAVA_HEAP_RETURN; + static void init_heap_settings() NOT_CDS_JAVA_HEAP_RETURN; #endif private: @@ -92,6 +92,7 @@ class AOTMetaspace : AllStatic { static void initialize_for_static_dump() NOT_CDS_RETURN; static void initialize_runtime_shared_and_meta_spaces() NOT_CDS_RETURN; static void post_initialize(TRAPS) NOT_CDS_RETURN; + static void get_aot_code_region_size() NOT_CDS_RETURN; static void print_on(outputStream* st); diff --git a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp index 393737e51b9..8fb48edb434 100644 --- a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp @@ -799,6 +799,8 @@ void AOTStreamedHeapLoader::cleanup() { } FREE_C_HEAP_ARRAY(_object_index_to_heap_object_table); + _object_index_to_heap_object_table = nullptr; + _roots_archive = nullptr; // Unmap regions FileMapInfo::current_info()->unmap_region(AOTMetaspace::hp); diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 576dc5562a8..4199902b57f 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -42,7 +42,6 @@ #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" -#include "code/aotCodeCache.hpp" #include "interpreter/abstractInterpreter.hpp" #include "jvm.h" #include "logging/log.hpp" @@ -183,6 +182,7 @@ ArchiveBuilder::ArchiveBuilder() : _ptrmap(mtClassShared), _rw_ptrmap(mtClassShared), _ro_ptrmap(mtClassShared), + _ac_ptrmap(mtClassShared), _rw_src_objs(), _ro_src_objs(), _src_obj_table(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE), @@ -322,9 +322,9 @@ void ArchiveBuilder::sort_klasses() { address ArchiveBuilder::reserve_buffer() { // On 64-bit: reserve address space for archives up to the max encoded offset limit. - // On 32-bit: use 256MB + AOT code size due to limited virtual address space. + // On 32-bit: use 256MB due to limited virtual address space. size_t buffer_size = LP64_ONLY(AOTCompressedPointers::MaxMetadataOffsetBytes) - NOT_LP64(256 * M + AOTCodeCache::max_aot_code_size()); + NOT_LP64(256 * M); ReservedSpace rs = MemoryReserver::reserve(buffer_size, AOTMetaspace::core_region_alignment(), os::vm_page_size(), @@ -1023,7 +1023,7 @@ narrowKlass ArchiveBuilder::get_requested_narrow_klass(Klass* k) { } #endif // INCLUDE_CDS_JAVA_HEAP -// RelocateBufferToRequested --- Relocate all the pointers in rw/ro, +// RelocateBufferToRequested --- Relocate all the pointers in rw/ro/ac, // so that the archive can be mapped to the "requested" location without runtime relocation. // // - See ArchiveBuilder header for the definition of "buffer", "mapped" and "requested" @@ -1145,11 +1145,12 @@ void ArchiveBuilder::write_archive(FileMapInfo* mapinfo, AOTMappedHeapInfo* mapp write_region(mapinfo, AOTMetaspace::ac, &_ac_region, /*read_only=*/false,/*allow_exec=*/false); // Split pointer map into read-write and read-only bitmaps - ArchivePtrMarker::initialize_rw_ro_maps(&_rw_ptrmap, &_ro_ptrmap); + ArchivePtrMarker::initialize_rw_ro_ac_maps(&_rw_ptrmap, &_ro_ptrmap, &_ac_ptrmap); size_t bitmap_size_in_bytes; char* bitmap = mapinfo->write_bitmap_region(ArchivePtrMarker::rw_ptrmap(), ArchivePtrMarker::ro_ptrmap(), + ArchivePtrMarker::ac_ptrmap(), mapped_heap_info, streamed_heap_info, bitmap_size_in_bytes); @@ -1169,8 +1170,8 @@ void ArchiveBuilder::write_archive(FileMapInfo* mapinfo, AOTMappedHeapInfo* mapp mapinfo->write_header(); mapinfo->close(); + aot_log_info(aot)("Full module graph = %s", CDSConfig::is_dumping_full_module_graph() ? "enabled" : "disabled"); if (log_is_enabled(Info, aot)) { - log_info(aot)("Full module graph = %s", CDSConfig::is_dumping_full_module_graph() ? "enabled" : "disabled"); print_stats(); } diff --git a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp index 6a9df87092b..43c8eb7812c 100644 --- a/src/hotspot/share/cds/archiveBuilder.hpp +++ b/src/hotspot/share/cds/archiveBuilder.hpp @@ -221,13 +221,14 @@ class ArchiveBuilder : public StackObj { DumpRegion _ro_region; DumpRegion _ac_region; // AOT code - // Combined bitmap to track pointers in both RW and RO regions. This is updated - // as objects are copied into RW and RO. + // Combined bitmap to track pointers in RW, RO and AC regions. This is updated + // as objects are copied into RW, RO and AC. CHeapBitMap _ptrmap; - // _ptrmap is split into these two bitmaps which are written into the archive. + // _ptrmap is split into these bitmaps which are written into the archive. CHeapBitMap _rw_ptrmap; // marks pointers in the RW region CHeapBitMap _ro_ptrmap; // marks pointers in the RO region + CHeapBitMap _ac_ptrmap; // marks pointers in the AC region SourceObjList _rw_src_objs; // objs to put in rw region SourceObjList _ro_src_objs; // objs to put in ro region diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index af0ae2c7a44..3d4d36ab486 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -53,6 +53,7 @@ CHeapBitMap* ArchivePtrMarker::_ptrmap = nullptr; CHeapBitMap* ArchivePtrMarker::_rw_ptrmap = nullptr; CHeapBitMap* ArchivePtrMarker::_ro_ptrmap = nullptr; +CHeapBitMap* ArchivePtrMarker::_ac_ptrmap = nullptr; VirtualSpace* ArchivePtrMarker::_vs; bool ArchivePtrMarker::_compacted; @@ -61,6 +62,7 @@ void ArchivePtrMarker::initialize(CHeapBitMap* ptrmap, VirtualSpace* vs) { assert(_ptrmap == nullptr, "initialize only once"); assert(_rw_ptrmap == nullptr, "initialize only once"); assert(_ro_ptrmap == nullptr, "initialize only once"); + assert(_ac_ptrmap == nullptr, "initialize only once"); _vs = vs; _compacted = false; _ptrmap = ptrmap; @@ -76,29 +78,34 @@ void ArchivePtrMarker::initialize(CHeapBitMap* ptrmap, VirtualSpace* vs) { _ptrmap->initialize(estimated_archive_size / sizeof(intptr_t)); } -void ArchivePtrMarker::initialize_rw_ro_maps(CHeapBitMap* rw_ptrmap, CHeapBitMap* ro_ptrmap) { +void ArchivePtrMarker::initialize_rw_ro_ac_maps(CHeapBitMap* rw_ptrmap, CHeapBitMap* ro_ptrmap, CHeapBitMap* ac_ptrmap) { address* buff_bottom = (address*)ArchiveBuilder::current()->buffer_bottom(); address* rw_bottom = (address*)ArchiveBuilder::current()->rw_region()->base(); address* ro_bottom = (address*)ArchiveBuilder::current()->ro_region()->base(); + address* ac_bottom = (address*)ArchiveBuilder::current()->ac_region()->base(); - // The bit in _ptrmap that cover the very first word in the rw/ro regions. + // The bit in _ptrmap that covers the very first word in the rw/ro/ac regions. size_t rw_start = rw_bottom - buff_bottom; size_t ro_start = ro_bottom - buff_bottom; + size_t ac_start = ac_bottom - buff_bottom; - // The number of bits used by the rw/ro ptrmaps. We might have lots of zero - // bits at the bottom and top of rw/ro ptrmaps, but these zeros will be + // The number of bits used by the rw/ro/ac ptrmaps. We might have lots of zero + // bits at the bottom and top of rw/ro/ac ptrmaps, but these zeros will be // removed by FileMapInfo::write_bitmap_region(). size_t rw_size = ArchiveBuilder::current()->rw_region()->used() / sizeof(address); size_t ro_size = ArchiveBuilder::current()->ro_region()->used() / sizeof(address); + size_t ac_size = ArchiveBuilder::current()->ac_region()->used() / sizeof(address); // The last (exclusive) bit in _ptrmap that covers the rw/ro regions. // Note: _ptrmap is dynamically expanded only when an actual pointer is written, so // it may not be as large as we want. size_t rw_end = MIN2(rw_start + rw_size, _ptrmap->size()); size_t ro_end = MIN2(ro_start + ro_size, _ptrmap->size()); + size_t ac_end = MIN2(ac_start + ac_size, _ptrmap->size()); rw_ptrmap->initialize(rw_size); ro_ptrmap->initialize(ro_size); + ac_ptrmap->initialize(ac_size); for (size_t rw_bit = rw_start; rw_bit < rw_end; rw_bit++) { rw_ptrmap->at_put(rw_bit - rw_start, _ptrmap->at(rw_bit)); @@ -108,8 +115,13 @@ void ArchivePtrMarker::initialize_rw_ro_maps(CHeapBitMap* rw_ptrmap, CHeapBitMap ro_ptrmap->at_put(ro_bit - ro_start, _ptrmap->at(ro_bit)); } + for (size_t ac_bit = ac_start; ac_bit < ac_end; ac_bit++) { + ac_ptrmap->at_put(ac_bit - ac_start, _ptrmap->at(ac_bit)); + } + _rw_ptrmap = rw_ptrmap; _ro_ptrmap = ro_ptrmap; + _ac_ptrmap = ac_ptrmap; } void ArchivePtrMarker::mark_pointer(address* ptr_loc) { @@ -479,8 +491,6 @@ void DumpRegion::pack(DumpRegion* next) { _end = (char*)align_up(_top, AOTMetaspace::core_region_alignment()); _is_packed = true; } - _end = (char*)align_up(_top, AOTMetaspace::core_region_alignment()); - _is_packed = true; if (next != nullptr) { next->_rs = _rs; next->_vs = _vs; diff --git a/src/hotspot/share/cds/archiveUtils.hpp b/src/hotspot/share/cds/archiveUtils.hpp index 5ed3ac140ca..735721abf2b 100644 --- a/src/hotspot/share/cds/archiveUtils.hpp +++ b/src/hotspot/share/cds/archiveUtils.hpp @@ -55,6 +55,7 @@ class ArchivePtrMarker : AllStatic { static CHeapBitMap* _ptrmap; static CHeapBitMap* _rw_ptrmap; static CHeapBitMap* _ro_ptrmap; + static CHeapBitMap* _ac_ptrmap; static VirtualSpace* _vs; // Once _ptrmap is compacted, we don't allow bit marking anymore. This is to @@ -66,7 +67,7 @@ class ArchivePtrMarker : AllStatic { public: static void initialize(CHeapBitMap* ptrmap, VirtualSpace* vs); - static void initialize_rw_ro_maps(CHeapBitMap* rw_ptrmap, CHeapBitMap* ro_ptrmap); + static void initialize_rw_ro_ac_maps(CHeapBitMap* rw_ptrmap, CHeapBitMap* ro_ptrmap, CHeapBitMap* ac_ptrmap); static void mark_pointer(address* ptr_loc); static void clear_pointer(address* ptr_loc); static void compact(address relocatable_base, address relocatable_end); @@ -95,10 +96,15 @@ class ArchivePtrMarker : AllStatic { return _ro_ptrmap; } + static CHeapBitMap* ac_ptrmap() { + return _ac_ptrmap; + } + static void reset_map_and_vs() { _ptrmap = nullptr; _rw_ptrmap = nullptr; _ro_ptrmap = nullptr; + _ac_ptrmap = nullptr; _vs = nullptr; } }; diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 35d28f5068b..48ec7f98066 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -24,6 +24,8 @@ #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" +#include "cds/aotMetaspace.hpp" +#include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/classListWriter.hpp" #include "cds/filemap.hpp" @@ -31,10 +33,12 @@ #include "classfile/classLoaderDataShared.hpp" #include "classfile/moduleEntry.hpp" #include "code/aotCodeCache.hpp" +#include "compiler/compilerDefinitions.inline.hpp" #include "include/jvm_io.h" #include "logging/log.hpp" #include "memory/universe.hpp" #include "prims/jvmtiAgentList.hpp" +#include "prims/jvmtiExport.hpp" #include "runtime/arguments.hpp" #include "runtime/globals.hpp" #include "runtime/globals_extension.hpp" @@ -63,6 +67,8 @@ const char* CDSConfig::_input_static_archive_path = nullptr; const char* CDSConfig::_input_dynamic_archive_path = nullptr; const char* CDSConfig::_output_archive_path = nullptr; +double CDSConfig::_aot_compile_threshold_scaling = 1.0; + JavaThread* CDSConfig::_dumper_thread = nullptr; int CDSConfig::get_status() { @@ -663,11 +669,17 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla } if (AOTClassLinking) { - // If AOTClassLinking is specified, enable all AOT optimizations by default. + // If AOTClassLinking is specified, enable all these optimizations by default. FLAG_SET_ERGO_IF_DEFAULT(AOTInvokeDynamicLinking, true); } else { - // AOTInvokeDynamicLinking depends on AOTClassLinking. + // All of these *might* depend on AOTClassLinking. Better be safe than sorry. FLAG_SET_ERGO(AOTInvokeDynamicLinking, false); + + if (CDSConfig::is_dumping_archive()) { + FLAG_SET_ERGO(AOTRecordTraining, false); + FLAG_SET_ERGO(AOTReplayTraining, false); + AOTCodeCache::disable_caching(); + } } if (is_dumping_static_archive()) { @@ -682,6 +694,12 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla // with AOT and -Xcomp. Arguments::set_mode_flags(Arguments::_mixed); } + _aot_compile_threshold_scaling = CompileThresholdScaling; // Preserve for A2 compilation + if (CompileThresholdScaling < 1.0) { + // Set flag to default value to avoid excessive normal JIT + // compilations during assembly phase. + FLAG_SET_ERGO(CompileThresholdScaling, 1.0); + } } else if (!mode_flag_cmd_line) { // By default, -Xshare:dump runs in interpreter-only mode, which is required for deterministic archive. // @@ -745,7 +763,8 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla void CDSConfig::setup_compiler_args() { // AOT profiles and AOT-compiled code are supported only in the JEP 483 workflow. - bool can_dump_profile_and_compiled_code = AOTClassLinking && new_aot_flags_used(); + bool can_dump_profile_and_compiled_code = !CompilerConfig::is_interpreter_only() && AOTClassLinking && new_aot_flags_used(); + bool can_use_profile_and_compiled_code = !CompilerConfig::is_interpreter_only() && new_aot_flags_used(); if (is_dumping_preimage_static_archive() && can_dump_profile_and_compiled_code) { // JEP 483 workflow -- training @@ -756,13 +775,23 @@ void CDSConfig::setup_compiler_args() { // JEP 483 workflow -- assembly FLAG_SET_ERGO(AOTRecordTraining, false); FLAG_SET_ERGO_IF_DEFAULT(AOTReplayTraining, true); - AOTCodeCache::enable_caching(); // Generate AOT code during assembly phase. + // Generate AOT code only when training data enabled + if (AOTReplayTraining) { + AOTCodeCache::enable_caching(); + } else { + AOTCodeCache::disable_caching(); + } disable_dumping_aot_code(); // Don't dump AOT code until metadata and heap are dumped. - } else if (is_using_archive() && new_aot_flags_used()) { + } else if (is_using_archive() && can_use_profile_and_compiled_code) { // JEP 483 workflow -- production FLAG_SET_ERGO(AOTRecordTraining, false); FLAG_SET_ERGO_IF_DEFAULT(AOTReplayTraining, true); - AOTCodeCache::enable_caching(); + // Use AOT code only when training data enabled + if (AOTReplayTraining) { + AOTCodeCache::enable_caching(); + } else { + AOTCodeCache::disable_caching(); + } } else { FLAG_SET_ERGO(AOTReplayTraining, false); FLAG_SET_ERGO(AOTRecordTraining, false); @@ -858,7 +887,7 @@ bool CDSConfig::is_using_only_default_archive() { } bool CDSConfig::is_logging_lambda_form_invokers() { - return ClassListWriter::is_enabled() || is_dumping_dynamic_archive(); + return ClassListWriter::is_enabled() || is_dumping_dynamic_archive() || is_dumping_preimage_static_archive(); } bool CDSConfig::is_dumping_regenerated_lambdaform_invokers() { diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index f722ba7b270..bc19167953b 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -54,6 +54,9 @@ class CDSConfig : public AllStatic { static bool _new_aot_flags_used; static bool _disable_heap_dumping; + // preserve CompileThresholdScaling for A2 compilation + static double _aot_compile_threshold_scaling; + static JavaThread* _dumper_thread; #endif @@ -203,6 +206,8 @@ class CDSConfig : public AllStatic { static void enable_dumping_aot_code() NOT_CDS_RETURN; static bool is_dumping_adapters() NOT_CDS_RETURN_(false); + static double aot_compile_threshold_scaling() { return CDS_ONLY(_aot_compile_threshold_scaling) NOT_CDS(1.0); } + // Some CDS functions assume that they are called only within a single-threaded context. I.e., // they are called from: // - The VM thread (e.g., inside VM_PopulateDumpSharedSpace) diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index e6320ad0f85..17405b6c079 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -147,18 +147,21 @@ product(bool, AOTVerifyTrainingData, trueInDebug, DIAGNOSTIC, \ "Verify archived training data") \ \ - product(bool, AOTCompileEagerly, false, EXPERIMENTAL, \ - "Compile methods as soon as possible") \ + product(bool, AOTCompileEagerly, false, DIAGNOSTIC, \ + "Compile methods as soon as possible during production run") \ \ /* AOT Code flags */ \ \ + product(bool, AOTCodeCaching, false, DIAGNOSTIC, \ + "Enable saving and restoring JIT compiled code in AOT cache") \ + \ product(bool, AOTAdapterCaching, false, DIAGNOSTIC, \ "Enable saving and restoring i2c2i adapters in AOT cache") \ \ product(bool, AOTStubCaching, false, DIAGNOSTIC, \ "Enable saving and restoring stubs and code blobs in AOT cache") \ \ - product(uint, AOTCodeMaxSize, 10*M, DIAGNOSTIC, \ + product(uint, AOTCodeMaxSize, 512*M, DIAGNOSTIC, \ "Buffer size in bytes for AOT code caching") \ range(1*M, CODE_CACHE_SIZE_LIMIT) \ \ diff --git a/src/hotspot/share/cds/cppVtables.cpp b/src/hotspot/share/cds/cppVtables.cpp index e300393bb55..13a76b7298b 100644 --- a/src/hotspot/share/cds/cppVtables.cpp +++ b/src/hotspot/share/cds/cppVtables.cpp @@ -353,6 +353,39 @@ void CppVtables::zero_archived_vtables() { bool CppVtables::is_valid_shared_method(const Method* m) { assert(AOTMetaspace::in_aot_cache(m), "must be"); - return vtable_of(m) == _index[Method_Kind]->cloned_vtable() || - vtable_of(m) == _archived_cpp_vtptrs[Method_Kind]; + const intptr_t* vt = vtable_of(m); + // _archived_cpp_vtptrs[kind] is not null only during AOT assembly phase + const intptr_t* archived_vt = _archived_cpp_vtptrs[Method_Kind]; + return vt == _index[Method_Kind]->cloned_vtable() || + (archived_vt != nullptr && vt == archived_vt); } + +#define CPP_VTABLE_KLASS_DO(f) \ + f(InstanceKlass) \ + f(InstanceClassLoaderKlass) \ + f(InstanceMirrorKlass) \ + f(InstanceRefKlass) \ + f(InstanceStackChunkKlass) \ + f(TypeArrayKlass) \ + f(ObjArrayKlass) \ + f(RefArrayKlass) \ + f(FlatArrayKlass) \ + f(ValueKlass) + +#define KLASS_CHECK(c) \ + archived_vt = _archived_cpp_vtptrs[c##_Kind]; \ + if (vt == _index[c##_Kind]->cloned_vtable() || \ + (archived_vt != nullptr && vt == archived_vt)) { \ + return true; \ + } + +bool CppVtables::is_valid_shared_klass(const Klass* k) { + assert(AOTMetaspace::in_aot_cache(k), "must be"); + const intptr_t* vt = vtable_of(k); + intptr_t* archived_vt; + CPP_VTABLE_KLASS_DO(KLASS_CHECK); + return false; +} + +#undef KLASS_CHECK +#undef CPP_VTABLE_KLASS_DO diff --git a/src/hotspot/share/cds/cppVtables.hpp b/src/hotspot/share/cds/cppVtables.hpp index 9e28ba020ee..81131b96a93 100644 --- a/src/hotspot/share/cds/cppVtables.hpp +++ b/src/hotspot/share/cds/cppVtables.hpp @@ -31,6 +31,7 @@ #include "utilities/globalDefinitions.hpp" class ArchiveBuilder; +class Klass; class Method; class SerializeClosure; class CppVtableInfo; @@ -44,6 +45,7 @@ class CppVtables : AllStatic { static intptr_t* get_archived_vtable(MetaspaceClosureType type, address obj); static void serialize(SerializeClosure* sc); static bool is_valid_shared_method(const Method* m) NOT_CDS_RETURN_(false); + static bool is_valid_shared_klass(const Klass* k) NOT_CDS_RETURN_(false); static char* vtables_serialized_base() { return _vtables_serialized_base; } }; diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 0bf4b164cd4..d0788871221 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -95,6 +95,10 @@ inline void CDSMustMatchFlags::do_print(outputStream* st, bool v) { inline void CDSMustMatchFlags::do_print(outputStream* st, uint v) { st->print("%u", v); } + +inline void CDSMustMatchFlags::do_print(outputStream* st, int v) { + st->print("%d", v); +} #endif inline void CDSMustMatchFlags::do_print(outputStream* st, intx v) { @@ -1039,6 +1043,7 @@ size_t FileMapInfo::remove_bitmap_zeros(CHeapBitMap* map) { char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, CHeapBitMap* ro_ptrmap, + CHeapBitMap* ac_ptrmap, AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeapInfo* streamed_heap_info, size_t &size_in_bytes) { @@ -1046,7 +1051,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, size_t removed_ro_leading_zeros = remove_bitmap_zeros(ro_ptrmap); header()->set_rw_ptrmap_start_pos(removed_rw_leading_zeros); header()->set_ro_ptrmap_start_pos(removed_ro_leading_zeros); - size_in_bytes = rw_ptrmap->size_in_bytes() + ro_ptrmap->size_in_bytes(); + size_in_bytes = rw_ptrmap->size_in_bytes() + ro_ptrmap->size_in_bytes() + ac_ptrmap->size_in_bytes(); if (mapped_heap_info != nullptr && mapped_heap_info->is_used()) { // Remove leading and trailing zeros @@ -1064,9 +1069,10 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, size_in_bytes += streamed_heap_info->oopmap()->size_in_bytes(); } - // The bitmap region contains up to 4 parts: + // The bitmap region contains up to 5 parts: // rw_ptrmap: metaspace pointers inside the read-write region // ro_ptrmap: metaspace pointers inside the read-only region + // ac_ptrmap: metaspace pointers inside the AOT code cache region // *_heap_info->oopmap(): Java oop pointers in the heap region // mapped_heap_info->ptrmap(): metaspace pointers in the heap region char* buffer = NEW_C_HEAP_ARRAY(char, size_in_bytes, mtClassShared); @@ -1078,6 +1084,11 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, region_at(AOTMetaspace::ro)->init_ptrmap(written, ro_ptrmap->size()); written = write_bitmap(ro_ptrmap, buffer, written); + if (ac_ptrmap->size() > 0) { + region_at(AOTMetaspace::ac)->init_ptrmap(written, ac_ptrmap->size()); + written = write_bitmap(ac_ptrmap, buffer, written); + } + if (mapped_heap_info != nullptr && mapped_heap_info->is_used()) { assert(HeapShared::is_writing_mapping_mode(), "unexpected dumping mode"); FileMapRegion* r = region_at(AOTMetaspace::hp); @@ -1273,6 +1284,9 @@ MapArchiveResult FileMapInfo::map_regions(int regions[], int num_regions, char* header()->set_mapped_base_address(header()->requested_base_address() + addr_delta); if (addr_delta != 0 && !relocate_pointers_in_core_regions(addr_delta)) { return MAP_ARCHIVE_OTHER_FAILURE; + } else if (VerifySharedSpaces) { + // Map and check the CRC of the bitmap region even if it may not be used. + map_bitmap_region(); } return MAP_ARCHIVE_SUCCESS; @@ -1344,8 +1358,10 @@ MapArchiveResult FileMapInfo::map_region(int i, intx addr_delta, char* mapped_ba // Note that this may either be a "fresh" mapping into unreserved address // space (Windows, first mapping attempt), or a mapping into pre-reserved // space (Posix). See also comment in AOTMetaspace::map_archives(). + bool read_only = r->read_only() && !CDSConfig::is_dumping_final_static_archive(); + char* base = map_memory(_fd, _full_path, r->file_offset(), - requested_addr, size, r->read_only(), + requested_addr, size, read_only, mtClassShared, r->allow_exec()); if (base != requested_addr) { AOTMetaspace::report_loading_error("Unable to map %s shared space at " INTPTR_FORMAT, @@ -1441,12 +1457,17 @@ bool FileMapInfo::map_aot_code_region(ReservedSpace rs) { if (VerifySharedSpaces && !r->check_region_crc(mapped_base)) { aot_log_error(aot)("region %d CRC error", AOTMetaspace::ac); - os::unmap_memory(mapped_base, r->used_aligned()); return false; } r->set_mapped_from_file(true); r->set_mapped_base(mapped_base); + if (!relocate_pointers_in_aot_code_region()) { + r->set_mapped_from_file(false); + r->set_mapped_base(nullptr); + return false; + } + r->set_in_reserved_space(true); aot_log_info(aot)("Mapped static region #%d at base " INTPTR_FORMAT " top " INTPTR_FORMAT " (%s)", AOTMetaspace::ac, p2i(r->mapped_base()), p2i(r->mapped_end()), shared_region_name[AOTMetaspace::ac]); @@ -1454,6 +1475,59 @@ bool FileMapInfo::map_aot_code_region(ReservedSpace rs) { } } +void FileMapInfo::unmap_aot_code_region() { + unmap_region(AOTMetaspace::ac); + return; +} + +class CachedCodeRelocator: public BitMapClosure { + address _code_requested_base; + address* _patch_base; + intx _code_delta; + intx _metadata_delta; + +public: + CachedCodeRelocator(address code_requested_base, address code_mapped_base, + intx metadata_delta) { + _code_requested_base = code_requested_base; + _patch_base = (address*)code_mapped_base; + _code_delta = code_mapped_base - code_requested_base; + _metadata_delta = metadata_delta; + } + + bool do_bit(size_t offset) { + address* p = _patch_base + offset; + address requested_ptr = *p; + if (requested_ptr < _code_requested_base) { + *p = requested_ptr + _metadata_delta; + } else { + *p = requested_ptr + _code_delta; + } + return true; // keep iterating + } +}; + +bool FileMapInfo::relocate_pointers_in_aot_code_region() { + FileMapRegion* r = region_at(AOTMetaspace::ac); + if (map_bitmap_region() == nullptr) { + return false; // OOM, or CRC check failure + } + BitMapView ac_ptrmap = ptrmap_view(AOTMetaspace::ac); + if (ac_ptrmap.size() == 0) { + return true; + } + + address core_regions_requested_base = (address)header()->requested_base_address(); + address core_regions_mapped_base = (address)header()->mapped_base_address(); + address ac_region_requested_base = core_regions_requested_base + r->mapping_offset(); + address ac_region_mapped_base = (address)r->mapped_base(); + + CachedCodeRelocator patcher(ac_region_requested_base, ac_region_mapped_base, + core_regions_mapped_base - core_regions_requested_base); + ac_ptrmap.iterate(&patcher); + return true; +} + class SharedDataRelocationTask : public ArchiveWorkerTask { private: BitMapView* const _rw_bm; diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 126e269db7b..5987e6d52d3 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -112,7 +112,9 @@ class FileMapRegion: private CDSFileMapRegion { f(UseNullFreeAtomicValueFlattening) \ f(UseNullableAtomicValueFlattening) \ f(UseNullableNonAtomicValueFlattening) \ - f(FlatteningBudget) + f(FlatteningBudget) \ + f(RestrictReservedStack) \ + f(DiagnoseSyncOnValueBasedClasses) class CDSMustMatchFlags { @@ -125,6 +127,7 @@ class CDSMustMatchFlags { inline static void do_print(outputStream* st, bool v); LP64_ONLY(inline static void do_print(outputStream* st, uint v);) + LP64_ONLY(inline static void do_print(outputStream* st, int v);) inline static void do_print(outputStream* st, intx v); inline static void do_print(outputStream* st, uintx v); inline static void do_print(outputStream* st, double v); @@ -410,6 +413,7 @@ class FileMapInfo : public CHeapObj { size_t remove_bitmap_zeros(CHeapBitMap* map); char* write_bitmap_region(CHeapBitMap* rw_ptrmap, CHeapBitMap* ro_ptrmap, + CHeapBitMap* ac_ptrmap, AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeapInfo* streamed_heap_info, size_t &size_in_bytes); @@ -430,6 +434,7 @@ class FileMapInfo : public CHeapObj { bool read_region(int i, char* base, size_t size, bool do_commit); char* map_bitmap_region(); bool map_aot_code_region(ReservedSpace rs); + void unmap_aot_code_region(); char* map_forwarding_region(); void unmap_region(int i); void close(); @@ -493,6 +498,7 @@ class FileMapInfo : public CHeapObj { MapArchiveResult map_region(int i, intx addr_delta, char* mapped_base_address, ReservedSpace rs); bool relocate_pointers_in_core_regions(intx addr_delta); + bool relocate_pointers_in_aot_code_region(); char* map_auxiliary_region(int region_index, bool read_only); public: diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index bb0d1abc3d6..9289bad0c67 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -51,6 +51,7 @@ #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" +#include "code/aotCodeCache.hpp" #include "gc/shared/collectedHeap.hpp" #include "gc/shared/gcLocker.hpp" #include "gc/shared/gcVMOperations.hpp" @@ -672,6 +673,10 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra AOTArtifactFinder::add_cached_class(method_holder); _dumptime_resolved_methods->append(HeapShared::append_root(obj)); } + } else if (AOTCodeCache::is_dumping_code() && + (java_lang_invoke_MethodHandle::is_instance(obj) || is_interned_string(obj))) { + // Needed by AOT compiler. + append_root(obj); } } @@ -1117,7 +1122,11 @@ void KlassSubGraphInfo::add_subgraph_entry_field(int static_field_offset, oop v) new (mtClass) GrowableArray(10, mtClass); } _subgraph_entry_fields->append(static_field_offset); - _subgraph_entry_fields->append(HeapShared::append_root(v)); + if (v == nullptr) { + _subgraph_entry_fields->append(-1); + } else { + _subgraph_entry_fields->append(HeapShared::append_root(v)); + } } // Add the Klass* for an object in the current KlassSubGraphInfo's subgraphs. @@ -1419,16 +1428,35 @@ void HeapShared::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) } } +static const char* java_lang_invoke_core_klasses[] = { + "java/lang/invoke/Invokers$Holder", + "java/lang/invoke/MethodHandle", + "java/lang/invoke/MethodHandleNatives", + "java/lang/invoke/DirectMethodHandle$Holder", + "java/lang/invoke/DelegatingMethodHandle$Holder", + "java/lang/invoke/LambdaForm$Holder", + "java/lang/invoke/BoundMethodHandle$Species_L", +}; + void HeapShared::initialize_java_lang_invoke(TRAPS) { if (CDSConfig::is_using_aot_linked_classes() || CDSConfig::is_dumping_method_handles()) { - resolve_or_init("java/lang/invoke/Invokers$Holder", true, CHECK); - resolve_or_init("java/lang/invoke/MethodHandle", true, CHECK); - resolve_or_init("java/lang/invoke/MethodHandleNatives", true, CHECK); - resolve_or_init("java/lang/invoke/DirectMethodHandle$Holder", true, CHECK); - resolve_or_init("java/lang/invoke/DelegatingMethodHandle$Holder", true, CHECK); - resolve_or_init("java/lang/invoke/LambdaForm$Holder", true, CHECK); - resolve_or_init("java/lang/invoke/BoundMethodHandle$Species_L", true, CHECK); + int len = sizeof(java_lang_invoke_core_klasses)/sizeof(char*); + for (int i = 0; i < len; i++) { + resolve_or_init(java_lang_invoke_core_klasses[i], true, CHECK); + } + } +} + +bool HeapShared::is_core_java_lang_invoke_klass(InstanceKlass* klass) { + ResourceMark rm; + char* s2 = klass->name()->as_C_string(); + int len = sizeof(java_lang_invoke_core_klasses)/sizeof(char*); + for (int i = 0; i < len; i++) { + if (strcmp(java_lang_invoke_core_klasses[i], s2) == 0) { + return true; + } } + return false; } // Initialize the InstanceKlasses of objects that are reachable from the following roots: @@ -1617,7 +1645,12 @@ void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphI int root_index = entry_field_records->at(i+1); // Load the subgraph entry fields from the record and store them back to // the corresponding fields within the mirror. - oop v = get_root(root_index, /*clear=*/true); + oop v; + if (root_index < 0) { + v = nullptr; + } else { + v = get_root(root_index, /*clear=*/true); + } oop m = k->java_mirror(); if (k->has_aot_initialized_mirror()) { assert(v == m->obj_field(field_offset), "must be aot-initialized"); diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index afdd819f612..78f493d17c8 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -477,6 +477,7 @@ class HeapShared: AllStatic { static void initialize_java_lang_invoke(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; static void init_classes_for_special_subgraph(Handle loader, TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + static bool is_core_java_lang_invoke_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); static bool is_lambda_form_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); static bool is_lambda_proxy_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); static bool is_string_concat_klass(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN_(false); diff --git a/src/hotspot/share/ci/ciConstant.cpp b/src/hotspot/share/ci/ciConstant.cpp index 6e09bb7a25a..adcf02beb12 100644 --- a/src/hotspot/share/ci/ciConstant.cpp +++ b/src/hotspot/share/ci/ciConstant.cpp @@ -81,6 +81,19 @@ bool ciConstant::is_loaded() const { return false; } +// ------------------------------------------------------------------ +// ciConstant::should_be_constant +bool ciConstant::should_be_constant() const { + if (is_valid()) { + if (is_reference_type(basic_type())) { + return as_object()->should_be_constant(); + } else { + return true; + } + } + return false; +} + // ------------------------------------------------------------------ // ciConstant::print void ciConstant::print() const { diff --git a/src/hotspot/share/ci/ciConstant.hpp b/src/hotspot/share/ci/ciConstant.hpp index de63fbab10d..f886f98bfe4 100644 --- a/src/hotspot/share/ci/ciConstant.hpp +++ b/src/hotspot/share/ci/ciConstant.hpp @@ -118,6 +118,8 @@ class ciConstant { bool is_loaded() const; + bool should_be_constant() const; + // Debugging output void print() const; }; diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index 783037535f4..505cbd65fba 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -22,6 +22,9 @@ * */ +#include "cds/aotCacheAccess.hpp" +#include "cds/aotConstantPoolResolver.hpp" +#include "cds/heapShared.hpp" #include "ci/ciConstant.hpp" #include "ci/ciEnv.hpp" #include "ci/ciField.hpp" @@ -37,6 +40,7 @@ #include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" +#include "code/aotCodeCache.hpp" #include "code/codeCache.hpp" #include "code/scopeDesc.hpp" #include "compiler/compilationLog.hpp" @@ -66,6 +70,7 @@ #include "oops/oopCast.inline.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/symbolHandle.hpp" +#include "oops/trainingData.hpp" #include "prims/jvmtiExport.hpp" #include "prims/methodHandles.hpp" #include "runtime/fieldDescriptor.inline.hpp" @@ -118,6 +123,7 @@ ciEnv::ciEnv(CompileTask* task) _debug_info = nullptr; _dependencies = nullptr; _inc_decompile_count_on_failure = true; + _codecache_full = false; _compilable = MethodCompilable; _break_at_compile = false; _compiler_data = nullptr; @@ -172,6 +178,8 @@ ciEnv::ciEnv(CompileTask* task) _jvmti_can_access_local_variables = false; _jvmti_can_post_on_exceptions = false; _jvmti_can_pop_frame = false; + _jvmti_can_get_owned_monitor_info = false; + _jvmti_can_walk_any_space = false; _dyno_klasses = nullptr; _dyno_locs = nullptr; @@ -255,6 +263,7 @@ ciEnv::ciEnv(Arena* arena) : _ciEnv_arena(mtCompiler, Arena::Tag::tag_cienv) { _debug_info = nullptr; _dependencies = nullptr; _inc_decompile_count_on_failure = true; + _codecache_full = false; _compilable = MethodCompilable_never; _break_at_compile = false; _compiler_data = nullptr; @@ -295,6 +304,8 @@ ciEnv::ciEnv(Arena* arena) : _ciEnv_arena(mtCompiler, Arena::Tag::tag_cienv) { _jvmti_can_access_local_variables = false; _jvmti_can_post_on_exceptions = false; _jvmti_can_pop_frame = false; + _jvmti_can_get_owned_monitor_info = false; + _jvmti_can_walk_any_space = false; _dyno_klasses = nullptr; _dyno_locs = nullptr; @@ -671,7 +682,7 @@ ciConstant ciEnv::get_constant_by_index_impl(const constantPoolHandle& cpool, ciInstanceKlass* accessor) { if (obj_index >= 0) { ciConstant con = get_resolved_constant(cpool, obj_index); - if (con.is_valid()) { + if (con.should_be_constant()) { return con; } } @@ -825,7 +836,15 @@ ciMethod* ciEnv::get_method_by_index_impl(const constantPoolHandle& cpool, // Patch the call site to the nmethod entry point of the static compiled lambda form. // As with other two-component call sites, both values must be independently verified. assert(index < cpool->cache()->resolved_indy_entries_length(), "impossible"); - Method* adapter = cpool->resolved_indy_entry_at(index)->method(); + ResolvedIndyEntry* indy_info = cpool->resolved_indy_entry_at(index); + Method* adapter = indy_info->method(); +#if INCLUDE_CDS + if (is_aot_compile() && !AOTConstantPoolResolver::is_resolution_deterministic(cpool(), indy_info->constant_pool_index())) { + // This is an indy callsite that was resolved as a side effect of VM bootstrap, but + // it cannot be cached in the resolved state, so AOT code should not reference it. + adapter = nullptr; + } +#endif // Resolved if the adapter is non null. if (adapter != nullptr) { return get_method(adapter); @@ -872,11 +891,11 @@ ciMethod* ciEnv::get_method_by_index_impl(const constantPoolHandle& cpool, constantTag tag = cpool->tag_ref_at(index, bc); assert(accessor->get_instanceKlass() == cpool->pool_holder(), "not the pool holder?"); Method* m = lookup_method(accessor, holder, name_sym, sig_sym, bc, tag); - if (m != nullptr && - (bc == Bytecodes::_invokestatic - ? m->method_holder()->is_not_initialized() - : !m->method_holder()->is_loaded())) { - m = nullptr; + if (m != nullptr) { + ciInstanceKlass* cik = get_instance_klass(m->method_holder()); + if ((bc == Bytecodes::_invokestatic && cik->is_not_initialized()) || !cik->is_loaded()) { + m = nullptr; + } } if (m != nullptr && ReplayCompiles && !ciReplay::is_loaded(m)) { m = nullptr; @@ -957,7 +976,7 @@ bool ciEnv::is_in_vm() { // Check for changes during compilation (e.g. class loads, evolution, // breakpoints, call site invalidation). void ciEnv::validate_compile_task_dependencies(ciMethod* target) { - if (failing()) return; // no need for further checks + assert(!failing(), "should not call this when failing"); Dependencies::DepType result = dependencies()->validate_dependencies(_task); if (result != Dependencies::end_marker) { @@ -972,8 +991,211 @@ void ciEnv::validate_compile_task_dependencies(ciMethod* target) { } } +// is_loading_aot_code = true implies loading compiled code from AOT code cache +bool ciEnv::is_compilation_valid(JavaThread* thread, ciMethod* target, bool install_code, bool is_loading_aot_code, bool aot_preload) { + methodHandle method(thread, target->get_Method()); + + // Change in Jvmti state may invalidate compilation. + if (!failing() && jvmti_state_changed()) { + record_failure("Jvmti state change invalidated dependencies"); + } + + if (!failing() && is_loading_aot_code && !AOTCodeCache::verify_jvmti_state(this)) { + record_failure("Jvmti state change invalidated dependencies for AOT code or other AOT code cache failure"); + } + + // Change in DTrace flags may invalidate compilation. + if (!failing() && + ( (!dtrace_method_probes() && DTraceMethodProbes) || + (!dtrace_alloc_probes() && DTraceAllocProbes) )) { + record_failure("DTrace flags change invalidated dependencies"); + } + + if (!failing() && target->needs_clinit_barrier() && + target->holder()->is_in_error_state()) { + record_failure("method holder is in error state"); + } + + if (!failing() && !is_loading_aot_code) { + if (log() != nullptr) { + // Log the dependencies which this compilation declares. + dependencies()->log_all_dependencies(); + } + + // Encode the dependencies now, so we can check them right away. + dependencies()->encode_content_bytes(); + } + // Check for {class loads, evolution, breakpoints, ...} during compilation. + // This check is skipped for RepeatCompilation request. + // Execute this check for VerifyAOTCode when loading AOT code. + // Note, RepeatCompilation request is ignored for AOT code load. + if (!failing() && (install_code || is_loading_aot_code)) { + // Check for {class loads, evolution, breakpoints, ...} during compilation + validate_compile_task_dependencies(target); + if (failing() && aot_preload) { + ResourceMark rm; + char* method_name = method->name_and_sig_as_C_string(); + log_info(aot, codecache, nmethod)("AOT preload code for '%s' failed dependency check", method_name); + } + } + + if (failing()) { + // While not a true deoptimization, it is a preemptive decompile. + MethodData* mdo = method()->method_data(); + if (mdo != nullptr && _inc_decompile_count_on_failure) { + mdo->inc_decompile_count(); + } + return false; + } + return true; +} + +void ciEnv::make_code_usable(JavaThread* thread, ciMethod* target, bool aot_preload, int entry_bci, AOTCodeEntry* aot_code_entry, nmethod* nm) { + methodHandle method(thread, target->get_Method()); + + if (entry_bci == InvocationEntryBci) { + // With TieredCompilation OFF we still have two versions + // of AOT code and also JIT compilation. + // During assembly phase we need to replace previous + // AOT preload code with normal AOT code to check + // success of compilation in AOTCompileIterator. + // After JDK-8380476 FIXME: AOT compiler should not be installing + // nmethods on the Method::code pointer. Find other way to report success. + if (TieredCompilation || AOTCodeCache::is_using_code() || is_aot_compile()) { + // If there is an old version we're done with it + nmethod* old = method->code(); + if (TraceMethodReplacement && old != nullptr) { + ResourceMark rm; + char *method_name = method->name_and_sig_as_C_string(); + tty->print_cr("Replacing method %s", method_name); + } + if (old != nullptr) { + old->make_not_used(); + } + } + + LogTarget(Info, nmethod, install) lt; + if (lt.is_enabled()) { + ResourceMark rm; + char *method_name = method->name_and_sig_as_C_string(); + lt.print("Installing method (L%d) %s id=%d aot=%s%s%u", + task()->comp_level(), method_name, compile_id(), + task()->is_aot_load() ? "A" : "", aot_preload ? "P" : "", + (aot_code_entry != nullptr ? aot_code_entry->offset() : 0)); + } + // Allow the code to be executed + MutexLocker ml(NMethodState_lock, Mutex::_no_safepoint_check_flag); + if (nm->make_in_use()) { + assert(!aot_preload || method->method_holder()->is_linked(), "AOT code preloaded only for linked method holder"); + method->set_code(method, nm); +#if INCLUDE_CDS + if (aot_preload) { + MethodCounters* mc = method->get_method_counters(thread); + assert(mc != nullptr, "CompileBroker should create MethodCounters if it is missing"); + mc->set_aot_preload_code_entry(aot_code_entry); + nm->set_aot_preloaded(true); + } +#endif + } + } else { + LogTarget(Info, nmethod, install) lt; + if (lt.is_enabled()) { + assert(aot_code_entry == nullptr && !task()->is_aot_load(), "OSR nmethods are not AOT compiled"); + ResourceMark rm; + char *method_name = method->name_and_sig_as_C_string(); + lt.print("Installing osr method (L%d) %s @ %d id=%u", + task()->comp_level(), method_name, entry_bci, compile_id()); + } + MutexLocker ml(NMethodState_lock, Mutex::_no_safepoint_check_flag); + if (nm->make_in_use()) { + method->method_holder()->add_osr_nmethod(nm); + } + } +} + +#if INCLUDE_CDS +// Register method loaded from AOT code cache +nmethod* ciEnv::register_aot_method(JavaThread* thread, + ciMethod* target, + AbstractCompiler* compiler, + nmethod* archived_nm, + AOTCodeReader* aot_code_reader, + bool install_code) +{ + AOTCodeEntry* aot_code_entry = task()->aot_code_entry(); + precond(aot_code_entry != nullptr); + nmethod* nm = nullptr; + { + methodHandle method(thread, target->get_Method()); + bool aot_preload = task()->is_aot_preload(); // AOT code to preload before Java method execution + + // We require method counters to store some method state (max compilation levels) required by the compilation policy. + if (method->get_method_counters(thread) == nullptr) { + record_failure("can't create method counters"); + return nullptr; + } + + // Check if memory should be freed before allocation + CodeCache::gc_on_allocation(); + + // To prevent compile queue updates. + MutexLocker locker(thread, MethodCompileQueue_lock); + + // Prevent InstanceKlass::add_to_hierarchy from running + // and invalidating our dependencies until we install this method. + // No safepoints are allowed. Otherwise, class redefinition can occur in between. + MutexLocker ml(Compile_lock); + NoSafepointVerifier nsv; + + // AOTCode entry indicates this shared code was marked invalid while it was loaded. + if (aot_code_entry->not_entrant()) { + record_failure("AOT entry was marked not-entrant"); + return nullptr; + } + + if (!is_compilation_valid(thread, target, install_code, /*is_loading_aot_code*/ true, aot_preload)) { + return nullptr; + } + + if (install_code) { + nm = nmethod::new_nmethod(archived_nm, method, aot_code_reader); + } + if (nm != nullptr) { +#ifdef ASSERT + LogStreamHandle(Trace, aot, codecache, nmethod) log; + if (log.is_enabled()) { + ResourceMark rm; + stringStream ss; + ss.print_cr("=== loaded AOT nmethod code ==="); + FlagSetting fs(PrintRelocations, true); + nm->print_on_impl(&ss); + nm->decode(&ss); + ss.print_cr("==============================="); + log.print_cr("%s", ss.freeze()); + } +#endif + assert(nm->aot_code_entry() == aot_code_entry, "mismatched entry"); + assert(aot_code_entry->is_loaded(), "AOT entry should be marked as loaded already"); + assert(nm->has_clinit_barriers() == aot_code_entry->has_clinit_barriers(), "should match"); + make_code_usable(thread, target, aot_preload, InvocationEntryBci, aot_code_entry, nm); + } + } + + NoSafepointVerifier nsv; + if (nm != nullptr) { + // Compilation succeeded, post what we know about it + nm->post_compiled_method(task()); + } else if (install_code) { + // The CodeCache is full. + record_codecache_full(); + } + // safepoints are allowed again + return nm; +} +#endif + // ------------------------------------------------------------------ -// ciEnv::register_method +// Register the result of a compilation including AOT compilation void ciEnv::register_method(ciMethod* target, int entry_bci, CodeOffsets* offsets, @@ -984,11 +1206,14 @@ void ciEnv::register_method(ciMethod* target, ExceptionHandlerTable* handler_table, ImplicitExceptionTable* inc_table, AbstractCompiler* compiler, + bool has_clinit_barriers, + bool for_aot_preload, bool has_unsafe_access, bool has_wide_vectors, bool has_monitors, bool has_scoped_access, - int immediate_oops_patched) { + int immediate_oops_patched, + bool install_code) { VM_ENTRY_MARK; nmethod* nm = nullptr; { @@ -1016,46 +1241,7 @@ void ciEnv::register_method(ciMethod* target, MutexLocker ml(Compile_lock); NoSafepointVerifier nsv; - // Change in Jvmti state may invalidate compilation. - if (!failing() && jvmti_state_changed()) { - record_failure("Jvmti state change invalidated dependencies"); - } - - // Change in DTrace flags may invalidate compilation. - if (!failing() && - ( (!dtrace_method_probes() && DTraceMethodProbes) || - (!dtrace_alloc_probes() && DTraceAllocProbes) )) { - record_failure("DTrace flags change invalidated dependencies"); - } - - if (!failing() && target->needs_clinit_barrier() && - target->holder()->is_in_error_state()) { - record_failure("method holder is in error state"); - } - - if (!failing()) { - if (log() != nullptr) { - // Log the dependencies which this compilation declares. - dependencies()->log_all_dependencies(); - } - - // Encode the dependencies now, so we can check them right away. - dependencies()->encode_content_bytes(); - - // Check for {class loads, evolution, breakpoints, ...} during compilation - validate_compile_task_dependencies(target); - } - - if (failing()) { - // While not a true deoptimization, it is a preemptive decompile. - MethodData* mdo = method()->method_data(); - if (mdo != nullptr && _inc_decompile_count_on_failure) { - mdo->inc_decompile_count(); - } - - // All buffers in the CodeBuffer are allocated in the CodeCache. - // If the code buffer is created on each compile attempt - // as in C2, then it must be freed. + if (!is_compilation_valid(THREAD, target, install_code, /*is_loading_aot_code*/ false, /*aot_preload*/ false)) { code_buffer->free_blob(); return; } @@ -1065,64 +1251,78 @@ void ciEnv::register_method(ciMethod* target, assert(compiler->type() == compiler_c2 || offsets->value(CodeOffsets::Exceptions) != -1, "must have exception entry"); - bool needs_stack_repair = (compiler->is_c2() && method()->needs_stack_repair()); - - nm = nmethod::new_nmethod(method, - compile_id(), - entry_bci, - offsets, - orig_pc_offset, - debug_info(), dependencies(), code_buffer, - frame_words, oop_map_set, - handler_table, inc_table, - compiler, CompLevel(task()->comp_level()), - nmethod::Flags(has_unsafe_access, has_wide_vectors, has_monitors, has_scoped_access, needs_stack_repair)); - + if (install_code) { + bool needs_stack_repair = (compiler->is_c2() && method()->needs_stack_repair()); + nm = nmethod::new_nmethod(method, + compile_id(), + entry_bci, + offsets, + orig_pc_offset, + debug_info(), dependencies(), code_buffer, + frame_words, oop_map_set, + handler_table, inc_table, + compiler, CompLevel(task()->comp_level()), + nmethod::Flags(has_unsafe_access, + has_wide_vectors, + has_monitors, + has_scoped_access, + needs_stack_repair, + has_clinit_barriers)); + } // Free codeBlobs code_buffer->free_blob(); if (nm != nullptr) { assert(!method->is_synchronized() || nm->has_monitors(), ""); - if (entry_bci == InvocationEntryBci) { - if (TieredCompilation) { - // If there is an old version we're done with it - nmethod* old = method->code(); - if (TraceMethodReplacement && old != nullptr) { - ResourceMark rm; - char *method_name = method->name_and_sig_as_C_string(); - tty->print_cr("Replacing method %s", method_name); - } - if (old != nullptr) { - old->make_not_used(); - } - } - - LogTarget(Info, nmethod, install) lt; - if (lt.is_enabled()) { - ResourceMark rm; - char *method_name = method->name_and_sig_as_C_string(); - lt.print("Installing method (%d) %s ", - task()->comp_level(), method_name); - } - // Allow the code to be executed - MutexLocker ml(NMethodState_lock, Mutex::_no_safepoint_check_flag); - if (nm->make_in_use()) { - method->set_code(method, nm); - } - } else { - LogTarget(Info, nmethod, install) lt; - if (lt.is_enabled()) { +#if INCLUDE_CDS + if (task()->is_aot_compile()) { +#ifdef ASSERT + LogStreamHandle(Trace, aot, codecache, nmethod) log; + if (log.is_enabled()) { ResourceMark rm; - char *method_name = method->name_and_sig_as_C_string(); - lt.print("Installing osr method (%d) %s @ %d", - task()->comp_level(), method_name, entry_bci); + stringStream ss; + ss.print_cr("=== storing AOT nmethod code ==="); + FlagSetting fs(PrintRelocations, true); + nm->print_on_impl(&ss); + nm->decode(&ss); + ss.print_cr("==============================="); + log.print_cr("%s", ss.freeze()); } - MutexLocker ml(NMethodState_lock, Mutex::_no_safepoint_check_flag); - if (nm->make_in_use()) { - method->method_holder()->add_osr_nmethod(nm); +#endif + AOTCodeEntry* aot_code_entry = AOTCodeCache::store_nmethod(nm, compiler, for_aot_preload); + if (aot_code_entry != nullptr) { + aot_code_entry->set_has_vectors(debug_info()->has_vectors()); + nm->set_aot_code_entry(aot_code_entry); + aot_code_entry->set_inlined_bytecodes(num_inlined_bytecodes()); + // Inline size was recorded during training. + CompileTrainingData* ctd = nullptr; + { + TrainingData::TrainingDataLocker l; + MethodTrainingData* mtd = MethodTrainingData::find(method); + assert(mtd != nullptr, "AOT compiled method should have MethodTrainingData"); + ctd = mtd->compile_data_for_aot_code(nm->comp_level()); + } + assert(ctd != nullptr, "AOT compiled method should have CompileTrainingData"); + int inline_size = ctd->inline_instructions_size(); + aot_code_entry->set_inline_instructions_size(inline_size); + if (for_aot_preload) { + // Set it only for printing purpose, otherwise it is unused + // during assembly phase. + nm->set_aot_preloaded(true); + } } + // After JDK-8380476 FIXME: The AOT compiler runs in a special AOT assembly + // phase, not part of any Java application. The compiled code is not + // runnable in this instance of the JVM. For consistency of access, + // we will fall through to make_code_usable and link the Method::code + // field to the new nmethod nm. But this would be a mistake if (for + // any reason) the compiled method were to be invoked in this JVM + // instance. Consider returning at this point, relying only on + // AOTCodeCache::store_nmethod to keep track of the new nmethod. } +#endif + make_code_usable(THREAD, target, /* aot_preload */ false, entry_bci, /* aot_code_entry */ nullptr, nm); } } @@ -1131,9 +1331,11 @@ void ciEnv::register_method(ciMethod* target, // Compilation succeeded, post what we know about it nm->post_compiled_method(task()); task()->set_num_inlined_bytecodes(num_inlined_bytecodes()); - } else { + } else if (install_code) { // The CodeCache is full. - record_failure("code cache is full"); + record_codecache_full(); + } else { + task()->set_num_inlined_bytecodes(num_inlined_bytecodes()); } // safepoints are allowed again @@ -1235,6 +1437,11 @@ void ciEnv::record_out_of_memory_failure() { record_method_not_compilable("out of memory"); } +void ciEnv::record_codecache_full() { + _codecache_full = true; + record_failure("code cache is full"); +} + ciInstance* ciEnv::unloaded_ciinstance() { GUARDED_VM_ENTRY(return _factory->get_unloaded_object_constant();) } @@ -1705,3 +1912,126 @@ void ciEnv::dump_inline_data(int compile_id) { void ciEnv::dump_replay_data_version(outputStream* out) { out->print_cr("version %d", REPLAY_VERSION); } + +#if INCLUDE_CDS + +bool ciEnv::is_aot_compile() const { + return (task() != nullptr) && task()->is_aot_compile(); +} + +// The AOT compiler must model clinit state before classes are actually loaded +// in the eventual production run. Its model is provisional and approximate, +// and any nmethod compiled from this model must conserve semantic +// correctness. +// +// For "preload" AOT code (AP tier), the model optimistically speculates that +// the class is probably loaded, but the speculation is checked before every +// static field or method use or instance allocation, just like the +// interpreter. (See Parse::do_call and GraphKit::clinit_barrier.) Thus, we +// usually return fully_initialized, when compiling preload code. +// +// For non-preload AOT code (A tier), we can make some predictions about which +// classes must be initialized by the time the code gets executed. First, +// 1. the top-level method's holder can be assumed initialized, since somebody +// else already triggered initialization before the method could be called. +// Also 2. the clinit dependencies listed in the CompileTrainingData (CTD) are +// always checked off before a non-preload nmethod is installed, so all those +// classes can be assumed initialized. Finally 3. certain core JDK classes are always +// force-initialized early enough that all compiled code can assume they are +// initialized. +// +// Neither the JIT nor the AOT compiler will attempt to optimize code classes +// that are unusable because their initializers have failed. This fallback +// is also used when there is not enough information available about a class +// during AOT cache assembly. In that case, we return the special sentinel +// value initialization_error, used here as a sentinel meaning "unknown"; a distinct +// value (unavailable_aot) is proposed for a later change. +// +// One thing this function does NOT do is look at the clinit state of any class. +// This may be surprising, since a normal JIT compilation task directly queries +// clinit states, via ciInstanceKlass::compute_init_state. But for an AOT +// compilation task, ciIK::compute_init_state calls here, and a future clinit state +// is predicted, instead of a current state. The nmethod code cannot be loaded +// until all such predictions come true. Eventually, as a future production run +// initializes classes in some order, a countdown counter on the CTD will tally +// every clinit event, and eventually reach zero, opening the gate to load the +// AOT code in the nmethod. +// +// These predictions, of which class needs to be initialized for the code to work, +// have already been made by a previous pass of the compiler, on the same method +// at the same level, in the earlier training run (TR). The CTD contains a dep-list +// of every class which the JIT, running in the TR, observed to be initialized. +// That dep-list gets passed forward to the AOT compiler, which re-runs a similar +// compilation, after the TR, in the assembly phase (APH) that creates the AOT cache. +// Since the APH is a fresh instance of the JVM, there are no interesting clinit +// states to be observed directly in the InstanceKlass metadata. (The JVM running +// the APH does have a few initialized class states, but they are not useful to +// AOT compilation, so they are deliberately not examined.) +// +InstanceKlass::ClassState ciEnv::compute_init_state_for_aot_compile(InstanceKlass* ik) { + ASSERT_IN_VM; + ResourceMark rm; + assert(is_aot_compile(), "should be called only for AOT compilation in assembly phase"); + assert(!ik->is_in_error_state(), "comp_id: %d, klass %s", task()->compile_id(), ik->external_name()); + + if (!AOTCacheAccess::can_generate_aot_code_for(ik)) { + log_debug(aot, compilation)("%d: klass (%s) %s is not archived", task()->compile_id(), InstanceKlass::state2name(ik->init_state()), ik->external_name()); + // Skip this class + return InstanceKlass::ClassState::initialization_error; + } + if (task()->method()->method_holder() == ik) { + log_trace(aot, compilation)("%d: method_holder: (%s) %s", task()->compile_id(), InstanceKlass::state2name(ik->init_state()), ik->external_name()); + if (task()->method()->is_class_initializer()) { // Happens with -Xcomp + return InstanceKlass::ClassState::being_initialized; + } else { + return InstanceKlass::ClassState::fully_initialized; + } + } + + switch (task()->compile_reason()) { + case CompileTask::Reason_AOTCompile: { + // Check init dependencies + MethodTrainingData* mtd = nullptr; + GUARDED_VM_ENTRY(mtd = MethodTrainingData::find(methodHandle(Thread::current(), task()->method())); ) + if (mtd != nullptr) { + CompileTrainingData* ctd = mtd->compile_data_for_aot_code(task()->comp_level()); + if (ctd != nullptr) { + for (int i = 0; i < ctd->init_dep_count(); i++) { + KlassTrainingData* ktd = ctd->init_dep(i); + if (ktd->has_holder() && (ktd->holder() == ik)) { + log_trace(aot, compilation)("%d: init_dependency: (%s) %s", task()->compile_id(), InstanceKlass::state2name(ik->init_state()), ik->external_name()); + return InstanceKlass::ClassState::fully_initialized; // init dependency present + } + } + } + } + + // Core java/lang/invoke classes are peculiar. They include LF invokers, which + // are initialized in production run, but they are not recorded as init dependencies. + // CI query should report their status as if in production run, otherwise AOT + // code would have uncommon traps at invokedynamic calls. + if (HeapShared::is_core_java_lang_invoke_klass(ik)) { + log_trace(aot, compilation)("%d: core_java_lang_invoke: (%s) %s", task()->compile_id(), InstanceKlass::state2name(ik->init_state()), ik->external_name()); + return InstanceKlass::ClassState::fully_initialized; + } + + // Class may be not present during TD creation for this method. + // It could happen when profiled data for inlined method + // was updated after this method was compiled during training. + break; + } + case CompileTask::Reason_AOTCompileForPreload: { + // Preload AOT code does not depend on Training Data, + // it has class init barriers to initialize class by + // going into interpreter or directly calling runtime. + log_trace(aot, compilation)("%d: for_aot_preload: (%s) %s", task()->compile_id(), InstanceKlass::state2name(ik->init_state()), ik->external_name()); + return InstanceKlass::ClassState::fully_initialized; + } + default: fatal("%s", CompileTask::reason_name(task()->compile_reason())); + } + log_debug(aot, compilation)("%d: klass (%s) %s is not recorded for this compilation", task()->compile_id(), InstanceKlass::state2name(ik->init_state()), ik->external_name()); + // Skip this class + return InstanceKlass::ClassState::initialization_error; +} + +#endif // INCLUDE_CDS diff --git a/src/hotspot/share/ci/ciEnv.hpp b/src/hotspot/share/ci/ciEnv.hpp index 4a844bd1f51..40633ddfb15 100644 --- a/src/hotspot/share/ci/ciEnv.hpp +++ b/src/hotspot/share/ci/ciEnv.hpp @@ -34,11 +34,14 @@ #include "compiler/cHeapStringHolder.hpp" #include "compiler/compiler_globals.hpp" #include "compiler/compilerThread.hpp" +#include "oops/methodCounters.hpp" #include "oops/methodData.hpp" #include "runtime/javaThread.hpp" class CompileTask; class OopMapSet; +class AOTCodeEntry; +class AOTCodeReader; // ciEnv // @@ -59,9 +62,10 @@ class ciEnv : StackObj { DebugInformationRecorder* _debug_info; Dependencies* _dependencies; CHeapStringHolder _failure_reason; + bool _codecache_full; bool _inc_decompile_count_on_failure; - int _compilable; bool _break_at_compile; + int _compilable; int _num_inlined_bytecodes; CompileTask* _task; // faster access to CompilerThread::task CompileLog* _log; // faster access to CompilerThread::log @@ -232,6 +236,10 @@ class ciEnv : StackObj { if (o == nullptr) return nullptr; return get_metadata(o)->as_method_data(); } + ciMetadata* get_method_counters(MethodCounters* o) { + if (o == nullptr) return nullptr; + return get_metadata((Metadata*)o); + } ciMethod* get_method_from_handle(Method* method); @@ -245,6 +253,7 @@ class ciEnv : StackObj { ciInstanceKlass* declared_holder = get_instance_klass_for_declared_method_holder(holder); return _factory->get_unloaded_method(declared_holder, name, signature, accessor); } + InstanceKlass::ClassState get_cached_init_state(uint id) { return (InstanceKlass::ClassState)_factory->cached_init_state(id); } @@ -303,6 +312,12 @@ class ciEnv : StackObj { // Helper routine for determining the validity of a compilation with // respect to method dependencies (e.g. concurrent class loading). void validate_compile_task_dependencies(ciMethod* target); + + // Helper routines to factor out common code used by routines that register a method + // i.e. register_aot_method() and register_method() + bool is_compilation_valid(JavaThread* thread, ciMethod* target, bool install_code, bool is_loading_aot_code, bool aot_preload); + void make_code_usable(JavaThread* thread, ciMethod* target, bool aot_preload, int entry_bci, AOTCodeEntry* aot_code_entry, nmethod* nm); + public: enum { MethodCompilable, @@ -331,6 +346,8 @@ class ciEnv : StackObj { // Reason this compilation is failing, such as "too many basic blocks". const char* failure_reason() const { return _failure_reason.get(); } + bool codecache_full() const { return _codecache_full; } + // Return state of appropriate compatibility int compilable() { return _compilable; } @@ -357,6 +374,8 @@ class ciEnv : StackObj { bool should_retain_local_variables() const { return _jvmti_can_access_local_variables || _jvmti_can_pop_frame; } + bool jvmti_can_access_local_variables() const { return _jvmti_can_access_local_variables; } + bool jvmti_can_pop_frame() const { return _jvmti_can_pop_frame; } bool jvmti_can_hotswap_or_post_breakpoint() const { return _jvmti_can_hotswap_or_post_breakpoint; } bool jvmti_can_post_on_exceptions() const { return _jvmti_can_post_on_exceptions; } bool jvmti_can_get_owned_monitor_info() const { return _jvmti_can_get_owned_monitor_info; } @@ -375,6 +394,15 @@ class ciEnv : StackObj { int comp_level(); // task()->comp_level() int compile_id(); // task()->compile_id() +#if INCLUDE_CDS + // Register method loaded from AOT code cache + nmethod* register_aot_method(JavaThread* thread, + ciMethod* target, + AbstractCompiler* compiler, + nmethod* archived_nm, + AOTCodeReader* aot_code_reader, + bool install_code); +#endif // Register the result of a compilation. void register_method(ciMethod* target, int entry_bci, @@ -386,11 +414,14 @@ class ciEnv : StackObj { ExceptionHandlerTable* handler_table, ImplicitExceptionTable* inc_table, AbstractCompiler* compiler, + bool has_clinit_barriers, + bool for_aot_preload, bool has_unsafe_access, bool has_wide_vectors, bool has_monitors, bool has_scoped_access, - int immediate_oops_patched); + int immediate_oops_patched, + bool install_code); // Access to certain well known ciObjects. #define VM_CLASS_FUNC(name, ignore_s) \ @@ -476,6 +507,7 @@ class ciEnv : StackObj { void report_failure(const char* reason); // Report failure immediately void record_method_not_compilable(const char* reason, bool all_tiers = false); void record_out_of_memory_failure(); + void record_codecache_full(); // RedefineClasses support void metadata_do(MetadataClosure* f) { _factory->metadata_do(f); } @@ -532,6 +564,9 @@ class ciEnv : StackObj { void process_invokedynamic(const constantPoolHandle &cp, int index, JavaThread* thread); void process_invokehandle(const constantPoolHandle &cp, int index, JavaThread* thread); void find_dynamic_call_sites(); + + bool is_aot_compile() const NOT_CDS_RETURN_(false); + InstanceKlass::ClassState compute_init_state_for_aot_compile(InstanceKlass* ik) NOT_CDS_RETURN_((InstanceKlass::ClassState)0); }; #endif // SHARE_CI_CIENV_HPP diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 08f6a692d1b..e31e88217aa 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -369,6 +369,9 @@ ciConstant ciField::constant_value() { if (FoldStableValues && is_stable() && _constant_value.is_null_or_zero()) { return ciConstant(); } + if (CURRENT_ENV->is_aot_compile()) { // Restrict only when we generate AOT code + return ciConstant(); + } return _constant_value; } @@ -382,6 +385,9 @@ ciConstant ciField::constant_value_of(ciObject* object) { if (FoldStableValues && is_stable() && field_value.is_null_or_zero()) { return ciConstant(); } + if (CURRENT_ENV->is_aot_compile()) { // Restrict only when we generate AOT code + return ciConstant(); + } return field_value; } diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 5cd14de9107..451da87494c 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -56,14 +56,21 @@ ciInstanceKlass::ciInstanceKlass(Klass* k) : ciKlass(k) { assert(get_Klass()->is_instance_klass(), "wrong type"); - assert(get_instanceKlass()->is_loaded(), "must be at least loaded"); + InstanceKlass* ik = get_instanceKlass(); +#ifdef ASSERT + if (!ik->is_loaded()) { + ResourceMark rm; + assert(false, "must be at least loaded: %s", ik->name()->as_C_string()); + } +#endif // ASSERT + AccessFlags access_flags = ik->access_flags(); _flags = ciFlags(access_flags); _has_finalizer = ik->has_finalizer(); _has_subklass = flags().is_final() ? subklass_false : subklass_unknown; - _init_state = ik->init_state(); + _init_state = compute_init_state(ik); _has_nonstatic_fields = ik->has_nonstatic_fields(); _has_nonstatic_concrete_methods = ik->has_nonstatic_concrete_methods(); _is_hidden = ik->is_hidden(); @@ -147,11 +154,28 @@ InstanceKlass::ClassState ciInstanceKlass::compute_init_state() { // Return cached init state of shared klass ciEnv* env = CURRENT_ENV; assert(env->task() != nullptr, "only calls from compilation are expected here"); - return env->get_cached_init_state(ident()); + if (env->is_aot_compile()) { + GUARDED_VM_ENTRY( return env->compute_init_state_for_aot_compile(get_instanceKlass()); ) + } else { + return env->get_cached_init_state(ident()); + } } return _init_state; } +InstanceKlass::ClassState ciInstanceKlass::compute_init_state(InstanceKlass* ik) { + ASSERT_IN_VM; +#if INCLUDE_CDS + ciEnv* env = CURRENT_ENV; + if (env != nullptr && env->is_aot_compile()) { + return env->compute_init_state_for_aot_compile(ik); + } else +#endif + { + return ik->init_state(); + } +} + // ------------------------------------------------------------------ // ciInstanceKlass::compute_shared_has_subklass bool ciInstanceKlass::compute_shared_has_subklass() { diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp index b2522697a0d..c2ac44bb9bc 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -126,6 +126,8 @@ class ciInstanceKlass : public ciKlass { void compute_nonstatic_fields_impl(const GrowableArray* super_declared_fields, const GrowableArray* super_fields); bool compute_has_trusted_loader(); + static InstanceKlass::ClassState compute_init_state(InstanceKlass* ik); + public: // Has this klass been initialized? bool is_initialized() { diff --git a/src/hotspot/share/ci/ciMethod.cpp b/src/hotspot/share/ci/ciMethod.cpp index 5e9ca7e8251..1db3f606e0e 100644 --- a/src/hotspot/share/ci/ciMethod.cpp +++ b/src/hotspot/share/ci/ciMethod.cpp @@ -34,6 +34,7 @@ #include "ci/ciSymbols.hpp" #include "ci/ciUtilities.inline.hpp" #include "classfile/vmIntrinsics.hpp" +#include "code/aotCodeCache.hpp" #include "compiler/abstractCompiler.hpp" #include "compiler/compilerDefinitions.inline.hpp" #include "compiler/compilerOracle.hpp" @@ -146,6 +147,7 @@ ciMethod::ciMethod(const methodHandle& h_m, ciInstanceKlass* holder) : constantPoolHandle cpool(Thread::current(), h_m->constants()); _signature = new (env->arena()) ciSignature(_holder, cpool, sig_symbol); _method_data = nullptr; + _method_data_recorded = nullptr; // Take a snapshot of these values, so they will be commensurate with the MDO. if (ProfileInterpreter || CompilerConfig::is_c1_profiling()) { int invcnt = h_m->interpreter_invocation_count(); @@ -177,6 +179,7 @@ ciMethod::ciMethod(ciInstanceKlass* holder, _name( name), _holder( holder), _method_data( nullptr), + _method_data_recorded( nullptr), _method_blocks( nullptr), _intrinsic_id( vmIntrinsics::_none), _inline_instructions_size(-1), @@ -1061,15 +1064,19 @@ bool ciMethod::has_member_arg() const { // // Generate new MethodData* objects at compile time. // Return true if allocation was successful or no MDO is required. -bool ciMethod::ensure_method_data(const methodHandle& h_m) { +bool ciMethod::ensure_method_data(const methodHandle& h_m, bool training_data_only) { EXCEPTION_CONTEXT; if (is_native() || is_abstract() || h_m()->is_accessor()) { return true; } if (h_m()->method_data() == nullptr) { - Method::build_profiling_method_data(h_m, THREAD); - if (HAS_PENDING_EXCEPTION) { - CLEAR_PENDING_EXCEPTION; + if (training_data_only) { + Method::install_training_method_data(h_m); + } else { + Method::build_profiling_method_data(h_m, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } } } if (h_m()->method_data() != nullptr) { @@ -1082,12 +1089,12 @@ bool ciMethod::ensure_method_data(const methodHandle& h_m) { } // public, retroactive version -bool ciMethod::ensure_method_data() { +bool ciMethod::ensure_method_data(bool training_data_only) { bool result = true; if (_method_data == nullptr || _method_data->is_empty()) { GUARDED_VM_ENTRY({ methodHandle mh(Thread::current(), get_Method()); - result = ensure_method_data(mh); + result = ensure_method_data(mh, training_data_only); }); } return result; @@ -1098,22 +1105,44 @@ bool ciMethod::ensure_method_data() { // ciMethod::method_data // ciMethodData* ciMethod::method_data() { +#if INCLUDE_CDS + if (CURRENT_ENV->is_aot_compile() && CURRENT_ENV->task()->comp_level() == CompLevel_full_optimization) { + if (_method_data_recorded == nullptr) { + VM_ENTRY_MARK; + methodHandle h_m(thread, get_Method()); + MethodTrainingData* mtd = MethodTrainingData::find(h_m); + MethodData* mdo = (mtd != nullptr ? mtd->final_profile() : nullptr); + int comp_id = CURRENT_ENV->task()->compile_id(); + if (mdo == nullptr) { + ResourceMark rm; + log_debug(aot, compilation)("%d: No profile for %s", comp_id, h_m->name_and_sig_as_C_string()); + _method_data_recorded = CURRENT_ENV->get_empty_methodData(); + } else { + _method_data_recorded = CURRENT_ENV->get_method_data(mdo); + _method_data_recorded->load_data(); + { + ResourceMark rm; + log_debug(aot, compilation)("%d: Recorded profile " PTR_FORMAT " for %s", comp_id, p2i(mdo), h_m->name_and_sig_as_C_string()); + } + } + } + assert(_method_data_recorded != nullptr, ""); + return _method_data_recorded; + } +#endif if (_method_data != nullptr) { return _method_data; } VM_ENTRY_MARK; - ciEnv* env = CURRENT_ENV; - Thread* my_thread = JavaThread::current(); - methodHandle h_m(my_thread, get_Method()); - - if (h_m()->method_data() != nullptr) { - _method_data = CURRENT_ENV->get_method_data(h_m()->method_data()); + methodHandle h_m(thread, get_Method()); + MethodData* mdo = h_m()->method_data(); + if (mdo != nullptr) { + _method_data = CURRENT_ENV->get_method_data(mdo); _method_data->load_data(); } else { _method_data = CURRENT_ENV->get_empty_methodData(); } return _method_data; - } // ------------------------------------------------------------------ @@ -1131,12 +1160,15 @@ ciMethodData* ciMethod::method_data_or_null() { // ------------------------------------------------------------------ // ciMethod::ensure_method_counters // -MethodCounters* ciMethod::ensure_method_counters() { +ciMetadata* ciMethod::ensure_method_counters() { check_is_loaded(); VM_ENTRY_MARK; methodHandle mh(THREAD, get_Method()); - MethodCounters* method_counters = mh->get_method_counters(CHECK_NULL); - return method_counters; + MethodCounters* method_counters = mh->get_method_counters(THREAD); + if (method_counters != nullptr) { + return CURRENT_ENV->get_method_counters(method_counters); + } + return nullptr; } // ------------------------------------------------------------------ @@ -1214,7 +1246,7 @@ int ciMethod::inline_instructions_size() { methodHandle top_level_mh(Thread::current(), CURRENT_ENV->task()->method()); MethodTrainingData* mtd = MethodTrainingData::find(top_level_mh); if (mtd != nullptr) { - CompileTrainingData* ctd = mtd->last_toplevel_compile(level); + CompileTrainingData* ctd = mtd->compile_data_for_aot_code(level); if (ctd != nullptr) { methodHandle mh(Thread::current(), get_Method()); MethodTrainingData* this_mtd = MethodTrainingData::find(mh); @@ -1233,7 +1265,8 @@ int ciMethod::inline_instructions_size() { GUARDED_VM_ENTRY( nmethod* code = get_Method()->code(); if (code != nullptr && (code->comp_level() == CompLevel_full_optimization)) { - int isize = code->insts_end() - code->verified_entry_point() - code->skipped_instructions_size(); + int isize = code->is_aot() ? code->aot_code_entry()->inline_instructions_size() + : code->inline_instructions_size(); _inline_instructions_size = isize > 0 ? isize : 0; } else { _inline_instructions_size = 0; @@ -1274,6 +1307,19 @@ bool ciMethod::is_not_reached(int bci) { // ------------------------------------------------------------------ // ciMethod::was_never_executed bool ciMethod::was_executed_more_than(int times) { +#if INCLUDE_CDS + if (CURRENT_ENV->is_aot_compile()) { // Look only on training data + // Invocation counter is reset when the Method* is compiled. + // If the method has compiled code we therefore assume it has + // been executed more than n times. + if (is_accessor() || is_empty() || has_compiled_code()) { + // interpreter doesn't bump invocation counter of trivial methods + // compiler does not bump invocation counter of compiled methods + return true; + } + return (method_data()->invocation_count() > times); + } +#endif VM_ENTRY_MARK; return get_Method()->was_executed_more_than(times); } diff --git a/src/hotspot/share/ci/ciMethod.hpp b/src/hotspot/share/ci/ciMethod.hpp index f5dee91e725..f690cbcf62f 100644 --- a/src/hotspot/share/ci/ciMethod.hpp +++ b/src/hotspot/share/ci/ciMethod.hpp @@ -74,7 +74,8 @@ class ciMethod : public ciMetadata { ciInstanceKlass* _holder; ciSignature* _signature; ciMethodData* _method_data; - ciMethodBlocks* _method_blocks; + ciMethodData* _method_data_recorded; + ciMethodBlocks* _method_blocks; // Code attributes. int _code_size; @@ -119,7 +120,7 @@ class ciMethod : public ciMetadata { void load_code(); - bool ensure_method_data(const methodHandle& h_m); + bool ensure_method_data(const methodHandle& h_m, bool training_data_only); void code_at_put(int bci, Bytecodes::Code code) { Bytecodes::check(code); @@ -320,8 +321,8 @@ class ciMethod : public ciMetadata { bool has_unloaded_classes_in_signature(); bool is_klass_loaded(int refinfo_index, Bytecodes::Code bc, bool must_be_resolved) const; bool check_call(int refinfo_index, bool is_static) const; - bool ensure_method_data(); // make sure it exists in the VM also - MethodCounters* ensure_method_counters(); + bool ensure_method_data(bool training_data_only = false); // make sure it exists in the VM also + ciMetadata* ensure_method_counters(); int inline_instructions_size(); int scale_count(int count, float prof_factor = 1.); // make MDO count commensurate with IIC diff --git a/src/hotspot/share/ci/ciObject.cpp b/src/hotspot/share/ci/ciObject.cpp index 23696b6b70a..91de0c9ee0c 100644 --- a/src/hotspot/share/ci/ciObject.cpp +++ b/src/hotspot/share/ci/ciObject.cpp @@ -202,20 +202,24 @@ void ciObject::add_to_constant_value_cache(int off, ciConstant val) { // ------------------------------------------------------------------ // ciObject::should_be_constant() bool ciObject::should_be_constant() { - if (ScavengeRootsInCode >= 2) return true; // force everybody to be a constant - if (is_null_object()) return true; - ciEnv* env = CURRENT_ENV; + if (ScavengeRootsInCode >= 2 && !env->is_aot_compile()) { + return true; // force everybody to be a constant + } + if (is_null_object()) { + return true; + } - // We want Strings and Classes to be embeddable by default since - // they used to be in the perm world. Not all Strings used to be - // embeddable but there's no easy way to distinguish the interned - // from the regulars ones so just treat them all that way. - if (klass() == env->String_klass() || klass() == env->Class_klass()) { - return true; - } - if (klass()->is_subclass_of(env->MethodHandle_klass()) || - klass()->is_subclass_of(env->CallSite_klass())) { + // We want Strings and Classes to be embeddable by default since + // they used to be in the perm world. Not all Strings used to be + // embeddable but there's no easy way to distinguish the interned + // from the regulars ones so just treat them all that way. + if (klass() == env->String_klass() || klass() == env->Class_klass()) { + return true; + } + if ((klass()->is_subclass_of(env->MethodHandle_klass()) || + klass()->is_subclass_of(env->CallSite_klass())) && + !env->is_aot_compile()) { // For now disable it when compiling AOT code. // We want to treat these aggressively. return true; } diff --git a/src/hotspot/share/ci/ciObjectFactory.cpp b/src/hotspot/share/ci/ciObjectFactory.cpp index 3891c4e6ee9..c05dbbc4162 100644 --- a/src/hotspot/share/ci/ciObjectFactory.cpp +++ b/src/hotspot/share/ci/ciObjectFactory.cpp @@ -113,7 +113,7 @@ ciObjectFactory::ciObjectFactory(Arena* arena, ciMetadata* obj = _ci_metadata.at(i); if (obj->is_loaded() && obj->is_instance_klass()) { ciInstanceKlass* cik = obj->as_instance_klass(); - precond(cik->is_shared()); + assert(cik->is_shared(), "only shared instances are expected here"); InstanceKlass::ClassState current_state = cik->_init_state; InstanceKlass::ClassState state = InstanceKlass::fully_initialized; if (current_state != state) { @@ -182,10 +182,10 @@ void ciObjectFactory::init_shared_objects() { ciEnv::_null_object_instance = new (_arena) ciNullObject(); init_ident_of(ciEnv::_null_object_instance); -#define VM_CLASS_DEFN(name, ignore_s) \ - if (vmClasses::name##_is_loaded()) \ - ciEnv::_##name = get_metadata(vmClasses::name())->as_instance_klass(); - +#define VM_CLASS_DEFN(name, ignore_s) \ + if (vmClasses::name##_is_loaded()) { \ + ciEnv::_##name = get_metadata(vmClasses::name())->as_instance_klass(); \ + } VM_CLASSES_DO(VM_CLASS_DEFN) #undef VM_CLASS_DEFN @@ -267,7 +267,9 @@ ciObject* ciObjectFactory::get(oop key) { NonPermObject* &bucket = find_non_perm(keyHandle); if (bucket != nullptr) { - return bucket->object(); + ciObject* obj = bucket->object(); + notice_new_object(obj); + return obj; } // The ciObject does not yet exist. Create it and insert it @@ -283,6 +285,7 @@ ciObject* ciObjectFactory::get(oop key) { return new_object; } +#if INCLUDE_CDS void ciObjectFactory::notice_new_object(ciBaseObject* new_object) { if (TrainingData::need_data()) { ciEnv* env = ciEnv::current(); @@ -295,6 +298,7 @@ void ciObjectFactory::notice_new_object(ciBaseObject* new_object) { } } } +#endif int ciObjectFactory::metadata_compare(Metadata* const& key, ciMetadata* const& elt) { Metadata* value = elt->constant_encoding(); @@ -378,7 +382,9 @@ ciMetadata* ciObjectFactory::get_metadata(Metadata* key) { notice_new_object(new_object); return new_object; } - return _ci_metadata.at(index)->as_metadata(); + ciMetadata* metadata = _ci_metadata.at(index)->as_metadata(); + notice_new_object(metadata); + return metadata; } // ------------------------------------------------------------------ @@ -453,9 +459,11 @@ ciMetadata* ciObjectFactory::create_new_metadata(Metadata* o) { ciInstanceKlass* holder = env->get_instance_klass(h_m()->method_holder()); return new (arena()) ciMethod(h_m, holder); } else if (o->is_methodData()) { - // Hold methodHandle alive - might not be necessary ??? - methodHandle h_m(THREAD, ((MethodData*)o)->method()); + // Callers ciMethod::ensure_method_data() and ::method_data() have MH already. return new (arena()) ciMethodData((MethodData*)o); + } else if (o->is_methodCounters()) { + // Caller ciMethod::ensure_method_counters() has MH already. + return new (arena()) ciMetadata(o); } // The Metadata* is of some type not supported by the compiler interface. diff --git a/src/hotspot/share/ci/ciObjectFactory.hpp b/src/hotspot/share/ci/ciObjectFactory.hpp index d495bd83e3b..ff216267139 100644 --- a/src/hotspot/share/ci/ciObjectFactory.hpp +++ b/src/hotspot/share/ci/ciObjectFactory.hpp @@ -114,7 +114,7 @@ class ciObjectFactory : public ArenaObj { static ciSymbol* vm_symbol_at(vmSymbolID index); // Called on every new object made. - void notice_new_object(ciBaseObject* new_object); + void notice_new_object(ciBaseObject* new_object) NOT_CDS_RETURN; // Get the ciMethod representing an unloaded/unfound method. ciMethod* get_unloaded_method(ciInstanceKlass* holder, diff --git a/src/hotspot/share/ci/ciReplay.cpp b/src/hotspot/share/ci/ciReplay.cpp index 83cb354651d..741e095bb6c 100644 --- a/src/hotspot/share/ci/ciReplay.cpp +++ b/src/hotspot/share/ci/ciReplay.cpp @@ -809,7 +809,7 @@ class CompileReplay : public StackObj { } replay_state = this; CompileBroker::compile_method(methodHandle(THREAD, method), entry_bci, comp_level, - 0, CompileTask::Reason_Replay, THREAD); + 0, nullptr, CompileTask::Reason_Replay, THREAD); replay_state = nullptr; } diff --git a/src/hotspot/share/classfile/classLoaderDataShared.cpp b/src/hotspot/share/classfile/classLoaderDataShared.cpp index d415fe64bac..d974d65772b 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.cpp +++ b/src/hotspot/share/classfile/classLoaderDataShared.cpp @@ -122,7 +122,7 @@ void ArchivedClassLoaderData::remove_unshareable_info() { } void ArchivedClassLoaderData::restore(ClassLoaderData* loader_data, bool do_entries, bool do_oops) { - assert(CDSConfig::is_using_archive(), "must be"); + assert(CDSConfig::is_using_full_module_graph(), "must be"); assert_valid(loader_data); if (_modules != nullptr) { // Could be null if we have archived no modules for platform/system loaders ModuleEntryTable* modules = loader_data->modules(); @@ -146,7 +146,7 @@ void ArchivedClassLoaderData::restore(ClassLoaderData* loader_data, bool do_entr } void ArchivedClassLoaderData::clear_archived_oops() { - assert(CDSConfig::is_using_archive(), "must be"); + assert(!CDSConfig::is_using_full_module_graph(), "must be"); if (_modules != nullptr) { for (int i = 0; i < _modules->length(); i++) { _modules->at(i)->clear_archived_oops(); diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index c73d15c44d3..456d6843207 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -443,7 +443,7 @@ void ModuleEntry::remove_unshareable_info() { } void ModuleEntry::load_from_archive(ClassLoaderData* loader_data) { - assert(CDSConfig::is_using_archive(), "runtime only"); + assert(CDSConfig::is_using_full_module_graph(), "runtime only"); set_loader_data(loader_data); JFR_ONLY(INIT_ID(this);) } @@ -453,7 +453,7 @@ void ModuleEntry::preload_archived_oops() { } void ModuleEntry::restore_archived_oops(ClassLoaderData* loader_data) { - assert(CDSConfig::is_using_archive(), "runtime only"); + assert(CDSConfig::is_using_full_module_graph(), "runtime only"); Handle module_handle(Thread::current(), HeapShared::get_root(_archived_module_index, /*clear=*/true)); assert(module_handle.not_null(), "huh"); set_module_handle(loader_data->add_handle(module_handle)); @@ -474,7 +474,7 @@ void ModuleEntry::restore_archived_oops(ClassLoaderData* loader_data) { } void ModuleEntry::clear_archived_oops() { - assert(CDSConfig::is_using_archive(), "runtime only"); + assert(CDSConfig::is_using_archive() && !CDSConfig::is_using_full_module_graph(), "runtime only"); HeapShared::clear_root(_archived_module_index); } @@ -509,7 +509,7 @@ Array* ModuleEntryTable::build_aot_table(ClassLoaderData* loader_d void ModuleEntryTable::load_archived_entries(ClassLoaderData* loader_data, Array* archived_modules) { - assert(CDSConfig::is_using_archive(), "runtime only"); + assert(CDSConfig::is_using_full_module_graph(), "runtime only"); for (int i = 0; i < archived_modules->length(); i++) { ModuleEntry* archived_entry = archived_modules->at(i); @@ -519,7 +519,7 @@ void ModuleEntryTable::load_archived_entries(ClassLoaderData* loader_data, } void ModuleEntryTable::restore_archived_oops(ClassLoaderData* loader_data, Array* archived_modules) { - assert(CDSConfig::is_using_archive(), "runtime only"); + assert(CDSConfig::is_using_full_module_graph(), "runtime only"); for (int i = 0; i < archived_modules->length(); i++) { ModuleEntry* archived_entry = archived_modules->at(i); archived_entry->restore_archived_oops(loader_data); diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 67c64a5c9a2..c7e16fce69d 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -919,7 +919,7 @@ void SystemDictionaryShared::link_all_exclusion_check_candidates(InstanceKlass* // it can be checked by should_be_excluded_impl(). bool SystemDictionaryShared::should_be_excluded(Klass* k) { assert(CDSConfig::is_dumping_archive(), "sanity"); - assert(CDSConfig::current_thread_is_vm_or_dumper(), "sanity"); + // This method could be called during AOT compilation from compiler thread. if (CDSConfig::is_dumping_dynamic_archive() && AOTMetaspace::in_aot_cache(k)) { // We have reached a super type that's already in the base archive. Treat it diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index e79405dc4b4..4d1bb8c8309 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -25,40 +25,75 @@ #include "asm/macroAssembler.hpp" #include "cds/aotCacheAccess.hpp" +#include "cds/aotCompressedPointers.hpp" #include "cds/aotMetaspace.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/heapShared.hpp" -#include "ci/ciUtilities.hpp" +#include "ci/ciEnv.hpp" +#include "ci/ciMethod.hpp" +#include "ci/ciUtilities.inline.hpp" #include "classfile/javaAssertions.hpp" +#include "classfile/systemDictionary.hpp" #include "code/aotCodeCache.hpp" +#include "code/codeBlob.hpp" #include "code/codeCache.hpp" +#include "code/nativeInst.hpp" +#include "code/oopRecorder.inline.hpp" +#include "code/relocInfo.hpp" +#include "compiler/abstractCompiler.hpp" +#include "compiler/compilationPolicy.hpp" +#include "compiler/compileBroker.hpp" +#include "compiler/compilerDefinitions.inline.hpp" +#include "compiler/compileTask.hpp" #include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/barrierSetNMethod.hpp" #include "gc/shared/cardTableBarrierSet.hpp" #include "gc/shared/gcConfig.hpp" #include "logging/logStream.hpp" #include "memory/memoryReserver.hpp" -#include "oops/klass.hpp" +#include "memory/universe.hpp" +#include "oops/flatArrayKlass.hpp" +#include "oops/klass.inline.hpp" +#include "oops/method.inline.hpp" #include "prims/jvmtiThreadState.hpp" #include "prims/upcallLinker.hpp" +#include "runtime/atomicAccess.hpp" #include "runtime/deoptimization.hpp" #include "runtime/flags/flagSetting.hpp" #include "runtime/globals_extension.hpp" +#include "runtime/handles.inline.hpp" #include "runtime/icache.hpp" #include "runtime/java.hpp" +#include "runtime/jniHandles.inline.hpp" +#include "runtime/mountUnmountDisabler.hpp" #include "runtime/mutexLocker.hpp" +#include "runtime/objectMonitorTable.hpp" #include "runtime/os.inline.hpp" #include "runtime/sharedRuntime.hpp" -#include "runtime/stubInfo.hpp" +#include "runtime/stubCodeGenerator.hpp" #include "runtime/stubRoutines.hpp" +#include "runtime/threadIdentifier.hpp" +#include "runtime/timerTrace.hpp" #include "utilities/copy.hpp" +#include "utilities/ostream.hpp" #ifdef COMPILER1 #include "c1/c1_Runtime1.hpp" -#endif +#include "gc/g1/c1/g1BarrierSetC1.hpp" +#include "gc/shared/c1/barrierSetC1.hpp" +#if INCLUDE_SHENANDOAHGC +#include "gc/shenandoah/c1/shenandoahBarrierSetC1.hpp" +#endif // INCLUDE_SHENANDOAHGC +#include "gc/z/c1/zBarrierSetC1.hpp" +#endif // COMPILER1 #ifdef COMPILER2 +#include "opto/c2_MacroAssembler.hpp" +#include "opto/parse.hpp" #include "opto/runtime.hpp" #endif +#if INCLUDE_JFR +#include "jfr/support/jfrIntrinsics.hpp" +#endif #if INCLUDE_G1GC #include "gc/g1/g1BarrierSetRuntime.hpp" #include "gc/g1/g1HeapRegion.hpp" @@ -70,6 +105,9 @@ #if INCLUDE_ZGC #include "gc/z/zBarrierSetRuntime.hpp" #endif +#if defined(X86) && !defined(ZERO) +#include "rdtsc_x86.hpp" +#endif #include #include @@ -93,6 +131,23 @@ static LogStream& load_failure_log() { } } +// Stream to print AOT code loading info +static LogStream& load_info_log() { + static LogStream log_aot(LogLevel::Info, LogTagSetMapping::tagset()); + static LogStream log_init(LogLevel::Info, LogTagSetMapping::tagset()); + return log_init.is_enabled() ? log_init : log_aot; +} + +static elapsedTimer _t_totalLoad; +static elapsedTimer _t_totalPreload; +static elapsedTimer _t_totalRegister; +static elapsedTimer _t_totalFind; +static elapsedTimer _t_totalStore; + +static bool enable_timers() { + return CITime || log_is_enabled(Info, aot, codecache, stats); +} + // Report AOT code cache failure and exit VM // if (AOTMode is `on` and AbortVMOnAOTCodeFailure is default) // or AbortVMOnAOTCodeFailure is `true`. @@ -106,20 +161,20 @@ static void report_load_failure() { if (abort_vm) { vm_exit_during_initialization("Unable to use AOT Code Cache.", nullptr); } - load_failure_log().print_cr("Unable to use AOT Code Cache."); + load_failure_log().print_cr("Unable to use AOT Code Cache"); AOTCodeCache::disable_caching(); } static void report_store_failure() { if (AbortVMOnAOTCodeFailure) { - tty->print_cr("Unable to create AOT Code Cache."); + tty->print_cr("Unable to create AOT Code Cache"); vm_abort(false); } - log_error(aot, codecache, exit)("Unable to create AOT Code Cache."); + log_error(aot, codecache, exit)("Unable to create AOT Code Cache"); AOTCodeCache::disable_caching(); } -// The sequence of AOT code caching flags and parametters settings. +// The sequence of AOT code caching flags and parameters settings. // // 1. The initial AOT code caching flags setting is done // during call to CDSConfig::check_vm_args_consistency(). @@ -128,14 +183,20 @@ static void report_store_failure() { // where we set number of compiler threads for AOT assembly phase. // // 3. We determine presence of AOT code in AOT Cache in -// AOTMetaspace::open_static_archive() which is calles +// AOTMetaspace::get_aot_code_region_size() which is called // after compilationPolicy_init() but before codeCache_init(). // // 4. AOTCodeCache::initialize() is called during universe_init() // and does final AOT state and flags settings. // -// 5. Finally AOTCodeCache::init2() is called after universe_init() -// when all GC settings are finalized. +// 5. AOTCodeCache::init2() is called after universe_init() +// when all GC settings are finalized. AOT code cache is +// verified and AOTCodeAddressTable is populated by +// external addresses. +// +// 6. AOTCodeCache::init3() is called just before +// continuation_stubs_init() to add more addresses +// to AOTCodeAddressTable to be used by these stubs. // Next methods determine which action we do with AOT code depending // on phase of AOT process: assembly or production. @@ -156,21 +217,48 @@ bool AOTCodeCache::is_using_stub() { return AOTStubCaching && is_on_for_use(); } -// Next methods could be called regardless AOT code cache status. -// Initially they are called during flags parsing and finilized +bool AOTCodeCache::is_dumping_code() { + return AOTCodeCaching && is_on_for_dump(); +} + +bool AOTCodeCache::is_using_code() { + return AOTCodeCaching && is_on_for_use(); +} + +// This is used before AOTCodeCache is initialized +// but after AOT (CDS) Cache flags consistency is checked. +bool AOTCodeCache::maybe_dumping_code() { + return AOTCodeCaching && CDSConfig::is_dumping_final_static_archive(); +} + +// Next methods could be called regardless of AOT code cache status. +// Initially they are called during AOT flags parsing and finalized // in AOTCodeCache::initialize(). void AOTCodeCache::enable_caching() { +#if defined(ZERO) || !(defined(AMD64) || defined(AARCH64) || defined(RISCV64)) + load_info_log().print_cr("AOT Code Cache is not supported on this platform"); + disable_caching(); +#else + if (Arguments::is_valhalla_enabled()) { + // Disable AOT code caching when Valhalla is enabled. + load_info_log().print_cr("AOT Code Caching is not supported with Valhalla"); + disable_caching(); + return; + } + FLAG_SET_ERGO_IF_DEFAULT(AOTCodeCaching, true); FLAG_SET_ERGO_IF_DEFAULT(AOTStubCaching, true); FLAG_SET_ERGO_IF_DEFAULT(AOTAdapterCaching, true); +#endif } void AOTCodeCache::disable_caching() { + FLAG_SET_ERGO(AOTCodeCaching, false); FLAG_SET_ERGO(AOTStubCaching, false); FLAG_SET_ERGO(AOTAdapterCaching, false); } bool AOTCodeCache::is_caching_enabled() { - return AOTStubCaching || AOTAdapterCaching; + return AOTCodeCaching || AOTStubCaching || AOTAdapterCaching; } static uint32_t encode_id(AOTCodeEntry::Kind kind, int id) { @@ -200,47 +288,41 @@ uint AOTCodeCache::max_aot_code_size() { return _max_aot_code_size; } +bool AOTCodeCache::is_code_load_thread_on() { + return AOTCodeCaching && !CDSConfig::is_dumping_final_static_archive(); +} + // It is called from AOTMetaspace::initialize_shared_spaces() // which is called from universe_init(). -// At this point all AOT class linking seetings are finilized +// At this point all AOT class linking settings are finalized // and AOT cache is open so we can map AOT code region. void AOTCodeCache::initialize() { -#if defined(ZERO) || !(defined(AMD64) || defined(AARCH64) || defined(RISCV64)) - log_info(aot, codecache, init)("AOT Code Cache is not supported on this platform."); - disable_caching(); - return; -#else - if (FLAG_IS_DEFAULT(AOTCache)) { - log_info(aot, codecache, init)("AOT Code Cache is not used: AOTCache is not specified."); - disable_caching(); - return; // AOTCache must be specified to dump and use AOT code + if (!is_caching_enabled()) { + load_info_log().print_cr("AOT Code Cache is not used: disabled"); + return; } + assert(!FLAG_IS_DEFAULT(AOTCache), "AOTCache should be specified"); if (VerifyOops && ValueTypePassFieldsAsArgs) { - log_info(aot, codecache, init)("AOT Adapter Caching is not supported with VerifyOops + ValueTypePassFieldsAsArgs."); + load_info_log().print_cr("AOT Adapter Caching is not supported with VerifyOops + ValueTypePassFieldsAsArgs."); FLAG_SET_ERGO(AOTAdapterCaching, false); } bool is_dumping = false; bool is_using = false; if (CDSConfig::is_dumping_final_static_archive() && CDSConfig::is_dumping_aot_linked_classes()) { - is_dumping = true; - enable_caching(); is_dumping = is_caching_enabled(); } else if (CDSConfig::is_using_archive() && CDSConfig::is_using_aot_linked_classes()) { - enable_caching(); is_using = is_caching_enabled(); - } else { - log_info(aot, codecache, init)("AOT Code Cache is not used: AOT Class Linking is not used."); - disable_caching(); - return; // nothing to do } if (!(is_dumping || is_using)) { + load_info_log().print_cr("AOT Code Cache is not used"); disable_caching(); - return; // AOT code caching disabled on command line + return; } + // Set AOT Code Cache max size when dumping. _max_aot_code_size = AOTCodeMaxSize; - if (!FLAG_IS_DEFAULT(AOTCodeMaxSize)) { + if (is_dumping && !FLAG_IS_DEFAULT(AOTCodeMaxSize)) { if (!is_aligned(AOTCodeMaxSize, os::vm_allocation_granularity())) { _max_aot_code_size = align_up(AOTCodeMaxSize, os::vm_allocation_granularity()); log_debug(aot,codecache,init)("Max AOT Code Cache size is aligned up to %uK", (int)(max_aot_code_size()/K)); @@ -248,7 +330,7 @@ void AOTCodeCache::initialize() { } size_t aot_code_size = is_using ? AOTCacheAccess::get_aot_code_region_size() : 0; if (is_using && aot_code_size == 0) { - log_info(aot, codecache, init)("AOT Code Cache is empty"); + load_info_log().print_cr("AOT Code Cache is empty"); disable_caching(); return; } @@ -261,10 +343,21 @@ void AOTCodeCache::initialize() { return; } if (is_dumping) { - FLAG_SET_DEFAULT(ForceUnreachable, true); + // We cannot dump the AOT code if these flags get set wrong. + // FoldStableValues=true can compile bad code, folding the wrong values for production runs. + // ForceUnreachable=false calls might not reach their targets in the production. + // DelayCompilerStubsGeneration=true the address table would fail to mention stubs. + // Such settings must be enforced. + FLAG_SET_ERGO(FoldStableValues, false); + FLAG_SET_ERGO(ForceUnreachable, true); +#if defined(COMPILER2) && !defined(PRODUCT) + // With PrintLockStatistics=true C2 creates dynamic named counters embedded + // into code. We can't record dynamic addresses in AOT external address table + // and, as result, can't patch them when loading AOT code. + FLAG_SET_ERGO(PrintLockStatistics, false); +#endif } - FLAG_SET_DEFAULT(DelayCompilerStubsGeneration, false); -#endif // defined(AMD64) || defined(AARCH64) || defined(RISCV64) + FLAG_SET_ERGO(DelayCompilerStubsGeneration, false); } static AOTCodeCache* opened_cache = nullptr; // Use this until we verify the cache @@ -277,7 +370,8 @@ void AOTCodeCache::init2() { if (opened_cache == nullptr) { return; } - if (!opened_cache->verify_config()) { + // After Universe initialized + if (!opened_cache->verify_config_on_use()) { // Check on AOT code loading delete opened_cache; opened_cache = nullptr; report_load_failure(); @@ -287,23 +381,35 @@ void AOTCodeCache::init2() { // Report contents of AOT code cache after verification passed Header* header = opened_cache->_load_header; if (header != nullptr) { // Loading AOT code - log_info (aot, codecache, init)("Loaded %u AOT code entries from AOT Code Cache", header->entries_count()); - log_debug(aot, codecache, init)(" Adapters: total=%u", header->adapters_count()); - log_debug(aot, codecache, init)(" Shared Blobs: total=%u", header->shared_blobs_count()); - log_debug(aot, codecache, init)(" StubGen Blobs: total=%d", header->stubgen_blobs_count()); - log_debug(aot, codecache, init)(" C1 Blobs: total=%u", header->C1_blobs_count()); - log_debug(aot, codecache, init)(" C2 Blobs: total=%u", header->C2_blobs_count()); - log_debug(aot, codecache, init)(" AOT code cache size: %u bytes", header->cache_size()); - // Read strings - opened_cache->load_strings(); + if (!opened_cache->load_strings()) { + delete opened_cache; + opened_cache = nullptr; + report_load_failure(); + return; + } + uint count = header->entries_count(); + uint total_count = count + header->preload_entries_count(); + load_info_log().print_cr("Loaded %u AOT code entries from AOT Code Cache", total_count); + LogStreamHandle(Info, aot, codecache, init) log; + if (log.is_enabled()) { + log.print_cr(" %s: total=%u", aot_code_entry_kind_name[AOTCodeEntry::Adapter], header->adapters_count()); + log.print_cr(" %s: total=%u", aot_code_entry_kind_name[AOTCodeEntry::SharedBlob], header->shared_blobs_count()); + log.print_cr(" %s: total=%u", aot_code_entry_kind_name[AOTCodeEntry::C1Blob], header->C1_blobs_count()); + log.print_cr(" %s: total=%u", aot_code_entry_kind_name[AOTCodeEntry::C2Blob], header->C2_blobs_count()); + log.print_cr(" %s: total=%u", aot_code_entry_kind_name[AOTCodeEntry::StubGenBlob], header->stubgen_blobs_count()); + log.print_cr(" %s: total=%u", aot_code_entry_kind_name[AOTCodeEntry::Nmethod], header->nmethods_count()); + log.print_cr(" AOT code total size: %u bytes", header->cache_size()); + } + // Set code entry table + opened_cache->set_load_entries(); } else if (opened_cache->for_dump()) { init_C_strings_caching(); } // initialize aot runtime constants as appropriate to this runtime AOTRuntimeConstants::initialize_from_runtime(); - // initialize the table of external routines so we can save + // initialize the table of external routines and initial stubs so we can save // generated code blobs that reference them AOTCodeAddressTable* table = opened_cache->_table; assert(table != nullptr, "should be initialized already"); @@ -311,6 +417,19 @@ void AOTCodeCache::init2() { // Now cache and address table are ready for AOT code generation _cache = opened_cache; + + if (is_using_code()) { + FLAG_SET_ERGO_IF_DEFAULT(AOTCompileEagerly, true); + } + // Set ClassInitBarrierMode after all checks since it affects code generation + if (is_dumping_code()) { + FLAG_SET_ERGO_IF_DEFAULT(ClassInitBarrierMode, 1); + } else { + if (ClassInitBarrierMode > 0) { + log_info(aot, codecache, init)("Set ClassInitBarrierMode to 0 because AOT Code caching is off"); + } + FLAG_SET_ERGO(ClassInitBarrierMode, 0); + } } bool AOTCodeCache::open_cache(bool is_dumping, bool is_using) { @@ -334,18 +453,86 @@ void AOTCodeCache::init3() { AOTCodeAddressTable* table = opened_cache->_table; assert(table != nullptr, "should be initialized already"); table->init_extrs2(); + + // Verify extrs lengths in production run + Header* header = opened_cache->load_header(); + if (header != nullptr) { + uint cached_extrs2_length = header->extrs2_length(); + uint extrs2_len = table->extrs2_length(); + if (cached_extrs2_length != extrs2_len) { + load_failure_log().print_cr("AOT Code Cache disabled: different extrs2_length %u vs current %u", + cached_extrs2_length, extrs2_len); + opened_cache->set_failed(); + report_load_failure(); + return; + } + } } -void AOTCodeCache::dump() { - if (is_on()) { - assert(is_on_for_dump(), "should be called only when dumping AOT code"); +static uint _dump_jvmti_state = 0; + +bool AOTCodeCache::dump() { + if (is_on_for_dump()) { // It will also return false if there was failure + _dump_jvmti_state = get_jvmti_state(); // call before Compile_lock MutexLocker ml(Compile_lock); - _cache->finish_write(); + return _cache->finish_write(); + } + return false; +} + +class CachedCodeDirectory { +public: + uint _aot_code_size; + char* _aot_code_data; + + void set_aot_code_data(uint size, char* aot_data) { + _aot_code_size = size; + AOTCacheAccess::set_pointer(&_aot_code_data, aot_data); } + + static CachedCodeDirectory* create(); +}; + +// Storing AOT code in the AOT code region (ac) of AOT Cache: +// +// [1] Use CachedCodeDirectory to keep track of all of data related to AOT code. +// E.g., you can build a hash table to record what methods have been archived. +// +// [2] Memory for all data for AOT code, including CachedCodeDirectory, should be +// allocated using AOTCacheAccess::allocate_aot_code_region(). +// +// [3] CachedCodeDirectory must be the very first allocation. +// +// [4] Two kinds of pointer can be stored: +// - A pointer p that points to metadata. AOTCacheAccess::can_generate_aot_code(p) must return true. +// - A pointer to a buffer returned by AOTCacheAccess::allocate_aot_code_region(). +// (It's OK to point to an interior location within this buffer). +// Such pointers must be stored using AOTCacheAccess::set_pointer() +// +// The buffers allocated by AOTCacheAccess::allocate_aot_code_region() are in a contiguous region. At runtime, this +// region is mapped to the process address space. All the pointers in this buffer are relocated as necessary +// (e.g., to account for the runtime location of the CodeCache). +// +// This is always at the very beginning of the mmapped CDS "ac" (AOT code) region +static CachedCodeDirectory* _aot_code_directory = nullptr; + +CachedCodeDirectory* CachedCodeDirectory::create() { + assert(AOTCacheAccess::is_aot_code_region_empty(), "must be"); + CachedCodeDirectory* dir = (CachedCodeDirectory*)AOTCacheAccess::allocate_aot_code_region(sizeof(CachedCodeDirectory)); + dir->set_aot_code_data(0, nullptr); + return dir; } #define DATA_ALIGNMENT HeapWordSize +AOTCodeCache::~AOTCodeCache() { + if (_reserved_space.is_reserved()) { + AOTCacheAccess::unmap_aot_code_region(); + MemoryReserver::release(_reserved_space); + _reserved_space = {}; + } +} + AOTCodeCache::AOTCodeCache(bool is_dumping, bool is_using) : _load_header(nullptr), _load_buffer(nullptr), @@ -362,34 +549,63 @@ AOTCodeCache::AOTCodeCache(bool is_dumping, bool is_using) : _load_entries(nullptr), _search_entries(nullptr), _store_entries(nullptr), - _store_entries_cnt(0) + _store_entries_cnt(0), + _compile_id(0), + _comp_level(0) { // Read header at the begining of cache if (_for_use) { // Read cache size_t load_size = AOTCacheAccess::get_aot_code_region_size(); + size_t header_size = sizeof(CachedCodeDirectory) + sizeof(Header); + assert(load_size >= header_size, "aot code region is too small: %zu < %zu", load_size, header_size); ReservedSpace rs = MemoryReserver::reserve(load_size, mtCode); if (!rs.is_reserved()) { - log_warning(aot, codecache, init)("Failed to reserved %u bytes of memory for mapping AOT code region into AOT Code Cache", (uint)load_size); + log_warning(aot, codecache, init)("Failed to reserve %u bytes of memory for mapping AOT code region from AOT Cache", (uint)load_size); set_failed(); return; } + _reserved_space = rs; if (!AOTCacheAccess::map_aot_code_region(rs)) { - log_warning(aot, codecache, init)("Failed to read/mmap cached code region into AOT Code Cache"); + log_warning(aot, codecache, init)("Failed to read/mmap AOT code region from AOT Cache"); + set_failed(); + return; + } + _aot_code_directory = (CachedCodeDirectory*)rs.base(); + + // Do several checks to make sure cache is valid. + + _load_size = _aot_code_directory->_aot_code_size; + if (_load_size < (uint)sizeof(Header)) { + load_failure_log().print_cr("AOT Code Cache disabled: _aot_code_size is small %u", _load_size); set_failed(); return; } - _load_size = (uint)load_size; - _load_buffer = (char*)rs.base(); - assert(is_aligned(_load_buffer, DATA_ALIGNMENT), "load_buffer is not aligned"); - log_debug(aot, codecache, init)("Mapped %u bytes at address " INTPTR_FORMAT " at AOT Code Cache", _load_size, p2i(_load_buffer)); + _load_buffer = _aot_code_directory->_aot_code_data; + if (!is_aligned(_load_buffer, DATA_ALIGNMENT)) { + load_failure_log().print_cr("AOT Code Cache disabled: _aot_code_data is not aligned"); + set_failed(); + return; + } + if (p2u(_load_buffer) < p2u(rs.base())) { + load_failure_log().print_cr("AOT Code Cache disabled: _aot_code_data " INTPTR_FORMAT " points outside reserved space " INTPTR_FORMAT, p2i(_load_buffer), p2i(rs.base())); + set_failed(); + return; + } + uintptr_t offset = p2u(_load_buffer) - p2u(rs.base()); + if (offset < sizeof(CachedCodeDirectory) || offset > load_size || _load_size > load_size - offset) { + load_failure_log().print_cr("AOT Code Cache disabled: damaged CachedCodeDirectory data"); + set_failed(); + return; + } _load_header = (Header*)addr(0); if (!_load_header->verify(_load_size)) { set_failed(); return; } + load_info_log().print_cr("Mapped %u bytes at address " INTPTR_FORMAT " from AOT Code Cache", _load_size, p2i(_load_buffer)); } if (_for_dump) { _C_store_buffer = NEW_C_HEAP_ARRAY(char, max_aot_code_size() + DATA_ALIGNMENT, mtCode); @@ -401,6 +617,14 @@ AOTCodeCache::AOTCodeCache(bool is_dumping, bool is_using) : _table = new AOTCodeAddressTable(); } +void AOTCodeCache::invalidate(AOTCodeEntry* entry) { + // Invalidate AOT code during production run. + // This could be concurrent execution. + if (entry != nullptr && is_on_for_use()) { + _cache->invalidate_entry(entry); + } +} + void AOTCodeCache::add_stub_entries(StubId stub_id, address start, GrowableArray
*entries, int begin_idx) { EntryId entry_id = StubInfo::entry_base(stub_id); add_stub_entry(entry_id, start); @@ -438,8 +662,38 @@ void AOTCodeCache::set_shared_stubs_complete() { void AOTCodeCache::set_c1_stubs_complete() { AOTCodeAddressTable* table = addr_table(); + bool success = true; if (table != nullptr) { + // Called by C1 compiler thread set_c1_stubs_complete() concurrently + // modifies external address table while other threads read it. + bool for_dump = opened_cache->_for_dump; + bool for_use = opened_cache->_for_use; + ConditionalMutexLocker mld(Compile_lock, for_dump, // Used during assembly phase + Mutex::_safepoint_check_flag); + ConditionalMutexLocker mlu(CodeCache_lock, for_use, // Used during production run + Mutex::_no_safepoint_check_flag); + if (opened_cache->failed()) { + return; + } table->set_c1_stubs_complete(); + Header* header = opened_cache->_load_header; + if (header != nullptr) { // Production run + uint cached_extrs2_length = header->extrs2_length(); + uint cached_final_extrs_length = header->final_extrs_length(); + // During assembly phase C1 may not be used, in such case + // extrs2 and final_extrs lengths will be equal. + // Do not discard AOT code cache if C1 was enabled only in production run. + if ((cached_final_extrs_length != cached_extrs2_length) && + (cached_final_extrs_length != table->extrs_length())) { + load_failure_log().print_cr("AOT Code Cache disabled: different final_extrs_length %u vs current %u", + cached_final_extrs_length, table->extrs_length()); + opened_cache->set_failed(); + success = false; + } + } + } + if (!success) { + report_load_failure(); } } @@ -457,7 +711,22 @@ void AOTCodeCache::set_stubgen_stubs_complete() { } } -void AOTCodeCache::Config::record(uint cpu_features_offset) { +#ifdef COMPILER2 +static uint get_c2_ea_state() { + uint state = 0; + bool eliminate_alloc = (DoEscapeAnalysis && EliminateAllocations) + || EliminateAutoBox + || EnableVectorAggressiveReboxing; + bool eliminate_lock = (DoEscapeAnalysis || EliminateNestedLocks) + && EliminateLocks; + state += DoEscapeAnalysis ? 1 : 0; + state += eliminate_alloc ? 2 : 0; + state += eliminate_lock ? 4 : 0; + return state; +} +#endif // COMPILER2 + +void AOTCodeCache::Config::record() { #define AOTCODECACHE_SAVE_VAR(type, name) _saved_ ## name = name; #define AOTCODECACHE_SAVE_FUN(type, name, fun) _saved_ ## name = fun; @@ -467,6 +736,8 @@ void AOTCodeCache::Config::record(uint cpu_features_offset) { // Special configs that cannot be checked with macros _compressedOopBase = CompressedOops::base(); _compressedOopShift = CompressedOops::shift(); + _compressedKlassBase = CompressedKlassPointers::base(); + _codeCacheSize = pointer_delta(CodeCache::high_bound(), CodeCache::low_bound(), 1); #if defined(X86) && !defined(ZERO) _useUnalignedLoadStores = UseUnalignedLoadStores; @@ -476,16 +747,92 @@ void AOTCodeCache::Config::record(uint cpu_features_offset) { _avoidUnalignedAccesses = AvoidUnalignedAccesses; #endif - _cpu_features_offset = cpu_features_offset; +#ifdef COMPILER2 + _reduceInitialCardMarks = ReduceInitialCardMarks; + _c2_ea_state = get_c2_ea_state(); +#endif // COMPILER2 + + _jvmti_state = _dump_jvmti_state; +} + +static uint encode_jvmti_state(bool hotswap_or_breakpoint, + bool access_local, + bool post_on_exceptions, + bool pop_frame, + bool owned_monitor_info, + bool walk_any_space) { + return (hotswap_or_breakpoint ? 1u << 0 : 0u) | + (access_local ? 1u << 1 : 0u) | + (post_on_exceptions ? 1u << 2 : 0u) | + (pop_frame ? 1u << 3 : 0u) | + (owned_monitor_info ? 1u << 4 : 0u) | + (walk_any_space ? 1u << 5 : 0u); +} + +uint AOTCodeCache::get_jvmti_state() { + // Get Jvmti capabilities under lock to get consistent values. + MutexLocker mu(JvmtiThreadState_lock); + return encode_jvmti_state(JvmtiExport::can_hotswap_or_post_breakpoint(), + JvmtiExport::can_access_local_variables(), + JvmtiExport::can_post_on_exceptions(), + JvmtiExport::can_pop_frame(), + JvmtiExport::can_get_owned_monitor_info(), + JvmtiExport::can_walk_any_space()); +} + +bool AOTCodeCache::Header::verify_jvmti_state(ciEnv* env) const { + uint cached_state = _config.jvmti_state(); + // Conservatively require exact match. + uint state = encode_jvmti_state(env->jvmti_can_hotswap_or_post_breakpoint(), + env->jvmti_can_access_local_variables(), + env->jvmti_can_post_on_exceptions(), + env->jvmti_can_pop_frame(), + env->jvmti_can_get_owned_monitor_info(), + env->jvmti_can_walk_any_space()); + return cached_state == state; +} + +bool AOTCodeCache::verify_jvmti_state(ciEnv* env) { + AOTCodeCache* cache = open_for_use(); + if (cache == nullptr) { + return false; + } + return cache->_load_header->verify_jvmti_state(env); +} + +#ifdef COMPILER2 +bool AOTCodeCache::Header::verify_c2_state() const { + uint current_c2_ea_state = get_c2_ea_state(); + uint cached_c2_ea_state = _config.c2_ea_state(); + if ((cached_c2_ea_state & ~current_c2_ea_state) != 0) { + log_trace(aot, codecache, nmethod)("AOT Tier4 code skipped: incompatible C2 EA state: %u vs current %u", cached_c2_ea_state, current_c2_ea_state); + return false; + } + bool cached_reduce_initial_cm = _config.reduce_initial_cm(); + if (cached_reduce_initial_cm && !ReduceInitialCardMarks) { + log_trace(aot, codecache, nmethod)("AOT Tier4 code skipped: incompatible ReduceInitialCardMarks: %u vs current %u", cached_reduce_initial_cm, ReduceInitialCardMarks); + return false; + } + return true; } +#endif // COMPILER2 -bool AOTCodeCache::Config::verify_cpu_features(AOTCodeCache* cache) const { +bool AOTCodeCache::Header::verify_cpu_features(AOTCodeCache* cache) const { LogStreamHandle(Debug, aot, codecache, init) log; - uint offset = _cpu_features_offset; - uint cpu_features_size = *(uint *)cache->addr(offset); - assert(cpu_features_size == (uint)VM_Version::cpu_features_size(), "must be"); - offset += sizeof(uint); + if (_cpu_features_size != (uint)VM_Version::cpu_features_size()) { + if (load_failure_log().is_enabled()) { + ResourceMark rm; + load_failure_log().print_cr("AOT Code Cache disabled: different cpu features size %d vs current %d", + _cpu_features_size, (uint)VM_Version::cpu_features_size()); + } + // Assert in debug VM + assert(false, "different cpu features size %d vs current %d", + _cpu_features_size, (uint)VM_Version::cpu_features_size()); + return false; + } + uint offset = _cpu_features_offset; + assert(is_aligned(offset, DATA_ALIGNMENT), "should be"); void* cached_cpu_features_buffer = (void *)cache->addr(offset); if (log.is_enabled()) { ResourceMark rm; // required for stringStream::as_string() @@ -560,13 +907,7 @@ bool check_config(T saved, T current, const char* name) { } } -bool AOTCodeCache::Config::verify(AOTCodeCache* cache) const { - // check CPU features before checking flags that may be - // auto-configured in response to them - if (!verify_cpu_features(cache)) { - return false; - } - +bool AOTCodeCache::Config::verify() const { // Tests for config options which might affect validity of adapters, // stubs or nmethods. Currently we take a pessemistic stand and // drop the whole cache if any of these are changed. @@ -592,6 +933,10 @@ bool AOTCodeCache::Config::verify(AOTCodeCache* cache) const { load_failure_log().print_cr(COMPRESSED_OOPS_HINT); return false; } + if ((_compressedKlassBase == nullptr || CompressedKlassPointers::base() == nullptr) && (_compressedKlassBase != CompressedKlassPointers::base())) { + load_failure_log().print_cr("AOT Code Cache disabled: incompatible CompressedKlassPointers::base(): %p vs current %p", _compressedKlassBase, CompressedKlassPointers::base()); + return false; + } #if defined(X86) && !defined(ZERO) // switching off UseUnalignedLoadStores can affect validity of fill @@ -611,21 +956,131 @@ bool AOTCodeCache::Config::verify(AOTCodeCache* cache) const { } #endif // (defined(AARCH64) || defined(RISCV64)) && !defined(ZERO) + uint current_jvmti_state = AOTCodeCache::get_jvmti_state(); + if (AOTCodeCaching &&_jvmti_state != current_jvmti_state) { + load_failure_log().print_cr("AOT nmethod loading disabled: incompatible JVMTI state: %u vs current %u", _jvmti_state, current_jvmti_state); + FLAG_SET_ERGO(AOTCodeCaching, false); + } + return true; } +// Layout of AOT code cache +// +// Header +// CPU features data +// AOT preload code entries 'AOTCodeEntry preload_entries[preload_entries_count]' array +// AOT preload code +// AOT entries for adapters, stubs, code 'AOTCodeEntry load_entries[entries_count]' array +// AOT adapters, stubs, code +// 'uint search_entries[2 * entries_count]' array +// C strings data +// 'uint string_lengths[strings_count]' array +// const char* strings; all strings +// + +// returns end of section offset +size_t AOTCodeCache::Header::verify_section(uint offset, + uint count, + size_t unit_size, + size_t low_limit, + const char* section_name) const { + if ((size_t)offset < low_limit || !is_aligned(offset, DATA_ALIGNMENT)) { + load_failure_log().print_cr("AOT Code Cache disabled: %s offset %u is invalid", section_name, offset); + return 0; + } + size_t end_offset = 0; + if (count > 0) { + size_t ssize = count * unit_size; + uint size = (uint)ssize; + if (ssize != (size_t)size) { // overflow + load_failure_log().print_cr("AOT Code Cache disabled: %s count too big %u", section_name, count); + return 0; + } + if (!contains(offset, size)) { + load_failure_log().print_cr("AOT Code Cache disabled: %s offset %u + size %u >= %u AOT Code Cache size", section_name, offset, size, _cache_size); + return 0; + } + end_offset = align_up(offset + ssize, DATA_ALIGNMENT); + } else { // count == 0 + if (offset > _cache_size) { + load_failure_log().print_cr("AOT Code Cache disabled: %s offset %u + 0 >= %u AOT Code Cache size", section_name, offset, _cache_size); + return 0; + } + end_offset = offset; + } + return end_offset; +} + bool AOTCodeCache::Header::verify(uint load_size) const { if (_version != AOT_CODE_VERSION) { load_failure_log().print_cr("AOT Code Cache disabled: different AOT Code version %d vs %d recorded in AOT Code header", AOT_CODE_VERSION, _version); return false; } - if (load_size < _cache_size) { - load_failure_log().print_cr("AOT Code Cache disabled: AOT Code Cache size %d < %d recorded in AOT Code header", load_size, _cache_size); + if (load_size != _cache_size) { + load_failure_log().print_cr("AOT Code Cache disabled: AOT Code Cache size %u != %u recorded in AOT Code header", load_size, _cache_size); + return false; + } + + // Check CPU features data + uint cpu_offset = cpu_features_offset(); + uint cpu_size = cpu_features_size(); + size_t head_end = align_up(sizeof(Header), DATA_ALIGNMENT); + if ((size_t)cpu_offset != head_end) { + load_failure_log().print_cr("AOT Code Cache disabled: CPU features offset %u is invalid", cpu_offset); + return 0; + } + size_t cpu_end = verify_section(cpu_offset, cpu_size, sizeof(char), head_end, "CPU features"); + if (cpu_end == 0) { + return false; + } + + // Check preload entries + uint preload_offset = preload_entries_offset(); + uint preload_count = preload_entries_count(); + size_t entry_size = sizeof(AOTCodeEntry); + size_t preload_end = verify_section(preload_offset, preload_count, entry_size, cpu_end, "preload entries"); + if (preload_end == 0) { + return false; + } + + // Check normal entries + uint normal_offset = entries_offset(); + uint normal_count = entries_count(); + size_t normal_end = verify_section(normal_offset, normal_count, entry_size, preload_end, "normal entries"); + if (normal_end == 0) { + return false; + } + + uint search_offset = search_table_offset(); + uint search_count = normal_count * 2; + size_t search_end = verify_section(search_offset, search_count, sizeof(uint), normal_end, "search table"); + if (search_end == 0) { + return false; + } + + // Check strings + uint str_offset = strings_offset(); + uint str_count = strings_count(); + if ((size_t)str_offset != search_end) { + load_failure_log().print_cr("AOT Code Cache disabled: strings table offset %u is invalid", str_offset); + return 0; + } + size_t str_end = verify_section(str_offset, str_count, sizeof(uint), search_end, "strings table"); + if (str_end == 0) { return false; } return true; } +bool AOTCodeCache::failed() const { + return AtomicAccess::load(&_failed); +} + +void AOTCodeCache::set_failed() { + AtomicAccess::store(&_failed, true); +} + AOTCodeCache* AOTCodeCache::open_for_use() { if (AOTCodeCache::is_on_for_use()) { return AOTCodeCache::cache(); @@ -642,37 +1097,112 @@ AOTCodeCache* AOTCodeCache::open_for_dump() { return nullptr; } -void copy_bytes(const char* from, address to, uint size) { - assert((int)size > 0, "sanity"); - memcpy(to, from, size); - log_trace(aot, codecache)("Copied %d bytes from " INTPTR_FORMAT " to " INTPTR_FORMAT, size, p2i(from), p2i(to)); +bool AOTCodeCache::is_address_in_aot_cache(address p) { + // AOT code cache may be not opened yet. + // There could be a failure reading from AOT code cache + // at some point and we can't use open_for_use() after that. + // cache_buffer() should not be null when cache is used + // unless there was failure loading AOT code cache. + AOTCodeCache* cache = _cache; + if (cache == nullptr || cache->cache_buffer() == nullptr) { + return false; + } + if ((p >= (address)cache->cache_buffer()) && + (p < (address)(cache->cache_buffer() + cache->load_size()))) { + return true; + } + return false; } -AOTCodeReader::AOTCodeReader(AOTCodeCache* cache, AOTCodeEntry* entry) { +AOTCodeReader::AOTCodeReader(AOTCodeCache* cache, AOTCodeEntry* entry, CompileTask* task) { + precond(cache != nullptr); _cache = cache; _entry = entry; - _load_buffer = cache->cache_buffer(); + + // entry's offset bound checks were done in find_entry() and verify_nmethod_entry(). + _entry_buffer = (address)cache->cache_buffer() + entry->offset(); + + // Next two fields will be reset when processing entry's code _read_position = 0; - _lookup_failed = false; - _name = nullptr; - _reloc_data = nullptr; - _reloc_count = 0; - _oop_maps = nullptr; + _read_limit = entry->size(); + + _failure = nullptr; + + // Values used by restore(code_blob). _entry_kind = AOTCodeEntry::None; - _stub_data = nullptr; _id = -1; + _stub_data = nullptr; + if (task != nullptr) { + _compile_id = task->compile_id(); + _comp_level = task->comp_level(); + assert((uint)task->comp_level() == entry->comp_level(), "sanity"); + assert(task->is_aot_preload() == entry->for_preload(), "sanity"); + } else { + _compile_id = 0; + _comp_level = 0; + } + _name = nullptr; + _reloc_data = nullptr; + _reloc_count = 0; + _oop_maps = nullptr; + _immutable_data = nullptr; + + _cached_stub_data_count = 0; + _cached_stub_data = nullptr; + + _id_for_reloc_count = 0; + _id_for_reloc = nullptr; + +#ifndef PRODUCT + _asm_remarks_count = 0; + _asm_remarks_data = nullptr; + _asm_remarks_strings = nullptr; + + _dbg_strings_count = 0; + _dbg_strings_data = nullptr; + _dbg_strings = nullptr; +#endif + + _oop_list = nullptr; + _metadata_list = nullptr; + _reloc_imm_oop_list = nullptr; + _reloc_imm_metadata_list = nullptr; } -void AOTCodeReader::set_read_position(uint pos) { - if (pos == _read_position) { - return; +// Get current pointer to AOT code entry and update it to next position. +bool AOTCodeReader::get_set_read_position(address* pos, uint nbytes) { + if (_read_position > _read_limit || nbytes > (_read_limit - _read_position)) { + set_lookup_failed("Damaged AOT code"); + return false; + } + *pos = position_addr(); + _read_position += nbytes; + return true; +} + +bool AOTCodeReader::update_read_position(uint nbytes) { + address dummy; + return get_set_read_position(&dummy, nbytes); +} + +bool AOTCodeReader::align_read_position(uint alignment) { + uint padding = alignment - (_read_position & (alignment - 1)); + if (padding == alignment) { + return true; } - assert(pos < _cache->load_size(), "offset:%d >= file size:%d", pos, _cache->load_size()); - _read_position = pos; + return update_read_position(padding); } -uint AOTCodeReader::align_read_int() { - return align_up(_read_position, sizeof(int)); +bool AOTCodeReader::read_int(int* val) { + if (!align_read_position(sizeof(int))) { + return false; + } + address pos; + if (!get_set_read_position(&pos, sizeof(int))) { + return false; + } + *val = *(int*)pos; + return true; } bool AOTCodeCache::set_write_position(uint pos) { @@ -694,8 +1224,7 @@ bool AOTCodeCache::align_write_bytes(uint alignment) { if (padding == alignment) { return true; } - uint n = write_bytes((const void*)&align_buffer, padding); - if (n != padding) { + if (!write_bytes((const void*)&align_buffer, padding)) { return false; } log_trace(aot, codecache)("Adjust write alignment to %d bytes in AOT Code Cache", alignment); @@ -714,11 +1243,14 @@ bool AOTCodeCache::align_write_int() { // Check to see if AOT code cache has required space to store "nbytes" of data address AOTCodeCache::reserve_bytes(uint nbytes) { + precond((int)nbytes > 0); assert(for_dump(), "Code Cache file is not created"); uint new_position = _write_position + nbytes; - if (new_position >= (uint)((char*)_store_entries - _store_buffer)) { - log_warning(aot,codecache)("Failed to ensure %d bytes at offset %d in AOT Code Cache. Increase AOTCodeMaxSize.", - nbytes, _write_position); + // Take into account new AOTCodeEntry created after code and data are stored + AOTCodeEntry* entries = _store_entries - 1; // Allocated in reverse + if (new_position >= (uint)((char*)entries - _store_buffer)) { + log_error(aot, codecache)("Failed to ensure %d bytes at offset %d in AOT Code Cache. Increase AOTCodeMaxSize.", + nbytes, _write_position); set_failed(); report_store_failure(); return nullptr; @@ -732,72 +1264,326 @@ address AOTCodeCache::reserve_bytes(uint nbytes) { return buffer; } -uint AOTCodeCache::write_bytes(const void* buffer, uint nbytes) { - assert(for_dump(), "Code Cache file is not created"); +static void copy_bytes(const char* from, address to, uint size) { + precond((int)size > 0); // size <= max_jint which is AOTCodeMaxSize limit + memcpy(to, from, size); + log_trace(aot, codecache)("Copied %d bytes from " INTPTR_FORMAT " to " INTPTR_FORMAT, size, p2i(from), p2i(to)); +} + +bool AOTCodeCache::write_bytes(const void* buffer, uint nbytes) { if (nbytes == 0) { - return 0; - } - uint new_position = _write_position + nbytes; - if (new_position >= (uint)((char*)_store_entries - _store_buffer)) { - log_warning(aot, codecache)("Failed to write %d bytes at offset %d to AOT Code Cache. Increase AOTCodeMaxSize.", - nbytes, _write_position); - set_failed(); - report_store_failure(); - return 0; + return true; } - copy_bytes((const char* )buffer, (address)(_store_buffer + _write_position), nbytes); - log_trace(aot, codecache)("Wrote %d bytes at offset %d to AOT Code Cache", nbytes, _write_position); - _write_position += nbytes; - if (_store_size < _write_position) { - _store_size = _write_position; + address dest = reserve_bytes(nbytes); + if (dest == nullptr) { + return false; } - return nbytes; + copy_bytes((const char* )buffer, dest, nbytes); + return true; } -void* AOTCodeEntry::operator new(size_t x, AOTCodeCache* cache) { - return (void*)(cache->add_entry()); +bool AOTCodeCache::write_int(const int value) { + if (!align_write_int()) { + return false; + } + int* dest = (int*)reserve_bytes(sizeof(int)); + if (dest == nullptr) { + return false; + } + *dest = value; + return true; } -static bool check_entry(AOTCodeEntry::Kind kind, uint id, AOTCodeEntry* entry) { - if (entry->kind() == kind) { - assert(entry->id() == id, "sanity"); - return true; // Found +bool AOTCodeCache::skip_aot_code(uint comp_level) { + // DisableAOTCode uses decimal values as bitmask: + // Tier 1 (A1) | 1 + // Tier 2 (A1 + counters) | 10 + // Tier 3 (A1 + counters + mdo) | 100 (not used now - Tier3 is not cached) + // Tier 4 (A4) | 1000 + // Tier 5 (AP4) | 10000 + // All C1 tiers | 111 + // All tiers | 11111 + switch (comp_level) { + case CompLevel_simple: + if ((DisableAOTCode % 10) == 1) { + return true; + } + break; + case CompLevel_limited_profile: + if ((DisableAOTCode / 10) % 10 == 1) { + return true; + } + break; + case CompLevel_full_profile: { + return true; // Tier3 is not cached now + } + case CompLevel_full_optimization: + if ((DisableAOTCode / 1000) % 10 == 1) { + return true; + } + break; + case CompLevel_preload_optimization: + if ((DisableAOTCode / 10000) % 10 == 1) { + return true; // skip AOT preload code (level 5); + } + break; + default: return true; } return false; } -AOTCodeEntry* AOTCodeCache::find_entry(AOTCodeEntry::Kind kind, uint id) { - assert(_for_use, "sanity"); - uint count = _load_header->entries_count(); - if (_load_entries == nullptr) { - // Read it - _search_entries = (uint*)addr(_load_header->entries_offset()); // [id, index] - _load_entries = (AOTCodeEntry*)(_search_entries + 2 * count); - log_debug(aot, codecache, init)("Read %d entries table at offset %d from AOT Code Cache", count, _load_header->entries_offset()); +AOTCodeEntry* AOTCodeCache::find_code_entry(const methodHandle& method, uint comp_level) { + assert(is_using_code(), "AOT code caching should be enabled"); + if (!method->in_aot_cache()) { + return nullptr; } - // Binary search - int l = 0; - int h = count - 1; - while (l <= h) { - int mid = (l + h) >> 1; - int ix = mid * 2; - uint is = _search_entries[ix]; - if (is == id) { - int index = _search_entries[ix + 1]; - AOTCodeEntry* entry = &(_load_entries[index]); - if (check_entry(kind, id, entry)) { - return entry; // Found - } - // Linear search around to handle id collission - for (int i = mid - 1; i >= l; i--) { // search back - ix = i * 2; - is = _search_entries[ix]; - if (is != id) { - break; + + MethodCounters* mc = method->method_counters(); + // Generically, the carry bit means that the counter has reached a saturation value. + // In that ending state, the counter has finished its work of gathering information. + // Therefore AOT T4 code uses the carry bit to indicate a recompilation request. + if (mc != nullptr && mc->invocation_counter()->carry()) { + return nullptr; // Already requested JIT compilation + } + + if (skip_aot_code(comp_level)) { + return nullptr; + } + +#ifdef COMPILER2 + if (comp_level == CompLevel_full_optimization && !_cache->_load_header->verify_c2_state()) { + return nullptr; // Mismatched C2 flags + } +#endif + + TraceTime t1("Total time to find AOT code", &_t_totalFind, enable_timers(), false); + if (is_on() && _cache->cache_buffer() != nullptr) { + uint id = (uint)cast_to_u4(AOTCacheAccess::method_to_narrow_ptr(method())); + AOTCodeEntry* entry = _cache->find_entry(AOTCodeEntry::Nmethod, id, comp_level); + if (entry == nullptr) { + LogStreamHandle(Info, aot, codecache, nmethod) log; + if (log.is_enabled()) { + ResourceMark rm; + const char* target_name = method->name_and_sig_as_C_string(); + log.print_cr("Missing entry for '%s' (comp_level %d, id: " UINT32_FORMAT_X_0 ")", target_name, (uint)comp_level, id); + } +#ifdef ASSERT + } else { + assert(!entry->has_clinit_barriers(), "only preload code should have clinit barriers"); + ResourceMark rm; + assert(method() == entry->method(), "AOTCodeCache: saved nmethod's method %p (name: %s id: " UINT32_FORMAT_X_0 + ") is different from the method %p (name: %s, id: " UINT32_FORMAT_X_0 ") being looked up" , + entry->method(), entry->method()->name_and_sig_as_C_string(), entry->id(), method(), method()->name_and_sig_as_C_string(), id); +#endif + } + +#ifdef COMPILER2 + if (entry != nullptr && entry->has_vectors() && !EnableVectorSupport) { + log_debug(aot, codecache, nmethod)("AOT Tier4 code skipped: incompatible EnableVectorSupport: true vs current %u", EnableVectorSupport); + return nullptr; + } +#endif + + CompilerDirectiveMatcher matcher(method, comp_level); + DirectiveSet* directives = matcher.directive_set(); + if (directives->IgnoreAOTCompiledOption || directives->ExcludeOption) { + LogStreamHandle(Info, aot, codecache, compilation) log; + if (log.is_enabled()) { + log.print_cr("Ignore AOT code entry on level %d for ", comp_level); + method->print_value_on(&log); + } + return nullptr; + } + + return entry; + } + return nullptr; +} + +Method* AOTCodeEntry::method() const { + assert(_kind == Nmethod, "invalid kind %d", _kind); + assert(AOTCodeCache::is_on_for_use(), "must be"); + return AOTCacheAccess::narrow_ptr_to_method(cast_from_u4(_id)); +} + +void* AOTCodeEntry::operator new(size_t x, AOTCodeCache* cache) { + return (void*)(cache->add_entry()); +} + +bool AOTCodeEntry::not_entrant() const { + return AtomicAccess::load(&_not_entrant); +} + +void AOTCodeEntry::set_not_entrant() { + AtomicAccess::store(&_not_entrant, true); +} + +bool AOTCodeEntry::is_verified() const { + return AtomicAccess::load(&_verified); +} + +void AOTCodeEntry::set_verified() { + AtomicAccess::store(&_verified, true); +} + +bool AOTCodeEntry::try_set_not_entrant() { + // If old value is 'true' - other thread set it already. + return AtomicAccess::xchg(&_not_entrant, true); +} + +void AOTCodeCache::set_load_entries() { + // Get address of entry tables + precond(_load_header != nullptr); // Loading AOT code + uint count = _load_header->entries_count(); + uint offset = _load_header->entries_offset(); + _load_entries = (AOTCodeEntry*)addr(offset); + _search_entries = (uint*)addr(_load_header->search_table_offset()); // [id, index] + log_debug(aot, codecache, init)("Read %d entries table at offset %d from AOT Code Cache", count, offset); +} + +// Bound checks for AOT nmethod. +bool AOTCodeCache::verify_nmethod_entry(AOTCodeEntry* entry, uint id, uint low_bound, uint high_bound) { + uint entry_offset = entry->offset(); + uint entry_size = entry->size(); + // entry_size for preload should be at least sizeof(uint) to read cached id. + if (entry_offset < low_bound || entry_offset >= high_bound || + !is_aligned(entry_offset, DATA_ALIGNMENT) || + entry_size < sizeof(uint) || entry_size > (high_bound - entry_offset)) { + log_warning(aot, codecache, nmethod)("Damaged AOT nmethod entry %u " INTPTR_FORMAT " [%u + %u]", id, p2i(entry), entry_offset, entry_size); + return false; // Data is damaged, skip the rest of entries + } + // First is cached 'uint id' and then method's name + uint name_off = entry->name_offset(); + uint name_sz = entry->name_size(); + if (name_sz == 0 || name_off != sizeof(uint) || name_off >= entry_size || + name_sz > (entry_size - name_off) || + *addr(entry_offset + name_off + name_sz - 1) != '\0') { + log_warning(aot, codecache, nmethod)("Damaged method's name in AOT nmethod entry %u " INTPTR_FORMAT " [%u + %u], name [%u + %u] ", id, p2i(entry), entry_offset, entry_size, name_off, name_sz); + return false; + } + uint code_off = entry->code_offset(); + if (code_off < (name_off + name_sz) || code_off >= entry_size || + sizeof(nmethod) > (entry_size - code_off) || + !is_aligned(entry_offset + code_off, DATA_ALIGNMENT)) { + log_warning(aot, codecache, nmethod)("Damaged code offset in AOT nmethod entry %u " INTPTR_FORMAT " [%u + %u], code [%u] ", id, p2i(entry), entry_offset, entry_size, code_off); + return false; + } + uint archived_nm_offset = entry_offset + code_off; + nmethod* archived_nm = (nmethod*)addr(archived_nm_offset); + if (!archived_nm->is_nmethod()) { + log_warning(aot, codecache, nmethod)("Damaged code kind in AOT nmethod entry %u " INTPTR_FORMAT " [%u + %u], code [%u] ", id, p2i(entry), entry_offset, entry_size, code_off); + return false; + } + int nm_size = archived_nm->size(); + if (nm_size < (int)sizeof(nmethod) || (uint)nm_size > (entry_size - code_off) || + !is_aligned(nm_size, oopSize)) { + log_warning(aot, codecache, nmethod)("Damaged nmethod size in AOT nmethod entry %u " INTPTR_FORMAT " [%u + %u], nmethod [%u + %d] ", id, p2i(entry), entry_offset, entry_size, code_off, nm_size); + return false; + } + return true; +} + +AOTCodeEntry* AOTCodeCache::find_entry(AOTCodeEntry::Kind kind, uint id, uint comp_level) { + AOTCodeEntry* entry = search_entry(kind, id, comp_level); + if (entry != nullptr) { + // Verify entry + uint entries_offset = _load_header->entries_offset(); + uint low_bound = entries_offset + _load_header->entries_count() * sizeof(AOTCodeEntry); + uint high_bound = _load_header->search_table_offset(); // Following section + + if (kind == AOTCodeEntry::Nmethod) { + if (!verify_nmethod_entry(entry, id, low_bound, high_bound)) { + return nullptr; // Data is damaged, skip + } + } else { // stubs, adapters + uint entry_offset = entry->offset(); + uint entry_size = entry->size(); + if (entry_offset < low_bound || entry_offset >= high_bound || + !is_aligned(entry_offset, DATA_ALIGNMENT) || + entry_size == 0 || entry_size > (high_bound - entry_offset)) { + log_warning(aot, codecache, stubs)("Damaged AOT code blob entry %u " INTPTR_FORMAT " [%u + %u]", id, p2i(entry), entry_offset, entry_size); + return nullptr; // Data is damaged, skip + } + uint code_off = entry->code_offset(); + // Non-nmethod blobs record code first and name last. + // sizeof(int) for _reloc_count. + if (code_off != 0 || entry_size < (sizeof(CodeBlob) + sizeof(int))) { + log_warning(aot, codecache, stubs)("Damaged code offset in AOT code blob entry %u " INTPTR_FORMAT " [%u + %u], code [%u] ", id, p2i(entry), entry_offset, entry_size, code_off); + return nullptr; + } + uint name_off = entry->name_offset(); + uint name_sz = entry->name_size(); + if (name_sz == 0 || name_off >= entry_size || + name_off < (sizeof(CodeBlob) + sizeof(int)) || + name_sz > (entry_size - name_off) || + *addr(entry_offset + name_off + name_sz - 1) != '\0') { + log_warning(aot, codecache, stubs)("Damaged code blob name in AOT code blob entry %u " INTPTR_FORMAT " [%u + %u], name [%u + %u] ", id, p2i(entry), entry_offset, entry_size, name_off, name_sz); + return nullptr; + } + CodeBlob* archived_blob = (CodeBlob*)addr(entry_offset); // + code_off (0) + if ((kind == AOTCodeEntry::Adapter) && !archived_blob->is_adapter_blob()) { + log_warning(aot, codecache, stubs)("Damaged code blob kind in AOT code blob entry %u " INTPTR_FORMAT " [%u + %u], code [%u] ", id, p2i(entry), entry_offset, entry_size, code_off); + return nullptr; + } + int blob_size = archived_blob->size(); + uint code_to_name_dist = name_off - code_off; + // sizeof(int) for the following _reloc_count + if (blob_size < (int)sizeof(CodeBlob) || (uint)blob_size > (code_to_name_dist - sizeof(int)) || + !is_aligned(blob_size, oopSize)) { + log_warning(aot, codecache, stubs)("Damaged code blob size in AOT code blob entry %u " INTPTR_FORMAT " [%u + %u], code [%u + %d] ", id, p2i(entry), entry_offset, entry_size, code_off, blob_size); + return nullptr; + } + } + } + return entry; +} + +static bool check_entry(AOTCodeEntry::Kind kind, uint id, uint comp_level, AOTCodeEntry* entry) { + if (entry->kind() == kind && entry->id() == id) { + if (kind != AOTCodeEntry::Nmethod || // adapters and stubs have only one version + // Look only for normal AOT code entry, preload code is handled separately + (!entry->not_entrant() && (entry->comp_level() == comp_level))) { + return true; // Found + } + } + return false; +} + +AOTCodeEntry* AOTCodeCache::search_entry(AOTCodeEntry::Kind kind, uint id, uint comp_level) { + assert(_for_use, "sanity"); + uint count = _load_header->entries_count(); + assert(_load_entries != nullptr && _search_entries != nullptr, "should be set in init2"); + // Binary search + int l = 0; + int h = count - 1; + while (l <= h) { + int mid = (l + h) >> 1; + int ix = mid * 2; + uint is = _search_entries[ix]; + if (is == id) { + uint index = _search_entries[ix + 1]; + if (index >= count) { + log_warning(aot, codecache)("Damaged entries search table: index %u >= count %u", index, count); + return nullptr; + } + AOTCodeEntry* entry = &(_load_entries[index]); + if (check_entry(kind, id, comp_level, entry)) { + return entry; // Found + } + // Linear search around + for (int i = mid - 1; i >= l; i--) { // search back + ix = i * 2; + is = _search_entries[ix]; + if (is != id) { + break; } index = _search_entries[ix + 1]; + if (index >= count) { + log_warning(aot, codecache)("Damaged entries search table: index %u >= count %u", index, count); + return nullptr; + } AOTCodeEntry* entry = &(_load_entries[index]); - if (check_entry(kind, id, entry)) { + if (check_entry(kind, id, comp_level, entry)) { return entry; // Found } } @@ -808,12 +1594,16 @@ AOTCodeEntry* AOTCodeCache::find_entry(AOTCodeEntry::Kind kind, uint id) { break; } index = _search_entries[ix + 1]; + if (index >= count) { + log_warning(aot, codecache)("Damaged entries search table: index %u >= count %u", index, count); + return nullptr; + } AOTCodeEntry* entry = &(_load_entries[index]); - if (check_entry(kind, id, entry)) { + if (check_entry(kind, id, comp_level, entry)) { return entry; // Found } } - break; // Not found match + break; // No match found } else if (is < id) { l = mid + 1; } else { @@ -823,30 +1613,133 @@ AOTCodeEntry* AOTCodeCache::find_entry(AOTCodeEntry::Kind kind, uint id) { return nullptr; } -extern "C" { - static int uint_cmp(const void *i, const void *j) { - uint a = *(uint *)i; - uint b = *(uint *)j; - return a > b ? 1 : a < b ? -1 : 0; +void AOTCodeCache::invalidate_entry(AOTCodeEntry* entry) { + assert(entry!= nullptr, "all entries should be read already"); + if (entry->try_set_not_entrant()) { + return; // Someone invalidated it already + } +#ifdef ASSERT + assert(_load_entries != nullptr, "sanity"); + assert(entry->is_loaded() || entry->for_preload(), "invalidate only AOT code in use or a preload code"); + bool found = false; + uint i = 0; + uint count = 0; + if (entry->for_preload()) { + count = _load_header->preload_entries_count(); + AOTCodeEntry* preload_entry = (AOTCodeEntry*)addr(_load_header->preload_entries_offset()); + for (; i < count; i++) { + if (entry == &preload_entry[i]) { + break; + } + } + } else { + count = _load_header->entries_count(); + for(; i < count; i++) { + if (entry == &(_load_entries[i])) { + break; + } + } + } + found = (i < count); + assert(found, "entry should exist"); +#endif // ASSERT + + uint name_offset = entry->offset() + entry->name_offset(); + const char* name = _load_buffer + name_offset; + uint level = entry->comp_level(); + uint comp_id = entry->comp_id(); + bool for_preload = entry->for_preload(); + bool clinit_brs = entry->has_clinit_barriers(); + log_info(aot, codecache, nmethod)("Invalidated entry for '%s' (comp_id %d, comp_level %d, hash: " UINT32_FORMAT_X_0 "%s%s)", + name, comp_id, level, entry->id(), (for_preload ? "P" : "A"), (clinit_brs ? ", has clinit barriers" : "")); + + if (!for_preload && (entry->comp_level() == CompLevel_full_optimization)) { + // Invalidate preload code if normal AOT C2 code is invalidated, + // most likely because some dependencies changed during run. + // We can still use normal AOT code if preload code is + // invalidated - normal AOT code has less restrictions. + MethodCounters* mc = entry->method()->method_counters(); + if (mc != nullptr && mc->aot_preload_code_entry() != nullptr) { + AOTCodeEntry* preload_entry = mc->aot_preload_code_entry(); + if (preload_entry != nullptr) { + assert(preload_entry->for_preload(), "expecting only such entries here"); + invalidate_entry(preload_entry); + } + } + } +} + +// +1 for preload code +const int AOTCompLevel_count = CompLevel_count + 1; // 6 levels indexed from 0 to 5 + +struct AOTCodeEntryStats { +private: + uint _kind_cnt[AOTCodeEntry::Kind_count]; + uint _nmethod_cnt[AOTCompLevel_count]; + uint _clinit_barriers_cnt; + +public: + static void check_kind(uint kind) { + assert(kind > AOTCodeEntry::None && kind < AOTCodeEntry::Kind_count, "Invalid AOTCodeEntry kind %d", kind); + } + static void check_complevel(uint lvl) { + assert(lvl > CompLevel_none && lvl < AOTCompLevel_count, "Invalid compilation level %d", lvl); + } + + void inc_entry_cnt(uint kind) { check_kind(kind); _kind_cnt[kind] += 1; } + void inc_nmethod_cnt(uint lvl) { check_complevel(lvl); _nmethod_cnt[lvl] += 1; } + void inc_clinit_barriers_cnt() { _clinit_barriers_cnt += 1; } + + AOTCodeEntryStats() { + memset(this, 0, sizeof(AOTCodeEntryStats)); + } + + void collect_entry_stats(AOTCodeEntry* entry) { + inc_entry_cnt(entry->kind()); + if (entry->is_nmethod()) { + entry->for_preload() ? inc_nmethod_cnt(AOTCompLevel_count-1) + : inc_nmethod_cnt(entry->comp_level()); + if (entry->has_clinit_barriers()) { + inc_clinit_barriers_cnt(); + } + } } + + uint entry_count(uint kind) const { check_kind(kind); return _kind_cnt[kind]; } + uint nmethod_count(uint lvl) const { check_complevel(lvl); return _nmethod_cnt[lvl]; } + uint clinit_barriers_count() const { return _clinit_barriers_cnt; } +}; + +static int uint_cmp(const void *i, const void *j) { + uint a = *(uint *)i; + uint b = *(uint *)j; + return a > b ? 1 : a < b ? -1 : 0; } -void AOTCodeCache::store_cpu_features(char*& buffer, uint buffer_size) { - uint* size_ptr = (uint *)buffer; - *size_ptr = buffer_size; - buffer += sizeof(uint); +static int preload_cmp(const void *i, const void *j) { + AOTCodeEntry* ea = *(AOTCodeEntry**)i; + AOTCodeEntry* eb = *(AOTCodeEntry**)j; + uint a = ea->comp_id(); + uint b = eb->comp_id(); + return a > b ? 1 : a < b ? -1 : 0; +} +char* AOTCodeCache::store_cpu_features(char* buffer, uint buffer_size) { + // Align to 8 bytes - see Aarch64 version of get_cpu_features_name() + precond(is_aligned(buffer, DATA_ALIGNMENT)); VM_Version::store_cpu_features(buffer); log_debug(aot, codecache, exit)("CPU features recorded in AOTCodeCache: %s", VM_Version::features_string()); buffer += buffer_size; - buffer = align_up(buffer, DATA_ALIGNMENT); + return align_up(buffer, DATA_ALIGNMENT); } bool AOTCodeCache::finish_write() { if (!align_write()) { return false; } - uint strings_offset = _write_position; + // End of AOT code + uint code_size = _write_position; + uint strings_offset = code_size; int strings_count = store_strings(); if (strings_count < 0) { return false; @@ -856,31 +1749,35 @@ bool AOTCodeCache::finish_write() { } uint strings_size = _write_position - strings_offset; - uint entries_count = 0; // Number of entrant (useful) code entries - uint entries_offset = _write_position; + uint code_count = _store_entries_cnt; + if (code_count == 0) { + log_info(aot, codecache, exit)("AOT Code Cache was not created: no entries"); + return false; // Nothing to write + } + { + _aot_code_directory = CachedCodeDirectory::create(); + assert(_aot_code_directory != nullptr, "Sanity check"); - uint store_count = _store_entries_cnt; - if (store_count > 0) { uint header_size = (uint)align_up(sizeof(AOTCodeCache::Header), DATA_ALIGNMENT); - uint code_count = store_count; uint search_count = code_count * 2; uint search_size = search_count * sizeof(uint); uint entries_size = (uint)align_up(code_count * sizeof(AOTCodeEntry), DATA_ALIGNMENT); // In bytes - // _write_position includes size of code and strings + // _write_position should include code and strings uint code_alignment = code_count * DATA_ALIGNMENT; // We align_up code size when storing it. uint cpu_features_size = VM_Version::cpu_features_size(); - uint total_cpu_features_size = sizeof(uint) + cpu_features_size; // sizeof(uint) to store cpu_features_size uint total_size = header_size + _write_position + code_alignment + search_size + entries_size + - align_up(total_cpu_features_size, DATA_ALIGNMENT); + align_up(cpu_features_size, DATA_ALIGNMENT); assert(total_size < max_aot_code_size(), "AOT Code size (" UINT32_FORMAT " bytes) is greater than AOTCodeMaxSize(" UINT32_FORMAT " bytes).", total_size, max_aot_code_size()); // Allocate in AOT Cache buffer char* buffer = (char *)AOTCacheAccess::allocate_aot_code_region(total_size + DATA_ALIGNMENT); + assert(buffer != nullptr, "CDS should already throw OOM error"); char* start = align_up(buffer, DATA_ALIGNMENT); char* current = start + header_size; // Skip header + current = align_up(current, DATA_ALIGNMENT); uint cpu_features_offset = current - start; - store_cpu_features(current, cpu_features_size); + current = store_cpu_features(current, cpu_features_size); assert(is_aligned(current, DATA_ALIGNMENT), "sanity check"); assert(current < start + total_size, "sanity check"); @@ -888,105 +1785,168 @@ bool AOTCodeCache::finish_write() { uint* search = NEW_C_HEAP_ARRAY(uint, search_count, mtCode); AOTCodeEntry* entries_address = _store_entries; // Pointer to latest entry - uint adapters_count = 0; - uint shared_blobs_count = 0; - uint stubgen_blobs_count = 0; - uint C1_blobs_count = 0; - uint C2_blobs_count = 0; + AOTCodeEntryStats stats; uint max_size = 0; + + // First, sort preload entries based on compile_id. + AOTCodeEntry** preload_sorted = NEW_C_HEAP_ARRAY(AOTCodeEntry*, code_count, mtCode); + uint preload_entries_cnt = 0; // AOTCodeEntry entries were allocated in reverse in store buffer. // Process them in reverse order to cache first code first. - for (int i = store_count - 1; i >= 0; i--) { - entries_address[i].set_next(nullptr); // clear pointers before storing data - uint size = align_up(entries_address[i].size(), DATA_ALIGNMENT); + for (int i = code_count - 1; i >= 0; i--) { + AOTCodeEntry* entry = &entries_address[i]; + if (entry->for_preload()) { + assert(!entry->not_entrant(), "AOT code should not be not_entrant during assembly phase"); + preload_sorted[preload_entries_cnt] = entry; + stats.collect_entry_stats(entry); + preload_entries_cnt++; + } + } + if (preload_entries_cnt > 0) { + qsort(preload_sorted, preload_entries_cnt, sizeof(AOTCodeEntry*), preload_cmp); + } + // Store AOTCodeEntry for preload code first. + current = align_up(current, DATA_ALIGNMENT); + uint preload_entries_offset = current - start; + AOTCodeEntry* preload_entries = (AOTCodeEntry*)current; + for (int i = 0; i < (int)preload_entries_cnt; i++) { + AOTCodeEntry* entry = preload_sorted[i]; + assert(entry->for_preload(), "sanity"); + copy_bytes((const char*)entry, (address)current, sizeof(AOTCodeEntry)); + current += sizeof(AOTCodeEntry); + } + + // Now write the data for preload AOTCodeEntry + current = align_up(current, DATA_ALIGNMENT); + for (int i = 0; i < (int)preload_entries_cnt; i++) { + AOTCodeEntry* entry = &preload_entries[i]; + uint size = align_up(entry->size(), DATA_ALIGNMENT); if (size > max_size) { max_size = size; } - copy_bytes((_store_buffer + entries_address[i].offset()), (address)current, size); - entries_address[i].set_offset(current - start); // New offset + copy_bytes((_store_buffer + entry->offset()), (address)current, size); + entry->set_offset(current - start); // New offset current += size; - uint n = write_bytes(&(entries_address[i]), sizeof(AOTCodeEntry)); - if (n != sizeof(AOTCodeEntry)) { - FREE_C_HEAP_ARRAY(search); - return false; + } + FREE_C_HEAP_ARRAY(preload_sorted); + + // Store the rest of AOTCodeEntry + uint entries_count = 0; + current = align_up(current, DATA_ALIGNMENT); + uint new_entries_offset = current - start; + AOTCodeEntry* code_entries = (AOTCodeEntry*)current; + for (int i = code_count - 1; i >= 0; i--) { + AOTCodeEntry* entry = &entries_address[i]; + if (entry->for_preload()) { + continue; } - search[entries_count*2 + 0] = entries_address[i].id(); + assert(!entry->not_entrant(), "AOT code should not be not_entrant during assembly phase"); + copy_bytes((const char*)entry, (address)current, sizeof(AOTCodeEntry)); + stats.collect_entry_stats(entry); + current += sizeof(AOTCodeEntry); + search[entries_count*2 + 0] = entry->id(); search[entries_count*2 + 1] = entries_count; entries_count++; - AOTCodeEntry::Kind kind = entries_address[i].kind(); - if (kind == AOTCodeEntry::Adapter) { - adapters_count++; - } else if (kind == AOTCodeEntry::SharedBlob) { - shared_blobs_count++; - } else if (kind == AOTCodeEntry::StubGenBlob) { - stubgen_blobs_count++; - } else if (kind == AOTCodeEntry::C1Blob) { - C1_blobs_count++; - } else if (kind == AOTCodeEntry::C2Blob) { - C2_blobs_count++; - } - } - if (entries_count == 0) { - log_info(aot, codecache, exit)("AOT Code Cache was not created: no entires"); - FREE_C_HEAP_ARRAY(search); - return true; // Nothing to write - } - assert(entries_count <= store_count, "%d > %d", entries_count, store_count); + } + + // Now write the data for AOTCodeEntry + current = align_up(current, DATA_ALIGNMENT); + for (int i = 0; i < (int)entries_count; i++) { + AOTCodeEntry* entry = &code_entries[i]; + uint size = align_up(entry->size(), DATA_ALIGNMENT); + if (size > max_size) { + max_size = size; + } + copy_bytes((_store_buffer + entry->offset()), (address)current, size); + entry->set_offset(current - start); // New offset + current += size; + } + + uint total_entries_cnt = preload_entries_cnt + entries_count; + assert(total_entries_cnt == code_count, "%d != %d", total_entries_cnt, code_count); + + current = align_up(current, DATA_ALIGNMENT); + uint search_table_offset = current - start; + if (entries_count > 0) { + // Sort and store search table + qsort(search, entries_count, 2*sizeof(uint), uint_cmp); + search_size = 2 * entries_count * sizeof(uint); + copy_bytes((const char*)search, (address)current, search_size); + current += search_size; + } + FREE_C_HEAP_ARRAY(search); + // Write strings if (strings_count > 0) { copy_bytes((_store_buffer + strings_offset), (address)current, strings_size); strings_offset = (current - start); // New offset current += strings_size; + } else { + strings_offset = (current - start); // New offset } - uint new_entries_offset = (current - start); // New offset - // Sort and store search table - qsort(search, entries_count, 2*sizeof(uint), uint_cmp); - search_size = 2 * entries_count * sizeof(uint); - copy_bytes((const char*)search, (address)current, search_size); - FREE_C_HEAP_ARRAY(search); - current += search_size; - - // Write entries - entries_size = entries_count * sizeof(AOTCodeEntry); // New size - copy_bytes((_store_buffer + entries_offset), (address)current, entries_size); - current += entries_size; + // align end to get aligned size + current = align_up(current, DATA_ALIGNMENT); uint size = (current - start); assert(size <= total_size, "%d > %d", size , total_size); - log_debug(aot, codecache, exit)(" Adapters: total=%u", adapters_count); - log_debug(aot, codecache, exit)(" Shared Blobs: total=%d", shared_blobs_count); - log_debug(aot, codecache, exit)(" StubGen Blobs: total=%d", stubgen_blobs_count); - log_debug(aot, codecache, exit)(" C1 Blobs: total=%d", C1_blobs_count); - log_debug(aot, codecache, exit)(" C2 Blobs: total=%d", C2_blobs_count); - log_debug(aot, codecache, exit)(" AOT code cache size: %u bytes, max entry's size: %u bytes", size, max_size); + uint extrs_len = _table->extrs_length(); + uint extrs2_len = _table->extrs2_length(); + assert(_table->final_extrs_length() == extrs_len, "%u != %u", + _table->final_extrs_length(), extrs_len); // Finalize header AOTCodeCache::Header* header = (AOTCodeCache::Header*)start; - header->init(size, (uint)strings_count, strings_offset, - entries_count, new_entries_offset, - adapters_count, shared_blobs_count, - stubgen_blobs_count, C1_blobs_count, - C2_blobs_count, cpu_features_offset); - - log_info(aot, codecache, exit)("Wrote %d AOT code entries to AOT Code Cache", entries_count); + header->init(size, + cpu_features_size, cpu_features_offset, + preload_entries_cnt, preload_entries_offset, + entries_count, new_entries_offset, search_table_offset, + (uint)strings_count, strings_offset, + stats.entry_count(AOTCodeEntry::Adapter), stats.entry_count(AOTCodeEntry::SharedBlob), + stats.entry_count(AOTCodeEntry::StubGenBlob), stats.entry_count(AOTCodeEntry::C1Blob), + stats.entry_count(AOTCodeEntry::C2Blob), + extrs2_len, extrs_len); + + _aot_code_directory->set_aot_code_data(size, start); + + log_stats_on_exit(stats); + LogStreamHandle(Info, aot, codecache, exit) log; + if (log.is_enabled()) { + log.print_cr(" AOT code cache size: %u bytes", size); + log.print_cr(" header size: %u", header_size); + log.print_cr(" CPU features data: %u", cpu_features_size); + log.print_cr(" total code size: %u (max code's size: %u)", code_size, max_size); + log.print_cr(" entries size: %u", entries_size); + log.print_cr(" entry search table: %u", search_size); + log.print_cr(" C strings size: %u", strings_size); + log.print_cr("Wrote %d AOT code entries to AOT Code Cache", total_entries_cnt); + } } return true; } //------------------Store/Load AOT code ---------------------- +// It is called during production run too to publish stub addresses. bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, uint id, const char* name, AOTStubData* stub_data, CodeBuffer* code_buffer) { assert(AOTCodeEntry::is_valid_entry_kind(entry_kind), "invalid entry_kind %d", entry_kind); // we only expect stub data and a code buffer for a multi stub blob - assert(AOTCodeEntry::is_multi_stub_blob(entry_kind) == (stub_data != nullptr), + bool is_multi_stub = AOTCodeEntry::is_multi_stub_blob(entry_kind); + assert(is_multi_stub == (stub_data != nullptr), "entry_kind %d does not match stub_data pointer %p", entry_kind, stub_data); assert((stub_data == nullptr) == (code_buffer == nullptr), "stub data and code buffer must both be null or both non null"); + // we need to take a lock to prevent race between compiler threads generating AOT code + // and the main thread generating adapter + MutexLocker ml(Compile_lock); + if (!is_on() || (is_on_for_dump() && _aot_code_directory != nullptr)) { + return false; // AOT code cache was already dumped or failed. + } + // If this is a stub and the cache is on for either load or dump we // need to insert the stub entries into the AOTCacheAddressTable so // that relocs which refer to entries defined by this blob get @@ -1018,7 +1978,8 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind if (AOTCodeEntry::is_blob(entry_kind) && !is_dumping_stub()) { return false; } - log_debug(aot, codecache, stubs)("Writing blob '%s' (id=%u, kind=%s) to AOT Code Cache", name, id, aot_code_entry_kind_name[entry_kind]); + log_debug(aot, codecache, stubs)("Writing blob '%s' (id=%u, kind=%s) to AOT Code Cache", + name, id, aot_code_entry_kind_name[entry_kind]); #ifdef ASSERT LogStreamHandle(Trace, aot, codecache, stubs) log; @@ -1027,12 +1988,7 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind blob.print_on(&log); } #endif - // we need to take a lock to prevent race between compiler threads generating AOT code - // and the main thread generating adapter - MutexLocker ml(Compile_lock); - if (!is_on()) { - return false; // AOT code cache was already dumped and closed. - } + if (!cache->align_write()) { return false; } @@ -1063,7 +2019,7 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind int reloc_count; address reloc_data; - if (AOTCodeEntry::is_multi_stub_blob(entry_kind)) { + if (is_multi_stub) { CodeSection* cs = code_buffer->code_section(CodeBuffer::SECT_INSTS); reloc_count = (cs->has_locs() ? cs->locs_count() : 0); reloc_data = (reloc_count > 0 ? (address)cs->locs_start() : nullptr); @@ -1071,22 +2027,24 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind reloc_count = blob.relocation_size() / sizeof(relocInfo); reloc_data = (address)blob.relocation_begin(); } - uint n = cache->write_bytes(&reloc_count, sizeof(int)); - if (n != sizeof(int)) { + + if (!cache->write_int(reloc_count)) { return false; } - if (AOTCodeEntry::is_multi_stub_blob(entry_kind)) { - // align to heap word size before writing the relocs so we can - // install them into a code buffer when they get restored - if (!cache->align_write()) { + bool store_relocations = (reloc_count > 0); + if (store_relocations) { + if (is_multi_stub) { + // align to heap word size before writing the relocs so we can + // install them into a code buffer when they get restored + if (!cache->align_write()) { + return false; + } + } + uint reloc_data_size = (uint)(reloc_count * sizeof(relocInfo)); + if (!cache->write_bytes(reloc_data, reloc_data_size)) { return false; } } - uint reloc_data_size = (uint)(reloc_count * sizeof(relocInfo)); - n = cache->write_bytes(reloc_data, reloc_data_size); - if (n != reloc_data_size) { - return false; - } bool has_oop_maps = false; if (blob.oop_maps() != nullptr) { @@ -1105,19 +2063,22 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind } } - // now we have added all the other data we can write details of any - // extra the AOT relocations + // All addresses referenced by the blob have now been added to the address + // table, so their IDs can be recorded in the relocation data. + + // Don't skip assert for unknown external address in adapter and stub code. + bool assert_for_unknown_address = true; bool write_ok = true; - if (AOTCodeEntry::is_multi_stub_blob(entry_kind)) { - if (reloc_count > 0) { + if (store_relocations) { + if (is_multi_stub) { CodeSection* cs = code_buffer->code_section(CodeBuffer::SECT_INSTS); RelocIterator iter(cs); - write_ok = cache->write_relocations(blob, iter); + write_ok = cache->write_relocations(blob, iter, assert_for_unknown_address); + } else { + RelocIterator iter(&blob); + write_ok = cache->write_relocations(blob, iter, assert_for_unknown_address); } - } else { - RelocIterator iter(&blob); - write_ok = cache->write_relocations(blob, iter); } if (!write_ok) { @@ -1129,11 +2090,8 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind } #ifndef PRODUCT - // Write asm remarks after relocation info - if (!cache->write_asm_remarks(blob)) { - return false; - } - if (!cache->write_dbg_strings(blob)) { + // Write asm remarks and dbg strings after relocation info + if (!cache->write_asm_rem_and_dbg_str(blob.asm_remarks(), blob.dbg_strings())) { return false; } #endif /* PRODUCT */ @@ -1141,8 +2099,7 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind // Write name after code comments uint name_offset = cache->_write_position - entry_position; uint name_size = (uint)strlen(name) + 1; // Includes '/0' - n = cache->write_bytes(name, name_size); - if (n != name_size) { + if (!cache->write_bytes(name, name_size)) { return false; } @@ -1150,38 +2107,40 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind AOTCodeEntry* entry = new(cache) AOTCodeEntry(entry_kind, encode_id(entry_kind, id), entry_position, entry_size, name_offset, name_size, - blob_offset, has_oop_maps, blob.content_begin()); + blob_offset, has_oop_maps); log_debug(aot, codecache, stubs)("Wrote code blob '%s' (id=%u, kind=%s) to AOT Code Cache", name, id, aot_code_entry_kind_name[entry_kind]); return true; } -bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, uint id, const char* name) { - assert(!AOTCodeEntry::is_blob(entry_kind), - "wrong entry kind for numeric id %d", id); - return store_code_blob(blob, entry_kind, (uint)id, name, nullptr, nullptr); +bool AOTCodeCache::store_adapter(CodeBlob& blob, uint id, const char* name) { + return store_code_blob(blob, AOTCodeEntry::Adapter, id, name, nullptr, nullptr); } bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, BlobId id) { assert(AOTCodeEntry::is_single_stub_blob(entry_kind), - "wrong entry kind for blob id %s", StubInfo::name(id)); + "wrong entry kind for blob %u '%s'", (uint)id, StubInfo::name(id)); return store_code_blob(blob, entry_kind, (uint)id, StubInfo::name(id), nullptr, nullptr); } -bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, BlobId id, AOTStubData* stub_data, CodeBuffer* code_buffer) { - assert(AOTCodeEntry::is_multi_stub_blob(entry_kind), - "wrong entry kind for multi stub blob id %s", StubInfo::name(id)); - return store_code_blob(blob, entry_kind, (uint)id, StubInfo::name(id), stub_data, code_buffer); +bool AOTCodeCache::store_multi_stub_blob(CodeBlob& blob, BlobId id, AOTStubData* stub_data, CodeBuffer* code_buffer) { + return store_code_blob(blob, AOTCodeEntry::StubGenBlob, (uint)id, StubInfo::name(id), stub_data, code_buffer); } bool AOTCodeCache::write_stub_data(CodeBlob &blob, AOTStubData *stub_data) { if (!align_write_int()) { return false; } + uint* count_ptr = (uint *)reserve_bytes(sizeof(uint)); + if (count_ptr == nullptr) { + return false; + } + uint count = 0; + BlobId blob_id = stub_data->blob_id(); StubId stub_id = StubInfo::stub_base(blob_id); + assert(sizeof(StubId) == sizeof(int), "sanity"); address blob_base = blob.code_begin(); int stub_cnt = StubInfo::stub_count(blob_id); - int n; LogStreamHandle(Trace, aot, codecache, stubs) log; @@ -1206,31 +2165,31 @@ bool AOTCodeCache::write_stub_data(CodeBlob &blob, AOTStubData *stub_data) { GrowableArray
& addresses = stub_data->address_array(); int base = range.start_index(); if (base >= 0) { - n = write_bytes(&stub_id, sizeof(StubId)); - if (n != sizeof(StubId)) { + if (!write_int(checked_cast(stub_id))) { return false; } + count++; address start = addresses.at(base); assert (blob_base <= start, "sanity"); uint offset = (uint)(start - blob_base); - n = write_bytes(&offset, sizeof(uint)); - if (n != sizeof(int)) { + if (!write_int(offset)) { return false; } + count++; address end = addresses.at(base + 1); assert (start < end, "sanity"); offset = (uint)(end - start); - n = write_bytes(&offset, sizeof(uint)); - if (n != sizeof(int)) { + if (!write_int(offset)) { return false; } + count++; // write number of secondary and extra entries - int count = range.count() - 2; - n = write_bytes(&count, sizeof(int)); - if (n != sizeof(int)) { + int range_count = range.count() - 2; + if (!write_int(range_count)) { return false; } - for (int j = 0; j < count; j++) { + count++; + for (int j = 0; j < range_count; j++) { address next = addresses.at(base + 2 + j); if (next != nullptr) { // n.b. This maps next == end to the stub length which @@ -1245,10 +2204,10 @@ bool AOTCodeCache::write_stub_data(CodeBlob &blob, AOTStubData *stub_data) { // extra is the common handler target offset = NULL_ADDRESS_MARKER; } - n = write_bytes(&offset, sizeof(uint)); - if (n != sizeof(int)) { + if (!write_int(offset)) { return false; } + count++; } if (log.is_enabled()) { log.print_cr("======== wrote stub %s and %d addresses up to offset %d", @@ -1259,10 +2218,11 @@ bool AOTCodeCache::write_stub_data(CodeBlob &blob, AOTStubData *stub_data) { // we should have exhausted all stub ids in the blob assert(stub_id == StubId::NO_STUBID, "sanity"); // write NO_STUBID as an end marker - n = write_bytes(&stub_id, sizeof(StubId)); - if (n != sizeof(StubId)) { + if (!write_int(checked_cast(stub_id))) { return false; } + count++; + *count_ptr = count; if (log.is_enabled()) { log.print_cr("======== Stub data ends at offset %d", _write_position); @@ -1288,72 +2248,137 @@ CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, uint id, c if (AOTCodeEntry::is_blob(entry_kind) && !is_using_stub()) { return nullptr; } - log_debug(aot, codecache, stubs)("Reading blob '%s' (id=%u, kind=%s) from AOT Code Cache", name, id, aot_code_entry_kind_name[entry_kind]); + log_debug(aot, codecache, stubs)("Loading blob '%s' (id=%u, kind=%s) from AOT Code Cache", + name, id, aot_code_entry_kind_name[entry_kind]); AOTCodeEntry* entry = cache->find_entry(entry_kind, encode_id(entry_kind, id)); if (entry == nullptr) { return nullptr; } - AOTCodeReader reader(cache, entry); + AOTCodeReader reader(cache, entry, nullptr); CodeBlob* blob = reader.compile_code_blob(name, entry_kind, id, stub_data); - log_debug(aot, codecache, stubs)("%sRead blob '%s' (id=%u, kind=%s) from AOT Code Cache", - (blob == nullptr? "Failed to " : ""), name, id, aot_code_entry_kind_name[entry_kind]); + log_debug(aot, codecache, stubs)("%s blob '%s' (id=%u, kind=%s) from AOT Code Cache", + (blob == nullptr? "Failed to load" : "Loaded"), name, id, + aot_code_entry_kind_name[entry_kind]); return blob; } -CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, uint id, const char* name) { - assert(!AOTCodeEntry::is_blob(entry_kind), - "wrong entry kind for numeric id %d", id); - return load_code_blob(entry_kind, (uint)id, name, nullptr); +CodeBlob* AOTCodeCache::load_adapter(uint id, const char* name) { + return load_code_blob(AOTCodeEntry::Adapter, id, name, nullptr); } CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, BlobId id) { assert(AOTCodeEntry::is_single_stub_blob(entry_kind), - "wrong entry kind for blob id %s", StubInfo::name(id)); + "wrong entry kind for blob %u %s", (uint)id, StubInfo::name(id)); return load_code_blob(entry_kind, (uint)id, StubInfo::name(id), nullptr); } -CodeBlob* AOTCodeCache::load_code_blob(AOTCodeEntry::Kind entry_kind, BlobId id, AOTStubData* stub_data) { - assert(AOTCodeEntry::is_multi_stub_blob(entry_kind), - "wrong entry kind for blob id %s", StubInfo::name(id)); - return load_code_blob(entry_kind, (uint)id, StubInfo::name(id), stub_data); +CodeBlob* AOTCodeCache::load_multi_stub_blob(BlobId id, AOTStubData* stub_data) { + return load_code_blob(AOTCodeEntry::StubGenBlob, (uint)id, StubInfo::name(id), stub_data); } -CodeBlob* AOTCodeReader::compile_code_blob(const char* name, AOTCodeEntry::Kind entry_kind, int id, AOTStubData* stub_data) { - uint entry_position = _entry->offset(); +// Cached code blob layout +// - copy of CodeBlob +// - reloc_count +// - relocations +// - oop maps +// - multi-stub blob data +// - count +// - int[] +// - id_for_relocations array +// - asm_remarks +// - dbg_strings +// - name - // Read name - uint name_offset = entry_position + _entry->name_offset(); - uint name_size = _entry->name_size(); // Includes '/0' - const char* stored_name = addr(name_offset); +CodeBlob* AOTCodeReader::compile_code_blob(const char* name, AOTCodeEntry::Kind entry_kind, int id, AOTStubData* stub_data) { + bool is_multi_stub = AOTCodeEntry::is_multi_stub_blob(entry_kind); - if (strncmp(stored_name, name, (name_size - 1)) != 0) { + // Read name. Name's bound checks were done in find_entry(). + uint name_offset = _entry->name_offset(); + const char* stored_name = (const char*)addr(name_offset); + if (strcmp(stored_name, name) != 0) { log_warning(aot, codecache, stubs)("Saved blob's name '%s' is different from the expected name '%s'", stored_name, name); - set_lookup_failed(); // Skip this blob + set_lookup_failed("Saved blob's name is different"); // Skip this blob return nullptr; } _name = stored_name; - // Read archived code blob and related info - uint offset = entry_position + _entry->blob_offset(); - CodeBlob* archived_blob = (CodeBlob*)addr(offset); - offset += archived_blob->size(); + // Name was recorded last in this entry. Use its offset as read limit. + _read_limit = name_offset; + + // Read archived code blob and related info. + // Code's bound checks were done in find_entry(). + assert(_entry->code_offset() == _read_position, "code offset for non-nmethod code blobs should be 0"); + CodeBlob* archived_blob = (CodeBlob*)position_addr(); + if (!update_read_position(archived_blob->size())) { + return nullptr; + } + + if (!read_int(&_reloc_count)) { + return nullptr; + } + int blob_reloc_count = (int)(archived_blob->relocation_size() / sizeof(relocInfo)); + if (!is_multi_stub &&_reloc_count != blob_reloc_count) { + log_warning(aot, codecache, stubs)("Saved blob '%s' has different relocation count %d vs cached %d ", + name, blob_reloc_count, _reloc_count); + set_lookup_failed("Saved blob's relocation count is different"); // Skip this blob + return nullptr; + } + bool load_relocations = (_reloc_count > 0); - _reloc_count = *(int*)addr(offset); - offset += sizeof(int); - if (AOTCodeEntry::is_multi_stub_blob(entry_kind)) { - // position of relocs will have been aligned to heap word size so - // we can install them into a code buffer - offset = align_up(offset, DATA_ALIGNMENT); + if (load_relocations) { + if (is_multi_stub) { + // position of relocs will have been aligned to heap word size so + // we can install them into a code buffer + if (!align_read_position(DATA_ALIGNMENT)) { + return nullptr; + } + } + uint reloc_size = _reloc_count * sizeof(relocInfo); + if (!get_set_read_position(&_reloc_data, reloc_size)) { + return nullptr; + } } - _reloc_data = (address)addr(offset); - offset += _reloc_count * sizeof(relocInfo); - set_read_position(offset); if (_entry->has_oop_maps()) { _oop_maps = read_oop_map_set(); + if (_oop_maps == nullptr) { + return nullptr; + } + } + + if (stub_data != nullptr) { + if (!read_int(&_cached_stub_data_count)) { + return nullptr; + } + uint stub_data_size = (uint)_cached_stub_data_count * sizeof(uint); + if (!get_set_read_position((address*)&_cached_stub_data, stub_data_size)) { + return nullptr; + } + } + + if (load_relocations) { + if (!read_int(&_id_for_reloc_count)) { + return nullptr; + } + uint id_for_reloc_size = (uint)_id_for_reloc_count * sizeof(uint); + if (!get_set_read_position((address*)&_id_for_reloc, id_for_reloc_size)) { + return nullptr; + } + } + +#ifndef PRODUCT + if (!read_asm_rem_and_dbg_str()) { + return nullptr; + } +#endif + + if (_read_position != _read_limit) { + assert(false, " _read_position '%u' != _read_limit '%u'", _read_position, _read_limit); + set_lookup_failed("Damaged AOT code blob: unaccounted data"); + return nullptr; } // record current context for use by that callback @@ -1382,15 +2407,17 @@ CodeBlob* AOTCodeReader::compile_code_blob(const char* name, AOTCodeEntry::Kind void AOTCodeReader::restore(CodeBlob* code_blob) { precond(AOTCodeCache::is_on_for_use()); precond(_name != nullptr); - precond(_reloc_data != nullptr); + precond(_reloc_data != nullptr || _reloc_count == 0); + bool is_multi_stub = AOTCodeEntry::is_multi_stub_blob(_entry_kind); code_blob->set_name(_name); // Saved relocations need restoring except for the case of a // multi-stub blob which has no runtime relocations. However, we may // still have saved some (re-)load time relocs that were attached to // the generator's code buffer. We don't attach them to the blob but - // they get processed below by fix_relocations. - if (!AOTCodeEntry::is_multi_stub_blob(_entry_kind)) { + // they get processed below by read_relocations. + if (!is_multi_stub) { + // Allocates space for mutable data and copy relocation data code_blob->restore_mutable_data(_reloc_data); } code_blob->set_oop_maps(_oop_maps); @@ -1404,6 +2431,15 @@ void AOTCodeReader::restore(CodeBlob* code_blob) { } // publish entries found either in stub_data or as offsets in blob AOTCodeCache::publish_stub_addresses(*code_blob, blob_id, _stub_data); + } else if (code_blob->is_nmethod()) { + nmethod* nm = code_blob->as_nmethod(); + nm->set_compile_id(_compile_id); + nm->set_aot_code_entry(_entry); + nm->set_immutable_data(_immutable_data); + // Restore metadata and oops sections which are used + // by read_relocations(). + nm->copy_values(_oop_list); + nm->copy_values(_metadata_list); } // Now that all the entry points are in the address table we can @@ -1422,7 +2458,7 @@ void AOTCodeReader::restore(CodeBlob* code_blob) { // wrap it with a CodeBuffer and then reattach the relocs to the // code buffer. - if (AOTCodeEntry::is_multi_stub_blob(_entry_kind)) { + if (is_multi_stub) { // the blob doesn't have any proper runtime relocs but we can // reinstate the AOT-load time relocs we saved from the code // buffer that generated this blob in a new code buffer and use @@ -1432,21 +2468,21 @@ void AOTCodeReader::restore(CodeBlob* code_blob) { relocInfo* locs = (relocInfo*)_reloc_data; code_buffer.insts()->initialize_shared_locs(locs, _reloc_count); code_buffer.insts()->set_locs_end(locs + _reloc_count); - CodeSection *cs = code_buffer.code_section(CodeBuffer::SECT_INSTS); + CodeSection* cs = code_buffer.code_section(CodeBuffer::SECT_INSTS); RelocIterator reloc_iter(cs); - fix_relocations(code_blob, reloc_iter); + read_relocations(code_blob, reloc_iter); } } else { // the AOT-load time relocs will be in the blob's restored relocs RelocIterator reloc_iter(code_blob); - fix_relocations(code_blob, reloc_iter); + read_relocations(code_blob, reloc_iter, _reloc_imm_oop_list, _reloc_imm_metadata_list); } #ifndef PRODUCT code_blob->asm_remarks().init(); - read_asm_remarks(code_blob->asm_remarks()); + restore_asm_remarks(code_blob->asm_remarks()); code_blob->dbg_strings().init(); - read_dbg_strings(code_blob->dbg_strings()); + restore_dbg_strings(code_blob->dbg_strings()); #endif // PRODUCT } @@ -1465,13 +2501,15 @@ void AOTCodeReader::read_stub_data(CodeBlob* code_blob, AOTStubData* stub_data) address blob_base = code_blob->code_begin(); uint blob_size = (uint)(code_blob->code_end() - blob_base); - uint offset = align_read_int(); + uint offset = (uint)((address)_cached_stub_data - _entry_buffer); LogStreamHandle(Trace, aot, codecache, stubs) log; if (log.is_enabled()) { - log.print_cr("======== Stub data starts at offset %d", offset); + log.print_cr("======== Stub data starts at offset %u", offset); } + int iter = 0; // read stub and entries until we see NO_STUBID - StubId stub_id = *(StubId*)addr(offset); offset += sizeof(StubId); + StubId stub_id = (StubId)_cached_stub_data[iter++]; + // we ought to have at least one saved stub in the blob assert(stub_id != StubId::NO_STUBID, "blob %s contains no stubs!", StubInfo::name(stub_data->blob_id())); while (stub_id != StubId::NO_STUBID) { @@ -1482,21 +2520,21 @@ void AOTCodeReader::read_stub_data(CodeBlob* code_blob, AOTStubData* stub_data) assert(range.start_index() < 0, "repeated entry for stub %s", StubInfo::name(stub_id)); int address_base = addresses.length(); // start is an offset from the blob base - uint start = *(uint*)addr(offset); offset += sizeof(uint); + uint start = _cached_stub_data[iter++]; assert(start < blob_size, "stub %s start offset %d exceeds buffer length %d", StubInfo::name(stub_id), start, blob_size); address stub_start = blob_base + start; addresses.append(stub_start); // end is an offset from the stub start - uint end = *(uint*)addr(offset); offset += sizeof(uint); + uint end = _cached_stub_data[iter++]; assert(start + end <= blob_size, "stub %s end offset %d exceeds remaining buffer length %d", StubInfo::name(stub_id), end, blob_size - start); addresses.append(stub_start + end); // read count of secondary entries plus extras - int entries_count = *(int*)addr(offset); offset += sizeof(int); + int entries_count = _cached_stub_data[iter++]; assert(entries_count >= (StubInfo::entry_count(stub_id) - 1), "not enough entries for %s", StubInfo::name(stub_id)); for (int i = 0; i < entries_count; i++) { // entry offset is an offset from the stub start less than or // equal to end - uint entry = *(uint*)addr(offset); offset += sizeof(uint); + uint entry = _cached_stub_data[iter++]; if (entry <= end) { // entry addresses may not address end but extras can assert(entry < end || i >= StubInfo::entry_count(stub_id), @@ -1511,17 +2549,16 @@ void AOTCodeReader::read_stub_data(CodeBlob* code_blob, AOTStubData* stub_data) } if (log.is_enabled()) { log.print_cr("======== read stub %s and %d addresses up to offset %d", - StubInfo::name(stub_id), 2 + entries_count, offset); + StubInfo::name(stub_id), 2 + entries_count, (int)(offset + iter*sizeof(uint))); } range.init_entry(address_base, 2 + entries_count); // move on to next stub or NO_STUBID - stub_id = *(StubId*)addr(offset); offset += sizeof(StubId); + stub_id = (StubId)_cached_stub_data[iter++]; } if (log.is_enabled()) { - log.print_cr("======== Stub data ends at offset %d", offset); + log.print_cr("======== Stub data ends at offset %d", (int)(offset + iter * sizeof(uint))); } - - set_read_position(offset); + assert(iter == _cached_stub_data_count, "%u != %u", iter, _cached_stub_data_count); } void AOTCodeCache::publish_external_addresses(GrowableArray
& addresses) { @@ -1582,53 +2619,688 @@ void AOTCodeCache::publish_stub_addresses(CodeBlob &code_blob, BlobId blob_id, A } } -// ------------ process code and data -------------- +AOTCodeEntry* AOTCodeCache::store_nmethod(nmethod* nm, AbstractCompiler* compiler, bool for_preload) { + if (!is_dumping_code()) { + return nullptr; + } + assert(CDSConfig::is_dumping_aot_code(), "should be called only when allowed"); + AOTCodeCache* cache = open_for_dump(); + precond(cache != nullptr); + precond(!nm->is_osr_method()); // AOT compilation is requested only during AOT cache assembly phase + if (!compiler->is_c1() && !compiler->is_c2()) { + // Only c1 and c2 compilers + return nullptr; + } + int comp_level = nm->comp_level(); + if (comp_level == CompLevel_full_profile) { + // Do not cache C1 compiles with full profile i.e. tier3 + return nullptr; + } + assert(comp_level == CompLevel_simple || comp_level == CompLevel_limited_profile || comp_level == CompLevel_full_optimization, "must be"); -// Can't use -1. It is valid value for jump to iteself destination -// used by static call stub: see NativeJump::jump_destination(). -#define BAD_ADDRESS_ID -2 + TraceTime t1("Total time to store AOT code", &_t_totalStore, enable_timers(), false); + AOTCodeEntry* entry = cache->write_nmethod(nm, for_preload); + { + ResourceMark rm; + const char* name = nm->method()->name_and_sig_as_C_string(); + const char* msg = (entry != nullptr) ? ": wrote to AOT Code Cache" : + "is skipped: store to AOT Code Cache failed"; + log_info(aot, codecache, nmethod)("%d (A%s%d) '%s' AOT%s %s", + nm->compile_id(), (for_preload ? "P" : ""), comp_level, name, + (nm->has_clinit_barriers() ? ", has clinit barriers" : ""), msg); + } + return entry; +} -bool AOTCodeCache::write_relocations(CodeBlob& code_blob, RelocIterator& iter) { - if (!align_write_int()) { - return false; +AOTCodeEntry* AOTCodeCache::write_nmethod(nmethod* nm, bool for_preload) { + assert(!nm->has_clinit_barriers() || (ClassInitBarrierMode > 0), "sanity"); + _compile_id = nm->compile_id(); + _comp_level = nm->comp_level(); + Method* method = nm->method(); + if (!AOTCacheAccess::can_generate_aot_code(method)) { + ResourceMark rm; + log_info(aot, codecache, nmethod)("%d (A%s%d) '%s' AOT is skipped: method is not in AOT cache", + compile_id(), (for_preload ? "P" : ""), comp_level(), method->name_and_sig_as_C_string()); + assert(AOTCacheAccess::can_generate_aot_code(method), "sanity"); + return nullptr; } - GrowableArray reloc_data; - LogStreamHandle(Trace, aot, codecache, reloc) log; - while (iter.next()) { - int idx = reloc_data.append(0); // default value - switch (iter.type()) { - case relocInfo::none: - break; - case relocInfo::runtime_call_type: { - // Record offset of runtime destination - CallRelocation* r = (CallRelocation*)iter.reloc(); - address dest = r->destination(); - if (dest == r->addr()) { // possible call via trampoline on Aarch64 - dest = (address)-1; // do nothing in this case when loading this relocation - } - int id = _table->id_for_address(dest, iter, &code_blob); - if (id == BAD_ADDRESS_ID) { - return false; - } - reloc_data.at_put(idx, id); - break; - } - case relocInfo::runtime_call_w_cp_type: - log_debug(aot, codecache, reloc)("runtime_call_w_cp_type relocation is not implemented"); - return false; - case relocInfo::external_word_type: { - // Record offset of runtime target - address target = ((external_word_Relocation*)iter.reloc())->target(); - int id = _table->id_for_address(target, iter, &code_blob); - if (id == BAD_ADDRESS_ID) { - return false; - } - reloc_data.at_put(idx, id); + InstanceKlass* holder = method->method_holder(); + bool builtin_loader = holder->class_loader_data()->is_builtin_class_loader_data(); + if (!builtin_loader) { + ResourceMark rm; + log_info(aot, codecache, nmethod)("%d (A%s%d) '%s' AOT is skipped: loaded by custom class loader %s", + compile_id(), (for_preload ? "P" : ""), comp_level(), method->name_and_sig_as_C_string(), + holder->class_loader_data()->loader_name()); + assert(builtin_loader, "sanity"); + return nullptr; + } + + assert(!nm->has_clinit_barriers() || for_preload, "only preload code has clinit barriers"); + + if (!align_write()) { + return nullptr; + } + + uint entry_position = _write_position; + + uint id = (uint)cast_to_u4(AOTCacheAccess::to_narrow_ptr(nm->method())); + // Write id to compare with AOTCodeEntry::_id during production run. + if (!write_int(id)) { + return nullptr; + } + // Write name + uint name_offset = 0; + uint name_size = 0; + { + ResourceMark rm; + const char* name = method->name_and_sig_as_C_string(); + log_info(aot, codecache, nmethod)("%d (A%s%d) '%s' AOT%s: writing to AOT Code Cache", + compile_id(), (for_preload ? "P" : ""), comp_level(), name, + (nm->has_clinit_barriers() ? ", has clinit barriers" : "")); + + LogStreamHandle(Info, aot, codecache, loader) log; + if (log.is_enabled()) { + oop loader = holder->class_loader(); + oop domain = holder->protection_domain(); + stringStream ss; + ss.print("Holder: "); + holder->print_value_on(&ss); + ss.print(" loader: "); + if (loader == nullptr) { + ss.print("nullptr"); + } else { + loader->print_value_on(&ss); + } + ss.print(" domain: "); + if (domain == nullptr) { + ss.print("nullptr"); + } else { + domain->print_value_on(&ss); + } + log.print_cr("%s", ss.freeze()); + } + name_offset = _write_position - entry_position; + name_size = (uint)strlen(name) + 1; // Includes '\0' + if (!write_bytes(name, name_size)) { + return nullptr; + } + } + + // Write nmethod's code blob + if (!align_write()) { + return nullptr; + } + uint blob_offset = _write_position - entry_position; + address archive_buffer = reserve_bytes(nm->size()); + if (archive_buffer == nullptr) { + return nullptr; + } + CodeBlob::archive_blob(nm, archive_buffer); + + uint reloc_data_size = nm->relocation_size(); + if (!write_int(reloc_data_size)) { // for verification on load + return nullptr; + } + if (!write_bytes((address)nm->relocation_begin(), reloc_data_size)) { + return nullptr; + } + + // Write oops and metadata present in the nmethod's data region + if (!write_oops(nm)) { + if (lookup_failed() && !failed()) { + // Skip this method and reposition file + set_write_position(entry_position); + } + return nullptr; + } + if (!write_metadata(nm)) { + if (lookup_failed() && !failed()) { + // Skip this method and reposition file + set_write_position(entry_position); + } + return nullptr; + } + + bool has_oop_maps = false; + if (nm->oop_maps() != nullptr) { + if (!write_oop_map_set(*nm)) { + return nullptr; + } + has_oop_maps = true; + } + + uint immutable_data_size = nm->immutable_data_size(); + if (!write_int(immutable_data_size)) { // for verification on load + return nullptr; + } + if (!write_bytes(nm->immutable_data_begin(), immutable_data_size)) { + return nullptr; + } + + JavaThread* thread = JavaThread::current(); + HandleMark hm(thread); + GrowableArray oop_list; + GrowableArray metadata_list; + + nm->create_reloc_immediates_list(thread, oop_list, metadata_list); + if (!write_nmethod_reloc_immediates(oop_list, metadata_list)) { + if (lookup_failed() && !failed()) { + // Skip this method and reposition file + set_write_position(entry_position); + } + return nullptr; + } + + RelocIterator iter(nm); + if (!write_relocations(*nm, iter, AOTAssertOnUnknownExternalAddress, + &oop_list, &metadata_list)) { + if (!failed()) { + // Skip this method and reposition file + set_write_position(entry_position); + } + return nullptr; + } + +#ifndef PRODUCT + // Write asm remarks and dbg strings after relocation info + if (!write_asm_rem_and_dbg_str(nm->asm_remarks(), nm->dbg_strings())) { + return nullptr; + } +#endif /* PRODUCT */ + + uint entry_size = _write_position - entry_position; + AOTCodeEntry* entry = new (this) AOTCodeEntry(AOTCodeEntry::Nmethod, id, + entry_position, entry_size, + name_offset, name_size, + blob_offset, has_oop_maps, + comp_level(), compile_id(), + nm->has_clinit_barriers(), for_preload); + return entry; +} + +bool AOTCodeCache::load_nmethod(ciEnv* env, ciMethod* target, int entry_bci, AbstractCompiler* compiler) { + precond(is_using_code()); + assert(entry_bci == InvocationEntryBci, "unexpected entry_bci=%d", entry_bci); + CompileTask* task = env->task(); + AOTCodeEntry* entry = task->aot_code_entry(); + precond(entry != nullptr); + if (entry->not_entrant()) { + // AOT entry could be invalidated while task was in queue + task->set_failure_reason("AOT code entry was marked not-entrant while waiting in compilation queue"); + return false; + } + TraceTime t1("Total time to load AOT code", &_t_totalLoad, enable_timers(), false); + task->mark_aot_load_start(os::elapsed_counter()); + + AOTCodeCache* cache = open_for_use(); + if (cache == nullptr) { + return false; + } + AOTCodeReader reader(cache, entry, task); + bool success = reader.compile_nmethod(env, target, compiler); + if (reader.lookup_failed()) { + assert(!success, "sanity"); + task->set_failure_reason(reader.lookup_failure()); + } + if (success) { + assert(task->is_success(), "sanity"); + task->set_num_inlined_bytecodes(entry->num_inlined_bytecodes()); + } else if (!env->failing() || !env->codecache_full()) { + task->set_failure_reason(reader.lookup_failure()); + // Invalidate AOT entry if there were issues in restoring AOT code or in dependencies + entry->set_load_fail(); + entry->set_not_entrant(); + } + task->mark_aot_load_finish(os::elapsed_counter()); + return success; +} + +// Cached nmethod layout +// - id (encoded Method*) +// - name of method +// - copy of nmethod +// - relocation_size +// - relocations +// - oops +// - metadata +// - oop maps +// - immutable_data_size +// - immutable_data +// - embedded oops and metadata +// - id_for_relocations array +// - asm_remarks +// - dbg_strings + +bool AOTCodeReader::compile_nmethod(ciEnv* env, ciMethod* target, AbstractCompiler* compiler) { + CompileTask* task = env->task(); + nmethod* nm = nullptr; + + // Read ID. + uint cached_id; + if (!read_int((int*)&cached_id)) { + return false; + } + // Read name next. Name's bound checks were done in find_entry(). + const char* cached_name; + if (!get_set_read_position((address*)&cached_name, _entry->name_size())) { + return false; + } + bool preload = task->is_aot_preload(); + { + VM_ENTRY_MARK; + ResourceMark rm; + methodHandle method(THREAD, target->get_Method()); + const char* target_name = method->name_and_sig_as_C_string(); + uint id = (uint)cast_to_u4(AOTCacheAccess::method_to_narrow_ptr(method())); + if (id != cached_id) { + log_warning(aot, codecache, nmethod)("Cached method id '%u' is different from the expected '%u'", + cached_id, id); + set_lookup_failed("Cached method id is different"); // Skip this nmethod + return false; + } + if (strcmp(cached_name, target_name) != 0) { + log_warning(aot, codecache, nmethod)("Saved method name '%s' is different from the expected name '%s'", + cached_name, target_name); + set_lookup_failed("Saved method name is different"); // Skip this nmethod + return false; + } + bool clinit_brs = _entry->has_clinit_barriers(); + log_info(aot, codecache, nmethod)("%d (A%s%d) '%s' AOT nmethod id: " UINT32_FORMAT_X_0 "%s: %sloading from AOT Code Cache", + compile_id(), (preload ? "P" : ""), comp_level(), target_name, id, + (clinit_brs ? ", has clinit barriers" : ""), (preload ? "pre" : "")); + } + + if (!align_read_position(DATA_ALIGNMENT)) { + return false; + } + if (_entry->code_offset() != _read_position) { + set_lookup_failed("Cached nmethod code offset is wrong"); // Skip this nmethod + return false; + } + assert(_entry->code_offset() == _read_position, "code for nmethod should be after name"); + nmethod* archived_nm = (nmethod*)position_addr(); + if (!update_read_position(archived_nm->size())) { + return false; + } + + OopRecorder* oop_recorder = new OopRecorder(env->arena()); + env->set_oop_recorder(oop_recorder); + + // Set fields for AOTCodeReader::restore() + _name = "nmethod"; + + int reloc_size; + if (!read_int(&reloc_size)) { + return false; + } + if (reloc_size != archived_nm->relocation_size()) { + set_lookup_failed("Cached nmethod relocation size is wrong"); // Skip this nmethod + return false; + } + if (!get_set_read_position(&_reloc_data, (uint)reloc_size)) { + return false; + } + + // Read oops and metadata + VM_ENTRY_MARK + GrowableArray oop_list; + GrowableArray metadata_list; + + if (!read_oop_metadata_list(THREAD, oop_list, metadata_list, oop_recorder)) { + return false; + } + _oop_list = &oop_list; + _metadata_list = &metadata_list; + + if (_entry->has_oop_maps()) { + _oop_maps = read_oop_map_set(); + if (_oop_maps == nullptr) { + return false; + } + } + + int immutable_data_size; + if (!read_int(&immutable_data_size)) { + return false; + } + if (immutable_data_size != archived_nm->immutable_data_size()) { + set_lookup_failed("Cached nmethod immutable_data size is wrong"); // Skip this nmethod + return false; + } + if (!get_set_read_position(&_immutable_data, (uint)immutable_data_size)) { + return false; + } + + GrowableArray reloc_immediate_oop_list; + GrowableArray reloc_immediate_metadata_list; + if (!read_oop_metadata_list(THREAD, reloc_immediate_oop_list, reloc_immediate_metadata_list, nullptr)) { + return false; + } + _reloc_imm_oop_list = &reloc_immediate_oop_list; + _reloc_imm_metadata_list = &reloc_immediate_metadata_list; + + if (!read_int(&_id_for_reloc_count)) { + return false; + } + uint id_for_reloc_size = (uint)_id_for_reloc_count * sizeof(uint); + if (!get_set_read_position((address*)&_id_for_reloc, id_for_reloc_size)) { + return false; + } + +#ifndef PRODUCT + if (!read_asm_rem_and_dbg_str()) { + return false; + } +#endif + + if (_read_position != _read_limit) { + assert(false, " _read_position '%u' != _read_limit '%u'", _read_position, _read_limit); + set_lookup_failed("Damaged AOT nmethod: unaccounted data"); + return false; + } + + // Read Dependencies (compressed already) + Dependencies* dependencies = new Dependencies(env); + dependencies->set_content(_immutable_data, archived_nm->dependencies_size()); + env->set_dependencies(dependencies); + + TraceTime t1("Total time to register AOT nmethod", &_t_totalRegister, enable_timers(), false); + bool install_code = !VerifyAOTCode; + nm = env->register_aot_method(THREAD, target, compiler, archived_nm, this, install_code); + + if (VerifyAOTCode && !env->failing()) { + // AOT code is not installed in CodeCache with VerifyAOTCode. + // Report AOT code sizes as the task result. + _entry->set_verified(); + task->mark_success(); + task->set_nm_content_size(archived_nm->content_size()); + task->set_nm_insts_size(archived_nm->insts_size()); + task->set_nm_total_size(archived_nm->total_size()); + log_info(aot, codecache, nmethod)("%d (A%s%d) '%s': Verified nmethod from AOT Code Cache", + compile_id(), (preload ? "P" : ""), comp_level(), cached_name); + return true; + } + bool success = task->is_success(); + if (success) { + log_info(aot, codecache, nmethod)("%d (A%s%d) '%s': Loaded nmethod from AOT Code Cache", + compile_id(), (preload ? "P" : ""), comp_level(), cached_name); +#ifdef ASSERT + LogStreamHandle(Debug, aot, codecache, nmethod) log; + if (log.is_enabled()) { + FlagSetting fs(PrintRelocations, true); + nm->print_on(&log); + nm->decode2(&log); + } +#endif + } + + return success; +} + +bool skip_preload(methodHandle mh) { + if (!mh->method_holder()->is_loaded()) { + return true; + } + CompilerDirectiveMatcher matcher(mh, CompLevel_full_optimization); + DirectiveSet* directives = matcher.directive_set(); + if (directives->DontPreloadOption || directives->ExcludeOption) { + LogStreamHandle(Info, aot, codecache, init) log; + if (log.is_enabled()) { + ResourceMark rm; + stringStream ss; + ss.print("Exclude preloading code for "); + mh->print_value_on(&ss); + log.print_cr("%s", ss.freeze()); + } + return true; + } + return false; +} + +void AOTCodeCache::preload_code(JavaThread* thread) { + if (!is_using_code()) { + return; + } + if (!CompilerConfig::is_c2_enabled()) { + log_debug(aot, codecache, init)("AOT preload code skipped: C2 compiler disabled"); + return; + } + + if (skip_aot_code(CompLevel_preload_optimization)) { + return; // no preloaded code (level 5); + } + + if (CompilationPolicy::compiler_count(CompLevel_full_optimization) == 0) { + // Since we reuse the CompilerBroker API to install AOT code, we're required to have a JIT compiler for the + // level we want (that is CompLevel_full_optimization). + return; + } + +#ifdef COMPILER2 + // Check C2 flags + if (!_cache->_load_header->verify_c2_state()) { + return; + } +#endif + + _cache->preload_aot_code(thread); +} + +void AOTCodeCache::preload_aot_code(TRAPS) { + TraceTime t1("Total time to preload AOT code", &_t_totalPreload, enable_timers(), false); + assert(_for_use, "sanity"); + uint preload_entries_count = _load_header->preload_entries_count(); + if (preload_entries_count > 0) { + load_info_log().print_cr("Read %d preload entries from AOT Code Cache", preload_entries_count); + uint entries_offset = _load_header->preload_entries_offset(); + // Boundaries of preload code + uint low_bound = entries_offset + preload_entries_count * sizeof(AOTCodeEntry); + uint high_bound = _load_header->entries_offset(); // Following section + AOTCodeEntry* preload_entry = (AOTCodeEntry*)addr(entries_offset); + // AOTCodePreloadStop is inclusive + uint last_preload_index = MIN2(preload_entries_count - 1, AOTCodePreloadStop); + for (uint i = AOTCodePreloadStart; i <= last_preload_index; i++) { + AOTCodeEntry* entry = &preload_entry[i]; + if (!entry->for_preload()) { + continue; + } + if (!verify_nmethod_entry(entry, i, low_bound, high_bound)) { + return; // Data is damaged, skip the rest of entries + } + uint entry_offset = entry->offset(); + uint entry_size = entry->size(); + + // Check entry->id() vs cached id - should match + uint cached_id = *(uint*)addr(entry_offset); + if (!check_entry(AOTCodeEntry::Nmethod, cached_id, CompLevel_full_optimization, entry)) { + continue; + } + Method* m = AOTCacheAccess::narrow_ptr_to_method(cast_from_u4(entry->id())); + if (m == nullptr) { + log_warning(aot, codecache, init)("Damaged method id '%u' in preload AOT code entry %u " INTPTR_FORMAT " [%u + %u]", entry->id(), i, p2i(entry), entry_offset, entry_size); + continue; + } + methodHandle mh(THREAD, m); + if (skip_preload(mh)) { + continue; // Exclude preloading for this method + } + assert(mh->method_holder()->is_loaded(), ""); + if (!mh->method_holder()->is_linked()) { + ResourceMark rm; + log_debug(aot, codecache, init)("Preload AOT code for %s skipped: method holder is not linked", + mh->name_and_sig_as_C_string()); + continue; // skip + } +#ifdef COMPILER2 + if (entry->has_vectors() && !EnableVectorSupport) { + log_debug(aot, codecache, init)("AOT preload code skipped: incompatible EnableVectorSupport: true vs current %u", EnableVectorSupport); + continue; + } +#endif + CompileBroker::preload_aot_method(mh, entry, CHECK); + } + } +} + +// ------------ process code and data -------------- + +// Do nothing for relocation when: +// - call's destination is trampoline in nmethod +// - jump's destination is address of the instruction itself +// - jump's destination is (address)-1 +// See NativeJump::jump_destination() for example. + +#define NO_RELOCATION_ID -1 + +// Bad address - skip this method compilation. + +#define BAD_ADDRESS_ID -2 + +// The next two functions, write_relocations and read_relocations, +// handle relocation information for AOT code. The relocations are written +// by the compiler backend during AOT cache assembly. Their targeting information +// gets re-encoded into the AOT cache so that the relocations can be restored later +// during the production run. +// +// Relocs targeting oops and metadata record a global list index. +// Relocs targeting internal addresses record a local offset. +// Relocs targeting external addresses (including calls) record a global address ID. +// Certain special non-address constants are treated like external addresses. +// In all cases, the reloc is edited to replace a live address with a small integer, +// as the nmethod is placed into the AOT cache. During the production run, the process +// is reversed, as the small integers are decoded back into live addresses. +// +// Consider a later refactoring cleanup that moves more of this logic into +// relocInfo.cpp. Perhaps relocInfo.cpp should "know more about" the external +// address IDs and oop indexes? Perhaps we want a protocol for relocs +// named [un]pack_aot_data? +// [[PR 30778 COMMENT: to fit the writer/reader pattern better, here is a proposed +// rename write_id_for_relocations/restore_relocations -> [write/read]_relocations. +// It is a pure name change, but we can save for after the PR if desired.]] +bool AOTCodeCache::write_relocations(CodeBlob& code_blob, RelocIterator& iter, + bool assert_for_unknown_external_address, + GrowableArray* oop_list, + GrowableArray* metadata_list) { + if (!align_write_int()) { + return false; + } + GrowableArray reloc_data; + LogStreamHandle(Trace, aot, codecache, reloc) log; + while (iter.next()) { + int idx = reloc_data.append(0); // default value + switch (iter.type()) { + case relocInfo::none: + break; + case relocInfo::oop_type: { + oop_Relocation* r = (oop_Relocation*)iter.reloc(); + if (r->oop_is_immediate()) { + assert(oop_list != nullptr, "sanity check"); + // store index of oop in the reloc immediate oop list + Handle h(JavaThread::current(), r->oop_value()); + int oop_idx = oop_list->find(h); + assert(oop_idx != -1, "sanity check"); + reloc_data.at_put(idx, (uint)oop_idx); + } + break; + } + case relocInfo::metadata_type: { + metadata_Relocation* r = (metadata_Relocation*)iter.reloc(); + if (r->metadata_is_immediate()) { + assert(metadata_list != nullptr, "sanity check"); + // store index of metadata in the reloc immediate metadata list + int metadata_idx = metadata_list->find(r->metadata_value()); + assert(metadata_idx != -1, "sanity check"); + reloc_data.at_put(idx, (uint)metadata_idx); + } + break; + } + case relocInfo::virtual_call_type: // Fall through. They all call resolve_*_call blobs. + case relocInfo::opt_virtual_call_type: + case relocInfo::static_call_type: + case relocInfo::runtime_call_type: { + CallRelocation* r = (CallRelocation*)iter.reloc(); + address dest = r->destination(); + int id; + if (dest == r->addr() || dest == (address)-1) { + // Unresolved jumps, see NativeJump::jump_destination() for example. + // Do nothing in this case when loading this relocation + id = NO_RELOCATION_ID; + } else { +#ifdef USE_TRAMPOLINE_STUB_FIX_OWNER + if (code_blob.is_nmethod()) { + address tr = trampoline_stub_Relocation::get_trampoline_for(r->addr(), code_blob.as_nmethod()); + // During assembly phase call's target should be trampoline. + // See trampoline_stub_Relocation::pd_fix_owner_after_move(). + if (tr != nullptr) { + if (nativeCall_at(r->addr())->raw_destination() != tr) { + return false; // Skip this entry + } + id = NO_RELOCATION_ID; + reloc_data.at_put(idx, id); + break; + } + } +#endif + id = _table->id_for_address(dest, iter, &code_blob, true /* assert_for_unknown_address */); + if (id == BAD_ADDRESS_ID) { + return false; + } + } + reloc_data.at_put(idx, id); + break; + } + case relocInfo::runtime_call_w_cp_type: + log_debug(aot, codecache, reloc)("runtime_call_w_cp_type relocation is not implemented"); + return false; + case relocInfo::trampoline_stub_type: { + address dest = ((trampoline_stub_Relocation*)iter.reloc())->destination(); + int id = _table->id_for_address(dest, iter, &code_blob, true /* assert_for_unknown_address */); + if (id == BAD_ADDRESS_ID) { + return false; + } + reloc_data.at_put(idx, id); + break; + } + case relocInfo::static_stub_type: + break; + case relocInfo::external_word_type: { + // Record offset of runtime target + address target = ((external_word_Relocation*)iter.reloc())->target(); + int id = _table->id_for_address(target, iter, &code_blob, assert_for_unknown_external_address); + if (id == BAD_ADDRESS_ID) { + return false; + } + reloc_data.at_put(idx, id); + break; + } + case relocInfo::internal_word_type: { + address target = ((internal_word_Relocation*)iter.reloc())->target(); + // assert to make sure that delta fits into 32 bits + assert(CodeCache::contains((void *)target), "Wrong internal_word_type relocation"); + uint delta = (uint)(target - code_blob.content_begin()); + reloc_data.at_put(idx, delta); + break; + } + case relocInfo::section_word_type: { + address target = ((section_word_Relocation*)iter.reloc())->target(); + assert(CodeCache::contains((void *)target), "Wrong section_word_type relocation"); + uint delta = (uint)(target - code_blob.content_begin()); + reloc_data.at_put(idx, delta); + break; + } + case relocInfo::patchable_barrier_type: // fall through + case relocInfo::barrier_type: { + // Patched by GC during nmethod registration + assert(code_blob.is_nmethod(), "Only nmethod has barrier relocation"); + if (!code_blob.is_nmethod()) { + log_debug(aot, codecache, reloc)("barrier relocation is used only for nmethods"); + return false; + } break; } - case relocInfo::internal_word_type: + case relocInfo::poll_type: break; - case relocInfo::section_word_type: + case relocInfo::poll_return_type: break; case relocInfo::post_call_nop_type: break; @@ -1645,7 +3317,9 @@ bool AOTCodeCache::write_relocations(CodeBlob& code_blob, RelocIterator& iter) { // Write additional relocation data: uint per relocation // Write the count first int count = reloc_data.length(); - write_bytes(&count, sizeof(int)); + if (!write_int(count)) { + return false; + } if (log.is_enabled()) { log.print_cr("======== extra relocations count=%d", count); log.print( " {"); @@ -1654,8 +3328,7 @@ bool AOTCodeCache::write_relocations(CodeBlob& code_blob, RelocIterator& iter) { for (GrowableArrayIterator iter = reloc_data.begin(); iter != reloc_data.end(); ++iter) { uint value = *iter; - int n = write_bytes(&value, sizeof(uint)); - if (n != sizeof(uint)) { + if (!write_int(value)) { return false; } if (log.is_enabled()) { @@ -1667,113 +3340,902 @@ bool AOTCodeCache::write_relocations(CodeBlob& code_blob, RelocIterator& iter) { } } } - if (log.is_enabled()) { - log.print_cr("}"); - } + if (log.is_enabled()) { + log.print_cr("}"); + } + return true; +} + +void AOTCodeReader::read_relocations(CodeBlob *code_blob, RelocIterator& iter, + GrowableArray* oop_list, + GrowableArray* metadata_list) { + uint* reloc_data = _id_for_reloc; + + LogStreamHandle(Trace, aot, codecache, reloc) log; + if (log.is_enabled()) { + log.print_cr("======== extra relocations count=%d", _id_for_reloc_count); + log.print(" {"); + for(int i = 0; i < _id_for_reloc_count; i++) { + if (i == 0) { + log.print("%d", reloc_data[i]); + } else { + log.print(", %d", reloc_data[i]); + } + } + log.print_cr("}"); + } + + int j = 0; + while (iter.next()) { + switch (iter.type()) { + case relocInfo::none: + break; + case relocInfo::oop_type: { + assert(code_blob->is_nmethod(), "sanity check"); + oop_Relocation* r = (oop_Relocation*)iter.reloc(); + if (r->oop_is_immediate()) { + assert(oop_list != nullptr, "sanity check"); + Handle h = oop_list->at(reloc_data[j]); + r->set_value(cast_from_oop
(h())); + } else { + r->fix_oop_relocation(); + } + break; + } + case relocInfo::metadata_type: { + assert(code_blob->is_nmethod(), "sanity check"); + metadata_Relocation* r = (metadata_Relocation*)iter.reloc(); + Metadata* m; + if (r->metadata_is_immediate()) { + assert(metadata_list != nullptr, "sanity check"); + m = metadata_list->at(reloc_data[j]); + } else { + // Get already updated value from nmethod. + int index = r->metadata_index(); + m = code_blob->as_nmethod()->metadata_at(index); + } + r->set_value((address)m); + break; + } + case relocInfo::virtual_call_type: // Fall through. They all call resolve_*_call blobs. + case relocInfo::opt_virtual_call_type: + case relocInfo::static_call_type: + case relocInfo::runtime_call_type: { + int id = reloc_data[j]; + if (id != NO_RELOCATION_ID) { // Skip relocation with such ID + address dest = _cache->address_for_id(id); + ((CallRelocation*)iter.reloc())->set_destination(dest); + } + break; + } + case relocInfo::runtime_call_w_cp_type: + // this relocation should not be in cache (see write_relocations) + assert(false, "runtime_call_w_cp_type relocation is not implemented"); + break; + case relocInfo::trampoline_stub_type: { + int id = reloc_data[j]; + address dest = _cache->address_for_id(id); + trampoline_stub_Relocation* r = (trampoline_stub_Relocation*)iter.reloc(); + r->set_destination(dest); +#ifdef USE_TRAMPOLINE_STUB_FIX_OWNER + if (code_blob->is_nmethod()) { + assert(nativeCall_at(r->owner())->raw_destination() == r->addr(), + "Unpatched call's target should be trampoline"); + // Change call's destination to direct target if distance allows. + r->fix_owner_after_move(); + } +#endif // USE_TRAMPOLINE_STUB_FIX_OWNER + break; + } + case relocInfo::static_stub_type: + break; + case relocInfo::external_word_type: { + address target = _cache->address_for_id(reloc_data[j]); + // Add external address to global table + int index = ExternalsRecorder::find_index(target); + // Update index in relocation + Relocation::add_jint(iter.data(), index); + external_word_Relocation* reloc = (external_word_Relocation*)iter.reloc(); + assert(reloc->target() == target, "sanity"); + reloc->set_value(target); // Patch address in the code + break; + } + case relocInfo::internal_word_type: { + uint delta = reloc_data[j]; + internal_word_Relocation* r = (internal_word_Relocation*)iter.reloc(); + r->fix_relocation_after_aot_load(code_blob->content_begin(), delta); + break; + } + case relocInfo::section_word_type: { + uint delta = reloc_data[j]; + section_word_Relocation* r = (section_word_Relocation*)iter.reloc(); + r->fix_relocation_after_aot_load(code_blob->content_begin(), delta); + break; + } + case relocInfo::patchable_barrier_type: // fall through + case relocInfo::barrier_type: { + // Patched by GC during nmethod registration + assert(code_blob->is_nmethod(), "Only nmethod has barrier relocation"); + break; + } + case relocInfo::poll_type: + break; + case relocInfo::poll_return_type: + break; + case relocInfo::post_call_nop_type: + break; + default: + assert(false,"relocation %d unimplemented", (int)iter.type()); + break; + } + if (log.is_enabled()) { + iter.print_current_on(&log); + } + j++; + } + assert(j == _id_for_reloc_count, "%d != %d", j, _id_for_reloc_count); +} + +bool AOTCodeCache::write_nmethod_reloc_immediates(GrowableArray& oop_list, GrowableArray& metadata_list) { + int count = oop_list.length(); + if (!write_int(count)) { + return false; + } + for (GrowableArrayIterator iter = oop_list.begin(); + iter != oop_list.end(); ++iter) { + Handle h = *iter; + if (!write_oop(h())) { + return false; + } + } + + count = metadata_list.length(); + if (!write_int(count)) { + return false; + } + for (GrowableArrayIterator iter = metadata_list.begin(); + iter != metadata_list.end(); ++iter) { + Metadata* m = *iter; + if (!write_metadata(m)) { + return false; + } + } + return true; +} + +bool AOTCodeCache::write_metadata(nmethod* nm) { + int count = nm->metadata_count()-1; + if (!write_int(count)) { + return false; + } + for (Metadata** p = nm->metadata_begin(); p < nm->metadata_end(); p++) { + if (!write_metadata(*p)) { + return false; + } + } + return true; +} + +bool AOTCodeCache::write_metadata(Metadata* m) { + if (m == nullptr) { + DataKind kind = DataKind::Null; + if (!write_kind(kind)) { + return false; + } + } else if (m == (Metadata*)Universe::non_oop_word()) { + DataKind kind = DataKind::No_Data; + if (!write_kind(kind)) { + return false; + } + } else if (m->is_klass()) { + if (!write_klass((Klass*)m)) { + return false; + } + } else if (m->is_method()) { + if (!write_method((Method*)m)) { + return false; + } + } else if (m->is_methodCounters()) { + DataKind kind = DataKind::MethodCnts; + if (!write_kind(kind)) { + return false; + } + if (!write_method(((MethodCounters*)m)->method())) { + return false; + } + log_debug(aot, codecache, metadata)("%d (A%d): Wrote MethodCounters : " INTPTR_FORMAT, compile_id(), comp_level(), p2i(m)); + } else { // Not supported + fatal("metadata : " INTPTR_FORMAT " unimplemented", p2i(m)); + return false; + } + return true; +} + +Metadata* AOTCodeReader::read_metadata(JavaThread* thread) { + DataKind kind; + if (!read_kind(&kind)) { + return nullptr; + } + Metadata* m = nullptr; + if (kind == DataKind::Null) { + m = (Metadata*)nullptr; + } else if (kind == DataKind::No_Data) { + m = (Metadata*)Universe::non_oop_word(); + } else if (kind == DataKind::Klass) { + m = (Metadata*)read_klass(thread); + } else if (kind == DataKind::Method) { + m = (Metadata*)read_method(); + } else if (kind == DataKind::MethodCnts) { + if (!read_kind(&kind)) { + return nullptr; + } + assert(kind == DataKind::Method, "%d is wrong kind for method", (int)kind); + m = (Metadata*)read_method(); + if (m != nullptr) { + Method* method = (Method*)m; + m = method->get_method_counters(Thread::current()); + if (m == nullptr) { + set_lookup_failed("Failed to read MethodCounters"); + log_debug(aot, codecache, metadata)("%d (A%d): Failed to read MethodCounters", compile_id(), comp_level()); + } else { + log_debug(aot, codecache, metadata)("%d (A%d): Read MethodCounters : " INTPTR_FORMAT, compile_id(), comp_level(), p2i(m)); + } + } + } else { + set_lookup_failed("Unknown metadata kind"); + log_debug(aot, codecache, metadata)("%d (A%d): Unknown metadata's kind: %d", compile_id(), comp_level(), (int)kind); + } + return m; +} + +bool AOTCodeCache::write_method(Method* method) { + ResourceMark rm; // To method's name printing + if (AOTCacheAccess::can_generate_aot_code(method)) { + DataKind kind = DataKind::Method; + if (!write_kind(kind)) { + return false; + } + narrowPtr method_narrow_ptr = AOTCacheAccess::to_narrow_ptr(method); + if (!write_int((int)checked_cast(method_narrow_ptr))) { + return false; + } + log_debug(aot, codecache, metadata)("%d (A%d): Registered method: %s @ 0x%08x", + compile_id(), comp_level(), method->name_and_sig_as_C_string(), cast_to_u4(method_narrow_ptr)); + return true; + } + log_debug(aot, codecache, metadata)("%d (A%d): Method can not be registered: %s", + compile_id(), comp_level(), method->name_and_sig_as_C_string()); + set_lookup_failed(); + return false; +} + +Method* AOTCodeReader::read_method() { + narrowPtr method_narrow_ptr; + if (!read_int((int*)&method_narrow_ptr)) { + return nullptr; + } + + Method* m = AOTCacheAccess::narrow_ptr_to_method(method_narrow_ptr); + if (m == nullptr || !AOTMetaspace::in_aot_cache((address)m)) { + // Something changed in CDS + set_lookup_failed("Method is not in AOT cache"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed for method: " INTPTR_FORMAT " is not in AOT cache", + compile_id(), comp_level(), p2i((address)m)); + return nullptr; + } + assert(m->is_method(), "sanity"); + ResourceMark rm; + if (m->is_old()) { + set_lookup_failed("Method was redefined"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed for '%s': method was redefined", + compile_id(), comp_level(), m->name_and_sig_as_C_string()); + return nullptr; + } + Klass* k = m->method_holder(); + if (!k->is_instance_klass()) { + set_lookup_failed("Method holder is not instance klass"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed for '%s': holder %s is not instance klass", + compile_id(), comp_level(), m->name_and_sig_as_C_string(), k->external_name()); + return nullptr; + } else if (!AOTMetaspace::in_aot_cache((address)k)) { + set_lookup_failed("Method holder is not in AOT cache"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed for '%s': holder %s is not in AOT cache", + compile_id(), comp_level(), m->name_and_sig_as_C_string(), k->external_name()); + return nullptr; + } else if (!InstanceKlass::cast(k)->is_loaded() || !InstanceKlass::cast(k)->is_linked()) { + set_lookup_failed("Method holder is not loaded or not linked"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed for '%s': holder %s is not %s", + compile_id(), comp_level(), m->name_and_sig_as_C_string(), k->external_name(), + (!InstanceKlass::cast(k)->is_loaded() ? "loaded" : "linked")); + return nullptr; + } + log_debug(aot, codecache, metadata)("%d (A%d): Found method '%s' in AOT cache", + compile_id(), comp_level(), m->name_and_sig_as_C_string()); + return m; +} + +bool AOTCodeCache::write_klass(Klass* klass) { + uint array_dim = 0; + uint array_desc = ArrayProperties::Invalid().value(); + bool can_write = true; + if (klass->is_objArray_klass()) { + // We must check klass and its bottom_klass. + can_write = AOTCacheAccess::can_generate_aot_code(klass); + ObjArrayKlass* oak = ObjArrayKlass::cast(klass); + array_dim = oak->dimension(); + if (klass->is_refined_objArray_klass()) { + // Preserve property of object array + Klass::KlassKind kind; + LayoutKind layout; + if (klass->is_flatArray_klass()) { + kind = Klass::FlatArrayKlassKind; + layout = FlatArrayKlass::cast(klass)->layout_kind(); + } else { + kind = Klass::RefArrayKlassKind; + layout = LayoutKind::REFERENCE; + } + array_desc = ArrayDescription(kind, oak->properties(), layout).value(); + } + klass = oak->bottom_klass(); // overwrites klass + } + uint init_state = 0; + if (klass->is_instance_klass()) { + // A few classes are initialized in the assembly phase before code generation. + // When run in production, the code will assume they are initialized, again. + InstanceKlass* ik = InstanceKlass::cast(klass); + init_state = (ik->is_initialized() ? 1 : 0); + can_write &= AOTCacheAccess::can_generate_aot_code_for(ik); + } else { + assert(klass->is_typeArray_klass(), "must be"); + can_write &= AOTCacheAccess::can_generate_aot_code(klass); + } + ResourceMark rm; + uint state = (array_dim << 1) | (init_state & 1); + if (can_write) { + DataKind kind = DataKind::Klass; + if (!write_kind(kind)) { + return false; + } + // Record state of instance klass initialization and array dimensions. + if (!write_int(state)) { + return false; + } + // Record array's properties + if (!write_int(array_desc)) { + return false; + } + narrowPtr klass_narrow_ptr = AOTCacheAccess::to_narrow_ptr(klass); + if (!write_int((int)checked_cast(klass_narrow_ptr))) { + return false; + } + log_debug(aot, codecache, metadata)("%d (A%d): Registered klass: %s%s%s @ 0x%08x", + compile_id(), comp_level(), klass->external_name(), + (!klass->is_instance_klass() ? "" : (init_state == 1 ? " (initialized)" : " (not-initialized)")), + (array_dim > 0 ? " (object array)" : ""), cast_to_u4(klass_narrow_ptr)); + return true; + } + log_debug(aot, codecache, metadata)("%d (A%d): Klass is not registered: %s%s%s", + compile_id(), comp_level(), klass->external_name(), + (!klass->is_instance_klass() ? "" : (init_state == 1 ? " (initialized)" : " (not-initialized)")), + (array_dim > 0 ? " (object array)" : "")); + set_lookup_failed(); + return false; +} + +Klass* AOTCodeReader::read_klass(JavaThread* thread) { + uint state; + if (!read_int((int*)&state)) { + return nullptr; + } + uint array_desc; + if (!read_int((int*)&array_desc)) { + return nullptr; + } + narrowPtr klass_narrow_ptr; + if (!read_int((int*)&klass_narrow_ptr)) { + return nullptr; + } + uint init_state = (state & 1); + uint array_dim = (state >> 1); + Klass* k = AOTCacheAccess::narrow_ptr_to_klass(klass_narrow_ptr); + if (k == nullptr || !AOTMetaspace::in_aot_cache((address)k)) { + // Something changed in CDS + set_lookup_failed("Klass is not in AOT cache"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed klass: " INTPTR_FORMAT " is not in AOT cache", + compile_id(), comp_level(), p2i((address)k)); + return nullptr; + } + assert(k->is_klass(), "sanity"); + ResourceMark rm; + if (k->is_instance_klass() && !InstanceKlass::cast(k)->is_loaded()) { + set_lookup_failed("Klass is not loaded"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed for klass %s: not loaded", + compile_id(), comp_level(), k->external_name()); + return nullptr; + } + // If the klass was seen to be initialized during AOT cache assembly, + // it must also be initialized here, in the production run, when this nmethod loads. + // Exception: "preload" code (AP4) can handle uninitialized classes. + // There is no exception for A1, A2 code or A4 code. + // + // Note: This init-state check is of minor importance compared to the clinit deps + // list/counter on this nmethod's CompileTrainingData, which has already determined + // the nmethod's readiness to load. (For many more details, see comments near + // CompileTrainingData::_init_deps_left.) This check is superficially similar, + // but it checks a relatively small number of classes, which may or may not be + // necessary to the nmethod's compiled code. Think of it as an extra sanity check. + // But it is an approximate check, and may fail spuriously, discarding good AOT code. + // After JDK-8380476 FIXME: Maybe get rid of this approximate check; replace with + // a more accurate and efficient check based on dependencies.hpp (new DepType). + // (Also, the metadata for arrays should have its own DataKind::ArrayKlass.) + if (k->is_instance_klass() && !InstanceKlass::cast(k)->is_initialized() && (init_state == 1) && !_entry->for_preload()) { + set_lookup_failed("Klass is not initialized"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed for klass %s: not initialized", + compile_id(), comp_level(), k->external_name()); + return nullptr; + } + if (array_dim > 0) { + assert(k->is_instance_klass() || k->is_typeArray_klass(), "sanity check"); + Klass* ak = k->array_klass_or_null(array_dim); + if (ak != nullptr && array_desc != ArrayProperties::Invalid().value()) { + ArrayDescription adesc = ArrayDescription::from_value(array_desc); + ak = ObjArrayKlass::cast(ak)->klass_from_description(adesc, thread); + } + if (ak == nullptr) { + set_lookup_failed("Lookup failed for array klass"); + log_debug(aot, codecache, metadata)("%d (A%d): Lookup failed for %d-dimensions array klass %s", + compile_id(), comp_level(), array_dim, k->external_name()); + return nullptr; + } + log_debug(aot, codecache, metadata)("%d (A%d): Found %d-dimensions array klass %s", + compile_id(), comp_level(), array_dim, k->external_name()); + return ak; + } else { + log_debug(aot, codecache, metadata)("%d (A%d): Found klass %s", + compile_id(), comp_level(), k->external_name()); + return k; + } +} + +bool AOTCodeCache::write_oop(jobject& jo) { + oop obj = JNIHandles::resolve(jo); + return write_oop(obj); +} + +bool AOTCodeCache::write_oop(oop obj) { + DataKind kind; + if (obj == nullptr) { + kind = DataKind::Null; + if (!write_kind(kind)) { + return false; + } + } else if (cast_from_oop(obj) == Universe::non_oop_word()) { + kind = DataKind::No_Data; + if (!write_kind(kind)) { + return false; + } + } else if (java_lang_Class::is_instance(obj)) { + if (java_lang_Class::is_primitive(obj)) { + int bt = (int)java_lang_Class::primitive_type(obj); + kind = DataKind::Primitive; + if (!write_kind(kind)) { + return false; + } + if (!write_int(bt)) { + return false; + } + log_debug(aot, codecache, oops)("%d (A%d): Wrote primitive type klass: %s", + compile_id(), comp_level(), type2name((BasicType)bt)); + return true; + } else { + Klass* klass = java_lang_Class::as_Klass(obj); + if (!write_klass(klass)) { + return false; + } + } + } else if (java_lang_String::is_instance(obj)) { + int k = AOTCacheAccess::get_archived_object_permanent_index(obj); // k >= 0 means obj is a "permanent heap object" + ResourceMark rm; + size_t length_sz = 0; + const char* string = java_lang_String::as_utf8_string(obj, length_sz); + if (k >= 0) { + kind = DataKind::String; + if (!write_kind(kind)) { + return false; + } + if (!write_int(k)) { + return false; + } + log_debug(aot, codecache, oops)("%d (A%d): Wrote String object: " PTR_FORMAT " : %s", + compile_id(), comp_level(), p2i(obj), string); + return true; + } + // Not archived String object - bailout + set_lookup_failed(); + log_debug(aot, codecache, oops)("%d (A%d): Failed to write String object: " PTR_FORMAT " : %s", + compile_id(), comp_level(), p2i(obj), string); + return false; + } else if (java_lang_Module::is_instance(obj)) { + set_lookup_failed(); + log_debug(aot, codecache, oops)("%d (A%d): Module object is not supported: " PTR_FORMAT " : %s", + compile_id(), comp_level(), p2i(obj), obj->klass()->external_name()); + return false; + } else if (java_lang_ClassLoader::is_instance(obj)) { + if (obj == SystemDictionary::java_system_loader()) { + kind = DataKind::SysLoader; + } else if (obj == SystemDictionary::java_platform_loader()) { + kind = DataKind::PlaLoader; + } else { + ResourceMark rm; + set_lookup_failed(); + log_debug(aot, codecache, oops)("%d (A%d): Not supported Class Loader: " PTR_FORMAT " : %s", + compile_id(), comp_level(), p2i(obj), obj->klass()->external_name()); + return false; + } + if (!write_kind(kind)) { + return false; + } + log_debug(aot, codecache, oops)("%d (A%d): Wrote ClassLoader: java_%s_loader", + compile_id(), comp_level(), (kind == DataKind::SysLoader ? "system" : "platform")); + return true; + } else { + ResourceMark rm; + int k = AOTCacheAccess::get_archived_object_permanent_index(obj); // k >= 0 means obj is a "permanent heap object" + if (k >= 0) { + kind = DataKind::MH_Oop; + if (!write_kind(kind)) { + return false; + } + if (!write_int(k)) { + return false; + } + log_debug(aot, codecache, oops)("%d (A%d): Wrote MH object: " PTR_FORMAT " : %s", + compile_id(), comp_level(), p2i(obj), obj->klass()->external_name()); + return true; + } + // Not archived Java object - bailout + set_lookup_failed(); + log_debug(aot, codecache, oops)("%d (A%d): Failed to write Java object: " PTR_FORMAT " : %s", + compile_id(), comp_level(), p2i(obj), obj->klass()->external_name()); + return false; + } + return true; +} + +oop AOTCodeReader::read_oop(JavaThread* thread) { + oop obj = nullptr; + DataKind kind; + if (!read_kind(&kind)) { + return nullptr; + } + if (kind == DataKind::Null) { + return nullptr; + } else if (kind == DataKind::No_Data) { + return cast_to_oop(Universe::non_oop_word()); + } else if (kind == DataKind::Klass) { + Klass* k = read_klass(thread); + if (k == nullptr) { + return nullptr; + } + obj = k->java_mirror(); + if (obj == nullptr) { + set_lookup_failed("Lookup failed for java_mirror"); + log_debug(aot, codecache, oops)("%d (A%d): Lookup failed for java_mirror of klass %s", + compile_id(), comp_level(), k->external_name()); + return nullptr; + } + } else if (kind == DataKind::Primitive) { + BasicType bt; + if (!read_int((int*)&bt)) { + return nullptr; + } + obj = java_lang_Class::primitive_mirror(bt); + log_debug(aot, codecache, oops)("%d (A%d): Read primitive type klass: %s", compile_id(), comp_level(), type2name(bt)); + } else if (kind == DataKind::String) { + int k; + if (!read_int(&k)) { + return nullptr; + } + obj = AOTCacheAccess::get_archived_object(k); + if (obj == nullptr) { + set_lookup_failed("Lookup failed for String object"); + log_debug(aot, codecache, oops)("%d (A%d): Lookup failed for String object", compile_id(), comp_level()); + return nullptr; + } + assert(java_lang_String::is_instance(obj), "must be string"); + + ResourceMark rm; + size_t length_sz = 0; + const char* string = java_lang_String::as_utf8_string(obj, length_sz); + log_debug(aot, codecache, oops)("%d (A%d): Read String object: %s", compile_id(), comp_level(), string); + } else if (kind == DataKind::SysLoader) { + obj = SystemDictionary::java_system_loader(); + log_debug(aot, codecache, oops)("%d (A%d): Read java_system_loader", compile_id(), comp_level()); + } else if (kind == DataKind::PlaLoader) { + obj = SystemDictionary::java_platform_loader(); + log_debug(aot, codecache, oops)("%d (A%d): Read java_platform_loader", compile_id(), comp_level()); + } else if (kind == DataKind::MH_Oop) { + int k; + if (!read_int(&k)) { + return nullptr; + } + obj = AOTCacheAccess::get_archived_object(k); + if (obj == nullptr) { + set_lookup_failed("Lookup failed for MH object"); + log_debug(aot, codecache, oops)("%d (A%d): Lookup failed for MH object", compile_id(), comp_level()); + return nullptr; + } + ResourceMark rm; + log_debug(aot, codecache, oops)("%d (A%d): Read MH object: " PTR_FORMAT " : %s", + compile_id(), comp_level(), p2i(obj), obj->klass()->external_name()); + } else { + set_lookup_failed("Unknown oop's kind"); + log_debug(aot, codecache, oops)("%d (A%d): Unknown oop's kind: %d", + compile_id(), comp_level(), (int)kind); + return nullptr; + } + return obj; +} + +bool AOTCodeReader::read_oop_metadata_list(JavaThread* current, GrowableArray &oop_list, GrowableArray &metadata_list, OopRecorder* oop_recorder) { + assert(oop_recorder == nullptr || oop_recorder->is_unused(), "only use fresh new OopRecorder"); + int count; + if (!read_int(&count)) { + return false; + } + for (int i = 0; i < count; i++) { + oop obj = read_oop(current); + if (lookup_failed()) { + return false; + } + Handle h(current, obj); + oop_list.append(h); + if (oop_recorder != nullptr) { + jobject jo = JNIHandles::make_local(current, obj); + // Use allocate_oop_index() to preserve indexes recorded in Dependencies + oop_recorder->allocate_oop_index(jo); + } + LogStreamHandle(Trace, aot, codecache, oops) log; + if (log.is_enabled()) { + ResourceMark rm; + stringStream ss; + ss.print("%d (A%d): %d: " INTPTR_FORMAT " ", compile_id(), comp_level(), i, p2i(obj)); + if (obj == Universe::non_oop_word()) { + ss.print("non-oop word"); + } else if (obj == nullptr) { + ss.print("nullptr-oop"); + } else { + obj->print_value_on(&ss); + } + log.print_cr("%s", ss.freeze()); + } + } + + if (!read_int(&count)) { + return false; + } + for (int i = 0; i < count; i++) { + Metadata* m = read_metadata(current); + if (lookup_failed()) { + return false; + } + metadata_list.append(m); + if (oop_recorder != nullptr) { + // Use allocate_metadata_index() to preserve indexes recorded in Dependencies + oop_recorder->allocate_metadata_index(m); + } + LogStreamHandle(Trace, aot, codecache, metadata) log; + if (log.is_enabled()) { + ResourceMark rm; + stringStream ss; + ss.print("%d (A%d): %d: " INTPTR_FORMAT " ", compile_id(), comp_level(), i, p2i(m)); + if (m == (Metadata*)Universe::non_oop_word()) { + ss.print("non-metadata word"); + } else if (m == nullptr) { + ss.print("nullptr-metadata"); + } else { + Metadata::print_value_on_maybe_null(&ss, m); + } + log.print_cr("%s", ss.freeze()); + } + } + return true; +} + +bool AOTCodeCache::write_oop_map_set(CodeBlob& cb) { + ImmutableOopMapSet* oopmaps = cb.oop_maps(); + int oopmaps_size = oopmaps->nr_of_bytes(); + if (!write_int(oopmaps_size)) { + return false; + } + if (!write_bytes(oopmaps, oopmaps->nr_of_bytes())) { + return false; + } + return true; +} + +ImmutableOopMapSet* AOTCodeReader::read_oop_map_set() { + int size; + if (!read_int(&size)) { + return nullptr; + } + address oopmaps; + if (!get_set_read_position(&oopmaps, (uint)size)) { + return nullptr; + } + return (ImmutableOopMapSet*)oopmaps; +} + +bool AOTCodeCache::write_oops(nmethod* nm) { + int count = nm->oops_count()-1; + if (!write_int(count)) { + return false; + } + for (oop* p = nm->oops_begin(); p < nm->oops_end(); p++) { + if (!write_oop(*p)) { + return false; + } + } + return true; +} + +#ifndef PRODUCT +bool AOTCodeCache::write_asm_rem_and_dbg_str(AsmRemarks& asm_remarks, DbgStrings& dbg_strings) { + GrowableArray remarks_str; + GrowableArray remarks_str_len; + uint remarks_str_size = 0; + if (!write_asm_remarks(asm_remarks, remarks_str, remarks_str_len, &remarks_str_size)) { + return false; + } + if (!write_int(remarks_str_size)) { + return false; + } + + GrowableArray dbg_str; + GrowableArray dbg_str_len; + uint dbg_str_size = 0; + if (!write_dbg_strings(dbg_strings, dbg_str, dbg_str_len, &dbg_str_size)) { + return false; + } + if (!write_int(dbg_str_size)) { + return false; + } + + uint remarks_str_start = _write_position; + assert(remarks_str.length() == remarks_str_len.length(), "sanity"); + for (int i = 0; i < remarks_str.length(); i++) { + const char* str = remarks_str.at(i); + uint len = remarks_str_len.at(i); + if (!write_bytes(str, len)) { + return false; + } + } + assert(remarks_str_size == (_write_position - remarks_str_start), "%u != (%u - %u)", + remarks_str_size, _write_position, remarks_str_start); + + uint dbg_str_start = _write_position; + for (int i = 0; i < dbg_str.length(); i++) { + const char* str = dbg_str.at(i); + uint len = dbg_str_len.at(i); + if (!write_bytes(str, len)) { + return false; + } + log_trace(aot, codecache)("Wrote dbg string %d '%s'", i, str); + } + assert(dbg_str_size == (_write_position - dbg_str_start), "%u != (%u - %u)", + dbg_str_size, _write_position, dbg_str_start); return true; } -void AOTCodeReader::fix_relocations(CodeBlob *code_blob, RelocIterator& iter) { - uint offset = align_read_int(); - int reloc_count = *(int*)addr(offset); - offset += sizeof(int); - uint* reloc_data = (uint*)addr(offset); - offset += (reloc_count * sizeof(uint)); - set_read_position(offset); +bool AOTCodeReader::read_asm_rem_and_dbg_str() { + if (!read_int(&_asm_remarks_count)) { + return false; + } + if (_asm_remarks_count < 0) { + log_warning(aot, codecache)("Damaged asm remarks: negative count %d", _asm_remarks_count); + return false; + } + uint asm_remarks_size = (uint)_asm_remarks_count * sizeof(AsmRemData); + if (!get_set_read_position((address*)&_asm_remarks_data, asm_remarks_size)) { + return false; + } + int asm_remarks_str_size; + if (!read_int(&asm_remarks_str_size)) { + return false; + } + if (asm_remarks_str_size < 0) { + log_warning(aot, codecache)("Damaged asm remarks: negative size %d", asm_remarks_str_size); + return false; + } - LogStreamHandle(Trace, aot, codecache, reloc) log; - if (log.is_enabled()) { - log.print_cr("======== extra relocations count=%d", reloc_count); - log.print(" {"); - for(int i = 0; i < reloc_count; i++) { - if (i == 0) { - log.print("%d", reloc_data[i]); - } else { - log.print(", %d", reloc_data[i]); - } - } - log.print_cr("}"); + if (!read_int(&_dbg_strings_count)) { + return false; + } + if (_dbg_strings_count < 0) { + log_warning(aot, codecache)("Damaged dbg strings: negative count %d", _dbg_strings_count); + return false; + } + uint dbg_str_size = (uint)_dbg_strings_count * sizeof(DbgStrData); + if (!get_set_read_position((address*)&_dbg_strings_data, dbg_str_size)) { + return false; + } + int dbg_strings_size; + if (!read_int(&dbg_strings_size)) { + return false; + } + if (dbg_strings_size < 0) { + log_warning(aot, codecache)("Damaged dbg strings: negative size %d", dbg_strings_size); + return false; } - int j = 0; - while (iter.next()) { - switch (iter.type()) { - case relocInfo::none: - break; - case relocInfo::runtime_call_type: { - address dest = _cache->address_for_id(reloc_data[j]); - if (dest != (address)-1) { - ((CallRelocation*)iter.reloc())->set_destination(dest); - } - break; - } - case relocInfo::runtime_call_w_cp_type: - // this relocation should not be in cache (see write_relocations) - assert(false, "runtime_call_w_cp_type relocation is not implemented"); - break; - case relocInfo::external_word_type: { - address target = _cache->address_for_id(reloc_data[j]); - // Add external address to global table - int index = ExternalsRecorder::find_index(target); - // Update index in relocation - Relocation::add_jint(iter.data(), index); - external_word_Relocation* reloc = (external_word_Relocation*)iter.reloc(); - assert(reloc->target() == target, "sanity"); - reloc->set_value(target); // Patch address in the code - break; - } - case relocInfo::internal_word_type: { - internal_word_Relocation* r = (internal_word_Relocation*)iter.reloc(); - r->fix_relocation_after_aot_load(aot_code_entry()->dumptime_content_start_addr(), code_blob->content_begin()); - break; - } - case relocInfo::section_word_type: { - section_word_Relocation* r = (section_word_Relocation*)iter.reloc(); - r->fix_relocation_after_aot_load(aot_code_entry()->dumptime_content_start_addr(), code_blob->content_begin()); - break; - } - case relocInfo::post_call_nop_type: - break; - default: - assert(false,"relocation %d unimplemented", (int)iter.type()); - break; + uint strings_size = static_cast(asm_remarks_str_size); + const char* strings_base; + if (!get_set_read_position((address*)&strings_base, strings_size)) { + return false; + } + _asm_remarks_strings = _asm_remarks_count == 0 ? nullptr : + NEW_C_HEAP_ARRAY(const char*, _asm_remarks_count, mtCode); + uint offset = 0; + int i = 0; + uint off = 0; + uint len = 0; + for (; i < _asm_remarks_count; i++) { + off = _asm_remarks_data[i].str_offset; + len = _asm_remarks_data[i].str_len; + if (offset != off || len == 0 || off > strings_size || len > (strings_size - off)) { + break; } - if (log.is_enabled()) { - iter.print_current_on(&log); + const char* str = strings_base + off; + if (str[len - 1] != '\0') { + break; } - j++; + _asm_remarks_strings[i] = str; + offset += len; + } + if (i < _asm_remarks_count) { + log_warning(aot, codecache)("Damaged asm remark %d [%u + %u] in AOT entry %u " INTPTR_FORMAT " [%u + %u]", i, off, len, _entry->id(), p2i(_entry), _entry->offset(), _entry->size()); + return false; + } + if (offset != strings_size) { + log_warning(aot, codecache)("Damaged asm remarks: size %u vs cached %u , in AOT entry %u " INTPTR_FORMAT " [%u + %u]", offset, (uint)asm_remarks_str_size, _entry->id(), p2i(_entry), _entry->offset(), _entry->size()); + return false; } - assert(j == reloc_count, "sanity"); -} -bool AOTCodeCache::write_oop_map_set(CodeBlob& cb) { - if (!align_write_int()) { + strings_size = static_cast(dbg_strings_size); + if (!get_set_read_position((address*)&strings_base, strings_size)) { return false; } - ImmutableOopMapSet* oopmaps = cb.oop_maps(); - int oopmaps_size = oopmaps->nr_of_bytes(); - if (!write_bytes(&oopmaps_size, sizeof(int))) { + _dbg_strings = _dbg_strings_count == 0 ? nullptr : + NEW_C_HEAP_ARRAY(const char*, _dbg_strings_count, mtCode); + offset = 0; + i = 0; + off = 0; + len = 0; + for (; i < _dbg_strings_count; i++) { + off = _dbg_strings_data[i].str_offset; + len = _dbg_strings_data[i].str_len; + if (offset != off || len == 0 || off > strings_size || len > (strings_size - off)) { + break; + } + const char* str = strings_base + off; + if (str[len - 1] != '\0') { + break; + } + _dbg_strings[i] = str; + offset += len; + } + if (i < _dbg_strings_count) { + log_warning(aot, codecache)("Damaged dbg strings %d [%u + %u] in AOT entry %u " INTPTR_FORMAT " [%u + %u]", i, off, len, _entry->id(), p2i(_entry), _entry->offset(), _entry->size()); return false; } - uint n = write_bytes(oopmaps, oopmaps->nr_of_bytes()); - if (n != (uint)oopmaps->nr_of_bytes()) { + if (offset != strings_size) { + log_warning(aot, codecache)("Damaged dbg strings: size %u vs cached %u , in AOT entry %u " INTPTR_FORMAT " [%u + %u]", offset, (uint)dbg_strings_size, _entry->id(), p2i(_entry), _entry->offset(), _entry->size()); return false; } return true; } -ImmutableOopMapSet* AOTCodeReader::read_oop_map_set() { - uint offset = align_read_int(); - int size = *(int *)addr(offset); - offset += sizeof(int); - ImmutableOopMapSet* oopmaps = (ImmutableOopMapSet *)addr(offset); - offset += size; - set_read_position(offset); - return oopmaps; -} - -#ifndef PRODUCT -bool AOTCodeCache::write_asm_remarks(CodeBlob& cb) { +bool AOTCodeCache::write_asm_remarks(AsmRemarks& asm_remarks, GrowableArray& remarks, GrowableArray& remarks_len, uint* size) { if (!align_write_int()) { return false; } @@ -1783,43 +4245,51 @@ bool AOTCodeCache::write_asm_remarks(CodeBlob& cb) { return false; } uint count = 0; - bool result = cb.asm_remarks().iterate([&] (uint offset, const char* str) -> bool { - log_trace(aot, codecache, stubs)("asm remark offset=%d, str='%s'", offset, str); - uint n = write_bytes(&offset, sizeof(uint)); - if (n != sizeof(uint)) { + uint str_offset = 0; + bool result = asm_remarks.iterate([&] (uint offset, const char* str) -> bool { + if (!write_int(offset)) { + return false; + } + if (!write_int(str_offset)) { return false; } - const char* cstr = add_C_string(str); - int id = _table->id_for_C_string((address)cstr); - assert(id != BAD_ADDRESS_ID, "asm remark string '%s' not found in AOTCodeAddressTable", str); - n = write_bytes(&id, sizeof(int)); - if (n != sizeof(int)) { + uint len = (uint)strlen(str) + 1; + if (!write_int(len)) { return false; } + remarks.append(str); + remarks_len.append(len); + str_offset += len; count += 1; return true; }); - *count_ptr = count; + if (result) { + *count_ptr = count; + *size = str_offset; + log_trace(aot, codecache)("Wrote asm remarks count=%u size=%u", count, str_offset); + } else { + *count_ptr = 0; + *size = 0; + log_trace(aot, codecache)("Failed write asm remark %u offset=%u", count, str_offset); + } return result; } -void AOTCodeReader::read_asm_remarks(AsmRemarks& asm_remarks) { +void AOTCodeReader::restore_asm_remarks(AsmRemarks& asm_remarks) { // Read asm remarks - uint offset = align_read_int(); - uint count = *(uint *)addr(offset); - offset += sizeof(uint); - for (uint i = 0; i < count; i++) { - uint remark_offset = *(uint *)addr(offset); - offset += sizeof(uint); - int remark_string_id = *(uint *)addr(offset); - offset += sizeof(int); - const char* remark = (const char*)_cache->address_for_C_string(remark_string_id); + int count = _asm_remarks_count; + log_trace(aot, codecache)("Restoring %d asm remarks", count); + for (int i = 0; i < count; i++) { + uint remark_offset = _asm_remarks_data[i].offset; + const char* remark = _asm_remarks_strings[i]; asm_remarks.insert(remark_offset, remark); } - set_read_position(offset); + if (_asm_remarks_strings != nullptr) { + FREE_C_HEAP_ARRAY(_asm_remarks_strings); + } } -bool AOTCodeCache::write_dbg_strings(CodeBlob& cb) { +bool AOTCodeCache::write_dbg_strings(DbgStrings& dbg_strings, GrowableArray& strings, GrowableArray&strings_len, uint* size) { if (!align_write_int()) { return false; } @@ -1829,34 +4299,45 @@ bool AOTCodeCache::write_dbg_strings(CodeBlob& cb) { return false; } uint count = 0; - bool result = cb.dbg_strings().iterate([&] (const char* str) -> bool { - log_trace(aot, codecache, stubs)("dbg string=%s", str); - const char* cstr = add_C_string(str); - int id = _table->id_for_C_string((address)cstr); - assert(id != BAD_ADDRESS_ID, "db string '%s' not found in AOTCodeAddressTable", str); - uint n = write_bytes(&id, sizeof(int)); - if (n != sizeof(int)) { + uint str_offset = 0; + bool result = dbg_strings.iterate([&] (const char* str) -> bool { + if (!write_int(str_offset)) { + return false; + } + uint len = (uint)strlen(str) + 1; + if (!write_int(len)) { return false; } + strings.append(str); + strings_len.append(len); + str_offset += len; + count += 1; return true; }); - *count_ptr = count; + if (result) { + *count_ptr = count; + *size = str_offset; + log_trace(aot, codecache)("Wrote dbg strings count=%u size=%u", count, str_offset); + } else { + *count_ptr = 0; + *size = 0; + log_trace(aot, codecache)("Failed write dbg string %u offset=%u", count, str_offset); + } return result; } -void AOTCodeReader::read_dbg_strings(DbgStrings& dbg_strings) { +void AOTCodeReader::restore_dbg_strings(DbgStrings& dbg_strings) { // Read dbg strings - uint offset = align_read_int(); - uint count = *(uint *)addr(offset); - offset += sizeof(uint); - for (uint i = 0; i < count; i++) { - int string_id = *(uint *)addr(offset); - offset += sizeof(int); - const char* str = (const char*)_cache->address_for_C_string(string_id); + int count = _dbg_strings_count; + log_trace(aot, codecache)("Restoring %d dbg strings", count); + for (int i = 0; i < count; i++) { + const char* str = _dbg_strings[i]; dbg_strings.insert(str); } - set_read_position(offset); + if (_dbg_strings != nullptr) { + FREE_C_HEAP_ARRAY(_dbg_strings); + } } #endif // PRODUCT @@ -1865,9 +4346,10 @@ void AOTCodeReader::read_dbg_strings(DbgStrings& dbg_strings) { // address table ids for generated routine entry adresses, external // addresses and C string addresses are partitioned into positive // integer ranges defined by the following positive base and max -// values i.e. [_extrs_base, _extrs_base + _extrs_max -1], -// [_stubs_base, _stubs_base + _stubs_max -1], [_c_str_base, -// _c_str_base + _C_strings_count], +// values i.e. +// [_extrs_base, _extrs_base + _extrs_max -1], +// [_stubs_base, _stubs_base + _stubs_max -1], +// [_c_str_base, _c_str_base + _C_strings_count], #define _extrs_max 500 #define _stubs_max static_cast(EntryId::NUM_ENTRYIDS) @@ -1917,16 +4399,17 @@ void AOTCodeAddressTable::init_extrs() { initializing_extrs = true; _extrs_addr = NEW_C_HEAP_ARRAY(address, _extrs_max, mtCode); - - _extrs_length = 0; + memset(_extrs_addr, 0, _extrs_max * sizeof(address)); { // Required by initial stubs ADD_EXTERNAL_ADDRESS(SharedRuntime::exception_handler_for_return_address); // used by forward_exception + ADD_EXTERNAL_ADDRESS(CompressedKlassPointers::base_addr()); ADD_EXTERNAL_ADDRESS(CompressedOops::base_addr()); // used by call_stub ADD_EXTERNAL_ADDRESS(Thread::current); // used by call_stub ADD_EXTERNAL_ADDRESS(SharedRuntime::throw_StackOverflowError); ADD_EXTERNAL_ADDRESS(SharedRuntime::throw_delayed_StackOverflowError); + ADD_EXTERNAL_ADDRESS(StubRoutines::crc_table_addr()); ADD_EXTERNAL_ADDRESS(StubRoutines::verify_oop_count_addr()); // used by generate_verify_oop() ADD_EXTERNAL_ADDRESS(StubRoutines::verify_oop_subroutine_entry_address()); if (ValueTypeReturnedAsFields) { @@ -1960,6 +4443,12 @@ void AOTCodeAddressTable::init_extrs() { #endif /* PRODUCT */ ADD_EXTERNAL_ADDRESS(SharedRuntime::enable_stack_reserved_zone); + ADD_EXTERNAL_ADDRESS(SharedRuntime::rc_trace_method_entry); + ADD_EXTERNAL_ADDRESS(SharedRuntime::reguard_yellow_pages); + ADD_EXTERNAL_ADDRESS(SharedRuntime::dtrace_method_entry); + ADD_EXTERNAL_ADDRESS(SharedRuntime::dtrace_method_exit); + ADD_EXTERNAL_ADDRESS(SharedRuntime::complete_monitor_unlocking_C); + ADD_EXTERNAL_ADDRESS(SharedRuntime::OSR_migration_end); #if defined(AMD64) && !defined(ZERO) ADD_EXTERNAL_ADDRESS(SharedRuntime::montgomery_multiply); @@ -1974,14 +4463,12 @@ void AOTCodeAddressTable::init_extrs() { ADD_EXTERNAL_ADDRESS(SharedRuntime::dlog); ADD_EXTERNAL_ADDRESS(SharedRuntime::dlog10); ADD_EXTERNAL_ADDRESS(SharedRuntime::dpow); -#ifndef ZERO - ADD_EXTERNAL_ADDRESS(SharedRuntime::drem); -#endif ADD_EXTERNAL_ADDRESS(SharedRuntime::dsin); ADD_EXTERNAL_ADDRESS(SharedRuntime::dtan); ADD_EXTERNAL_ADDRESS(SharedRuntime::f2i); ADD_EXTERNAL_ADDRESS(SharedRuntime::f2l); #ifndef ZERO + ADD_EXTERNAL_ADDRESS(SharedRuntime::drem); ADD_EXTERNAL_ADDRESS(SharedRuntime::frem); #endif ADD_EXTERNAL_ADDRESS(SharedRuntime::l2d); @@ -1990,28 +4477,39 @@ void AOTCodeAddressTable::init_extrs() { ADD_EXTERNAL_ADDRESS(SharedRuntime::lmul); ADD_EXTERNAL_ADDRESS(SharedRuntime::lrem); -#if INCLUDE_JVMTI - ADD_EXTERNAL_ADDRESS(&JvmtiExport::_should_notify_object_alloc); -#endif /* INCLUDE_JVMTI */ - ADD_EXTERNAL_ADDRESS(ThreadIdentifier::unsafe_offset()); - // already added - // ADD_EXTERNAL_ADDRESS(Thread::current); + ADD_EXTERNAL_ADDRESS(ObjectMonitorTable::current_table_address()); ADD_EXTERNAL_ADDRESS(os::javaTimeMillis); ADD_EXTERNAL_ADDRESS(os::javaTimeNanos); #ifndef PRODUCT ADD_EXTERNAL_ADDRESS(os::breakpoint); -#endif - - ADD_EXTERNAL_ADDRESS(StubRoutines::crc_table_addr()); -#ifndef PRODUCT ADD_EXTERNAL_ADDRESS(&SharedRuntime::_partial_subtype_ctr); +#if defined(AARCH64) + ADD_EXTERNAL_ADDRESS(JavaThread::verify_cross_modify_fence_failure); #endif +#endif // !PRODUCT + +#if INCLUDE_JVMTI + ADD_EXTERNAL_ADDRESS(&JvmtiExport::_should_notify_object_alloc); +#endif /* INCLUDE_JVMTI */ + // For JVMTI + ADD_EXTERNAL_ADDRESS(MountUnmountDisabler::notify_jvmti_events_address()); + ADD_EXTERNAL_ADDRESS(MountUnmountDisabler::global_vthread_transition_disable_count_address()); #if INCLUDE_JFR ADD_EXTERNAL_ADDRESS(JfrIntrinsicSupport::write_checkpoint); ADD_EXTERNAL_ADDRESS(JfrIntrinsicSupport::return_lease); + ADD_EXTERNAL_ADDRESS(JfrIntrinsicSupport::load_barrier); + ADD_EXTERNAL_ADDRESS(JfrIntrinsicSupport::epoch_address()); + ADD_EXTERNAL_ADDRESS(JfrIntrinsicSupport::epoch_generation_address()); + ADD_EXTERNAL_ADDRESS(JfrIntrinsicSupport::signal_address()); + ADD_EXTERNAL_ADDRESS(&VMContinuations) +#endif + // For JFR + ADD_EXTERNAL_ADDRESS(os::elapsed_counter); +#if defined(X86) && !defined(ZERO) + ADD_EXTERNAL_ADDRESS(Rdtsc::elapsed_counter); #endif ADD_EXTERNAL_ADDRESS(UpcallLinker::handle_uncaught_exception); // used by upcall_stub_exception_handler @@ -2024,8 +4522,6 @@ void AOTCodeAddressTable::init_extrs() { ADD_EXTERNAL_ADDRESS(SharedRuntime::resolve_opt_virtual_call_C); ADD_EXTERNAL_ADDRESS(SharedRuntime::resolve_virtual_call_C); ADD_EXTERNAL_ADDRESS(SharedRuntime::resolve_static_call_C); - // already added - // ADD_EXTERNAL_ADDRESS(SharedRuntime::throw_delayed_StackOverflowError); ADD_EXTERNAL_ADDRESS(SharedRuntime::throw_AbstractMethodError); ADD_EXTERNAL_ADDRESS(SharedRuntime::throw_IncompatibleClassChangeError); ADD_EXTERNAL_ADDRESS(SharedRuntime::throw_NullPointerException_at_call); @@ -2060,6 +4556,7 @@ void AOTCodeAddressTable::init_extrs() { ADD_EXTERNAL_ADDRESS(Runtime1::move_appendix_patching); ADD_EXTERNAL_ADDRESS(Runtime1::predicate_failed_trap); ADD_EXTERNAL_ADDRESS(Runtime1::unimplemented_entry); + ADD_EXTERNAL_ADDRESS(Runtime1::trace_block_entry); ADD_EXTERNAL_ADDRESS(Runtime1::new_null_free_array); ADD_EXTERNAL_ADDRESS(Runtime1::load_flat_array); ADD_EXTERNAL_ADDRESS(Runtime1::store_flat_array); @@ -2068,11 +4565,14 @@ void AOTCodeAddressTable::init_extrs() { ADD_EXTERNAL_ADDRESS(Runtime1::buffer_value_args_no_receiver); ADD_EXTERNAL_ADDRESS(Runtime1::throw_identity_exception); ADD_EXTERNAL_ADDRESS(Runtime1::throw_illegal_monitor_state_exception); - // already added - // ADD_EXTERNAL_ADDRESS(Thread::current); - ADD_EXTERNAL_ADDRESS(CompressedKlassPointers::base_addr()); + + ResourceMark rm; + GrowableArray
external_addresses; + // publish static addresses referred to by C1 runtime + Runtime1::init_AOTAddressTable(external_addresses); + add_external_addresses(external_addresses); } -#endif +#endif // COMPILER1 #ifdef COMPILER2 { @@ -2093,66 +4593,89 @@ void AOTCodeAddressTable::init_extrs() { ADD_EXTERNAL_ADDRESS(OptoRuntime::rethrow_C); ADD_EXTERNAL_ADDRESS(OptoRuntime::slow_arraycopy_C); ADD_EXTERNAL_ADDRESS(OptoRuntime::register_finalizer_C); + ADD_EXTERNAL_ADDRESS(OptoRuntime::compile_method_C); ADD_EXTERNAL_ADDRESS(OptoRuntime::load_unknown_value_C); ADD_EXTERNAL_ADDRESS(OptoRuntime::store_unknown_value_C); ADD_EXTERNAL_ADDRESS(OptoRuntime::vthread_end_first_transition_C); ADD_EXTERNAL_ADDRESS(OptoRuntime::vthread_start_final_transition_C); ADD_EXTERNAL_ADDRESS(OptoRuntime::vthread_start_transition_C); ADD_EXTERNAL_ADDRESS(OptoRuntime::vthread_end_transition_C); -#if defined(AARCH64) && ! defined(PRODUCT) - ADD_EXTERNAL_ADDRESS(JavaThread::verify_cross_modify_fence_failure); -#endif // AARCH64 && !PRODUCT + ADD_EXTERNAL_ADDRESS(Parse::trap_stress_counter_address()); // Used by lookup_secondary_supers_table ADD_EXTERNAL_ADDRESS(Klass::on_secondary_supers_verification_failure); } +#if defined(AMD64) || defined(AARCH64) + ADD_EXTERNAL_ADDRESS(C2_MacroAssembler::abort_verify_int_in_range); + ADD_EXTERNAL_ADDRESS(C2_MacroAssembler::abort_verify_long_in_range); +#endif // defined(AMD64) || defined(AARCH64) + +#ifndef PRODUCT + if (CountCompiledCalls) { + ADD_EXTERNAL_ADDRESS(SharedRuntime::nof_normal_calls_addr()); + ADD_EXTERNAL_ADDRESS(SharedRuntime::nof_static_calls_addr()); + ADD_EXTERNAL_ADDRESS(SharedRuntime::nof_interface_calls_addr()); + ADD_EXTERNAL_ADDRESS(SharedRuntime::nof_inlined_calls_addr()); + ADD_EXTERNAL_ADDRESS(SharedRuntime::nof_inlined_static_calls_addr()); + ADD_EXTERNAL_ADDRESS(SharedRuntime::nof_inlined_interface_calls_addr()); + ADD_EXTERNAL_ADDRESS(SharedRuntime::nof_megamorphic_calls_addr()); + } +#endif // NOT PRODUCT #endif // COMPILER2 + ADD_EXTERNAL_ADDRESS(BarrierSetNMethod::nmethod_stub_entry_barrier); // used by method_entry_barrier #if INCLUDE_G1GC - ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::write_ref_field_pre_entry); - ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::write_ref_array_pre_narrow_oop_entry); // used by arraycopy stubs - ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::write_ref_array_pre_oop_entry); // used by arraycopy stubs - ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::write_ref_array_post_entry); // used by arraycopy stubs + if (UseG1GC) { + ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::write_ref_field_pre_entry); + ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::write_ref_array_pre_narrow_oop_entry); // used by arraycopy stubs + ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::write_ref_array_pre_oop_entry); // used by arraycopy stubs + ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::write_ref_array_post_entry); // used by arraycopy stubs + ADD_EXTERNAL_ADDRESS(G1BarrierSetRuntime::clone_addr()); + } #endif - ADD_EXTERNAL_ADDRESS(BarrierSetNMethod::nmethod_stub_entry_barrier); // used by method_entry_barrier - #if INCLUDE_SHENANDOAHGC - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::write_barrier_pre); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::write_barrier_pre_narrow); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_strong); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_strong_narrow); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_strong_narrow_narrow); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_weak); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_weak_narrow); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_weak_narrow_narrow); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_phantom); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_phantom_narrow); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_phantom_narrow_narrow); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::arraycopy_barrier_oop); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::arraycopy_barrier_narrow_oop); - ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::clone); + if (UseShenandoahGC) { + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::write_barrier_pre); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::write_barrier_pre_narrow); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_strong); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_strong_narrow); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_strong_narrow_narrow); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_weak); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_weak_narrow); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_weak_narrow_narrow); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_phantom); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_phantom_narrow); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::load_reference_barrier_phantom_narrow_narrow); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::arraycopy_barrier_oop); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::arraycopy_barrier_narrow_oop); + ADD_EXTERNAL_ADDRESS(ShenandoahRuntime::clone); + } #endif #if INCLUDE_ZGC - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded_store_good_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_weak_oop_field_preloaded_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_phantom_oop_field_preloaded_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::no_keepalive_load_barrier_on_weak_oop_field_preloaded_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::no_keepalive_load_barrier_on_phantom_oop_field_preloaded_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::store_barrier_on_oop_field_with_healing_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::store_barrier_on_oop_field_without_healing_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::no_keepalive_store_barrier_on_oop_field_without_healing_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::store_barrier_on_native_oop_field_without_healing_addr()); - ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_oop_array_addr()); - - ADD_EXTERNAL_ADDRESS(ZPointerVectorLoadBadMask); - ADD_EXTERNAL_ADDRESS(ZPointerVectorStoreBadMask); - ADD_EXTERNAL_ADDRESS(ZPointerVectorStoreGoodMask); + if (UseZGC) { + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded_store_good_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_weak_oop_field_preloaded_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_phantom_oop_field_preloaded_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::no_keepalive_load_barrier_on_weak_oop_field_preloaded_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::no_keepalive_load_barrier_on_phantom_oop_field_preloaded_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::store_barrier_on_oop_field_with_healing_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::store_barrier_on_oop_field_without_healing_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::no_keepalive_store_barrier_on_oop_field_without_healing_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::store_barrier_on_native_oop_field_without_healing_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::load_barrier_on_oop_array_addr()); + ADD_EXTERNAL_ADDRESS(ZBarrierSetRuntime::clone_addr()); + + ADD_EXTERNAL_ADDRESS(ZPointerVectorLoadBadMask); + ADD_EXTERNAL_ADDRESS(ZPointerVectorStoreBadMask); + ADD_EXTERNAL_ADDRESS(ZPointerVectorStoreGoodMask); #if defined(AMD64) - ADD_EXTERNAL_ADDRESS(&ZPointerLoadShift); - extern address ZPointerLoadShiftTableAddr; - ADD_EXTERNAL_ADDRESS(&ZPointerLoadShiftTableAddr); + ADD_EXTERNAL_ADDRESS(&ZPointerLoadShift); + extern address ZPointerLoadShiftTableAddr; + ADD_EXTERNAL_ADDRESS(&ZPointerLoadShiftTableAddr); #endif + } // UseZGC #endif // INCLUDE_ZGC + #ifndef ZERO #if defined(AMD64) || defined(AARCH64) || defined(RISCV64) ADD_EXTERNAL_ADDRESS(MacroAssembler::debug64); @@ -2172,21 +4695,26 @@ void AOTCodeAddressTable::init_extrs() { log_debug(aot, codecache, init)("External addresses opened and recorded"); // allocate storage for stub entries _stubs_addr = NEW_C_HEAP_ARRAY(address, _stubs_max, mtCode); + memset(_stubs_addr, 0, _stubs_max * sizeof(address)); log_debug(aot, codecache, init)("Stub addresses opened"); } void AOTCodeAddressTable::init_extrs2() { assert(initializing_extrs && !_extrs_complete, "invalid sequence for init_extrs2"); - { - ADD_EXTERNAL_ADDRESS(Continuation::prepare_thaw); // used by cont_thaw - ADD_EXTERNAL_ADDRESS(Continuation::thaw_entry()); // used by cont_thaw - ADD_EXTERNAL_ADDRESS(ContinuationEntry::thaw_call_pc_address()); // used by cont_preempt_stub - } + ADD_EXTERNAL_ADDRESS(Continuation::prepare_thaw); // used by cont_thaw + ADD_EXTERNAL_ADDRESS(Continuation::thaw_entry()); // used by cont_thaw + ADD_EXTERNAL_ADDRESS(ContinuationEntry::thaw_call_pc_address()); // used by cont_preempt_stub + ADD_EXTERNAL_ADDRESS(Continuation::freeze_entry()); // used by gen_continuation_yield + } + _extrs2_length = _extrs_length; + _final_extrs_length = _extrs_length + add_c1_extrs_blobs(true /* count_only*/); + // C1 GC blobs have to be recorded later but we need to mark current + // extrs table as complete for stubs loading. _extrs_complete = true; initializing_extrs = false; - log_debug(aot, codecache, init)("External addresses recorded and closed"); + log_info(aot, codecache, init)("External addresses %d recorded", _extrs_length); } void AOTCodeAddressTable::add_external_addresses(GrowableArray
& addresses) { @@ -2215,25 +4743,85 @@ void AOTCodeAddressTable::add_stub_entry(EntryId entry_id, address a) { void AOTCodeAddressTable::set_shared_stubs_complete() { assert(!_shared_stubs_complete, "repeated close for shared stubs!"); _shared_stubs_complete = true; - log_debug(aot, codecache, init)("Shared stubs closed"); + log_info(aot, codecache, init)("Shared stubs recorded and closed"); +} + +uint AOTCodeAddressTable::add_c1_extrs_blobs(bool count_only) { + if (CompilationPolicy::c1_count() == 0 && CompilationPolicy::ac1_count() == 0) { + assert(count_only, "should be called only from init_extrs2 when C1 is disabled"); + return 0; + } + uint count = 0; + // Use the same conditions as in set_c1_stubs_complete() +#ifdef COMPILER1 +#if INCLUDE_G1GC + if (UseG1GC) { + if (!count_only) { + G1BarrierSetC1* bs = (G1BarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); + ADD_EXTERNAL_ADDRESS(bs->pre_barrier_c1_runtime_code_blob()->code_begin()); + } + count += 1; + } +#endif // INCLUDE_G1GC +#if INCLUDE_ZGC + if (UseZGC) { + if (!count_only) { + ZBarrierSetC1* bs = (ZBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); + ADD_EXTERNAL_ADDRESS(bs->_load_barrier_on_oop_field_preloaded_runtime_stub); + ADD_EXTERNAL_ADDRESS(bs->_load_barrier_on_weak_oop_field_preloaded_runtime_stub); + ADD_EXTERNAL_ADDRESS(bs->_store_barrier_on_oop_field_with_healing); + ADD_EXTERNAL_ADDRESS(bs->_store_barrier_on_oop_field_without_healing); + } + count += 4; + } +#endif // INCLUDE_ZGC +#if INCLUDE_SHENANDOAHGC + if (UseShenandoahGC) { + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); + if (ShenandoahSATBBarrier) { + if (!count_only) { + ADD_EXTERNAL_ADDRESS(bs->keepalive_barrier_c1_runtime_code_blob()->code_begin()); + } + count += 1; + } + if (ShenandoahLoadRefBarrier) { + if (!count_only) { + ADD_EXTERNAL_ADDRESS(bs->load_reference_barrier_strong_rt_code_blob()->code_begin()); + ADD_EXTERNAL_ADDRESS(bs->load_reference_barrier_strong_native_rt_code_blob()->code_begin()); + ADD_EXTERNAL_ADDRESS(bs->load_reference_barrier_weak_rt_code_blob()->code_begin()); + ADD_EXTERNAL_ADDRESS(bs->load_reference_barrier_phantom_rt_code_blob()->code_begin()); + } + count += 4; + } + } +#endif // INCLUDE_SHENANDOAHGC +#endif // COMPILER1 + return count; } void AOTCodeAddressTable::set_c1_stubs_complete() { assert(!_c1_stubs_complete, "repeated close for c1 stubs!"); + assert(_extrs_length == _extrs2_length, "%u != %u", _extrs_length, _extrs2_length); + + uint count = add_c1_extrs_blobs(false /* count_only*/); + assert(_extrs2_length == _extrs_length - count, "%u != %u", _extrs2_length, (_extrs_length - count)); + assert(_extrs_length == _final_extrs_length, "%u != %u", _extrs_length, _final_extrs_length); + _c1_stubs_complete = true; - log_debug(aot, codecache, init)("C1 stubs closed"); + log_info(aot, codecache, init)("C1 stubs recorded and closed"); + log_info(aot, codecache, init)("External C1 addresses %u recorded", count); } void AOTCodeAddressTable::set_c2_stubs_complete() { assert(!_c2_stubs_complete, "repeated close for c2 stubs!"); _c2_stubs_complete = true; - log_debug(aot, codecache, init)("C2 stubs closed"); + log_info(aot, codecache, init)("C2 stubs recorded and closed"); } void AOTCodeAddressTable::set_stubgen_stubs_complete() { assert(!_stubgen_stubs_complete, "repeated close for stubgen stubs!"); _stubgen_stubs_complete = true; - log_debug(aot, codecache, init)("StubGen stubs closed"); + log_info(aot, codecache, init)("StubGen stubs recorded and closed"); } #ifdef PRODUCT @@ -2259,32 +4847,47 @@ void AOTCodeCache::init_C_strings_caching() { _C_strings_ix = new(mtCode) GrowableArray(INITIAL_STR_CACHE_SIZE, 0, -1, mtCode); } -void AOTCodeCache::load_strings() { +bool AOTCodeCache::load_strings() { uint strings_count = _load_header->strings_count(); if (strings_count == 0) { - return; + return true; } uint strings_offset = _load_header->strings_offset(); // First is the array of cached strings length uint* string_lengths = (uint*)addr(strings_offset); strings_offset += (strings_count * sizeof(uint)); + uint space_left = _load_size - strings_offset; // We don't need to duplcate strings from AOT cache to C heap // because we don't remove AOT code cache anymore. - _cached_C_strings = NEW_C_HEAP_ARRAY(const char*, strings_count, mtCode); + const char** strings_ref = NEW_C_HEAP_ARRAY(const char*, strings_count, mtCode); + uint strings_size = 0; char* start = (char*)addr(strings_offset); char* p = start; for (uint i = 0; i < strings_count; i++) { - _cached_C_strings[i] = p; uint len = string_lengths[i]; + strings_size += len; + if (len == 0 || len > space_left || start[strings_size - 1] != '\0') { + load_failure_log().print_cr("AOT Code Cache disabled: C string %u is damaged", i); + FREE_C_HEAP_ARRAY(strings_ref); + return false; + } + space_left -= len; + strings_ref[i] = p; log_trace(aot, codecache, stringtable)("load_strings: _C_strings[%d] " INTPTR_FORMAT " '%s'", i, p2i(p), p); p += len; } - uint strings_size = _load_header->entries_offset() - strings_offset; - assert((uint)(p - start) <= strings_size, "(" INTPTR_FORMAT " - " INTPTR_FORMAT ") = %d > %d ", p2i(p), p2i(start), (uint)(p - start), strings_size); - _C_strings_count = strings_count; - log_debug(aot, codecache, init)(" Loaded %d C strings of total length %d at offset %d from AOT Code Cache", _C_strings_count, strings_size, strings_offset); + if (space_left >= DATA_ALIGNMENT) { + load_failure_log().print_cr("AOT Code Cache disabled: unaccounted space after C strings"); + FREE_C_HEAP_ARRAY(strings_ref); + return false; + } + + _cached_C_strings = strings_ref; + _C_strings_count = strings_count; + log_info(aot, codecache, init)(" Loaded %d C strings of total length %d at offset %d from AOT Code Cache", _C_strings_count, strings_size, strings_offset); + return true; } int AOTCodeCache::store_strings() { @@ -2304,8 +4907,7 @@ int AOTCodeCache::store_strings() { length += len; assert(len < 1000, "big string: %s", str); lengths[i] = len; - uint n = write_bytes(str, len); - if (n != len) { + if (!write_bytes(str, len)) { return -1; } } @@ -2387,9 +4989,6 @@ static int search_address(address addr, address* table, uint length) { address AOTCodeAddressTable::address_for_id(int idx) { assert(_extrs_complete || initializing_extrs, "AOT Code Cache VM runtime addresses table is not complete"); - if (idx == -1) { - return (address)-1; - } if (idx >= (_c_str_base + _C_strings_count)) { fatal("recorded id: %d > recorded count %d", idx, (_c_str_base + _C_strings_count)); return nullptr; @@ -2399,82 +4998,98 @@ address AOTCodeAddressTable::address_for_id(int idx) { return nullptr; } uint id = (uint)idx; + address result = nullptr; // no need to compare unsigned id against 0 if (id < _extrs_length) { - return _extrs_addr[id - _extrs_base]; - } + result = _extrs_addr[id - _extrs_base]; + } else if (id >= _stubs_base && id < _c_str_base) { - return _stubs_addr[id - _stubs_base]; - } + result = _stubs_addr[id - _stubs_base]; + } else if (id >= _c_str_base && id < (uint)(_c_str_base + _C_strings_count)) { - return address_for_C_string(id - _c_str_base); + result = address_for_C_string(id - _c_str_base); } - fatal("Incorrect id %d for AOT Code Cache addresses table", id); - return nullptr; + if (result == nullptr) { + fatal("Incorrect id %d for AOT Code Cache addresses table", id); + } + log_trace(aot, codecache)("Address " INTPTR_FORMAT " retrieved from AOT Code Cache address table for index '%u'", + p2i(result), id); + + return result; } -int AOTCodeAddressTable::id_for_address(address addr, RelocIterator reloc, CodeBlob* code_blob) { +int AOTCodeAddressTable::id_for_address(address addr, RelocIterator reloc, CodeBlob* code_blob, bool assert_for_unknown_address) { assert(_extrs_complete || initializing_extrs, "AOT Code Cache VM runtime addresses table is not complete"); - int id = -1; - if (addr == (address)-1) { // Static call stub has jump to itself - return id; - } // fast path for stubs and external addresses if (_hash_table != nullptr) { - int *result = _hash_table->get(addr); + int* result = _hash_table->get(addr); if (result != nullptr) { - id = *result; + int id = *result; log_trace(aot, codecache)("Address " INTPTR_FORMAT " retrieved from AOT Code Cache address hash table with index '%d'", p2i(addr), id); return id; } } // Seach for C string - id = id_for_C_string(addr); + int id = id_for_C_string(addr); if (id != BAD_ADDRESS_ID) { return id + _c_str_base; } if (StubRoutines::contains(addr) || CodeCache::find_blob(addr) != nullptr) { // Search for a matching stub entry id = search_address(addr, _stubs_addr, _stubs_max); - if (id == BAD_ADDRESS_ID) { + if (id != BAD_ADDRESS_ID) { + return id + _stubs_base; + } else if (assert_for_unknown_address) { +#ifdef ASSERT StubCodeDesc* desc = StubCodeDesc::desc_for(addr); + reloc.print_current_on(tty); + code_blob->print_on(tty); + code_blob->print_code_on(tty); const char* sub_name = (desc != nullptr) ? desc->name() : ""; assert(false, "Address " INTPTR_FORMAT " for Stub:%s is missing in AOT Code Cache addresses table", p2i(addr), sub_name); - } else { - return id + _stubs_base; +#endif } } else { - // Search in runtime functions - id = search_address(addr, _extrs_addr, _extrs_length); - if (id == BAD_ADDRESS_ID) { + // Search in runtime functions. + // Until C1 runtime finish initialization search only addresses + // recorded before it. + uint len = _c1_stubs_complete ? _extrs_length : _extrs2_length; + id = search_address(addr, _extrs_addr, len); + if (id != BAD_ADDRESS_ID) { + return _extrs_base + id; + } else if (assert_for_unknown_address) { +#ifdef ASSERT ResourceMark rm; const int buflen = 1024; char* func_name = NEW_RESOURCE_ARRAY(char, buflen); int offset = 0; if (os::dll_address_to_function_name(addr, func_name, buflen, &offset)) { -#ifdef ASSERT reloc.print_current_on(tty); code_blob->print_on(tty); code_blob->print_code_on(tty); - assert(false, "Address " INTPTR_FORMAT " for runtime target <%s+%d>/('%s') is missing in AOT Code Cache addresses table", p2i(addr), func_name, offset, (const char*)addr); -#endif + assert(false, "Address " INTPTR_FORMAT " for runtime target <%s+%d> is missing in AOT Code Cache addresses table", p2i(addr), func_name, offset); } else { -#ifdef ASSERT reloc.print_current_on(tty); code_blob->print_on(tty); code_blob->print_code_on(tty); os::find(addr, tty); - assert(false, "Address " INTPTR_FORMAT " for /('%s') is missing in AOT Code Cache addresses table", p2i(addr), (const char*)addr); -#endif + assert(false, "Address " INTPTR_FORMAT " for is missing in AOT Code Cache addresses table", p2i(addr)); } - } else { - return _extrs_base + id; +#endif } } + assert(id == BAD_ADDRESS_ID, "id: %d", id); + log_debug(aot, codecache)("Address " INTPTR_FORMAT " is missing in AOT Code Cache addresses table", p2i(addr)); return id; } +#undef _extrs_max +#undef _stubs_max +#undef _extrs_base +#undef _stubs_base +#undef _all_max + AOTRuntimeConstants AOTRuntimeConstants::_aot_runtime_constants; void AOTRuntimeConstants::initialize_from_runtime() { @@ -2521,17 +5136,45 @@ address AOTRuntimeConstants::card_table_base_address() { return (address)&_aot_runtime_constants._card_table_base; } +void AOTCodeEntry::print(outputStream* st) const { + st->print_cr(" AOT Code Cache entry " INTPTR_FORMAT " [kind: %d, id: " UINT32_FORMAT_X_0 ", offset: %d, size: %d, comp_level: %d, comp_id: %d, %s%s%s%s]", + p2i(this), (int)_kind, _id, _offset, _size, _comp_level, _comp_id, + (_not_entrant? "not_entrant" : "entrant"), + (_loaded ? ", loaded" : ""), + (_has_clinit_barriers ? ", has_clinit_barriers" : ""), + (_for_preload ? ", for_preload" : "")); +} + // This is called after initialize() but before init2() -// and _cache is not set yet. +// and _cache is not set yet - use `opened_cache`. void AOTCodeCache::print_on(outputStream* st) { if (opened_cache != nullptr && opened_cache->for_use()) { - st->print_cr("\nAOT Code Cache"); + st->print_cr("\nAOT Code Cache Preload entries"); + + uint preload_count = opened_cache->_load_header->preload_entries_count(); + AOTCodeEntry* preload_entries = (AOTCodeEntry*)opened_cache->addr(opened_cache->_load_header->preload_entries_offset()); + for (uint i = 0; i < preload_count; i++) { + AOTCodeEntry* entry = &preload_entries[i]; + + uint entry_position = entry->offset(); + uint name_offset = entry->name_offset() + entry_position; + const char* saved_name = opened_cache->addr(name_offset); + + st->print_cr("%4u: %10s Id:%u AP%u size=%u '%s' %s%s%s", + i, aot_code_entry_kind_name[entry->kind()], entry->id(), entry->comp_level(), + entry->size(), saved_name, + entry->has_clinit_barriers() ? " has_clinit_barriers" : "", + entry->is_loaded() ? " loaded" : "", + entry->not_entrant() ? " not_entrant" : ""); + } + + st->print_cr("\nAOT Code Cache entries"); + uint count = opened_cache->_load_header->entries_count(); - uint* search_entries = (uint*)opened_cache->addr(opened_cache->_load_header->entries_offset()); // [id, index] - AOTCodeEntry* load_entries = (AOTCodeEntry*)(search_entries + 2 * count); + uint* search_entries = (uint*)opened_cache->addr(opened_cache->_load_header->search_table_offset()); // [id, index] + AOTCodeEntry* load_entries = (AOTCodeEntry*)opened_cache->addr(opened_cache->_load_header->entries_offset()); for (uint i = 0; i < count; i++) { - // Use search_entries[] to order ouput int index = search_entries[2*i + 1]; AOTCodeEntry* entry = &(load_entries[index]); @@ -2539,15 +5182,17 @@ void AOTCodeCache::print_on(outputStream* st) { uint name_offset = entry->name_offset() + entry_position; const char* saved_name = opened_cache->addr(name_offset); - st->print_cr("%4u: %10s idx:%4u Id:%u size=%u '%s'", - i, aot_code_entry_kind_name[entry->kind()], index, entry->id(), entry->size(), saved_name); + st->print_cr("%4u: %10s idx:%4u Id:%u A%u size=%u '%s' %s%s", + i, aot_code_entry_kind_name[entry->kind()], index, entry->id(), entry->comp_level(), + entry->size(), saved_name, + entry->is_loaded() ? " loaded" : "", + entry->not_entrant() ? " not_entrant" : ""); } } } // methods for managing entries in multi-stub blobs - AOTStubData::AOTStubData(BlobId blob_id) : _blob_id(blob_id), _cached_blob(nullptr), @@ -2588,9 +5233,7 @@ AOTStubData::AOTStubData(BlobId blob_id) : bool AOTStubData::load_code_blob() { assert(is_using(), "should not call"); assert(!is_invalid() && _cached_blob == nullptr, "repeated init"); - _cached_blob = AOTCodeCache::load_code_blob(AOTCodeEntry::StubGenBlob, - _blob_id, - this); + _cached_blob = AOTCodeCache::load_multi_stub_blob(_blob_id, this); if (_cached_blob == nullptr) { set_invalid(); return false; @@ -2602,9 +5245,7 @@ bool AOTStubData::load_code_blob() { bool AOTStubData::store_code_blob(CodeBlob& new_blob, CodeBuffer *code_buffer) { assert(is_dumping(), "should not call"); assert(_cached_blob == nullptr, "should not be loading and storing!"); - if (!AOTCodeCache::store_code_blob(new_blob, - AOTCodeEntry::StubGenBlob, - _blob_id, this, code_buffer)) { + if (!AOTCodeCache::store_multi_stub_blob(new_blob, _blob_id, this, code_buffer)) { set_invalid(); return false; } else { @@ -2719,3 +5360,170 @@ void AOTStubData::check_stored(StubId stub_id) { } } #endif + +//============ Statistic ============ + +void AOTCodeCache::print_timers_on(outputStream* st) { + if (is_using_code()) { + st->print_cr (" AOT Code Preload Time: %7.3f s", _t_totalPreload.seconds()); + st->print_cr (" AOT Code Load Time: %7.3f s", _t_totalLoad.seconds()); + st->print_cr (" nmethod register: %7.3f s", _t_totalRegister.seconds()); + st->print_cr (" find AOT code entry: %7.3f s", _t_totalFind.seconds()); + } + if (is_dumping_code()) { + st->print_cr (" AOT Code Store Time: %7.3f s", _t_totalStore.seconds()); + } +} + +void AOTCodeCache::log_stats_on_exit(AOTCodeEntryStats& stats) { + LogStream log_aot(LogLevel::Info, LogTagSetMapping::tagset()); + LogStream log_exit(LogLevel::Info, LogTagSetMapping::tagset()); + LogStream& log = log_aot.is_enabled() ? log_aot : log_exit; + if (log.is_enabled()) { + for (uint kind = AOTCodeEntry::Adapter; kind < AOTCodeEntry::Kind_count; kind++) { + log.print_cr(" %s: total=%u", aot_code_entry_kind_name[kind], stats.entry_count(kind)); + if (kind == AOTCodeEntry::Nmethod) { + for (uint lvl = CompLevel_simple; lvl < AOTCompLevel_count; lvl++) { + log.print(" Tier %d: total=%u", lvl, stats.nmethod_count(lvl)); + if (lvl == AOTCompLevel_count-1) { // AOT Preload + log.print(", has_clinit_barriers=%u", stats.clinit_barriers_count()); + } + log.cr(); + } + } + } + } +} + +struct AOTCodeStats { +private: + struct { + uint _loaded_cnt; + uint _invalidated_cnt; + uint _load_failed_cnt; + } _entry_kinds[AOTCodeEntry::Kind_count], + _nmethods[AOTCompLevel_count]; + + static void check_kind(uint kind) { + AOTCodeEntryStats::check_kind(kind); + } + static void check_complevel(uint lvl) { + AOTCodeEntryStats::check_complevel(lvl); + } + void inc_entry_loaded_cnt(uint kind) { check_kind(kind); _entry_kinds[kind]._loaded_cnt += 1; } + void inc_entry_invalidated_cnt(uint kind) { check_kind(kind); _entry_kinds[kind]._invalidated_cnt += 1; } + void inc_entry_load_failed_cnt(uint kind) { check_kind(kind); _entry_kinds[kind]._load_failed_cnt += 1; } + + void inc_nmethod_loaded_cnt(uint lvl) { check_complevel(lvl); _nmethods[lvl]._loaded_cnt += 1; } + void inc_nmethod_invalidated_cnt(uint lvl) { check_complevel(lvl); _nmethods[lvl]._invalidated_cnt += 1; } + void inc_nmethod_load_failed_cnt(uint lvl) { check_complevel(lvl); _nmethods[lvl]._load_failed_cnt += 1; } + + void inc_loaded_cnt(AOTCodeEntry* entry) { + inc_entry_loaded_cnt(entry->kind()); + if (entry->is_nmethod()) { + entry->for_preload() ? inc_nmethod_loaded_cnt(CompLevel_preload_optimization) + : inc_nmethod_loaded_cnt(entry->comp_level()); + } + } + + void inc_invalidated_cnt(AOTCodeEntry* entry) { + inc_entry_invalidated_cnt(entry->kind()); + if (entry->is_nmethod()) { + entry->for_preload() ? inc_nmethod_invalidated_cnt(CompLevel_preload_optimization) + : inc_nmethod_invalidated_cnt(entry->comp_level()); + } + } + + void inc_load_failed_cnt(AOTCodeEntry* entry) { + inc_entry_load_failed_cnt(entry->kind()); + if (entry->is_nmethod()) { + entry->for_preload() ? inc_nmethod_load_failed_cnt(CompLevel_preload_optimization) + : inc_nmethod_load_failed_cnt(entry->comp_level()); + } + } +public: + AOTCodeStats() { + memset(this, 0, sizeof(AOTCodeStats)); + } + void collect_entry_runtime_stats(AOTCodeEntry* entry) { + if (entry->is_loaded()) { + inc_loaded_cnt(entry); + } + if (entry->not_entrant()) { + inc_invalidated_cnt(entry); + } + if (entry->load_fail()) { + inc_load_failed_cnt(entry); + } + } + + uint entry_loaded_count(uint kind) const { check_kind(kind); return _entry_kinds[kind]._loaded_cnt; } + uint entry_invalidated_count(uint kind) const { check_kind(kind); return _entry_kinds[kind]._invalidated_cnt; } + uint entry_load_failed_count(uint kind) const { check_kind(kind); return _entry_kinds[kind]._load_failed_cnt; } + + uint nmethod_loaded_count(uint lvl) const { check_complevel(lvl); return _nmethods[lvl]._loaded_cnt; } + uint nmethod_invalidated_count(uint lvl) const { check_complevel(lvl); return _nmethods[lvl]._invalidated_cnt; } + uint nmethod_load_failed_count(uint lvl) const { check_complevel(lvl); return _nmethods[lvl]._load_failed_cnt; } +}; + +static void print_helper(outputStream* st, const char* name, int count) { + if (count > 0) { + st->print(" %s=%d", name, count); + } +} + +void AOTCodeCache::print_statistics_on(outputStream* st) { + AOTCodeCache* cache = open_for_use(); + if (cache != nullptr) { + AOTCodeEntryStats estats; + AOTCodeStats cstats; + + uint preload_count = cache->_load_header->preload_entries_count(); + uint count = cache->_load_header->entries_count(); + if ((preload_count + count) == 0) { + return; + } + AOTCodeEntry* preload_entries = (AOTCodeEntry*)cache->addr(cache->_load_header->preload_entries_offset()); + for (uint i = 0; i < preload_count; i++) { + AOTCodeEntry* entry = &preload_entries[i]; + estats.collect_entry_stats(entry); + cstats.collect_entry_runtime_stats(entry); + + } + + AOTCodeEntry* load_entries = (AOTCodeEntry*)cache->addr(cache->_load_header->entries_offset()); + for (uint i = 0; i < count; i++) { + AOTCodeEntry* entry = &load_entries[i]; + estats.collect_entry_stats(entry); + cstats.collect_entry_runtime_stats(entry); + } + + st->print_cr("AOT Code Cache: "); + + for (uint kind = AOTCodeEntry::Adapter; kind < AOTCodeEntry::Kind_count; kind++) { + if (estats.entry_count(kind) > 0) { + st->print(" %s:", aot_code_entry_kind_name[kind]); + print_helper(st, "total", estats.entry_count(kind)); + print_helper(st, "loaded", cstats.entry_loaded_count(kind)); + print_helper(st, "invalidated", cstats.entry_invalidated_count(kind)); + print_helper(st, "failed", cstats.entry_load_failed_count(kind)); + st->cr(); + } + if (kind == AOTCodeEntry::Nmethod) { + for (uint lvl = CompLevel_simple; lvl < AOTCompLevel_count; lvl++) { + if (estats.nmethod_count(lvl) > 0) { + st->print(" AOT Code T%d", lvl); + print_helper(st, "total", estats.nmethod_count(lvl)); + print_helper(st, "loaded", cstats.nmethod_loaded_count(lvl)); + print_helper(st, "invalidated", cstats.nmethod_invalidated_count(lvl)); + print_helper(st, "failed", cstats.nmethod_load_failed_count(lvl)); + if (lvl == AOTCompLevel_count-1) { + print_helper(st, "has_clinit_barriers", estats.clinit_barriers_count()); + } + st->cr(); + } + } + } + } + } +} diff --git a/src/hotspot/share/code/aotCodeCache.hpp b/src/hotspot/share/code/aotCodeCache.hpp index 4cfa0064ff0..6fe864f1f71 100644 --- a/src/hotspot/share/code/aotCodeCache.hpp +++ b/src/hotspot/share/code/aotCodeCache.hpp @@ -26,29 +26,45 @@ #define SHARE_CODE_AOTCODECACHE_HPP #include "gc/shared/collectedHeap.hpp" -#include "gc/shared/gc_globals.hpp" +#include "memory/allocation.hpp" +#include "nmt/memTag.hpp" +#include "oops/oopsHierarchy.hpp" #include "runtime/stubInfo.hpp" +#include "runtime/vm_version.hpp" +#include "utilities/exceptions.hpp" #include "utilities/hashTable.hpp" /* - * AOT Code Cache collects code from Code Cache and corresponding metadata - * during application training run. - * In following "production" runs this code and data can be loaded into - * Code Cache skipping its generation. + * AOTCodeCache compiles AOT code during "assembly" phase by using metadata + * (classes and profiling) collected during application training run. + * Additionally special AOT "preload" code is generated with class initialization + * barriers which can be called on first Java method invocation. + * Compiled AOT code is collected and stored in AOT cache. + * In following "production" runs this code and data can be loaded from + * AOT cache skipping execution in the Interpreter and JIT compilation + * for corresponding hot methods. */ -class CodeBuffer; -class RelocIterator; +class AbstractCompiler; class AOTCodeCache; -class AOTCodeReader; -class AdapterBlob; -class ExceptionBlob; -class ImmutableOopMapSet; class AsmRemarks; +class ciEnv; +class ciMethod; +class CodeBlob; +class CompileTask; class DbgStrings; - -enum class vmIntrinsicID : int; -enum CompLevel : signed char; +template +class GrowableArray; +class ImmutableOopMapSet; +class JavaThread; +class Klass; +class methodHandle; +class Metadata; +class Method; +class nmethod; +class OopRecorder; +class outputStream; +class RelocIterator; #define DO_AOTCODEENTRY_KIND(Fn) \ Fn(None) \ @@ -57,9 +73,28 @@ enum CompLevel : signed char; Fn(C1Blob) \ Fn(C2Blob) \ Fn(StubGenBlob) \ + Fn(Nmethod) \ // Descriptor of AOT Code Cache's entry +// +// These entries live in an array at the beginning of the "ac" region of the AOT cache. +// State bits are mutated in place (monotonically) to manage the AOT code asset. +// Main states are stored only, loaded, loaded & not_entrant, loaded & unloaded nmethod. +// +// For an nmethod, the id field is a narrow Method pointer. +// For an adapter or blob, it is an adapter ID or BlobId. +// Many fields are used only for nmethods; see comments per declaration. +// +// The nmethod is not directly pointed at from here; we indirect through Method::code, +// which adds some uncertainty, since code is subject to concurrent update. +// +// Most of the ac region is read-only (never copied on write), but the AOTCodeEntry +// blocks are modified. An alternative could be to move all remaining mutable ac data +// into unmapped per-process data, indexed in parallel to the AOTCodeEntry blocks. +// That does not seem to pay off, although it may if large pages ever store the ac bits. +// The current organization is simpler, given small pages. class AOTCodeEntry { + friend class VMStructs; public: enum Kind : s1 { #define DECL_KIND_ENUM(kind) kind, @@ -69,40 +104,69 @@ class AOTCodeEntry { }; private: - AOTCodeEntry* _next; - Kind _kind; - uint _id; // Adapter's id, vmIntrinsic::ID for stub or name's hash for nmethod + Kind _kind; + // Next field is exposed to external profilers - keep it as boolean. + bool _for_preload; // "preload" nmethod - runs before classes initialized + bool _not_entrant; // Deoptimized + bool _verified; // VerifyAOTCode + uint8_t _has_clinit_barriers:1, // Generated code has class init checks (only in for_preload code) + _has_oop_maps:1, + _has_vectors:1, // nmethod uses Vector API + _loaded:1, // Code was loaded for use + _load_fail:1; // Failed to load due to some klass state + // Note: Both _loaded and _load_fail are written only by a compiler thread which owns + // this AOTCodeEntry. The other bitfields are constants (set at AOT dump). Still, + // consider refactoring this loose cluster of booleans. + + uint _id; // Adapter's id, BlobId for stub or Method's offset in AOTCache for nmethod uint _offset; // Offset to entry uint _size; // Entry size - uint _name_offset; // Code blob name + uint _name_offset; // Compiled method name, adapter name, StubInfo::name() uint _name_size; - uint _blob_offset; // Start of code in cache - bool _has_oop_maps; - address _dumptime_content_start_addr; // CodeBlob::content_begin() at dump time; used for applying relocations + uint _code_offset; // Start of code in cache (zero for blobs) + uint _comp_level; // compilation level, nmethod only + uint _comp_id; // compilation id, nmethod only + uint _num_inlined_bytecodes; // nmethod only + uint _inline_instructions_size; // size from training run, nmethod only public: AOTCodeEntry(Kind kind, uint id, uint offset, uint size, uint name_offset, uint name_size, - uint blob_offset, bool has_oop_maps, - address dumptime_content_start_addr) { - _next = nullptr; + uint code_offset, bool has_oop_maps, + uint comp_level = 0, + uint comp_id = 0, + bool has_clinit_barriers = false, + bool for_preload = false) { _kind = kind; + + _for_preload = for_preload; + _has_clinit_barriers = has_clinit_barriers; + _has_oop_maps = has_oop_maps; + _has_vectors = false; // will be set later + _loaded = false; + _load_fail = false; + _not_entrant = false; + _verified = false; + _id = id; _offset = offset; _size = size; _name_offset = name_offset; _name_size = name_size; - _blob_offset = blob_offset; - _has_oop_maps = has_oop_maps; - _dumptime_content_start_addr = dumptime_content_start_addr; + _code_offset = code_offset; + + _comp_level = comp_level; + _comp_id = comp_id; + _num_inlined_bytecodes = 0; + _inline_instructions_size = 0; } + void* operator new(size_t x, AOTCodeCache* cache); // Delete is a NOP void operator delete( void *ptr ) {} - AOTCodeEntry* next() const { return _next; } - void set_next(AOTCodeEntry* next) { _next = next; } + Method* method() const; Kind kind() const { return _kind; } uint id() const { return _id; } @@ -113,15 +177,46 @@ class AOTCodeEntry { uint size() const { return _size; } uint name_offset() const { return _name_offset; } uint name_size() const { return _name_size; } - uint blob_offset() const { return _blob_offset; } + uint code_offset() const { return _code_offset; } + bool has_oop_maps() const { return _has_oop_maps; } - address dumptime_content_start_addr() const { return _dumptime_content_start_addr; } + + bool has_vectors() const { return _has_vectors; } + void set_has_vectors(bool val) { _has_vectors = val; } + + uint num_inlined_bytecodes() const { return _num_inlined_bytecodes; } + void set_inlined_bytecodes(int bytes) { _num_inlined_bytecodes = bytes; } + + uint inline_instructions_size() const { return _inline_instructions_size; } + void set_inline_instructions_size(int size) { _inline_instructions_size = size; } + + uint comp_level() const { return _comp_level; } + uint comp_id() const { return _comp_id; } + + bool has_clinit_barriers() const { return _has_clinit_barriers; } + bool for_preload() const { return _for_preload; } + bool is_loaded() const { return _loaded; } + void set_loaded() { _loaded = true; } + + bool is_verified() const; + void set_verified(); + + // Use atomic access to _not_entrant field. + bool not_entrant() const; + void set_not_entrant(); + bool try_set_not_entrant(); + + bool load_fail() const { return _load_fail; } + void set_load_fail() { _load_fail = true; } + + void print(outputStream* st) const NOT_CDS_RETURN; static bool is_valid_entry_kind(Kind kind) { return kind > None && kind < Kind_count; } static bool is_blob(Kind kind) { return kind == SharedBlob || kind == C1Blob || kind == C2Blob || kind == StubGenBlob; } static bool is_single_stub_blob(Kind kind) { return kind == SharedBlob || kind == C1Blob || kind == C2Blob; } static bool is_multi_stub_blob(Kind kind) { return kind == StubGenBlob; } static bool is_adapter(Kind kind) { return kind == Adapter; } + bool is_nmethod() const { return _kind == Nmethod; } }; // we use a hash table to speed up translation of external addresses @@ -134,37 +229,75 @@ class AOTCodeAddressHashTable : public HashTable< AnyObj::C_HEAP, mtCode> {}; -// Addresses of stubs, blobs and runtime finctions called from compiled code. +// Addresses of stubs, blobs and runtime functions called from compiled code. +// There is a separate naming/indexing scheme for each. +// +// - The extrs are native C addresses used by code (via relocInfo::external_word). +// They are keyed by their insertion position, according to the order of calls to +// ADD_EXTERNAL_ADDRESS. The indexes must agree across the processes sharing the +// AOT cache. The JVM currently checks only the lengths, to detect missing or +// extra calls to ADD_EXTERNAL_ADDRESS in the APH or PR, perhaps due to flag +// mismatches, which should have been caught earlier. Consider making an enum +// for these guys, like the stubs have. +// +// - The stubs are well-known runtime support subroutines used by compiled code. +// They are keyed by an enum EntryId, and populated by publish_stub_entries. They are +// not C addresses but rather pointers to JVM-assembled code. Some stubs are +// found in the AOT cache, but they are always OK to regenerate. Some stubs are +// optional, in which case their array slots could be empty (null), and then you +// get a fatal crash if you try to refer to one. Consistency of stub presence/absence +// and stub (code) format and content between assembly and production runs should be +// guaranteed by checks which enforce compatible command line compiler and runtime +// configuration options across those runs. Missing checks or incorrect assumptions +// regarding compatibility may also lead to a crash. +// +// - C strings are addresses of strings that are used in compiled code. They are +// addressed by content. That is, if you need a string, spell it out, and either +// a pre-existing string of the same spelling will be found for you, or a new +// string will be added. They are sequentially assigned id numbers, one per +// unique string, in the order of request. +// class AOTCodeAddressTable : public CHeapObj { private: - address* _extrs_addr; - address* _stubs_addr; + AOTCodeAddressHashTable* _hash_table; + + address* _extrs_addr; // array sequenced by dynamic ADD_EXTERNAL_ADDRESS calls + address* _stubs_addr; // array indexed by statically defined enum EntryId uint _extrs_length; + uint _extrs2_length; // recorded in init_extrs2() + uint _final_extrs_length; // expected final count + //GrowableArray _C_strings; // array searched by content, indexed by int id + // _C_strings is a C static pointer, not a field; see init_C_strings_caching. bool _extrs_complete; bool _shared_stubs_complete; bool _c1_stubs_complete; bool _c2_stubs_complete; bool _stubgen_stubs_complete; - AOTCodeAddressHashTable* _hash_table; void hash_address(address addr, int idx); public: AOTCodeAddressTable() : + _hash_table(nullptr), _extrs_addr(nullptr), _stubs_addr(nullptr), _extrs_length(0), + _extrs2_length(0), + _final_extrs_length(0), _extrs_complete(false), _shared_stubs_complete(false), _c1_stubs_complete(false), _c2_stubs_complete(false), - _stubgen_stubs_complete(false), - _hash_table(nullptr) + _stubgen_stubs_complete(false) { } void init_extrs(); void init_extrs2(); + uint extrs_length() const { return _extrs_length; } + uint extrs2_length() const { return _extrs2_length; } + uint final_extrs_length() const { return _final_extrs_length;} void add_stub_entry(EntryId entry_id, address entry); void add_external_addresses(GrowableArray
& addresses) NOT_CDS_RETURN; + uint add_c1_extrs_blobs(bool count_only); void set_shared_stubs_complete(); void set_c1_stubs_complete(); void set_c2_stubs_complete(); @@ -172,7 +305,7 @@ class AOTCodeAddressTable : public CHeapObj { const char* add_C_string(const char* str); int id_for_C_string(address str); address address_for_C_string(int idx); - int id_for_address(address addr, RelocIterator iter, CodeBlob* code_blob); + int id_for_address(address addr, RelocIterator iter, CodeBlob* code_blob, bool assert_for_unknown_address); address address_for_id(int id); }; @@ -218,7 +351,7 @@ class AOTStubData : public StackObj { // Array of addresses owned by stubs. Each stub appends addresses to // this array as a block, whether at the end of generation or at the // end of restoration from the cache. The first two addresses in - // each block are the "start" and "end2 address of the stub. Any + // each block are the "start" and "end" address of the stub. Any // other visible addresses located within the range [start,end) // follow, either extra entries, data addresses or SEGV-protected // subrange start, end and handler addresses. In the special case @@ -279,10 +412,20 @@ class AOTStubData : public StackObj { do_var(int, AllocatePrefetchLines) /* stubs and nmethods */ \ do_var(int, AllocatePrefetchStepSize) /* stubs and nmethods */ \ do_var(uint, CodeEntryAlignment) /* array copy stubs and nmethods */ \ - do_var(bool, UseCompressedOops) /* stubs and nmethods */ \ do_var(bool, EnableContended) /* nmethods */ \ do_var(intx, OptoLoopAlignment) /* array copy stubs and nmethods */ \ do_var(bool, RestrictContended) /* nmethods */ \ + do_var(int, ContendedPaddingWidth) \ + do_var(uint, GCCardSizeInBytes) \ + do_var(bool, AlwaysSafeConstructors) \ + do_var(bool, UseCondCardMark) \ + do_var(bool, DoJVMTIVirtualThreadTransitions) \ + do_var(bool, PreserveFramePointer) \ + do_var(intx, StackReservedPages) \ + do_var(intx, StackShadowPages) \ + do_var(bool, UseTLAB) \ + do_var(bool, ZeroTLAB) \ + do_var(bool, UseAdler32Intrinsics) \ do_var(bool, UseAESCTRIntrinsics) \ do_var(bool, UseAESIntrinsics) \ do_var(bool, UseBASE64Intrinsics) \ @@ -301,16 +444,42 @@ class AOTStubData : public StackObj { do_var(bool, UseSHA3Intrinsics) \ do_var(bool, UseSHA512Intrinsics) \ do_var(bool, UseIntPolyIntrinsics) \ + do_var(bool, UseVectorizedHashCodeIntrinsic) \ do_var(bool, UseVectorizedMismatchIntrinsic) \ - do_var(bool, ValueTypeReturnedAsFields) \ do_var(bool, VMContinuations) \ do_var(bool, VerifyOops) \ + do_var(bool, CountCompiledCalls) \ + do_var(bool, DTraceMethodProbes) \ + do_var(bool, DTraceAllocProbes) \ do_fun(int, CompressedKlassPointers_shift, CompressedKlassPointers::shift()) \ do_fun(bool, JavaAssertions_systemClassDefault, JavaAssertions::systemClassDefault()) \ do_fun(bool, JavaAssertions_userClassDefault, JavaAssertions::userClassDefault()) \ + do_fun(bool, COOP_use_implicit_null_checks, CompressedOops::use_implicit_null_checks()) \ + do_fun(size_t, os_vm_page_size, os::vm_page_size()) \ do_fun(CollectedHeap::Name, Universe_heap_kind, Universe::heap()->kind()) \ // END +#if INCLUDE_ZGC +#define AOTCODECACHE_CONFIGS_ZGC_DO(do_var, do_fun) \ + do_fun(uintptr_t, ZAddressHeapBaseShift_for_VerifyOops, (UseZGC && VerifyOops) ? ZAddressHeapBaseShift : 0) \ + // END +#else +#define AOTCODECACHE_CONFIGS_ZGC_DO(do_var, do_fun) +#endif + +#if INCLUDE_SHENANDOAHGC +#define AOTCODECACHE_CONFIGS_SHENANDOAHGC_DO(do_var, do_fun) \ + do_var(bool, ExplicitGCInvokesConcurrent) \ + do_var(bool, ShenandoahImplicitGCInvokesConcurrent) \ + do_var(bool, ShenandoahLoadRefBarrier) \ + do_var(bool, ShenandoahSATBBarrier) \ + do_var(bool, ShenandoahCloneBarrier) \ + do_var(bool, ShenandoahCardBarrier) \ + // END +#else +#define AOTCODECACHE_CONFIGS_SHENANDOAHGC_DO(do_var, do_fun) +#endif + #ifdef COMPILER2 #define AOTCODECACHE_CONFIGS_COMPILER2_DO(do_var, do_fun) \ do_var(intx, ArrayOperationPartialInlineSize) /* array copy stubs and nmethods */ \ @@ -320,6 +489,8 @@ class AOTStubData : public StackObj { do_var(bool, UseMulAddIntrinsic) \ do_var(bool, UseMultiplyToLenIntrinsic) \ do_var(bool, UseSquareToLenIntrinsic) \ + do_var(bool, StackTraceInThrowable) \ + do_var(bool, OmitStackTraceInFastThrow) \ // END #else #define AOTCODECACHE_CONFIGS_COMPILER2_DO(do_var, do_fun) @@ -331,12 +502,16 @@ class AOTStubData : public StackObj { do_var(intx, PrefetchCopyIntervalInBytes) /* array copy stubs */ \ do_var(int, SoftwarePrefetchHintDistance) /* array fill stubs */ \ do_var(bool, UseBlockZeroing) \ + do_var(bool, UseCryptoPmullForCRC32) \ do_var(bool, UseSecondarySupersCache) \ do_var(bool, UseSIMDForArrayEquals) /* array copy stubs and nmethods */ \ do_var(bool, UseSIMDForBigIntegerShiftIntrinsics) \ do_var(bool, UseSIMDForMemoryOps) /* array copy stubs and nmethods */ \ do_var(bool, UseSIMDForSHA3Intrinsic) /* SHA3 stubs */ \ do_var(bool, UseSimpleArrayEquals) \ + do_fun(int, VM_zva_length, UseBlockZeroing ? VM_Version::zva_length() : 0) \ + do_fun(int, VM_dcache_line_size, VM_Version::dcache_line_size()) \ + do_fun(bool, VM_use_rop_protection, VM_Version::use_rop_protection()) \ // END #else #define AOTCODECACHE_CONFIGS_AARCH64_DO(do_var, do_fun) @@ -345,8 +520,12 @@ class AOTStubData : public StackObj { #if defined(X86) && !defined(ZERO) #define AOTCODECACHE_CONFIGS_X86_DO(do_var, do_fun) \ do_var(int, AVX3Threshold) /* array copy stubs and nmethods */ \ + do_var(int, CopyAVX3Threshold) \ do_var(bool, EnableX86ECoreOpts) /* nmethods */ \ + do_var(bool, UseCountTrailingZerosInstruction) \ do_var(bool, UseLibmIntrinsic) \ + do_var(bool, UseSSE42Intrinsics) \ + do_var(bool, CheckJNICalls) \ // END #else #define AOTCODECACHE_CONFIGS_X86_DO(do_var, do_fun) @@ -360,6 +539,7 @@ class AOTStubData : public StackObj { do_var(bool, UseCtxFencei) /* method entry barrier stub */ \ do_var(bool, UseSecondarySupersCache) /* secondary supers cache in nmethods */ \ do_var(bool, UseZabha) /* narrow cmpxchg selection in nmethods */ \ + do_var(bool, UsePopCountInstruction) /* other platforms record it in CPU features */ \ do_fun(int, SatpMode, (int)VM_Version::satp_mode.value()) \ do_fun(int, RVZicbozBlockSize, (int)VM_Version::zicboz_block_size.value()) \ // END @@ -369,6 +549,8 @@ class AOTStubData : public StackObj { #define AOTCODECACHE_CONFIGS_DO(do_var, do_fun) \ AOTCODECACHE_CONFIGS_GENERIC_DO(do_var, do_fun) \ + AOTCODECACHE_CONFIGS_ZGC_DO(do_var, do_fun) \ + AOTCODECACHE_CONFIGS_SHENANDOAHGC_DO(do_var, do_fun) \ AOTCODECACHE_CONFIGS_COMPILER2_DO(do_var, do_fun) \ AOTCODECACHE_CONFIGS_AARCH64_DO(do_var, do_fun) \ AOTCODECACHE_CONFIGS_X86_DO(do_var, do_fun) \ @@ -378,7 +560,98 @@ class AOTStubData : public StackObj { #define AOTCODECACHE_DECLARE_VAR(type, name) type _saved_ ## name; #define AOTCODECACHE_DECLARE_FUN(type, name, func) type _saved_ ## name; +enum class DataKind: int { + No_Data = -1, + Null = 0, + Klass = 1, + Method = 2, + String = 3, + MH_Oop = 4, + Primitive = 5, // primitive Class object + SysLoader = 6, // java_system_loader + PlaLoader = 7, // java_platform_loader + MethodCnts= 8 +}; + +struct AOTCodeEntryStats; + +// The AOT code cache lives in two places: First, an AOTCache file that contains code +// also has a memory-mappable "ac region", which in turn contains mappable C data +// structures. Second, a per-process, heap-allocated AOTCodeCache contains all the +// pointers and metadata required to properly read or write the mapped ac region. +// The AOTCodeCache object is always only using or dumping the region, never both. +// +// The region layout is described in detail below, near the field declarations. +// Most of the region is used read-only, but AOTCodeEntry state flags are updated. +// The region has very few relocatable pointers (unlike other AOT cache regions). +// Barring AOT cache load failure, the whole AOT cache lives forever (in the process). +// This makes the ac region a good place to keep immutable data that might be needed +// in the future, such as code, metadata, strings, and tables. +// +// Each code asset is described by an AOTCodeEntry block, with an offset to more data. +// That data is a pre-image of an AOT-compiled nmethod, not directly executable. +// It can be quickly copied into the JVM code heap and patched to make it executable. +// Some immutable, position-independent parts of the nmethod are kept in the ac region. +// These parts do not need to be copied. They include scopes, dependencies, PC tables. +// +// The CDS function FileMapHeader::validate validates the AOTCache as a whole. +// After the AOTCodeCache is mapped, additional code-specific validation is done: +// - the cpu_features array is checked against the JVM's VM_Version +// - the Header::config checks code-sensitive flags (AOTCodeCache::Config::verify) +// - each entry lookup/load also checks flags (verify_c2_state, is_compilation_valid) +// +// Compiled code in the JVM depends on several environmental assumptions. +// - clinit (state, order) - this is guarded in code (AP4) or before load (A1/A2/A4) +// - constants (stable/final) - the JIT can just read and trust; AOT must always load +// - native addresses/data - link via relocInfo; AOT sometimes needs a GOT indirection +// - machine features - the JIT can just assume; AOT refuses to load mismatched code +// +// AOT and JIT use many of the same relocInfo features. Some constants have to be +// AOT-compiled using extra indirection through a GOT-like table (AOTRuntimeConstants). +// While the JIT often compiles native constants straight into code, the AOT compiler +// needs to perform link-editing when installing code, for addresses which could not be +// determined when the AOT cache was dumped. The AOTCodeAddressTable provides the +// mappings required when link-editing the installed AOT code. One subtle disadvantage +// of AOT is that the size of the JVM code heap, and its placement relative to native C +// addresses, are hard to predict, so AOT code must prepare its call sites and external +// data references to handle the longest possible addressing distances. A JIT can +// sometimes predict that a short call will be just fine, and avoid the overhead of a +// long call instruction sequence. +// +// In light of how JIT and AOT code work, here is the value of joining them together. +// +// Because the JIT always compiles with full knowledge of the present machine, and the +// most recent application code (classes) and behavior (clinit states, profiles), the +// JIT can usually produce better, faster code than an AOT compiler that must guess at +// future conditions on the same machine. This is why AOT code does not replace JIT +// code. Rather it allows the JIT to focus on the most important optimization tasks, +// while less performance sensitive parts of the application, which used to run in the +// interpreter or lower tiers, can run faster in high-tier AOT modes (AP4 and A4). +// +// Because AP4 executes clinit barriers like the interpreter, it can serve as a faster +// alternative to the interpreter. This is why AP4 methods are loaded very eagerly at +// startup. (Note that they are in their own array, ready to load ASAP.) However, as +// soon as all the clinits required by an A4 method are done, the JVM prefers it to the +// AP4 method, since performing clinit barriers is expensive, even in optimized code. +// So the AP4 method is a win, but only until the corresponding A4 method is done +// waiting for the clinits it depends on. (See the declaration of CompileTrainingData +// for a full discussion of how that waiting is managed.) +// +// After the A4 method is swapped in, there is still a mild performance penalty due to +// constants being loaded (instead of folded into other constants, which is normal +// business for the JIT). To enable replacement of A4 methods by faster code, each A4 +// method has an invocation counter (like a T2/T3 method). When that invocation counter +// reaches a level similar to what was seen in the training run (before the training +// run JIT compiled a T4 method), the JVM launches a JIT task to recompile the A4 +// method with a fresh T4 method. When that JIT task is done, calls to the T4 method +// run at full speed. +// +// Because there was less reliance on the interpreter during startup, and because the +// JIT didn't have to work so hard to speed startup (since AOT code filled the gap), +// the JIT can get right to work on the methods that need the most attention. +// class AOTCodeCache : public CHeapObj { + friend class AOTMapLogger; // Classes used to describe AOT code cache. protected: @@ -387,7 +660,9 @@ class AOTCodeCache : public CHeapObj { // Special configs that cannot be checked with macros address _compressedOopBase; - int _compressedOopShift; + int _compressedOopShift; + address _compressedKlassBase; + size_t _codeCacheSize; #if defined(X86) && !defined(ZERO) bool _useUnalignedLoadStores; @@ -397,75 +672,139 @@ class AOTCodeCache : public CHeapObj { bool _avoidUnalignedAccesses; #endif - uint _cpu_features_offset; // offset in the cache where cpu features are stored +#ifdef COMPILER2 + bool _reduceInitialCardMarks; + uint _c2_ea_state; +#endif + + uint _jvmti_state; + public: - void record(uint cpu_features_offset); - bool verify_cpu_features(AOTCodeCache* cache) const; - bool verify(AOTCodeCache* cache) const; + void record(); + bool verify() const; + +#ifdef COMPILER2 + bool reduce_initial_cm() const { return _reduceInitialCardMarks; } + uint c2_ea_state() const { return _c2_ea_state; } +#endif + uint jvmti_state() const { return _jvmti_state; } }; + // Header of the "ac" region within the AOT cache. class Header : public CHeapObj { private: + // Here should be version and other verification fields enum { - AOT_CODE_VERSION = 1 + AOT_CODE_VERSION = 2 // Bump this number in releases where Header layout changes }; uint _version; // AOT code version (should match when reading code cache) uint _cache_size; // cache size in bytes - uint _strings_count; // number of recorded C strings - uint _strings_offset; // offset to recorded C strings + uint _cpu_features_size; + uint _cpu_features_offset; // offset in the cache where cpu features are stored + uint _preload_entries_count; // entries for pre-loading code + uint _preload_entries_offset; uint _entries_count; // number of recorded entries uint _entries_offset; // offset of AOTCodeEntry array describing entries + uint _search_table_offset; // offset of table for looking up an AOTCodeEntry + uint _strings_count; // number of recorded C strings + uint _strings_offset; // offset to recorded C strings uint _adapters_count; uint _shared_blobs_count; uint _stubgen_blobs_count; uint _C1_blobs_count; uint _C2_blobs_count; + uint _extrs2_length; // recorded in init_extrs2() + uint _final_extrs_length; // final external addresses count + Config _config; // must be the last element as there is trailing data stored immediately after Config + bool contains(uint offset, uint size) const { + return offset <= _cache_size && size <= (_cache_size - offset); + } + public: void init(uint cache_size, - uint strings_count, uint strings_offset, - uint entries_count, uint entries_offset, - uint adapters_count, uint shared_blobs_count, - uint stubgen_blobs_count, uint C1_blobs_count, - uint C2_blobs_count, uint cpu_features_offset) { - _version = AOT_CODE_VERSION; - _cache_size = cache_size; - _strings_count = strings_count; - _strings_offset = strings_offset; - _entries_count = entries_count; - _entries_offset = entries_offset; - _adapters_count = adapters_count; - _shared_blobs_count = shared_blobs_count; - _stubgen_blobs_count = stubgen_blobs_count; - _C1_blobs_count = C1_blobs_count; - _C2_blobs_count = C2_blobs_count; - _config.record(cpu_features_offset); + uint cpu_features_size, uint cpu_features_offset, + uint preload_entries_count, uint preload_entries_offset, + uint entries_count, uint entries_offset, + uint search_table_offset, + uint strings_count, uint strings_offset, + uint adapters_count, uint shared_blobs_count, + uint stubgen_blobs_count, + uint C1_blobs_count, uint C2_blobs_count, + uint extrs2_length, uint final_extrs_length) { + _version = AOT_CODE_VERSION; + _cache_size = cache_size; + _cpu_features_size = cpu_features_size; + _cpu_features_offset = cpu_features_offset; + _preload_entries_count = preload_entries_count; + _preload_entries_offset = preload_entries_offset; + _entries_count = entries_count; + _entries_offset = entries_offset; + _search_table_offset = search_table_offset; + _strings_count = strings_count; + _strings_offset = strings_offset; + _adapters_count = adapters_count; + _shared_blobs_count = shared_blobs_count; + _stubgen_blobs_count = stubgen_blobs_count; + _C1_blobs_count = C1_blobs_count; + _C2_blobs_count = C2_blobs_count; + _extrs2_length = extrs2_length; + _final_extrs_length = final_extrs_length; + + _config.record(); } - uint cache_size() const { return _cache_size; } - uint strings_count() const { return _strings_count; } - uint strings_offset() const { return _strings_offset; } + uint cpu_features_size() const { return _cpu_features_size; } + uint cpu_features_offset() const { return _cpu_features_offset; } + uint preload_entries_count() const { return _preload_entries_count; } + uint preload_entries_offset() const { return _preload_entries_offset; } uint entries_count() const { return _entries_count; } uint entries_offset() const { return _entries_offset; } + uint strings_count() const { return _strings_count; } + uint strings_offset() const { return _strings_offset; } + uint search_table_offset() const { return _search_table_offset; } uint adapters_count() const { return _adapters_count; } uint stubgen_blobs_count() const { return _stubgen_blobs_count; } uint shared_blobs_count() const { return _shared_blobs_count; } uint C1_blobs_count() const { return _C1_blobs_count; } uint C2_blobs_count() const { return _C2_blobs_count; } - + uint extrs2_length() const { return _extrs2_length; } + uint final_extrs_length() const { return _final_extrs_length; } + uint nmethods_count() const { return _preload_entries_count + + _entries_count + - _stubgen_blobs_count + - _shared_blobs_count + - _C1_blobs_count + - _C2_blobs_count + - _adapters_count; } +#ifdef COMPILER2 + bool verify_c2_state() const; +#endif + bool verify_jvmti_state(ciEnv* env) const; + bool verify_cpu_features(AOTCodeCache* cache) const; bool verify(uint load_size) const; + size_t verify_section(uint offset, + uint count, + size_t unit_size, + size_t low_limit, + const char* section_name) const; bool verify_config(AOTCodeCache* cache) const { // Called after Universe initialized - return _config.verify(cache); + // check CPU features before checking flags that may be + // auto-configured in response to them + if (!verify_cpu_features(cache)) { + return false; + } + return _config.verify(); } }; // Continue with AOTCodeCache class definition. private: Header* _load_header; - char* _load_buffer; // Aligned buffer for loading cached code - char* _store_buffer; // Aligned buffer for storing cached code + char* _load_buffer; // Aligned buffer for loading AOT code + char* _store_buffer; // Aligned buffer for storing AOT code char* _C_store_buffer; // Original unaligned buffer uint _write_position; // Position in _store_buffer @@ -478,10 +817,62 @@ class AOTCodeCache : public CHeapObj { AOTCodeAddressTable* _table; - AOTCodeEntry* _load_entries; // Used when reading cache - uint* _search_entries; // sorted by ID table [id, index] + // What's in the AOT cache "ac" region? (It's what this object indexes.) + // + // The first read is of a CDS-style size/pointer handle to the ac region. + // CachedCodeDirectory { uint ac_size; char* ac_data; } + // After this, the @N values are relative to ac_data. + // It might be right after the CachedCodeDirectory. It might be far away. + // + // @0: Header (all the fixed fields that size and place objects in the ac region. + // See AOTCodeCache::Header::verify for a detailed walk through what follows. + // + // @cpu_features_offset: uchar[cpu_features_size] same layout as VM_Version + // + // @preload_entries_offset: AOTCodeEntry[preload_entries_count] "preload" AP4 code + // Sorted by compile_id (from the training run). + // Queued for load at startup, in that order (see preload_aot_code). + // See AOTCodePreload{Start,Stop} if bisection debugging needed. + // + // @(preload_entries[0]._offset): nmethod preimage for preload entry #0 + // @(preload_entries[1]._offset): nmethod preimage for preload entry #1 + // ... + // + // @entries_offset: AOTCodeEntry[entries_count] other code (A1/A2/A4/stub/adapter) + // Main (non-preload) code array, presented in dump order (arbitrary). + // + // @(entries[0]._offset): preimage for main entry #0 (nmethod, blob, etc.) + // @(entries[1]._offset): preimage for main entry #1 + // ... + // + // @search_table_offset: uint[entries_count][2] id/index pairs, sorted by id + // The id matches AOTCodeEntry::_id. The index point into the entries array. + // If several entries for one id, use local linear search. + // + // @strings_offset: uint[strings_count] length of each C string, in recording order + // @strings_offset+4*strings_count: each string, in recording order + // This is not indexed; it is linearly processed at startup into _cached_C_strings. + // The AOTCodeAddressTable provides access to them. + // + // _table: AOTCodeAddressTable (in C heap) + // This table contains all the extrs, stubs, C strings, organized in heap arrays. + // + // compiled code also refers to oops and metadata, not accessed here. + // They are stored in other parts of the AOT cache, found via AOTCacheAccess. + + // The following items are used for reading and writing the AOT cache. + AOTCodeEntry* _load_entries; // Used when reading cache @entries_offset + uint* _search_entries; // sorted by ID table [id, index] @search_table_offset AOTCodeEntry* _store_entries; // Used when writing cache - uint _store_entries_cnt; + uint _store_entries_cnt; // total entries count + + // These guys can stash data to help log messages in the APH. Move to the writer obj? + uint _compile_id; + uint _comp_level; + uint compile_id() const { return _compile_id; } + uint comp_level() const { return _comp_level; } + + ReservedSpace _reserved_space; // Reserved space to map AOT code cache static AOTCodeCache* open_for_use(); static AOTCodeCache* open_for_dump(); @@ -491,7 +882,12 @@ class AOTCodeCache : public CHeapObj { bool align_write_int(); bool align_write_bytes(uint alignment); address reserve_bytes(uint nbytes); - uint write_bytes(const void* buffer, uint nbytes); + bool write_bytes(const void* buffer, uint nbytes); + bool write_int(const int val); + bool write_kind(DataKind kind) { + return write_int(static_cast(kind)); + } + Header* load_header() const { return _load_header; } const char* addr(uint offset) const { return _load_buffer + offset; } static AOTCodeAddressTable* addr_table() { return is_on() && (cache()->_table != nullptr) ? cache()->_table : nullptr; @@ -502,20 +898,26 @@ class AOTCodeCache : public CHeapObj { bool lookup_failed() const { return _lookup_failed; } void add_stub_entry(EntryId entry_id, address entry) NOT_CDS_RETURN; + + ~AOTCodeCache(); + public: AOTCodeCache(bool is_dumping, bool is_using); const char* cache_buffer() const { return _load_buffer; } - bool failed() const { return _failed; } - void set_failed() { _failed = true; } + // Use atomic access to _failed field. + bool failed() const; + void set_failed(); + + static bool is_address_in_aot_cache(address p) NOT_CDS_RETURN_(false); static uint max_aot_code_size(); uint load_size() const { return _load_size; } uint write_position() const { return _write_position; } static void init_C_strings_caching(); - void load_strings(); + bool load_strings(); int store_strings(); static void set_shared_stubs_complete() NOT_CDS_RETURN; @@ -528,27 +930,51 @@ class AOTCodeCache : public CHeapObj { address address_for_C_string(int idx) const { return _table->address_for_C_string(idx); } address address_for_id(int id) const { return _table->address_for_id(id); } - bool for_use() const { return _for_use && !_failed; } - bool for_dump() const { return _for_dump && !_failed; } + bool for_use() const { return _for_use && !failed(); } + bool for_dump() const { return _for_dump && !failed(); } AOTCodeEntry* add_entry() { _store_entries_cnt++; _store_entries -= 1; return _store_entries; } + void preload_aot_code(TRAPS); - AOTCodeEntry* find_entry(AOTCodeEntry::Kind kind, uint id); + void set_load_entries(); + bool verify_nmethod_entry(AOTCodeEntry* entry, uint id, uint low_bound, uint high_bound); + AOTCodeEntry* find_entry(AOTCodeEntry::Kind kind, uint id, uint comp_level = 0); + AOTCodeEntry* search_entry(AOTCodeEntry::Kind kind, uint id, uint comp_level); + void invalidate_entry(AOTCodeEntry* entry); - void store_cpu_features(char*& buffer, uint buffer_size); + char* store_cpu_features(char* buffer, uint buffer_size); + static uint get_jvmti_state(); bool finish_write(); - bool write_relocations(CodeBlob& code_blob, RelocIterator& iter); + void log_stats_on_exit(AOTCodeEntryStats& stats); + + bool write_klass(Klass* klass); + bool write_method(Method* method); + + bool write_relocations(CodeBlob& code_blob, RelocIterator& iter, + bool assert_for_unknown_external_address, + GrowableArray* oop_list = nullptr, + GrowableArray* metadata_list = nullptr); + bool write_oop_map_set(CodeBlob& cb); + bool write_nmethod_reloc_immediates(GrowableArray& oop_list, GrowableArray& metadata_list); + + bool write_oop(jobject& jo); + bool write_oop(oop obj); + bool write_metadata(Metadata* m); + bool write_oops(nmethod* nm); + bool write_metadata(nmethod* nm); bool write_stub_data(CodeBlob& blob, AOTStubData *stub_data); + #ifndef PRODUCT - bool write_asm_remarks(CodeBlob& cb); - bool write_dbg_strings(CodeBlob& cb); + bool write_asm_remarks(AsmRemarks& asm_remarks, GrowableArray& remarks, GrowableArray& remarks_len, uint* size); + bool write_dbg_strings(DbgStrings& dbg_strings, GrowableArray& strings, GrowableArray&strings_len, uint* size); + bool write_asm_rem_and_dbg_str(AsmRemarks& asm_remarks, DbgStrings& dbg_strings); #endif // PRODUCT private: @@ -565,37 +991,30 @@ class AOTCodeCache : public CHeapObj { const char* name, AOTStubData* stub_data) NOT_CDS_RETURN_(nullptr); -public: - // save and restore API for non-enumerable code blobs - static bool store_code_blob(CodeBlob& blob, - AOTCodeEntry::Kind entry_kind, - uint id, - const char* name) NOT_CDS_RETURN_(false); + AOTCodeEntry* write_nmethod(nmethod* nm, bool for_preload); - static CodeBlob* load_code_blob(AOTCodeEntry::Kind kind, - uint id, const char* name) NOT_CDS_RETURN_(nullptr); +public: + // save and restore API for adapters + static bool store_adapter(CodeBlob& blob, uint id, const char* name) NOT_CDS_RETURN_(false); - // save and restore API for enumerable code blobs + static CodeBlob* load_adapter(uint id, const char* name) NOT_CDS_RETURN_(nullptr); - // API for single-stub blobs - static bool store_code_blob(CodeBlob& blob, - AOTCodeEntry::Kind entry_kind, + // save and restore for single-stub blobs + static bool store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind, BlobId id) NOT_CDS_RETURN_(false); - static CodeBlob* load_code_blob(AOTCodeEntry::Kind kind, - BlobId id) NOT_CDS_RETURN_(nullptr); + static CodeBlob* load_code_blob(AOTCodeEntry::Kind kind, BlobId id) NOT_CDS_RETURN_(nullptr); - // API for multi-stub blobs -- for use by class StubGenerator. + // save and restore for multi-stub blobs - for use by class StubGenerator + static bool store_multi_stub_blob(CodeBlob& blob, BlobId id, AOTStubData* stub_data, + CodeBuffer *code_buffer) NOT_CDS_RETURN_(false); - static bool store_code_blob(CodeBlob& blob, - AOTCodeEntry::Kind kind, - BlobId id, - AOTStubData* stub_data, - CodeBuffer *code_buffer) NOT_CDS_RETURN_(false); + static CodeBlob* load_multi_stub_blob(BlobId id, AOTStubData* stub_data) NOT_CDS_RETURN_(nullptr); - static CodeBlob* load_code_blob(AOTCodeEntry::Kind kind, - BlobId id, - AOTStubData* stub_data) NOT_CDS_RETURN_(nullptr); + // save and restore API nmethods + static AOTCodeEntry* store_nmethod(nmethod* nm, AbstractCompiler* compiler, bool for_preload) NOT_CDS_RETURN_(nullptr); + + static bool load_nmethod(ciEnv* env, ciMethod* target, int entry_bci, AbstractCompiler* compiler) NOT_CDS_RETURN_(false); static void publish_external_addresses(GrowableArray
& addresses) NOT_CDS_RETURN; // publish all entries for a code blob in code cache address table @@ -615,7 +1034,8 @@ class AOTCodeCache : public CHeapObj { DEBUG_ONLY( static bool _passed_init2; ) static bool open_cache(bool is_dumping, bool is_using); - bool verify_config() { + + bool verify_config_on_use() { if (for_use()) { return _load_header->verify_config(this); } @@ -630,67 +1050,151 @@ class AOTCodeCache : public CHeapObj { static void initialize() NOT_CDS_RETURN; static void init2() NOT_CDS_RETURN; static void init3() NOT_CDS_RETURN; - static void dump() NOT_CDS_RETURN; + static bool dump() NOT_CDS_RETURN_(false); + static bool is_code_load_thread_on() NOT_CDS_RETURN_(false); static bool is_on() CDS_ONLY({ return cache() != nullptr; }) NOT_CDS_RETURN_(false); static bool is_on_for_use() CDS_ONLY({ return is_on() && _cache->for_use(); }) NOT_CDS_RETURN_(false); static bool is_on_for_dump() CDS_ONLY({ return is_on() && _cache->for_dump(); }) NOT_CDS_RETURN_(false); + static bool is_dumping_code() NOT_CDS_RETURN_(false); static bool is_dumping_stub() NOT_CDS_RETURN_(false); static bool is_dumping_adapter() NOT_CDS_RETURN_(false); + static bool is_using_code() NOT_CDS_RETURN_(false); static bool is_using_stub() NOT_CDS_RETURN_(false); static bool is_using_adapter() NOT_CDS_RETURN_(false); static void enable_caching() NOT_CDS_RETURN; static void disable_caching() NOT_CDS_RETURN; static bool is_caching_enabled() NOT_CDS_RETURN_(false); + static bool verify_jvmti_state(ciEnv* env) NOT_CDS_RETURN_(true); + + // It is used before AOTCodeCache is initialized. + static bool maybe_dumping_code() NOT_CDS_RETURN_(false); + + static void invalidate(AOTCodeEntry* entry) NOT_CDS_RETURN; + static bool skip_aot_code(uint comp_level) NOT_CDS_RETURN_(true); + static AOTCodeEntry* find_code_entry(const methodHandle& method, uint comp_level) NOT_CDS_RETURN_(nullptr); + static void preload_code(JavaThread* thread) NOT_CDS_RETURN; static const char* add_C_string(const char* str) NOT_CDS_RETURN_(str); static void print_on(outputStream* st) NOT_CDS_RETURN; + static void print_statistics_on(outputStream* st) NOT_CDS_RETURN; + static void print_timers_on(outputStream* st) NOT_CDS_RETURN; }; -// Concurent AOT code reader +// Control block for loading a single AOTCodeEntry asset from the "ac" region +// into the code cache. We use a few JIT-like worker threads to do this work, +// so loading is done concurrently. Each AOTCodeEntry is processed as its own +// sequential task within a worker thread, using the reader's position cursor. +// Loading copies code from the "ac" region, and also fixes up pointers used by +// the code, including reloc-info and oops and metadata arrays. class AOTCodeReader { private: AOTCodeCache* _cache; - const AOTCodeEntry* _entry; - const char* _load_buffer; // Loaded cached code buffer - uint _read_position; // Position in _load_buffer + AOTCodeEntry* _entry; + address _entry_buffer; // AOT code buffer for this entry + uint _read_position; // Position in _entry_buffer + uint _read_limit; // for bound checks uint read_position() const { return _read_position; } - void set_read_position(uint pos); - uint align_read_int(); - const char* addr(uint offset) const { return _load_buffer + offset; } - bool _lookup_failed; // Failed to lookup for info (skip only this code load) - void set_lookup_failed() { _lookup_failed = true; } - void clear_lookup_failed() { _lookup_failed = false; } - bool lookup_failed() const { return _lookup_failed; } + // convenience method to convert offset in AOTCodeEntry data to its address + address addr(uint offset) const { + assert(offset <= _read_limit, "%u > %u ", offset, _read_limit); + return _entry_buffer + offset; + } + address position_addr() const { return addr(_read_position); } + bool get_set_read_position(address* pos, uint nbytes); + bool update_read_position(uint nbytes); + bool align_read_position(uint alignment); + bool read_int(int* val); + bool read_kind(DataKind* kind) { + return read_int(reinterpret_cast(kind)); + } + + + uint _compile_id; + uint _comp_level; + uint compile_id() const { return _compile_id; } + uint comp_level() const { return _comp_level; } // Values used by restore(code_blob). // They should be set before calling it. - const char* _name; - address _reloc_data; - int _reloc_count; - ImmutableOopMapSet* _oop_maps; AOTCodeEntry::Kind _entry_kind; int _id; AOTStubData* _stub_data; - AOTCodeEntry* aot_code_entry() { return (AOTCodeEntry*)_entry; } + const char* _name; + int _reloc_count; + address _reloc_data; + ImmutableOopMapSet* _oop_maps; + address _immutable_data; + + int _cached_stub_data_count; + uint* _cached_stub_data; // uint[] array + + int _id_for_reloc_count; + uint* _id_for_reloc; + +#ifndef PRODUCT + struct AsmRemData { + uint offset; // remark's offset in code + uint str_offset; // string's offset in code's entry + uint str_len; // string's len for bound check + }; + + struct DbgStrData { + uint str_offset; // string's offset in code's entry + uint str_len; // string's len for bound check + }; + + int _asm_remarks_count; + AsmRemData* _asm_remarks_data; + const char** _asm_remarks_strings; + + int _dbg_strings_count; + DbgStrData* _dbg_strings_data; + const char** _dbg_strings; +#endif + + GrowableArray* _oop_list; + GrowableArray* _metadata_list; + GrowableArray* _reloc_imm_oop_list; + GrowableArray* _reloc_imm_metadata_list; + + const char* _failure; // Failed to lookup for info (skip only this code load) + void set_lookup_failed(const char* failure) { _failure = failure; } + + Klass* read_klass(JavaThread* thread); + Method* read_method(); + + oop read_oop(JavaThread* thread); + Metadata* read_metadata(JavaThread* thread); + + bool read_oop_metadata_list(JavaThread* thread, GrowableArray &oop_list, GrowableArray &metadata_list, OopRecorder* oop_recorder); ImmutableOopMapSet* read_oop_map_set(); void read_stub_data(CodeBlob* code_blob, AOTStubData *stub_data); - void fix_relocations(CodeBlob* code_blob, RelocIterator& iter); + void read_relocations(CodeBlob* code_blob, RelocIterator& iter, + GrowableArray* oop_list = nullptr, + GrowableArray* metadata_list = nullptr); + #ifndef PRODUCT - void read_asm_remarks(AsmRemarks& asm_remarks); - void read_dbg_strings(DbgStrings& dbg_strings); + void restore_asm_remarks(AsmRemarks& asm_remarks); + void restore_dbg_strings(DbgStrings& dbg_strings); + bool read_asm_rem_and_dbg_str(); #endif // PRODUCT public: - AOTCodeReader(AOTCodeCache* cache, AOTCodeEntry* entry); + AOTCodeReader(AOTCodeCache* cache, AOTCodeEntry* entry, CompileTask* task); + + bool compile_nmethod(ciEnv* env, ciMethod* target, AbstractCompiler* compiler); CodeBlob* compile_code_blob(const char* name, AOTCodeEntry::Kind entry_kind, int id, AOTStubData* stub_data = nullptr); void restore(CodeBlob* code_blob); + + bool lookup_failed() const { return _failure != nullptr; } + const char* lookup_failure() const { return _failure; } }; // code cache internal runtime constants area used by AOT code diff --git a/src/hotspot/share/code/codeBlob.cpp b/src/hotspot/share/code/codeBlob.cpp index 4d136b76906..5d323f5f015 100644 --- a/src/hotspot/share/code/codeBlob.cpp +++ b/src/hotspot/share/code/codeBlob.cpp @@ -194,6 +194,12 @@ CodeBlob::CodeBlob(const char* name, CodeBlobKind kind, int size, uint16_t heade assert(_mutable_data == blob_end(), "sanity"); } +#ifdef ASSERT +CodeBlob::~CodeBlob() { + assert(_oop_maps == nullptr || AOTCodeCache::is_address_in_aot_cache((address)_oop_maps), "Not flushed"); +} +#endif + void CodeBlob::purge() { assert(_mutable_data != nullptr, "should never be null"); if (_mutable_data != blob_end()) { @@ -202,7 +208,7 @@ void CodeBlob::purge() { _mutable_data_size = 0; _relocation_size = 0; } - if (_oop_maps != nullptr) { + if (_oop_maps != nullptr && !AOTCodeCache::is_address_in_aot_cache((address)_oop_maps)) { delete _oop_maps; _oop_maps = nullptr; } @@ -252,8 +258,8 @@ void CodeBlob::prepare_for_archiving_impl() { _oop_maps = nullptr; _mutable_data = nullptr; #ifndef PRODUCT - asm_remarks().clear(); - dbg_strings().clear(); + asm_remarks().clear_ref(); + dbg_strings().clear_ref(); #endif /* PRODUCT */ } diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp index e95836c8dd3..9e2fc0b2ade 100644 --- a/src/hotspot/share/code/codeBlob.hpp +++ b/src/hotspot/share/code/codeBlob.hpp @@ -176,9 +176,7 @@ class CodeBlob { public: - ~CodeBlob() { - assert(_oop_maps == nullptr, "Not flushed"); - } + ~CodeBlob() NOT_DEBUG_RETURN; // Returns the space needed for CodeBlob static unsigned int allocation_size(CodeBuffer* cb, int header_size); diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index 05785f694ff..0236ea9bc44 100644 --- a/src/hotspot/share/code/codeCache.cpp +++ b/src/hotspot/share/code/codeCache.cpp @@ -22,6 +22,7 @@ * */ +#include "code/aotCodeCache.hpp" #include "code/codeBlob.hpp" #include "code/codeCache.hpp" #include "code/codeHeapState.hpp" @@ -233,14 +234,31 @@ void CodeCache::initialize_heaps() { assert(heap_available(CodeBlobType::MethodNonProfiled), "MethodNonProfiled heap is always available for segmented code heap"); uint64_t compiler_buffer_size_uint64 = 0; - COMPILER1_PRESENT(compiler_buffer_size_uint64 += (uint64_t)CompilationPolicy::c1_count() * Compiler::code_buffer_size()); - COMPILER2_PRESENT(compiler_buffer_size_uint64 += (uint64_t)CompilationPolicy::c2_count() * C2Compiler::initial_code_buffer_size()); + // Take into account AOT C1 threads in case C1 JIT compilation is disabled, + // the space is needed for, at least, one buffer to initialize C1 runtime. + int c1_count = MAX2(CompilationPolicy::c1_count(), (CompilationPolicy::ac1_count() > 0 ? 1 : 0)); + int c2_count = CompilationPolicy::c2_count(); + COMPILER1_PRESENT(compiler_buffer_size_uint64 += (uint64_t)c1_count * Compiler::code_buffer_size()); + COMPILER2_PRESENT(compiler_buffer_size_uint64 += (uint64_t)c2_count * C2Compiler::initial_code_buffer_size()); if (compiler_buffer_size_uint64 > (uint64_t)CODE_CACHE_SIZE_LIMIT) { err_msg msg("CICompilerCount is too large (%" PRIdPTR "): compiler buffer size exceeds the CodeCache size limit", CICompilerCount); vm_exit_during_initialization(msg); } size_t compiler_buffer_size = integer_cast_permit_tautology(compiler_buffer_size_uint64); + // During AOT assembly phase more compiler threads are used + // and C2 temp buffer is bigger. + // But due to rounding issue the total code cache size could be smaller + // than during production run. We can not use AOT code in such case + // because branch and call instructions will be incorrect. + // + // Increase code cache size to guarantee that total size + // will be bigger during assembly phase. + if (AOTCodeCache::maybe_dumping_code()) { + cache_size += align_up(compiler_buffer_size, min_size); + cache_size = MIN2(cache_size, CODE_CACHE_SIZE_LIMIT); + } + if (!non_nmethod.set) { non_nmethod.size += compiler_buffer_size; } @@ -1348,11 +1366,12 @@ static void check_live_nmethods_dependencies(DepChange& changes) { // Determine if dependency is already checked. table->put(...) returns // 'true' if the dependency is added (i.e., was not in the hashtable). if (table->put(*current_sig, 1)) { - if (deps.check_dependency() != nullptr) { + Klass* witness = deps.check_dependency(); + if (witness != nullptr) { // Dependency checking failed. Print out information about the failed // dependency and finally fail with an assert. We can fail here, since // dependency checking is never done in a product build. - tty->print_cr("Failed dependency:"); + deps.print_dependency(tty, witness, true); changes.print(); nm->print(); nm->print_dependencies_on(tty); @@ -1378,6 +1397,13 @@ void CodeCache::mark_for_deoptimization(DeoptimizationScope* deopt_scope, KlassD NoSafepointVerifier nsv; for (DepChange::ContextStream str(changes, nsv); str.next(); ) { InstanceKlass* d = str.klass(); + { + LogStreamHandle(Trace, dependencies) log; + if (log.is_enabled()) { + log.print("Processing context "); + d->name()->print_value_on(&log); + } + } d->mark_dependent_nmethods(deopt_scope, changes); } @@ -1655,20 +1681,16 @@ void CodeCache::print_internals() { int i = 0; FOR_ALL_ALLOCABLE_HEAPS(heap) { - if ((_nmethod_heaps->length() >= 1) && Verbose) { - tty->print_cr("-- %s --", (*heap)->name()); - } + int heap_total = 0; + tty->print_cr("-- %s --", (*heap)->name()); FOR_ALL_BLOBS(cb, *heap) { total++; + heap_total++; if (cb->is_nmethod()) { nmethod* nm = (nmethod*)cb; - if (Verbose && nm->method() != nullptr) { - ResourceMark rm; - char *method_name = nm->method()->name_and_sig_as_C_string(); - tty->print("%s", method_name); - if(nm->is_not_entrant()) { tty->print_cr(" not-entrant"); } - } + tty->print("%4d: ", heap_total); + CompileTask::print(tty, nm, (nm->is_not_entrant() ? "non-entrant" : ""), true, true); nmethodCount++; diff --git a/src/hotspot/share/code/debugInfoRec.cpp b/src/hotspot/share/code/debugInfoRec.cpp index 9eda46ad682..c4e71ad640b 100644 --- a/src/hotspot/share/code/debugInfoRec.cpp +++ b/src/hotspot/share/code/debugInfoRec.cpp @@ -138,6 +138,8 @@ DebugInformationRecorder::DebugInformationRecorder(OopRecorder* oop_recorder) _all_chunks = new GrowableArray(300); _next_chunk = _next_chunk_limit = nullptr; + _has_vectors = false; + add_new_pc_offset(PcDesc::lower_offset_limit); // sentinel record DEBUG_ONLY(_recording_state = rs_null); diff --git a/src/hotspot/share/code/debugInfoRec.hpp b/src/hotspot/share/code/debugInfoRec.hpp index b38f663f34f..03482581dfa 100644 --- a/src/hotspot/share/code/debugInfoRec.hpp +++ b/src/hotspot/share/code/debugInfoRec.hpp @@ -154,6 +154,8 @@ class DebugInformationRecorder: public ResourceObj { DebugInfoWriteStream* stream() const { return _stream; } + bool has_vectors() const { return _has_vectors; } + void set_has_vectors() { _has_vectors = true; } private: friend class ScopeDesc; @@ -203,6 +205,8 @@ class DebugInformationRecorder: public ResourceObj { int serialize_scope_values(GrowableArray* values); int find_sharable_decode_offset(int stream_offset); + bool _has_vectors; + #ifndef PRODUCT bool recorders_frozen(); void mark_recorders_frozen(); diff --git a/src/hotspot/share/code/dependencies.cpp b/src/hotspot/share/code/dependencies.cpp index 627b72dfcd3..d62e8d19c28 100644 --- a/src/hotspot/share/code/dependencies.cpp +++ b/src/hotspot/share/code/dependencies.cpp @@ -604,9 +604,13 @@ void Dependencies::print_dependency(DepType dept, GrowableArray* ar } if (witness != nullptr) { bool put_star = !Dependencies::is_concrete_klass(witness); - st->print_cr(" witness = %s%s", - (put_star? "*": ""), - witness->external_name()); + st->print(" witness = %s%s", + (put_star ? "*": ""), + witness->external_name()); + if (witness->is_instance_klass()) { + st->print(" (%s)", InstanceKlass::cast(witness)->init_state_name()); + } + st->cr(); } } diff --git a/src/hotspot/share/code/dependencies.hpp b/src/hotspot/share/code/dependencies.hpp index ee756e1fbf2..849984da785 100644 --- a/src/hotspot/share/code/dependencies.hpp +++ b/src/hotspot/share/code/dependencies.hpp @@ -360,6 +360,12 @@ class Dependencies: public ResourceObj { return _size_in_bytes; } + void set_content(address content_bytes, int size_in_bytes) { + assert(_content_bytes == nullptr, "not initialized expected"); + _content_bytes = content_bytes; + _size_in_bytes = size_in_bytes; + } + OopRecorder* oop_recorder() { return _oop_recorder; } CompileLog* log() { return _log; } diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index 635853b549a..dcced8bf929 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -24,6 +24,7 @@ #include "asm/assembler.inline.hpp" #include "cds/cdsConfig.hpp" +#include "code/aotCodeCache.hpp" #include "code/codeCache.hpp" #include "code/compiledIC.hpp" #include "code/dependencies.hpp" @@ -57,6 +58,7 @@ #include "oops/method.inline.hpp" #include "oops/methodData.hpp" #include "oops/oop.inline.hpp" +#include "oops/trainingData.hpp" #include "oops/weakHandle.inline.hpp" #include "prims/jvmtiImpl.hpp" #include "prims/jvmtiThreadState.hpp" @@ -984,14 +986,17 @@ int nmethod::total_size() const { } const char* nmethod::compile_kind() const { - if (is_osr_method()) return "osr"; + if (is_osr_method()) return "osr"; + if (aot_preloaded()) return "AP"; + if (is_aot()) return "A"; + if (method() != nullptr && is_native_method()) { if (method()->is_continuation_native_intrinsic()) { return "cnt"; } return "c2n"; } - return nullptr; + return "jit"; } const char* nmethod::compiler_name() const { @@ -1088,6 +1093,31 @@ nmethod* nmethod::new_native_nmethod(const methodHandle& method, return nm; } +void nmethod::record_nmethod_dependency() { + // To make dependency checking during class loading fast, record + // the nmethod dependencies in the classes it is dependent on. + // This allows the dependency checking code to simply walk the + // class hierarchy above the loaded class, checking only nmethods + // which are dependent on those classes. The slow way is to + // check every nmethod for dependencies which makes it linear in + // the number of methods compiled. For applications with a lot + // classes the slow way is too slow. + for (Dependencies::DepStream deps(this); deps.next(); ) { + if (deps.type() == Dependencies::call_site_target_value) { + // CallSite dependencies are managed on per-CallSite instance basis. + oop call_site = deps.argument_oop(0); + MethodHandles::add_dependent_nmethod(call_site, this); + } else { + InstanceKlass* ik = deps.context_type(); + if (ik == nullptr) { + continue; // ignore things like evol_method + } + // record this nmethod as dependent on this klass + ik->add_dependent_nmethod(this); + } + } +} + nmethod* nmethod::new_nmethod(const methodHandle& method, int compile_id, int entry_bci, @@ -1139,29 +1169,59 @@ nmethod* nmethod::new_nmethod(const methodHandle& method, handler_table, nul_chk_table, compiler, comp_level, flags); if (nm != nullptr) { - // To make dependency checking during class loading fast, record - // the nmethod dependencies in the classes it is dependent on. - // This allows the dependency checking code to simply walk the - // class hierarchy above the loaded class, checking only nmethods - // which are dependent on those classes. The slow way is to - // check every nmethod for dependencies which makes it linear in - // the number of methods compiled. For applications with a lot - // classes the slow way is too slow. - for (Dependencies::DepStream deps(nm); deps.next(); ) { - if (deps.type() == Dependencies::call_site_target_value) { - // CallSite dependencies are managed on per-CallSite instance basis. - oop call_site = deps.argument_oop(0); - MethodHandles::add_dependent_nmethod(call_site, nm); - } else { - InstanceKlass* ik = deps.context_type(); - if (ik == nullptr) { - continue; // ignore things like evol_method - } - // record this nmethod as dependent on this klass - ik->add_dependent_nmethod(nm); - } - } - NOT_PRODUCT(if (nm != nullptr) note_java_nmethod(nm)); + nm->record_nmethod_dependency(); + NOT_PRODUCT(note_java_nmethod(nm)); + } + } + // Do verification and logging outside CodeCache_lock. + if (nm != nullptr) { + // Safepoints in nmethod::verify aren't allowed because nm hasn't been installed yet. + DEBUG_ONLY(nm->verify();) + nm->log_new_nmethod(); + } + return nm; +} + +#if INCLUDE_CDS +nmethod* nmethod::restore(address code_cache_buffer, + const methodHandle& method, + AOTCodeReader* aot_code_reader) +{ + // This will call AOTCodeReader::restore() which restores some nmethods data + CodeBlob::restore(code_cache_buffer, aot_code_reader); + nmethod* nm = (nmethod*)code_cache_buffer; + nm->set_method(method()); + nm->_gc_epoch = CodeCache::gc_epoch(); + + // Create cache after PcDesc data is copied - it will be used to initialize cache + nm->_pc_desc_container = new PcDescContainer(nm->scopes_pcs_begin()); + + nm->post_init(); + return nm; +} + +nmethod* nmethod::new_nmethod(nmethod* archived_nm, + const methodHandle& method, + AOTCodeReader* aot_code_reader) +{ + nmethod* nm = nullptr; + int nmethod_size = archived_nm->size(); + int comp_level = archived_nm->comp_level(); + // create nmethod + { + MutexLocker mu(CodeCache_lock, Mutex::_no_safepoint_check_flag); + address code_cache_buffer = (address)CodeCache::allocate(nmethod_size, CodeCache::get_code_blob_type(comp_level)); + if (code_cache_buffer != nullptr) { + nm = archived_nm->restore(code_cache_buffer, method, aot_code_reader); + AOTCodeEntry* entry = nm->aot_code_entry(); + assert(entry != nullptr, "AOTCodeReader::restore() should set it"); + // Mark the AOT code entry as loaded before releasing CodeCache_lock. + // This guarantees that CodeCache iterators can not observe the restored + // AOT nmethod before its AOT entry is loaded, preserving the normal + // installation vs deoptimization protocol. + entry->set_loaded(); + nm->record_nmethod_dependency(); + NOT_PRODUCT(note_java_nmethod(nm)); } } // Do verification and logging outside CodeCache_lock. @@ -1172,6 +1232,7 @@ nmethod* nmethod::new_nmethod(const methodHandle& method, } return nm; } +#endif // INCLUDE_CDS // Fill in default values for various fields void nmethod::init_defaults(CodeBuffer *code_buffer, CodeOffsets* offsets) { @@ -1182,6 +1243,7 @@ void nmethod::init_defaults(CodeBuffer *code_buffer, CodeOffsets* offsets) { _gc_data = nullptr; _oops_do_mark_link = nullptr; _compiled_ic_data = nullptr; + _aot_code_entry = nullptr; _is_unloading_state = 0; _state = not_installed; @@ -1189,6 +1251,7 @@ void nmethod::init_defaults(CodeBuffer *code_buffer, CodeOffsets* offsets) { _has_flushed_dependencies = false; _is_unlinked = false; _load_reported = false; // jvmti state + _aot_preloaded = false; _deoptimization_status = not_marked; @@ -1217,6 +1280,7 @@ void nmethod::post_init() { // Flush generated code ICache::invalidate_range(code_begin(), code_size()); + // This will disarm entry barrier. Universe::heap()->register_nmethod(this); #ifdef COMPILER2 @@ -1327,7 +1391,7 @@ nmethod::nmethod( #if defined(SUPPORT_DATA_STRUCTS) if (AbstractDisassembler::show_structs()) { if (PrintRelocations) { - print_relocations(); + print_relocations_on(tty); tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); } } @@ -1391,6 +1455,7 @@ nmethod::nmethod(const nmethod &nm) : CodeBlob(nm._name, nm._kind, nm._size, nm. _gc_data = nullptr; _oops_do_mark_link = nullptr; _compiled_ic_data = nullptr; + _aot_code_entry = nm._aot_code_entry; // Relocate the OSR entry point from nm to the new nmethod. if (nm._osr_entry_point == nullptr) { @@ -1440,6 +1505,7 @@ nmethod::nmethod(const nmethod &nm) : CodeBlob(nm._name, nm._kind, nm._size, nm. _has_flushed_dependencies = nm._has_flushed_dependencies; _is_unlinked = nm._is_unlinked; _load_reported = nm._load_reported; + _aot_preloaded = nm._aot_preloaded; _deoptimization_status = nm._deoptimization_status; @@ -1590,6 +1656,10 @@ bool nmethod::is_relocatable() { return false; } + if (is_aot()) { + return false; + } + if (is_marked_for_deoptimization()) { return false; } @@ -1756,7 +1826,7 @@ nmethod::nmethod( void nmethod::log_identity(xmlStream* log) const { log->print(" compile_id='%d'", compile_id()); const char* nm_kind = compile_kind(); - if (nm_kind != nullptr) log->print(" compile_kind='%s'", nm_kind); + log->print(" compile_kind='%s'", nm_kind); log->print(" compiler='%s'", compiler_name()); if (TieredCompilation) { log->print(" level='%d'", comp_level()); @@ -1900,7 +1970,7 @@ void nmethod::print_nmethod(bool printmethod) { tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); } if (printmethod || PrintRelocations || CompilerOracle::has_option(mh, CompileCommandEnum::PrintRelocations)) { - print_relocations(); + print_relocations_on(tty); tty->print_cr("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "); } if (printmethod || PrintDependencies || CompilerOracle::has_option(mh, CompileCommandEnum::PrintDependencies)) { @@ -1940,6 +2010,14 @@ inline void nmethod::initialize_immediate_oop(oop* dest, jobject handle) { } } +void nmethod::copy_values(GrowableArray* array) { + int length = array->length(); + assert((address)(oops_begin() + length) <= (address)oops_end(), "oops big enough"); + oop* dest = oops_begin(); + for (int index = 0 ; index < length; index++) { + dest[index] = array->at(index)(); + } +} // Have to have the same name because it's called by a template void nmethod::copy_values(GrowableArray* array) { @@ -2005,6 +2083,26 @@ void nmethod::fix_oop_relocations(ICacheInvalidationContext* icic) { } } +void nmethod::create_reloc_immediates_list(JavaThread* thread, GrowableArray& oop_list, GrowableArray& metadata_list) { + RelocIterator iter(this); + while (iter.next()) { + if (iter.type() == relocInfo::oop_type) { + oop_Relocation* reloc = iter.oop_reloc(); + if (reloc->oop_is_immediate()) { + oop dest = reloc->oop_value(); + Handle h(thread, dest); + oop_list.append_if_missing(h); + } + } else if (iter.type() == relocInfo::metadata_type) { + metadata_Relocation* reloc = iter.metadata_reloc(); + if (reloc->metadata_is_immediate()) { + Metadata* m = reloc->metadata_value(); + metadata_list.append_if_missing(m); + } + } + } +} + static void install_post_call_nop_displacement(nmethod* nm, address pc) { NativePostCallNop* nop = nativePostCallNop_at((address) pc); intptr_t cbaddr = (intptr_t) nm; @@ -2223,8 +2321,22 @@ void nmethod::unlink_from_method() { } } -// Invalidate code -bool nmethod::make_not_entrant(InvalidationReason invalidation_reason) { +// Invalidate code, making it impossible for future calls to the nmethod to +// proceed past its entry point. Future calls will instead be finished by a +// different code path, lazily updating call sites that link to this this +// nmethod. +// +// This function does nothing about existing activations of the nmethod. The +// nmethod code is sometimes out of date, so it cannot execute further; in +// such cases the user of this function must also ensure that existing +// activations are deoptimized. In other cases, the nmethod code works +// well enough to keep running the existing activations to completion. +// +// The invalidation reason is used as a label in logging the nmethod +// transition. It also affects invalidation policy for any associated +// AOTCodeCache entry. +// TODO: move keep_aot_entry to caller or derive from invalidation_reason. +bool nmethod::make_not_entrant(InvalidationReason invalidation_reason, bool keep_aot_entry) { // This can be called while the system is already at a safepoint which is ok NoSafepointVerifier nsv; @@ -2288,6 +2400,13 @@ bool nmethod::make_not_entrant(InvalidationReason invalidation_reason) { // Remove nmethod from method. unlink_from_method(); + if (!keep_aot_entry) { + // Keep AOT code if it was simply replaced + // otherwise make it not entrant too. + AOTCodeCache::invalidate(_aot_code_entry); + } + + CompileBroker::log_not_entrant(this); } // leave critical region under NMethodState_lock #ifdef ASSERT @@ -2301,7 +2420,19 @@ bool nmethod::make_not_entrant(InvalidationReason invalidation_reason) { return true; } -// For concurrent GCs, there must be a handshake between unlink and flush +// Remove an nmethod from service when a GC has determined its class is unreachable. +// For concurrent GCs, there must be a handshake between unlink and flush. +// +// This function is roughly parallel to nmethod::make_not_entrant. Both remove +// the nmethod from the in_use state, this one because the nmethod is unreachable, +// and the other because its code is may be reachable, but should not be executed. +// AOT interaction: While make_not_entrant marks associated AOT code assets unloaded, +// this function does not touch them (see AOTCodeEntry). +// After JDK-8380476 TODO: Maybe unload the AOT method and record a reason like UNLOADING, +// so that the activity can be traced. AOT methods could get GC-ed if we support +// custom class loaders. Perhaps two versions of a custom class loader could use +// the same asset twice? In any case we need a comment like the above showing the +// missing logic, even if we don't do anything yet. Log messages would be nice too. void nmethod::unlink() { if (is_unlinked()) { // Already unlinked. @@ -2334,7 +2465,7 @@ void nmethod::purge(bool unregister_nmethod) { MutexLocker ml(CodeCache_lock, Mutex::_no_safepoint_check_flag); // completely deallocate this method - Events::log_nmethod_flush(Thread::current(), "flushing %s nmethod " INTPTR_FORMAT, is_osr_method() ? "osr" : "", p2i(this)); + Events::log_nmethod_flush(Thread::current(), "flushing %s nmethod " INTPTR_FORMAT, compile_kind(), p2i(this)); LogTarget(Debug, codecache) lt; if (lt.is_enabled()) { @@ -2343,9 +2474,9 @@ void nmethod::purge(bool unregister_nmethod) { const char* method_name = method()->name()->as_C_string(); const size_t codecache_capacity = CodeCache::capacity()/1024; const size_t codecache_free_space = CodeCache::unallocated_capacity(CodeCache::get_code_blob_type(this))/1024; - ls.print("Flushing nmethod %6d/" INTPTR_FORMAT ", level=%d, osr=%d, cold=%d, epoch=" UINT64_FORMAT ", cold_count=" UINT64_FORMAT ". " + ls.print("Flushing %s nmethod %6d/" INTPTR_FORMAT ", level=%d, cold=%d, epoch=" UINT64_FORMAT ", cold_count=" UINT64_FORMAT ". " "Cache capacity: %zuKb, free space: %zuKb. method %s (%s)", - _compile_id, p2i(this), _comp_level, is_osr_method(), is_cold(), _gc_epoch, CodeCache::cold_gc_count(), + compile_kind(), _compile_id, p2i(this), _comp_level, is_cold(), _gc_epoch, CodeCache::cold_gc_count(), codecache_capacity, codecache_free_space, method_name, compiler_name()); } @@ -2361,9 +2492,12 @@ void nmethod::purge(bool unregister_nmethod) { if (_pc_desc_container != nullptr) { delete _pc_desc_container; } - delete[] _compiled_ic_data; + if (_compiled_ic_data != nullptr) { + delete[] _compiled_ic_data; + } - if (_immutable_data != blob_end()) { + // Don't remove _immutable_data reference from AOT code + if (_immutable_data != blob_end() && !AOTCodeCache::is_address_in_aot_cache((address)_immutable_data)) { // Free memory if this was the last nmethod referencing immutable data if (dec_immutable_data_ref_count() == 0) { os::free(_immutable_data); @@ -2434,6 +2568,21 @@ void nmethod::post_compiled_method(CompileTask* task) { task->set_nm_insts_size(insts_size()); task->set_nm_total_size(total_size()); + CompileTrainingData* ctd = task->training_data(); + if (ctd != nullptr) { + // Record inline code size during training to help inlining during production run + assert(TrainingData::need_data(), "should be called only during training"); // training run + int inline_size = inline_instructions_size(); + if (inline_size < 0) inline_size = 0; + ctd->set_inline_instructions_size(inline_size); + } + + // task->is_aot_load() is true only for loaded AOT code. + // nmethod::_aot_code_entry is set for loaded and stored AOT code + // to invalidate the entry when nmethod is deoptimized. + // VerifyAOTCode is option to verify AOT code without installing. + guarantee((_aot_code_entry != nullptr) || !task->is_aot_load() || VerifyAOTCode, "sanity"); + // JVMTI -- compiled method notification (must be done outside lock) post_compiled_method_load_event(); @@ -3192,9 +3341,13 @@ void nmethod::verify() { fatal("find_nmethod did not find this nmethod (" INTPTR_FORMAT ")", p2i(this)); } - for (PcDesc* p = scopes_pcs_begin(); p < scopes_pcs_end(); p++) { - if (! p->verify(this)) { - tty->print_cr("\t\tin nmethod at " INTPTR_FORMAT " (pcs)", p2i(this)); + // If the Scopes data is in the AOT code cache, then we should avoid verification during shutdown. + // Note that the AOT cache can be alive even during shutdown (it is never closed). + if (!is_aot() || AOTCodeCache::is_on()) { + for (PcDesc* p = scopes_pcs_begin(); p < scopes_pcs_end(); p++) { + if (! p->verify(this)) { + tty->print_cr("\t\tin nmethod at " INTPTR_FORMAT " (pcs)", p2i(this)); + } } } @@ -3205,7 +3358,9 @@ void nmethod::verify() { assert(_oops_do_mark_link == nullptr, "_oops_do_mark_link for %s should be nullptr but is " PTR_FORMAT, nm->method()->external_name(), p2i(_oops_do_mark_link)); - verify_scopes(); + if (!is_aot() || AOTCodeCache::is_on()) { + verify_scopes(); + } CompiledICLocker nm_verify(this); VerifyMetadataClosure vmc; @@ -3360,6 +3515,9 @@ void nmethod::print_on_impl(outputStream* st) const { p2i(scopes_data_begin()), p2i(scopes_data_end()), scopes_data_size()); + if (AOTCodeCache::is_on() && _aot_code_entry != nullptr) { + _aot_code_entry->print(st); + } } void nmethod::print_code() { @@ -3459,11 +3617,11 @@ void nmethod::print_scopes_on(outputStream* st) { #endif #ifndef PRODUCT // RelocIterator does support printing only then. -void nmethod::print_relocations() { +void nmethod::print_relocations_on(outputStream* st) { ResourceMark m; // in case methods get printed via the debugger - tty->print_cr("relocations:"); + st->print_cr("relocations:"); RelocIterator iter(this); - iter.print_on(tty); + iter.print_on(st); } #endif @@ -4336,3 +4494,22 @@ void nmethod::print_statistics() { } #endif // !PRODUCT + +void nmethod::prepare_for_archiving_impl() { + CodeBlob::prepare_for_archiving_impl(); + _deoptimization_generation = 0; + _gc_epoch = 0; + _osr_link = nullptr; + _method = nullptr; + _immutable_data = nullptr; + _pc_desc_container = nullptr; + _exception_cache = nullptr; + _gc_data = nullptr; + _oops_do_mark_link = nullptr; + _compiled_ic_data = nullptr; + _osr_entry_point = nullptr; + _compile_id = -1; + _deoptimization_status = not_marked; + _is_unloading_state = 0; + _state = not_installed; +} diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp index 5e4a18d1245..8a81ed0a1c4 100644 --- a/src/hotspot/share/code/nmethod.hpp +++ b/src/hotspot/share/code/nmethod.hpp @@ -48,6 +48,8 @@ class JvmtiThreadState; class MetadataClosure; class NativeCallWrapper; class OopIterateClosure; +class AOTCodeReader; +class AOTCodeEntry; class ScopeDesc; class xmlStream; @@ -205,6 +207,8 @@ class nmethod : public CodeBlob { CompiledICData* _compiled_ic_data; + AOTCodeEntry* _aot_code_entry; + // offsets for entry points address _osr_entry_point; // entry point for on stack replacement uint16_t _entry_offset; // entry point with class check @@ -259,19 +263,26 @@ class nmethod : public CodeBlob { enum : uint8_t { UNSAFE_ACCESS = 1 << 0, - WIDE_VECTORS = 1 << 1, - MONITORS = 1 << 2, + WIDE_VECTORS = 1 << 1, + MONITORS = 1 << 2, SCOPED_ACCESS = 1 << 3, NEEDS_STACK_REPAIR = 1 << 4, + CLINIT_BARRIERS = 1 << 5 }; Flags() : _bits(0) {} - Flags(bool has_unsafe_access, bool has_wide_vectors, bool has_monitors, bool has_scoped_access, bool needs_stack_repair) : - _bits((has_unsafe_access ? UNSAFE_ACCESS : 0) | - (has_wide_vectors ? WIDE_VECTORS : 0) | - (has_monitors ? MONITORS : 0) | - (has_scoped_access ? SCOPED_ACCESS : 0) | - (needs_stack_repair ? NEEDS_STACK_REPAIR : 0)) + Flags(bool has_unsafe_access, + bool has_wide_vectors, + bool has_monitors, + bool has_scoped_access, + bool needs_stack_repair, + bool has_clinit_barriers) : + _bits((has_unsafe_access ? UNSAFE_ACCESS : 0) | + (has_wide_vectors ? WIDE_VECTORS : 0) | + (has_monitors ? MONITORS : 0) | + (has_scoped_access ? SCOPED_ACCESS : 0) | + (needs_stack_repair ? NEEDS_STACK_REPAIR : 0) | + (has_clinit_barriers ? CLINIT_BARRIERS : 0)) {} // May fault due to unsafe access @@ -288,6 +299,9 @@ class nmethod : public CodeBlob { // Stack has been extended and needs repair. See comment in MacroAssembler::remove_frame bool needs_stack_repair() const { return (_bits & NEEDS_STACK_REPAIR) != 0; } + + // AOT preload code has clinit barriers + bool has_clinit_barriers() const { return (_bits & CLINIT_BARRIERS) != 0; } }; private: @@ -303,6 +317,9 @@ class nmethod : public CodeBlob { // Used by JVMTI to track if an event has been posted for this nmethod bool _load_reported; + // This AOT code was preloaded before method is called + bool _aot_preloaded; + enum DeoptimizationStatus : u1 { not_marked, deoptimize, @@ -494,6 +511,18 @@ class nmethod : public CodeBlob { // transitions). void oops_do_set_strong_done(nmethod* old_head); + void record_nmethod_dependency(); +#if INCLUDE_CDS + nmethod* restore(address code_cache_buffer, + const methodHandle& method, + AOTCodeReader* aot_code_reader); + +public: + // create nmethod using archived nmethod from AOT code cache + static nmethod* new_nmethod(nmethod* archived_nm, + const methodHandle& method, + AOTCodeReader* aot_code_reader); +#endif public: enum class InvalidationReason : s1 { NOT_INVALIDATED = -1, @@ -624,10 +653,12 @@ class nmethod : public CodeBlob { int exception_handler = -1); Method* method () const { return _method; } + uint16_t entry_bci () const { return _entry_bci; } bool is_native_method() const { return _method != nullptr && _method->is_native(); } bool is_java_method () const { return _method != nullptr && !_method->is_native(); } bool is_osr_method () const { return _entry_bci != InvocationEntryBci; } + int orig_pc_offset() const { return _orig_pc_offset; } bool is_relocatable(); // Compiler task identification. Note that all OSR methods @@ -635,6 +666,8 @@ class nmethod : public CodeBlob { // and native method wrappers are also numbered independently if // CICountNative is true. int compile_id() const { return _compile_id; } + void set_compile_id(int compile_id) { _compile_id = compile_id; } + int comp_level() const { return _comp_level; } const char* compile_kind() const; inline bool is_compiled_by_c1 () const { return _compiler_type == compiler_c1; } @@ -675,6 +708,8 @@ class nmethod : public CodeBlob { address scopes_data_end () const { return _immutable_data + _immutable_data_ref_count_offset ; } address immutable_data_ref_count_begin () const { return _immutable_data + _immutable_data_ref_count_offset ; } + void set_immutable_data(address data) { _immutable_data = data; } + // Sizes int immutable_data_size() const { return _immutable_data_size; } int consts_size () const { return int( consts_end () - consts_begin ()); } @@ -715,6 +750,8 @@ class nmethod : public CodeBlob { address verified_value_entry_point() const { return code_begin() + _verified_value_entry_offset; } // value type entry point (unpack all value type args) without class check address verified_value_ro_entry_point() const { return code_begin() + _verified_value_ro_entry_offset; } // value type entry point (only unpack receiver) without class check + int inline_instructions_size() const { return insts_end() - verified_entry_point() - skipped_instructions_size(); } + enum : signed char { not_installed = -1, // in construction, only the owner doing the construction is // allowed to advance state in_use = 0, // executable nmethod @@ -736,12 +773,13 @@ class nmethod : public CodeBlob { bool make_in_use() { return try_transition(in_use); } + // Make the nmethod non entrant. The nmethod will continue to be // alive. It is used when an uncommon trap happens. Returns true // if this thread changed the state of the nmethod or false if // another thread performed the transition. - bool make_not_entrant(InvalidationReason invalidation_reason); - bool make_not_used() { return make_not_entrant(InvalidationReason::NOT_USED); } + bool make_not_entrant(InvalidationReason invalidation_reason, bool keep_aot_entry = false); + bool make_not_used() { return make_not_entrant(InvalidationReason::NOT_USED, true /* keep AOT entry */); } bool is_marked_for_deoptimization() const { return deoptimization_status() != not_marked; } bool has_been_deoptimized() const { return deoptimization_status() == deoptimize_done; } @@ -773,6 +811,10 @@ class nmethod : public CodeBlob { bool has_scoped_access() const { return _flags.has_scoped_access(); } bool has_wide_vectors() const { return _flags.has_wide_vectors(); } bool needs_stack_repair() const { return _flags.needs_stack_repair(); } + bool has_clinit_barriers() const { return _flags.has_clinit_barriers(); } + + bool aot_preloaded() const { return _aot_preloaded; } + void set_aot_preloaded(bool z) { _aot_preloaded = z; } bool has_flushed_dependencies() const { return _has_flushed_dependencies; } void set_has_flushed_dependencies(bool z) { @@ -786,7 +828,9 @@ class nmethod : public CodeBlob { _is_unlinked = true; } - int comp_level() const { return _comp_level; } + bool is_aot() const { return _aot_code_entry != nullptr; } + void set_aot_code_entry(AOTCodeEntry* entry) { _aot_code_entry = entry; } + AOTCodeEntry* aot_code_entry() const { return _aot_code_entry; } // Support for oops in scopes and relocs: // Note: index 0 is reserved for null. @@ -807,6 +851,7 @@ class nmethod : public CodeBlob { return &metadata_begin()[index - 1]; } + void copy_values(GrowableArray* array); void copy_values(GrowableArray* oops); void copy_values(GrowableArray* metadata); void copy_values(GrowableArray
* metadata) {} // Nothing to do @@ -823,6 +868,8 @@ class nmethod : public CodeBlob { void fix_oop_relocations(ICacheInvalidationContext* icic); void fix_oop_relocations(); + void create_reloc_immediates_list(JavaThread* thread, GrowableArray& oop_list, GrowableArray& metadata_list); + bool is_at_poll_return(address pc); bool is_at_poll_or_poll_return(address pc); @@ -989,8 +1036,6 @@ class nmethod : public CodeBlob { void copy_scopes_pcs(PcDesc* pcs, int count); void copy_scopes_data(address buffer, int size); - int orig_pc_offset() { return _orig_pc_offset; } - // Post successful compilation void post_compiled_method(CompileTask* task); @@ -1020,7 +1065,7 @@ class nmethod : public CodeBlob { #if defined(SUPPORT_DATA_STRUCTS) // print output in opt build for disassembler library - void print_relocations() PRODUCT_RETURN; + void print_relocations_on(outputStream* st) PRODUCT_RETURN; void print_pcs_on(outputStream* st); void print_scopes() { print_scopes_on(tty); } void print_scopes_on(outputStream* st) PRODUCT_RETURN; @@ -1090,6 +1135,8 @@ class nmethod : public CodeBlob { void make_deoptimized(); void finalize_relocations(); + void prepare_for_archiving_impl(); + class Vptr : public CodeBlob::Vptr { void print_on(const CodeBlob* instance, outputStream* st) const override { ttyLocker ttyl; @@ -1098,6 +1145,9 @@ class nmethod : public CodeBlob { void print_value_on(const CodeBlob* instance, outputStream* st) const override { instance->as_nmethod()->print_value_on_impl(st); } + void prepare_for_archiving(CodeBlob* instance) const override { + ((nmethod*)instance)->prepare_for_archiving_impl(); + }; }; static const Vptr _vpntr; diff --git a/src/hotspot/share/code/relocInfo.cpp b/src/hotspot/share/code/relocInfo.cpp index 1d17f40b0c5..40717b6a741 100644 --- a/src/hotspot/share/code/relocInfo.cpp +++ b/src/hotspot/share/code/relocInfo.cpp @@ -179,12 +179,12 @@ RelocIterator::RelocIterator(CodeSection* cs, address begin, address limit) { } RelocIterator::RelocIterator(CodeBlob* cb) { - initialize_misc(); if (cb->is_nmethod()) { - _code = cb->as_nmethod(); - } else { - _code = nullptr; + initialize(cb->as_nmethod(), nullptr, nullptr); + return; } + initialize_misc(); + _code = nullptr; _current = cb->relocation_begin() - 1; _end = cb->relocation_end(); _addr = cb->content_begin(); @@ -819,11 +819,11 @@ void internal_word_Relocation::fix_relocation_after_move(const CodeBuffer* src, set_value(target); } -void internal_word_Relocation::fix_relocation_after_aot_load(address orig_base_addr, address current_base_addr) { +void internal_word_Relocation::fix_relocation_after_aot_load(address current_base_addr, int delta) { address target = _target; if (target == nullptr) { target = this->target(); - target = current_base_addr + (target - orig_base_addr); + target = current_base_addr + delta; } set_value(target); } @@ -889,8 +889,8 @@ void RelocIterator::print_current_on(outputStream* st) { raw_oop = *oop_addr; oop_value = r->oop_value(); } - st->print(" | [oop_addr=" INTPTR_FORMAT " *=" INTPTR_FORMAT "]", - p2i(oop_addr), p2i(raw_oop)); + st->print(" | [oop_addr=" INTPTR_FORMAT " *=" INTPTR_FORMAT " index=%d]", + p2i(oop_addr), p2i(raw_oop), r->oop_index()); // Do not print the oop by default--we want this routine to // work even during GC or other inconvenient times. if (WizardMode && oop_value != nullptr) { @@ -912,8 +912,8 @@ void RelocIterator::print_current_on(outputStream* st) { raw_metadata = *metadata_addr; metadata_value = r->metadata_value(); } - st->print(" | [metadata_addr=" INTPTR_FORMAT " *=" INTPTR_FORMAT "]", - p2i(metadata_addr), p2i(raw_metadata)); + st->print(" | [metadata_addr=" INTPTR_FORMAT " *=" INTPTR_FORMAT " index=%d]", + p2i(metadata_addr), p2i(raw_metadata), r->metadata_index()); if (metadata_value != nullptr) { st->print("metadata_value=" INTPTR_FORMAT ": ", p2i(metadata_value)); metadata_value->print_value_on(st); @@ -953,7 +953,7 @@ void RelocIterator::print_current_on(outputStream* st) { } else { CodeBlob* cb = CodeCache::find_blob(dest); if (cb != nullptr) { - st->print(" %s", cb->name()); + st->print(" Blob::%s", cb->name()); } else { ResourceMark rm; const int buflen = 1024; diff --git a/src/hotspot/share/code/relocInfo.hpp b/src/hotspot/share/code/relocInfo.hpp index ee5faea4206..c3bff6519a7 100644 --- a/src/hotspot/share/code/relocInfo.hpp +++ b/src/hotspot/share/code/relocInfo.hpp @@ -1310,6 +1310,31 @@ class trampoline_stub_Relocation : public Relocation { void pack_data_to(CodeSection * dest) override; void unpack_data() override; +// Platforms that keep the call destination in a trampoline stub read and write it +// through these. A platform needing USE_TRAMPOLINE_STUB_FIX_OWNER also needs them. +#if defined(USE_TRAMPOLINE_STUB_DESTINATION) || defined(USE_TRAMPOLINE_STUB_FIX_OWNER) + address pd_destination (); + void pd_set_destination (address x); +#else + address pd_destination () { + fatal("trampoline_stub_Relocation::destination() unimplemented"); + return (address)-1; + } + void pd_set_destination (address x) { + fatal("trampoline_stub_Relocation::set_destination() unimplemented"); + } +#endif +#ifdef USE_TRAMPOLINE_STUB_FIX_OWNER + void fix_owner_after_move() { + pd_fix_owner_after_move(); + } +#endif + address destination() { + return pd_destination(); + } + void set_destination(address x) { + pd_set_destination(x); + } // Find the trampoline stub for a call. static address get_trampoline_for(address call, nmethod* code); @@ -1400,7 +1425,7 @@ class internal_word_Relocation : public DataRelocation { void unpack_data() override; void fix_relocation_after_move(const CodeBuffer* src, CodeBuffer* dest) override; - void fix_relocation_after_aot_load(address orig_base_addr, address current_base_addr); + void fix_relocation_after_aot_load(address current_base_addr, int delta); address target(); // if _target==nullptr, fetch addr from code stream int section() { return _section; } diff --git a/src/hotspot/share/compiler/abstractCompiler.cpp b/src/hotspot/share/compiler/abstractCompiler.cpp index 386243850dd..09b9b292b3e 100644 --- a/src/hotspot/share/compiler/abstractCompiler.cpp +++ b/src/hotspot/share/compiler/abstractCompiler.cpp @@ -41,15 +41,27 @@ bool AbstractCompiler::should_perform_init() { return false; } -bool AbstractCompiler::should_perform_shutdown() { +void AbstractCompiler::wait_for_initialization() { + MonitorLocker locker(CompileThread_lock); + // Wait for initialized, failed or shut_down state + while (_compiler_state == uninitialized || _compiler_state == initializing) { + locker.wait(); + } +} + +bool AbstractCompiler::should_perform_shutdown(bool is_aot_comp_thread) { // Since this method can be called by multiple threads, the lock ensures atomicity of // decrementing '_num_compiler_threads' and the following operations. MutexLocker only_one(CompileThread_lock); - _num_compiler_threads--; + if (is_aot_comp_thread) { + _num_aot_compiler_threads--; + } else { + _num_compiler_threads--; + } assert (CompileBroker::is_compilation_disabled_forever(), "Must be set, otherwise thread waits forever"); // Only the last thread will perform shutdown operations - if (_num_compiler_threads == 0) { + if (_num_compiler_threads == 0 && _num_aot_compiler_threads == 0) { return true; } return false; diff --git a/src/hotspot/share/compiler/abstractCompiler.hpp b/src/hotspot/share/compiler/abstractCompiler.hpp index 5ed256d640c..db02a1fd471 100644 --- a/src/hotspot/share/compiler/abstractCompiler.hpp +++ b/src/hotspot/share/compiler/abstractCompiler.hpp @@ -33,6 +33,7 @@ typedef void (*initializer)(void); // Per-compiler statistics class CompilerStatistics { + public: class Data { friend class VMStructs; public: @@ -50,9 +51,11 @@ class CompilerStatistics { } }; - public: Data _standard; // stats for non-OSR compilations Data _osr; // stats for OSR compilations + Data _bailout; + Data _invalidated; + Data _made_not_entrant; uint _nmethods_size; // uint _nmethods_code_size; @@ -73,6 +76,7 @@ class CompilerStatistics { class AbstractCompiler : public CHeapObj { private: volatile int _num_compiler_threads; + volatile int _num_aot_compiler_threads; protected: volatile int _compiler_state; @@ -83,17 +87,22 @@ class AbstractCompiler : public CHeapObj { // This thread will initialize the compiler runtime. bool should_perform_init(); + void wait_for_initialization(); + private: const CompilerType _type; CompilerStatistics _stats; public: - AbstractCompiler(CompilerType type) : _num_compiler_threads(0), _compiler_state(uninitialized), _type(type) {} + AbstractCompiler(CompilerType type) : _num_compiler_threads(0), + _num_aot_compiler_threads(0), + _compiler_state(uninitialized), + _type(type) {} // This function determines the compiler thread that will perform the // shutdown of the corresponding compiler runtime. - bool should_perform_shutdown(); + bool should_perform_shutdown(bool is_aot_comp_thread); // Name of this compiler virtual const char* name() = 0; @@ -152,11 +161,14 @@ class AbstractCompiler : public CHeapObj { virtual void initialize () = 0; void set_num_compiler_threads(int num) { _num_compiler_threads = num; } - int num_compiler_threads() { return _num_compiler_threads; } + int num_compiler_threads() const { return _num_compiler_threads; } + + void set_num_aot_compiler_threads(int num) { _num_aot_compiler_threads = num; } + int num_aot_compiler_threads() const { return _num_aot_compiler_threads; } // Get/set state of compiler objects - bool is_initialized() { return _compiler_state == initialized; } - bool is_failed () { return _compiler_state == failed;} + bool is_initialized() const { return _compiler_state == initialized; } + bool is_failed () const { return _compiler_state == failed;} void set_state (int state); void set_shut_down () { set_state(shut_down); } // Compilation entry point for methods diff --git a/src/hotspot/share/compiler/aotCompileBroker.cpp b/src/hotspot/share/compiler/aotCompileBroker.cpp new file mode 100644 index 00000000000..bf5fe0d815c --- /dev/null +++ b/src/hotspot/share/compiler/aotCompileBroker.cpp @@ -0,0 +1,242 @@ +/* + * 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 + * 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 "cds/aotCacheAccess.hpp" +#include "cds/archiveBuilder.hpp" +#include "cds/cdsConfig.hpp" +#include "code/aotCodeCache.hpp" +#include "compiler/aotCompileBroker.hpp" +#include "compiler/compilationPolicy.hpp" +#include "compiler/compileBroker.hpp" +#include "compiler/compiler_globals.hpp" +#include "compiler/compilerDefinitions.hpp" +#include "logging/logStream.hpp" +#include "memory/allocation.hpp" +#include "oops/method.inline.hpp" +#include "oops/trainingData.hpp" +#include "runtime/handles.inline.hpp" + +class AOTCompileIterator : StackObj { +private: + CompLevel _comp_level; + bool _for_preload; + Thread* _thread; + GrowableArray _methods; + +public: + AOTCompileIterator(CompLevel comp_level, bool for_preload, JavaThread* thread) + : _comp_level(comp_level), _for_preload(for_preload), _thread(thread) { + assert(TrainingData::have_data(), "sanity"); + } + + bool include(MethodTrainingData* mtd) { + Method* m = mtd->holder(); + if (m->is_native() || m->is_abstract() || !m->method_holder()->is_linked()) { + return false; + } + CompilerDirectiveMatcher matcher(methodHandle(_thread, m), _comp_level); + if (matcher.directive_set()->DontAOTCompileOption) { + return false; + } + int high_top_level = mtd->highest_top_level(); + switch (_comp_level) { + case CompLevel_simple: + case CompLevel_full_optimization: + // For final C1/C2 compilations, we only compile when there was relevant compilation during training. + return _comp_level == high_top_level; + case CompLevel_limited_profile: + // For profiled C1 compilations, generate limited profile when there was limited/full + // profiled compilation in training. + return CompLevel_limited_profile <= high_top_level && high_top_level <= CompLevel_full_profile; + default: + // This includes CompLevel_full_profile which should be filtered out. + assert(false, "Unexpected AOT compilation level: %d", _comp_level); + } + // Do not include methods by default. + return false; + } + + void apply(TrainingData* td) { + if (td->is_MethodTrainingData()) { + MethodTrainingData* mtd = td->as_MethodTrainingData(); + if (mtd->has_holder() && include(mtd)) { + _methods.push(mtd->holder()); + } + } + } + + static MethodTrainingData* method_training_data(Method* m) { + if (m->method_holder()->is_loaded()) { + return MethodTrainingData::find(methodHandle(Thread::current(), m)); + } + return nullptr; + } + + // We sort methods by compile ID, presuming the methods that compiled earlier + // are more important. This only matters for preload code, which is loaded + // asynchronously; other levels are sorted for better consistency between training + // runs. Since we can accept methods from multiple levels, we use the compile ID + // from the lowest level. + static int compare_methods(Method** m1, Method** m2) { + int c1 = compile_id(*m1); + int c2 = compile_id(*m2); + if (c1 < c2) return -1; + if (c1 > c2) return +1; + return 0; + } + + static int compile_id(Method* m) { + // Methods without recorded compilations are treated as "compiled last" + int id = INT_MAX; + MethodTrainingData* mtd = method_training_data(m); + if (mtd != nullptr) { + for (int level = CompLevel_simple; level <= CompilationPolicy::highest_compile_level(); level++) { + CompileTrainingData* ctd = mtd->last_toplevel_compile(level); + if (ctd != nullptr) { + id = MIN2(id, ctd->compile_id()); + } + } + } + return id; + } + + void schedule_compilations(TRAPS) { + for (int i = 0; i < _methods.length(); i++) { + Method* m = _methods.at(i); + methodHandle mh(THREAD, m); + assert(mh()->method_holder()->is_linked(), "required"); + if (!AOTCacheAccess::can_generate_aot_code(m)) { + continue; // Method is not archived + } + assert(!HAS_PENDING_EXCEPTION, ""); + CompileBroker::compile_method(mh, InvocationEntryBci, _comp_level, + 0, nullptr /* AOTCodeEntry* */, + _for_preload ? CompileTask::Reason_AOTCompileForPreload : CompileTask::Reason_AOTCompile, + THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } + } + } + + void print_compilation_status(ArchiveBuilder* builder) { + LogStreamHandle(Info, aot, compilation) logi; + if (!logi.is_enabled()) { + return; + } + int success_count = 0; + const int log_comp_level = _comp_level + (_for_preload ? 1 : 0); + + for (int i = 0; i < _methods.length(); i++) { + Method* m = _methods.at(i); + + bool is_preloaded_code = m->has_compiled_code() && m->code()->aot_preloaded(); + bool is_success = !m->is_not_compilable(_comp_level) && + m->has_compiled_code() && m->code()->is_aot(); + // Successful preloaded AP4 code may mask failed normal A4 code later load. + if (is_success && is_preloaded_code != _for_preload) { + is_success = false; + } + if (is_success) { + success_count++; + } + + LogStreamHandle(Debug, aot, compilation) log; + if (log.is_enabled()) { + ResourceMark rm; + log.print("[%4d] A%d Compiled %s [%p", i, log_comp_level, m->external_name(), m); + Method* requested_m = builder->to_requested(builder->get_buffered_addr(m)); + log.print(" -> %p", requested_m); + log.print_cr("] {%d} [%d] (%s)", compile_id(m), AOTCodeCache::store_entries_cnt(), (is_success ? "success" : "FAILED")); + } + } + + logi.print_cr("AOT Compilation for level A%d finished (%d successful out of %d total)", + log_comp_level, success_count, _methods.length()); + } + + void compile(ArchiveBuilder* builder, TRAPS) { + _methods.sort(&compare_methods); + + intx tier2_invoke_notify_freq_log = Tier2InvokeNotifyFreqLog; + intx tier2_backedge_notify_freq_log = Tier2BackedgeNotifyFreqLog; + double compile_threshold_scaling = CompileThresholdScaling; + bool restore_freq_logs_flags = false; + if (_comp_level == CompLevel_limited_profile && _methods.length() > 0 && + CompileThresholdScaling != CDSConfig::aot_compile_threshold_scaling()) { + // CDSConfig reset CompileThresholdScaling to default 1.0 and preserved original value. + // Restore scaling for A2 compilation based on preserved CompileThresholdScaling. + CompileThresholdScaling = CDSConfig::aot_compile_threshold_scaling(); + Tier2InvokeNotifyFreqLog = CompilerConfig::jvmflag_scaled_freq_log(Tier2InvokeNotifyFreqLog); + Tier2BackedgeNotifyFreqLog = CompilerConfig::jvmflag_scaled_freq_log(Tier2BackedgeNotifyFreqLog); + restore_freq_logs_flags = true; + } + schedule_compilations(THREAD); + CompileBroker::wait_for_no_active_tasks(); + print_compilation_status(builder); + if (restore_freq_logs_flags) { + Tier2InvokeNotifyFreqLog = tier2_invoke_notify_freq_log; + Tier2BackedgeNotifyFreqLog = tier2_backedge_notify_freq_log; + CompileThresholdScaling = compile_threshold_scaling; + } + } +}; + +// Top level driver for AOT code compilation. Runs after metadata and +// Java object heap have been added to the AOT cache. Inside this pass, +// the scope function CDSConfig::is_dumping_aot_code returns true. +// Normal JIT compilation is suppressed, and the JIT pipeline is repurposed +// to create AOT code (tasks are marked Reason_AOTCompileForPreload, etc.). +// Some JIT policy flags are forcibly adjusted to settings required by AOT code. +// These include -FoldStableValues, +ForceUnreachable, -DelayCompilerStubsGeneration, +// +AOTCompileEagerly, CompileThresholdScaling=1.0 +void AOTCompileBroker::compile_aot_code(ArchiveBuilder* builder, TRAPS) { + assert(AOTCodeCache::is_dumping_code(), "sanity"); + assert(CDSConfig::is_dumping_aot_code(), "AOT compilation must be enabled"); + if (TrainingData::have_data()) { + // Drain normal JIT compilation tasks before submitting AOT compilation. + CompileBroker::wait_for_no_active_tasks(); + + ResourceMark rm; + CompLevel highest_level = CompilationPolicy::highest_compile_level(); + if (highest_level >= CompLevel_full_optimization && ClassInitBarrierMode > 0 && + !AOTCodeCache::skip_aot_code(CompLevel_preload_optimization)) { + AOTCompileIterator aci(CompLevel_full_optimization, true /*for_preload*/, THREAD); + TrainingData::iterate([&](TrainingData* td) { + aci.apply(td); + }); + aci.compile(builder, THREAD); + } + + for (int level = CompLevel_simple; level <= highest_level; level++) { + if (!AOTCodeCache::skip_aot_code(level) && CompileBroker::compiler(level) != nullptr) { + AOTCompileIterator aci((CompLevel)level, false /*for_preload*/, THREAD); + TrainingData::iterate([&](TrainingData* td) { + aci.apply(td); + }); + aci.compile(builder, THREAD); + } + } + } +} diff --git a/src/hotspot/share/compiler/aotCompileBroker.hpp b/src/hotspot/share/compiler/aotCompileBroker.hpp new file mode 100644 index 00000000000..2bb41fca875 --- /dev/null +++ b/src/hotspot/share/compiler/aotCompileBroker.hpp @@ -0,0 +1,38 @@ +/* + * 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 + * 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. + * + */ + +#ifndef SHARE_COMPILER_AOTCOMPILEBROKER_HPP +#define SHARE_COMPILER_AOTCOMPILEBROKER_HPP + +#include "memory/allStatic.hpp" +#include "utilities/exceptions.hpp" + +class ArchiveBuilder; + +class AOTCompileBroker : AllStatic { +public: + static void compile_aot_code(ArchiveBuilder* builder, TRAPS) NOT_CDS_RETURN; +}; + +#endif // SHARE_COMPILER_AOTCOMPILEBROKER_HPP diff --git a/src/hotspot/share/compiler/compilationLog.cpp b/src/hotspot/share/compiler/compilationLog.cpp index 88bd29d6843..915aa1a4f11 100644 --- a/src/hotspot/share/compiler/compilationLog.cpp +++ b/src/hotspot/share/compiler/compilationLog.cpp @@ -36,6 +36,7 @@ CompilationLog::CompilationLog() : StringEventLog("Compilation events", "jit") { } void CompilationLog::log_compile(JavaThread* thread, CompileTask* task) { + ResourceMark rm; StringLogMessage lm; stringStream sstr(lm.buffer(), lm.size()); // msg.time_stamp().update_to(tty->time_stamp().ticks()); @@ -44,12 +45,13 @@ void CompilationLog::log_compile(JavaThread* thread, CompileTask* task) { } void CompilationLog::log_nmethod(JavaThread* thread, nmethod* nm) { - log(thread, "nmethod %d%s " INTPTR_FORMAT " code [" INTPTR_FORMAT ", " INTPTR_FORMAT "]", - nm->compile_id(), nm->is_osr_method() ? "%" : "", + log(thread, "nmethod %d %s " INTPTR_FORMAT " code [" INTPTR_FORMAT ", " INTPTR_FORMAT "]", + nm->compile_id(), nm->compile_kind(), p2i(nm), p2i(nm->code_begin()), p2i(nm->code_end())); } void CompilationLog::log_failure(JavaThread* thread, CompileTask* task, const char* reason, const char* retry_message) { + ResourceMark rm; StringLogMessage lm; stringStream sstr(lm.buffer(), lm.size()); if (task == nullptr) { diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index 2b451ebb5b3..47ad8e65010 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -23,6 +23,7 @@ */ #include "cds/aotLinkedClassBulkLoader.hpp" +#include "code/aotCodeCache.hpp" #include "code/scopeDesc.hpp" #include "compiler/compilationPolicy.hpp" #include "compiler/compileBroker.hpp" @@ -52,6 +53,8 @@ int64_t CompilationPolicy::_start_time = 0; int CompilationPolicy::_c1_count = 0; int CompilationPolicy::_c2_count = 0; +int CompilationPolicy::_ac1_count = 0; +int CompilationPolicy::_ac2_count = 0; double CompilationPolicy::_increase_threshold_at_ratio = 0; CompilationPolicy::TrainingReplayQueue CompilationPolicy::_training_replay_queue; @@ -82,28 +85,59 @@ bool CompilationPolicy::must_be_compiled(const methodHandle& m, int comp_level) return !UseInterpreter; // must compile all methods } -void CompilationPolicy::maybe_compile_early(const methodHandle& m, TRAPS) { +AOTCodeEntry* find_aot_code_entry(const methodHandle& method, int comp_level, + CompileTask::CompileReason compile_reason) { + precond(compile_reason == CompileTask::Reason_AOTLoad || + compile_reason == CompileTask::Reason_Tiered || + compile_reason == CompileTask::Reason_MustBeCompiled); + if (AOTCodeCache::is_using_code()) { + AOTCodeEntry* aot_code_entry = AOTCodeCache::find_code_entry(method, comp_level); + // There is harmless concurrency here when reading fields in the AOTCodeEntry. + // The _loaded field is monotonic, going true in ciEnv::register_aot_method under + // MethodCompileQueue_lock. We might race with other threads which quickly queue + // and then load the method, and harmlessly conclude that this code entry is + // still loadable. Even if we manage to queue a second load concurrently, + // CompileBroker::compile_method_base checks, under the MCQ lock, with + // compilation_is_in_queue and compilation_is_complete, to avoid duplicate work. + if (aot_code_entry != nullptr && !aot_code_entry->is_loaded() && + !aot_code_entry->not_entrant() && !aot_code_entry->is_verified()) { + return aot_code_entry; + } + } + return nullptr; +} + +void CompilationPolicy::maybe_compile_early(const methodHandle& m, MethodTrainingData* mtd, TRAPS) { if (m->method_holder()->is_not_initialized()) { // 'is_not_initialized' means not only '!is_initialized', but also that // initialization has not been started yet ('!being_initialized') // Do not force compilation of methods in uninitialized classes. return; } - if (!m->is_native() && MethodTrainingData::have_data()) { - MethodTrainingData* mtd = MethodTrainingData::find_fast(m); - if (mtd == nullptr) { - return; // there is no training data recorded for m + // Consider replacing conservatively compiled AOT Preload code with faster AOT code or normal JITed code + nmethod* nm = m->code(); + bool recompile = (nm != nullptr) && nm->aot_preloaded(); + CompLevel cur_level = static_cast(m->highest_comp_level()); + CompLevel next_level = trained_transition(m, cur_level, mtd, THREAD); + if ((next_level != cur_level || recompile) && can_be_compiled(m, next_level) && !CompileBroker::compilation_is_in_queue(m)) { + // We are here because some of CTD have all init dependencies satisfied. + CompileTrainingData* ctd = mtd->compile_data_for_aot_code(next_level); + if (ctd == nullptr || (ctd->init_deps_left_acquire() > 0)) { + // Skip compilation because CTD is absent or not all dependencies are ready + return; + } + CompileTask::CompileReason reason = CompileTask::Reason_AOTLoad; + AOTCodeEntry* aot_code_entry = find_aot_code_entry(m, next_level, reason); + if (aot_code_entry == nullptr) { + // Request normal JIT compilation if there is no next_level aot code for this method + reason = CompileTask::Reason_MustBeCompiled; } - CompLevel cur_level = static_cast(m->highest_comp_level()); - CompLevel next_level = trained_transition(m, cur_level, mtd, THREAD); - if (next_level != cur_level && can_be_compiled(m, next_level) && !CompileBroker::compilation_is_in_queue(m)) { - if (PrintTieredEvents) { - print_event(FORCE_COMPILE, m(), m(), InvocationEntryBci, next_level); - } - CompileBroker::compile_method(m, InvocationEntryBci, next_level, 0, CompileTask::Reason_MustBeCompiled, THREAD); - if (HAS_PENDING_EXCEPTION) { - CLEAR_PENDING_EXCEPTION; - } + if (PrintTieredEvents) { + print_event(FORCE_COMPILE, m(), m(), InvocationEntryBci, next_level); + } + CompileBroker::compile_method(m, InvocationEntryBci, next_level, 0, aot_code_entry, reason, THREAD); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; } } } @@ -124,17 +158,42 @@ void CompilationPolicy::compile_if_required(const methodHandle& m, TRAPS) { return; } - if (must_be_compiled(m)) { + if (must_be_compiled(m) && !CompileBroker::compilation_is_in_queue(m)) { // This path is unusual, mostly used by the '-Xcomp' stress test mode. CompLevel level = initial_compile_level(m); + CompileTask::CompileReason reason = CompileTask::Reason_MustBeCompiled; if (PrintTieredEvents) { print_event(FORCE_COMPILE, m(), m(), InvocationEntryBci, level); } - CompileBroker::compile_method(m, InvocationEntryBci, level, 0, CompileTask::Reason_MustBeCompiled, THREAD); + // Check AOT code too + if (TrainingData::have_data()) { + MethodTrainingData* mtd = MethodTrainingData::find_fast(m); + if (mtd != nullptr) { + CompileTrainingData* ctd = mtd->compile_data_for_aot_code(level); + if (ctd != nullptr && (ctd->init_deps_left_acquire() == 0)) { + AOTCodeEntry* aot_code_entry = find_aot_code_entry(m, level, reason); + if (aot_code_entry != nullptr) { + // This is blocked compilation - return here after it is finished + CompileBroker::compile_method(m, InvocationEntryBci, level, 0, aot_code_entry, reason, THREAD); + } // Request normal JIT compilation too for -Xcomp + } + } + } + // Request normal JIT compilation + CompileBroker::compile_method(m, InvocationEntryBci, level, 0, nullptr, reason, THREAD); } } +// Exactly once, when each class is initialized, check it for +// dependent CompileTrainingData blocks, and notify each one that +// one of its class initialization dependencies has been satisfied. +// See comments in class CompileTrainingData for details. void CompilationPolicy::replay_training_at_init_impl(InstanceKlass* klass, JavaThread* current) { + // This method must run in a single TrainingReplayThread which + // services the training replay queue. There is no need for atomic + // test-and-set of has_init_deps_processed because it is only set + // here, after a test. The single thread cannot race with itself. + // FIXME: Add asserts (a) we're in a TRT, and (b) there's just one. if (!klass->has_init_deps_processed()) { ResourceMark rm; log_debug(training)("Replay training: %s", klass->external_name()); @@ -144,16 +203,22 @@ void CompilationPolicy::replay_training_at_init_impl(InstanceKlass* klass, JavaT guarantee(ktd->has_holder(), ""); ktd->notice_fully_initialized(); // sets klass->has_init_deps_processed bit assert(klass->has_init_deps_processed(), ""); + if (AOTCompileEagerly) { + GrowableArray mtds; ktd->iterate_comp_deps([&](CompileTrainingData* ctd) { if (ctd->init_deps_left_acquire() == 0) { MethodTrainingData* mtd = ctd->method(); if (mtd->has_holder()) { - const methodHandle mh(current, const_cast(mtd->holder())); - CompilationPolicy::maybe_compile_early(mh, current); + mtds.push(mtd); } } }); + for (int i = 0; i < mtds.length(); i++) { + MethodTrainingData* mtd = mtds.at(i); + const methodHandle mh(current, const_cast(mtd->holder())); + CompilationPolicy::maybe_compile_early(mh, mtd, current); + } } } } @@ -161,7 +226,8 @@ void CompilationPolicy::replay_training_at_init_impl(InstanceKlass* klass, JavaT void CompilationPolicy::replay_training_at_init(InstanceKlass* klass, JavaThread* current) { assert(klass->is_initialized(), ""); - if (TrainingData::have_data() && klass->in_aot_cache()) { + if (TrainingData::have_data() && klass->in_aot_cache() && + !CDSConfig::is_dumping_final_static_archive()) { // No need during assembly phase _training_replay_queue.push(klass, TrainingReplayQueue_lock, current); } } @@ -518,6 +584,16 @@ void CompilationPolicy::initialize() { #ifdef _LP64 // Turn on ergonomic compiler count selection + if (AOTCodeCache::maybe_dumping_code()) { + // Assembly phase runs C1 and C2 compilation in separate phases, + // and can use all the CPU threads it can reach. Adjust the common + // options before policy starts overwriting them. + FLAG_SET_ERGO_IF_DEFAULT(UseDynamicNumberOfCompilerThreads, false); + FLAG_SET_ERGO_IF_DEFAULT(CICompilerCountPerCPU, false); + if (FLAG_IS_DEFAULT(CICompilerCount)) { + count = MAX2(count, os::active_processor_count()); + } + } if (FLAG_IS_DEFAULT(CICompilerCountPerCPU) && FLAG_IS_DEFAULT(CICompilerCount)) { FLAG_SET_DEFAULT(CICompilerCountPerCPU, true); } @@ -526,6 +602,8 @@ void CompilationPolicy::initialize() { int log_cpu = log2i(os::active_processor_count()); int loglog_cpu = log2i(MAX2(log_cpu, 1)); count = MAX2(log_cpu * loglog_cpu * 3 / 2, min_count); + } + if (FLAG_IS_DEFAULT(CICompilerCount)) { // Make sure there is enough space in the code cache to hold all the compiler buffers size_t c1_size = 0; #ifdef COMPILER1 @@ -574,6 +652,15 @@ void CompilationPolicy::initialize() { set_c1_count(MAX2(count / 3, 1)); set_c2_count(MAX2(count - c1_count(), 1)); } + if (AOTCodeCache::is_code_load_thread_on()) { + // At minimum we need 2 threads to load C1 and C2 AOT code in parallel + if (!c2_only) { + set_ac1_count(1); + } + if (!c1_only) { + set_ac2_count(1); + } + } assert(count == c1_count() + c2_count(), "inconsistent compiler thread count"); set_increase_threshold_at_ratio(); } else { @@ -693,7 +780,7 @@ CompileTask* CompilationPolicy::select_task(CompileQueue* compile_queue, JavaThr Method* max_method = nullptr; int64_t t = nanos_to_millis(os::javaTimeNanos()); - // Iterate through the queue and find a method with a maximum rate. + // Iterate through the queue and select highest priority task. for (CompileTask* task = compile_queue->first(); task != nullptr;) { CompileTask* next_task = task->next(); // If a method was unloaded or has been stale for some time, remove it from the queue. @@ -703,6 +790,23 @@ CompileTask* CompilationPolicy::select_task(CompileQueue* compile_queue, JavaThr task = next_task; continue; } + if (task->is_aot_load()) { + // AOTCodeCache tasks are on separate queue, and they should load fast. There is no need to walk + // the rest of the queue, just take the task and go. + // + // If the task will load "preload" (AP4) code, we could do a quick poll here + // to see if, in fact, the fully optimized AOT code (A4) is ready, because the + // clinit dependencies have been concurrently satisfied while the AP4 task was + // waiting on the ac2 loading queue. In addition, when the JVM finds that the + // A4 task is ready, it tries to queue it, but if the AP4 is already queued, + // the JVM will quietly drop the A4 request. This means the poorer AP4 code + // will be used, until the invocation counter times out and a proper JIT task + // is queued - the benefit from A4 is lost in this case. + // + // Patching this hole did not give performance benefits in early testing, but + // it may help with certain scales or configurations in the future. + return task; + } if (task->is_blocking() && task->compile_reason() == CompileTask::Reason_Whitebox) { // CTW tasks, submitted as blocking Whitebox requests, do not participate in rate // selection and/or any level adjustments. Just return them in order. @@ -714,20 +818,20 @@ CompileTask* CompilationPolicy::select_task(CompileQueue* compile_queue, JavaThr if (PrintTieredEvents) { print_event(REMOVE_FROM_QUEUE, method, method, task->osr_bci(), (CompLevel) task->comp_level()); } - method->clear_queued_for_compilation(); compile_queue->remove_and_mark_stale(task); + method->clear_queued_for_compilation(); task = next_task; continue; } update_rate(t, mh); - if (max_task == nullptr || compare_methods(method, max_method)) { - // Select a method with the highest rate + // Prefer Reason_MustBeCompiled task. Otherwise use method heuristics selection. + if (max_task == nullptr || compare_tasks(task, max_task)) { max_task = task; max_method = method; } if (task->is_blocking()) { - if (max_blocking_task == nullptr || compare_methods(method, max_blocking_task->method())) { + if (max_blocking_task == nullptr || compare_tasks(task, max_blocking_task)) { max_blocking_task = task; } } @@ -753,7 +857,9 @@ CompileTask* CompilationPolicy::select_task(CompileQueue* compile_queue, JavaThr max_task->set_comp_level(CompLevel_limited_profile); max_task->transfer_directive(directive_matcher); - if (CompileBroker::compilation_is_complete(max_method_h, max_task->osr_bci(), CompLevel_limited_profile)) { + if (CompileBroker::compilation_is_complete(max_method_h, max_task->osr_bci(), CompLevel_limited_profile, + nullptr /* AOTCodeEntry* */, + CompileTask::Reason_None)) { if (PrintTieredEvents) { print_event(REMOVE_FROM_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level()); } @@ -839,7 +945,9 @@ void CompilationPolicy::compile(const methodHandle& mh, int bci, CompLevel level MutexLocker ml(Compile_lock); NoSafepointVerifier nsv; if (mh->has_compiled_code()) { + // All new activations will use the interpreter. mh->code()->make_not_used(); + // And handle old activations by deoptimizing them... } // Deoptimize immediately (we don't have to wait for a compile). JavaThread* jt = THREAD; @@ -886,7 +994,24 @@ void CompilationPolicy::compile(const methodHandle& mh, int bci, CompLevel level } int hot_count = (bci == InvocationEntryBci) ? mh->invocation_count() : mh->backedge_count(); update_rate(nanos_to_millis(os::javaTimeNanos()), mh); - CompileBroker::compile_method(mh, bci, level, hot_count, CompileTask::Reason_Tiered, THREAD); + CompileTask::CompileReason reason = CompileTask::Reason_Tiered; + // Check AOT code too + if (TrainingData::have_data() && (bci == InvocationEntryBci)) { + MethodTrainingData* mtd = MethodTrainingData::find_fast(mh); + if (mtd != nullptr) { + CompileTrainingData* ctd = mtd->compile_data_for_aot_code(level); + if (ctd != nullptr && (ctd->init_deps_left_acquire() == 0)) { + AOTCodeEntry* aot_code_entry = find_aot_code_entry(mh, level, reason); + if (aot_code_entry != nullptr) { + CompileBroker::compile_method(mh, InvocationEntryBci, level, hot_count, aot_code_entry, reason, THREAD); + if (UseInterpreter) { + return; + } // Request normal JIT compilation too for -Xcomp + } + } + } + } + CompileBroker::compile_method(mh, bci, level, hot_count, nullptr, reason, THREAD); } } @@ -969,6 +1094,17 @@ bool CompilationPolicy::compare_methods(Method* x, Method* y) { return false; } +bool CompilationPolicy::compare_tasks(CompileTask* x, CompileTask* y) { + assert(!x->is_aot_load() && !y->is_aot_load(), "AOT code caching tasks are not expected here"); + bool x_must_compile = x->compile_reason() == CompileTask::Reason_MustBeCompiled; + bool y_must_compile = y->compile_reason() == CompileTask::Reason_MustBeCompiled; + if (x_must_compile != y_must_compile) { + return x_must_compile; + } + // Tasks have the same MustBeCompiled priority. Compare methods. + return compare_methods(x->method(), y->method()); +} + // Is method profiled enough? bool CompilationPolicy::is_method_profiled(const methodHandle& method) { MethodData* mdo = method->method_data(); @@ -1198,6 +1334,10 @@ CompLevel CompilationPolicy::trained_transition(const methodHandle& method, Comp * 3 - C1 with full profiling (CompLevel_full_profile) * 4 - C2 (CompLevel_full_optimization) * + * Special AOT states: + * AP4 - C2 with "preload" clinit barriers (CompLevel_preload_optimization) + * A4 - C2 with less static final folding (CompLevel_full_optimization) + * * Common state transition patterns: * a. 0 -> 3 -> 4. * The most common path. But note that even in this straightforward case @@ -1223,6 +1363,11 @@ CompLevel CompilationPolicy::trained_transition(const methodHandle& method, Comp * or because of a deopt that didn't require reprofiling (compilation won't happen in this case because * the compiled version already exists). * + * f. AP4 -> A4 -> 4 or just AP4 -> 4 + * AP4 "preload" code is loaded before clinit dependencies are satisfied. + * After clinit dependencies A4 is loaded, with an invocation counter. + * After that counter finishes, the JIT may reoptimize to level 4. + * * Note that since state 0 can be reached from any other state via deoptimization different loops * are possible. * diff --git a/src/hotspot/share/compiler/compilationPolicy.hpp b/src/hotspot/share/compiler/compilationPolicy.hpp index e9a2de076ff..a9fdc3cb81e 100644 --- a/src/hotspot/share/compiler/compilationPolicy.hpp +++ b/src/hotspot/share/compiler/compilationPolicy.hpp @@ -246,7 +246,7 @@ class CompilationPolicy : AllStatic { typedef CompilationPolicyUtils::Queue TrainingReplayQueue; static int64_t _start_time; - static int _c1_count, _c2_count; + static int _c1_count, _c2_count, _ac1_count, _ac2_count; static double _increase_threshold_at_ratio; static TrainingReplayQueue _training_replay_queue; @@ -297,6 +297,7 @@ class CompilationPolicy : AllStatic { inline static double weight(Method* method); // Apply heuristics and return true if x should be compiled before y inline static bool compare_methods(Method* x, Method* y); + inline static bool compare_tasks(CompileTask* x, CompileTask* y); // Compute event rate for a given method. The rate is the number of event (invocations + backedges) // per millisecond. inline static void update_rate(int64_t t, const methodHandle& method); @@ -313,6 +314,8 @@ class CompilationPolicy : AllStatic { static void set_c1_count(int x) { _c1_count = x; } static void set_c2_count(int x) { _c2_count = x; } + static void set_ac1_count(int x) { _ac1_count = x; } + static void set_ac2_count(int x) { _ac2_count = x; } enum EventType { CALL, LOOP, COMPILE, FORCE_COMPILE, FORCE_RECOMPILE, REMOVE_FROM_QUEUE, UPDATE_IN_QUEUE, REPROFILE, MAKE_NOT_ENTRANT }; static void print_event_on(outputStream *st, EventType type, Method* m, Method* im, int bci, CompLevel level); @@ -336,12 +339,14 @@ class CompilationPolicy : AllStatic { // m must be compiled before executing it static bool must_be_compiled(const methodHandle& m, int comp_level = CompLevel_any); - static void maybe_compile_early(const methodHandle& m, TRAPS); + static void maybe_compile_early(const methodHandle& m, MethodTrainingData* mtd, TRAPS); static void replay_training_at_init_impl(InstanceKlass* klass, JavaThread* current); public: static int min_invocations() { return Tier4MinInvocationThreshold; } static int c1_count() { return _c1_count; } static int c2_count() { return _c2_count; } + static int ac1_count() { return _ac1_count; } + static int ac2_count() { return _ac2_count; } static int compiler_count(CompLevel comp_level); // If m must_be_compiled then request a compilation from the CompileBroker. // This supports the -Xcomp option. diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index d97feeece77..0349e1f83b0 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -27,6 +27,7 @@ #include "classfile/symbolTable.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" +#include "code/aotCodeCache.hpp" #include "code/codeCache.hpp" #include "code/codeHeapState.hpp" #include "code/dependencyContext.hpp" @@ -67,7 +68,7 @@ #include "runtime/safepointVerifiers.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/threads.hpp" -#include "runtime/threadSMR.hpp" +#include "runtime/threadSMR.inline.hpp" #include "runtime/timerTrace.hpp" #include "runtime/vframe.inline.hpp" #include "utilities/debug.hpp" @@ -128,13 +129,19 @@ AbstractCompiler* CompileBroker::_compilers[2]; // The maximum numbers of compiler threads to be determined during startup. int CompileBroker::_c1_count = 0; int CompileBroker::_c2_count = 0; +int CompileBroker::_ac1_count = 0; +int CompileBroker::_ac2_count = 0; // An array of compiler names as Java String objects jobject* CompileBroker::_compiler1_objects = nullptr; jobject* CompileBroker::_compiler2_objects = nullptr; +jobject* CompileBroker::_ac1_objects = nullptr; +jobject* CompileBroker::_ac2_objects = nullptr; CompileLog** CompileBroker::_compiler1_logs = nullptr; CompileLog** CompileBroker::_compiler2_logs = nullptr; +CompileLog** CompileBroker::_ac1_logs = nullptr; +CompileLog** CompileBroker::_ac2_logs = nullptr; // These counters are used to assign an unique ID to each compilation. volatile jint CompileBroker::_compilation_id = 0; @@ -174,6 +181,7 @@ elapsedTimer CompileBroker::_t_bailedout_compilation; uint CompileBroker::_total_bailout_count = 0; uint CompileBroker::_total_invalidated_count = 0; +uint CompileBroker::_total_not_entrant_count = 0; uint CompileBroker::_total_compile_count = 0; uint CompileBroker::_total_osr_compile_count = 0; uint CompileBroker::_total_standard_compile_count = 0; @@ -188,9 +196,13 @@ uint CompileBroker::_sum_nmethod_code_size = 0; jlong CompileBroker::_peak_compilation_time = 0; CompilerStatistics CompileBroker::_stats_per_level[CompLevel_full_optimization]; +CompilerStatistics CompileBroker::_aot_stats; +CompilerStatistics CompileBroker::_aot_stats_per_level[CompLevel_full_optimization + 1]; CompileQueue* CompileBroker::_c2_compile_queue = nullptr; CompileQueue* CompileBroker::_c1_compile_queue = nullptr; +CompileQueue* CompileBroker::_ac1_compile_queue = nullptr; +CompileQueue* CompileBroker::_ac2_compile_queue = nullptr; bool compileBroker_init() { if (LogEvents) { @@ -341,35 +353,34 @@ void CompileQueue::add(CompileTask* task) { * compilation is disabled. */ void CompileQueue::delete_all() { - MutexLocker mu(MethodCompileQueue_lock); - CompileTask* current = _first; - + CompileTask* current; + { + MonitorLocker ml(MethodCompileQueue_lock); + current = _first; + // Detach queue + _first = nullptr; + _last = nullptr; + ml.notify_all(); + } // Iterate over all tasks in the compile queue while (current != nullptr) { CompileTask* next = current->next(); + current->set_prev(nullptr); + current->set_next(nullptr); if (!current->is_blocking()) { // Non-blocking task. No one is waiting for it, delete it now. delete current; } else { // Blocking task. By convention, it is the waiters responsibility // to delete the task. We cannot delete it here, because we do not - // coordinate with waiters. We will notify the waiters later. + // coordinate with waiters. Complete it and notify the waiters. + MonitorLocker ml(CompileTaskWait_lock); + current->set_failure_reason("Compilation is disabled."); + current->mark_complete(); + ml.notify_all(); } current = next; } - _first = nullptr; - _last = nullptr; - - // Wake up all blocking task waiters to deal with remaining blocking - // tasks. This is not a performance sensitive path, so we do this - // unconditionally to simplify coding/testing. - { - MonitorLocker ml(Thread::current(), CompileTaskWait_lock); - ml.notify_all(); - } - - // Wake up all threads that block on the queue. - MethodCompileQueue_lock->notify_all(); } /** @@ -491,17 +502,15 @@ void CompileQueue::remove_and_mark_stale(CompileTask* task) { // methods in the compile queue need to be marked as used on the stack // so that they don't get reclaimed by Redefine Classes void CompileQueue::mark_on_stack() { - CompileTask* task = _first; - while (task != nullptr) { + for (CompileTask* task = _first; task != nullptr; task = task->next()) { task->mark_on_stack(); - task = task->next(); } } -CompileQueue* CompileBroker::compile_queue(int comp_level) { - if (is_c2_compile(comp_level)) return _c2_compile_queue; - if (is_c1_compile(comp_level)) return _c1_compile_queue; +CompileQueue* CompileBroker::compile_queue(int comp_level, bool is_aot) { + if (is_c2_compile(comp_level)) return ((is_aot && (_ac2_count > 0)) ? _ac2_compile_queue : _c2_compile_queue); + if (is_c1_compile(comp_level)) return ((is_aot && (_ac1_count > 0)) ? _ac1_compile_queue : _c1_compile_queue); return nullptr; } @@ -527,6 +536,12 @@ void CompileBroker::print_compile_queues(outputStream* st) { if (_c2_compile_queue != nullptr) { _c2_compile_queue->print(st); } + if (_ac1_compile_queue != nullptr) { + _ac1_compile_queue->print(st); + } + if (_ac2_compile_queue != nullptr) { + _ac2_compile_queue->print(st); + } } void CompileQueue::print(outputStream* st) { @@ -587,15 +602,17 @@ void CompileBroker::compilation_init(JavaThread* THREAD) { // Set the interface to the current compiler(s). _c1_count = CompilationPolicy::c1_count(); _c2_count = CompilationPolicy::c2_count(); + _ac1_count = AOTCodeCache::is_using_code() ? CompilationPolicy::ac1_count() : 0; + _ac2_count = AOTCodeCache::is_using_code() ? CompilationPolicy::ac2_count() : 0; #ifdef COMPILER1 - if (_c1_count > 0) { + if (_c1_count > 0 || _ac1_count > 0) { _compilers[0] = new Compiler(); } #endif // COMPILER1 #ifdef COMPILER2 - if (_c2_count > 0) { + if (_c2_count > 0 || _ac2_count > 0) { _compilers[1] = new C2Compiler(); // Register c2 as c2 CompilerPhaseType idToPhase mapping is explicit. // idToPhase mapping for c2 is in opto/phasetype.hpp @@ -708,9 +725,17 @@ void CompileBroker::compilation_init(JavaThread* THREAD) { CHECK); } + if (AOTCodeCache::is_on()) { + log_info(aot, codecache, init)("CompileBroker is initialized"); + } _initialized = true; } +Handle CompileBroker::create_thread_oop(const char* name, TRAPS) { + Handle thread_oop = JavaThread::create_system_thread_object(name, CHECK_NH); + return thread_oop; +} + void TrainingReplayThread::training_replay_thread_entry(JavaThread* thread, TRAPS) { CompilationPolicy::replay_training_at_init_loop(thread); } @@ -812,7 +837,16 @@ JavaThread* CompileBroker::make_thread(ThreadType type, jobject thread_handle, C if (new_thread != nullptr && new_thread->osthread() != nullptr) { if (type == compiler_t) { - CompilerThread::cast(new_thread)->set_compiler(comp); + CompilerThread* comp_thread = CompilerThread::cast(new_thread); + comp_thread->set_compiler(comp); + // Count AOT compiler thread + bool is_aot_thread = (queue != nullptr) && + (queue == _ac1_compile_queue || queue == _ac2_compile_queue); + if (is_aot_thread) { + comp_thread->set_is_aot_thread(); + int num = comp->num_aot_compiler_threads() + 1; + comp->set_num_aot_compiler_threads(num); + } } // Note that we cannot call os::set_priority because it expects Java @@ -835,6 +869,11 @@ JavaThread* CompileBroker::make_thread(ThreadType type, jobject thread_handle, C JavaThread::start_internal_daemon(THREAD, new_thread, thread_oop, NearMaxPriority); } else { // osthread initialization failure + if (type == compiler_t && (queue == _ac1_compile_queue || queue == _ac2_compile_queue)) { + // Failure to create AOT compilation threads + vm_exit_during_initialization("java.lang.OutOfMemoryError", + os::native_thread_creation_failed_msg()); + } if (UseDynamicNumberOfCompilerThreads && type == compiler_t && comp->num_compiler_threads() > 0) { // The new thread is not known to Thread-SMR yet so we can just delete. @@ -874,11 +913,22 @@ static void print_compiler_threads(stringStream& msg) { } } +static void print_compiler_thread(JavaThread *ct) { + if (trace_compiler_threads()) { + ResourceMark rm; + ThreadsListHandle tlh; // name() depends on the TLH. + assert(tlh.includes(ct), "ct=" INTPTR_FORMAT " exited unexpectedly.", p2i(ct)); + stringStream msg; + msg.print("Added initial compiler thread %s", ct->name()); + print_compiler_threads(msg); + } +} + void CompileBroker::init_compiler_threads() { // Ensure any exceptions lead to vm_exit_during_initialization. EXCEPTION_MARK; #if !defined(ZERO) - assert(_c2_count > 0 || _c1_count > 0, "No compilers?"); + assert(_c2_count > 0 || _c1_count > 0 || _ac2_count > 0 || _ac1_count > 0, "No compilers?"); #endif // !ZERO // Initialize the compilation queue if (_c2_count > 0) { @@ -893,6 +943,18 @@ void CompileBroker::init_compiler_threads() { _compiler1_logs = NEW_C_HEAP_ARRAY(CompileLog*, _c1_count, mtCompiler); } + if (_ac1_count > 0) { // AOT C1 is present + _ac1_compile_queue = new CompileQueue("C1 AOT code compile queue"); + _ac1_objects = NEW_C_HEAP_ARRAY(jobject, _ac1_count, mtCompiler); + _ac1_logs = NEW_C_HEAP_ARRAY(CompileLog*, _ac1_count, mtCompiler); + } + if (_ac2_count > 0) { // AOT C2 is present + _ac2_compile_queue = new CompileQueue("C2 AOT code compile queue"); + _ac2_objects = NEW_C_HEAP_ARRAY(jobject, _ac2_count, mtCompiler); + _ac2_logs = NEW_C_HEAP_ARRAY(CompileLog*, _ac2_count, mtCompiler); + } + char name_buffer[256]; + for (int i = 0; i < _c2_count; i++) { // Create a name for our thread. jobject thread_handle = create_compiler_thread(_compilers[1], i, CHECK); @@ -903,14 +965,7 @@ void CompileBroker::init_compiler_threads() { JavaThread *ct = make_thread(compiler_t, thread_handle, _c2_compile_queue, _compilers[1], THREAD); assert(ct != nullptr, "should have been handled for initial thread"); _compilers[1]->set_num_compiler_threads(i + 1); - if (trace_compiler_threads()) { - ResourceMark rm; - ThreadsListHandle tlh; // name() depends on the TLH. - assert(tlh.includes(ct), "ct=" INTPTR_FORMAT " exited unexpectedly.", p2i(ct)); - stringStream msg; - msg.print("Added initial compiler thread %s", ct->name()); - print_compiler_threads(msg); - } + print_compiler_thread(ct); } } @@ -924,18 +979,36 @@ void CompileBroker::init_compiler_threads() { JavaThread *ct = make_thread(compiler_t, thread_handle, _c1_compile_queue, _compilers[0], THREAD); assert(ct != nullptr, "should have been handled for initial thread"); _compilers[0]->set_num_compiler_threads(i + 1); - if (trace_compiler_threads()) { - ResourceMark rm; - ThreadsListHandle tlh; // name() depends on the TLH. - assert(tlh.includes(ct), "ct=" INTPTR_FORMAT " exited unexpectedly.", p2i(ct)); - stringStream msg; - msg.print("Added initial compiler thread %s", ct->name()); - print_compiler_threads(msg); - } + print_compiler_thread(ct); } } - if (UsePerfData) { + if (_ac2_count > 0) { // AOT C2 is present + assert(_ac2_count == 1, "update this code if more AOT threads are needed"); + os::snprintf_checked(name_buffer, sizeof(name_buffer), "C2 AOT code caching CompilerThread"); + Handle thread_oop = create_thread_oop(name_buffer, CHECK); + jobject thread_handle = JNIHandles::make_global(thread_oop); + _ac2_objects[0] = thread_handle; + _ac2_logs[0] = nullptr; + + JavaThread *ct = make_thread(compiler_t, thread_handle, _ac2_compile_queue, _compilers[1], THREAD); + assert(ct != nullptr, "should have been handled for initial thread"); + print_compiler_thread(ct); + } + if (_ac1_count > 0) { // AOT C1 is present + assert(_ac1_count == 1, "update this code if more AOT threads are needed"); + os::snprintf_checked(name_buffer, sizeof(name_buffer), "C1 AOT code caching CompilerThread"); + Handle thread_oop = create_thread_oop(name_buffer, CHECK); + jobject thread_handle = JNIHandles::make_global(thread_oop); + _ac1_objects[0] = thread_handle; + _ac1_logs[0] = nullptr; + + JavaThread *ct = make_thread(compiler_t, thread_handle, _ac1_compile_queue, _compilers[0], THREAD); + assert(ct != nullptr, "should have been handled for initial thread"); + print_compiler_thread(ct); + } + + if (UsePerfData && (_c1_count + _c2_count) > 0) { PerfDataManager::create_constant(SUN_CI, "threads", PerfData::U_Bytes, _c1_count + _c2_count, CHECK); } @@ -955,7 +1028,8 @@ void CompileBroker::init_compiler_threads() { void CompileBroker::init_training_replay() { // Ensure any exceptions lead to vm_exit_during_initialization. EXCEPTION_MARK; - if (TrainingData::have_data()) { + // No need to replay training during assembly phase. Only training data is used. + if (TrainingData::have_data() && !CDSConfig::is_dumping_final_static_archive()) { Handle thread_oop = JavaThread::create_system_thread_object("Training replay thread", CHECK); jobject thread_handle = JNIHandles::make_local(THREAD, thread_oop()); make_thread(training_replay_t, thread_handle, nullptr, nullptr, THREAD); @@ -1055,6 +1129,12 @@ void CompileBroker::mark_on_stack() { if (_c1_compile_queue != nullptr) { _c1_compile_queue->mark_on_stack(); } + if (_ac1_compile_queue != nullptr) { + _ac1_compile_queue->mark_on_stack(); + } + if (_ac2_compile_queue != nullptr) { + _ac2_compile_queue->mark_on_stack(); + } } // ------------------------------------------------------------------ @@ -1062,6 +1142,7 @@ void CompileBroker::mark_on_stack() { // // Request compilation of a method. void CompileBroker::compile_method_base(const methodHandle& method, + AOTCodeEntry* aot_code_entry, int osr_bci, int comp_level, int hot_count, @@ -1069,29 +1150,51 @@ void CompileBroker::compile_method_base(const methodHandle& method, bool blocking, Thread* thread) { guarantee(!method->is_abstract(), "cannot compile abstract methods"); - assert(method->method_holder()->is_instance_klass(), - "sanity check"); - assert(!method->method_holder()->is_not_initialized(), - "method holder must be initialized"); + precond(method->method_holder()->is_instance_klass()); + precond(compile_reason != CompileTask::Reason_AOTPreload || aot_code_entry != nullptr); + assert(!method->method_holder()->is_not_initialized() || + compile_reason == CompileTask::Reason_AOTPreload || + CompileTask::reason_is_aot_compile(compile_reason), "method holder must be initialized"); assert(!method->is_method_handle_intrinsic(), "do not enqueue these guys"); + bool aot_preload_comp = (compile_reason == CompileTask::Reason_AOTPreload) || + (compile_reason == CompileTask::Reason_AOTCompileForPreload); + bool aot_comp = (compile_reason == CompileTask::Reason_AOTCompile) || + (aot_code_entry != nullptr); + const char* aotc = aot_preload_comp ? "AP" : (aot_comp ? " A" : " "); + if (CIPrintRequests) { - tty->print("request: "); - method->print_short_name(tty); + ResourceMark rm; + stringStream ss; + ss.print("request %16s: %s%d", CompileTask::reason_name(compile_reason), aotc, comp_level); if (osr_bci != InvocationEntryBci) { - tty->print(" osr_bci: %d", osr_bci); + ss.print(" osr_bci: %d", osr_bci); } - tty->print(" level: %d comment: %s count: %d", comp_level, CompileTask::reason_name(compile_reason), hot_count); - if (hot_count > 0) { - tty->print(" hot: yes"); + ss.print(" hot_count: %d", hot_count); + method->print_short_name(&ss); + tty->print_cr("%s", ss.freeze()); + } + LogStreamHandle(Debug, aot, codecache, compilation) log; + if (log.is_enabled() && (AOTCodeCache::is_using_code() || AOTCodeCache::is_dumping_code())) { + ResourceMark rm; + MethodTrainingData* mtd = MethodTrainingData::have_data() ? MethodTrainingData::find_fast(method) : nullptr; + MethodCounters* mc = method->method_counters(); + const char* name = method->name_and_sig_as_C_string(true /* use_double_colon */); + const char* osrn = (osr_bci != InvocationEntryBci) ? "% " : ""; + log.print("request %16s: %s%d %s%s", CompileTask::reason_name(compile_reason), aotc, comp_level, osrn, name); + if (mtd != nullptr) { + log.print(" (MTD invoke: %d, backedge: %d)", mtd->invocation_count(), mtd->backedge_count()); } - tty->cr(); + if (mc != nullptr) { + log.print(" (MC invoke: %d, backedge: %d)", mc->invocation_counter()->count(), + mc->backedge_counter()->count()); + } + log.cr(); } - // A request has been made for compilation. Before we do any // real work, check to see if the method has been compiled // in the meantime with a definitive result. - if (compilation_is_complete(method, osr_bci, comp_level)) { + if (compilation_is_complete(method, osr_bci, comp_level, aot_code_entry, compile_reason)) { return; } @@ -1120,11 +1223,15 @@ void CompileBroker::compile_method_base(const methodHandle& method, // Tiered policy requires MethodCounters to exist before adding a method to // the queue. Create if we don't have them yet. - method->get_method_counters(thread); + if (method->get_method_counters(thread) == nullptr) { + return; // metaspace has hit an OOM + } + + bool is_aot = (aot_code_entry != nullptr); // Outputs from the following MutexLocker block: - CompileTask* task = nullptr; - CompileQueue* queue = compile_queue(comp_level); + CompileTask* task = nullptr; + CompileQueue* queue = compile_queue(comp_level, is_aot); // Acquire our lock. { @@ -1140,7 +1247,7 @@ void CompileBroker::compile_method_base(const methodHandle& method, // We need to check again to see if the compilation has // completed. A previous compilation may have registered // some result. - if (compilation_is_complete(method, osr_bci, comp_level)) { + if (compilation_is_complete(method, osr_bci, comp_level, aot_code_entry, compile_reason)) { return; } @@ -1194,7 +1301,7 @@ void CompileBroker::compile_method_base(const methodHandle& method, task = create_compile_task(queue, compile_id, method, osr_bci, comp_level, - hot_count, compile_reason, + hot_count, aot_code_entry, compile_reason, blocking); } @@ -1203,9 +1310,54 @@ void CompileBroker::compile_method_base(const methodHandle& method, } } +static bool is_blocking_compilation(DirectiveSet* directive, CompileTask::CompileReason compile_reason) { + return ReplayCompiles || !directive->BackgroundCompilationOption || + (AOTPreloadBlocking && (compile_reason == CompileTask::Reason_AOTPreload)); +} + +void CompileBroker::preload_aot_method(const methodHandle& method, AOTCodeEntry* aot_code_entry, TRAPS) { + // Don't need most of the checks for AOT code preloading. + precond(_initialized); + precond(aot_code_entry != nullptr && aot_code_entry->for_preload()); + // If the compiler is shut off due to code cache getting full + // fail out now so blocking compiles don't hang the java thread + if (should_compile_new_jobs()) { + int osr_bci = InvocationEntryBci; + int comp_level = CompLevel_full_optimization; + int hot_count = 0; + CompileTask::CompileReason compile_reason = CompileTask::Reason_AOTPreload; + + AbstractCompiler *comp = CompileBroker::compiler(comp_level); + assert(comp != nullptr, "Ensure we have a compiler"); + + nmethod* method_code = method->code(); + if (method_code != nullptr) { + if (compilation_is_complete(method, osr_bci, comp_level, aot_code_entry, compile_reason)) { + return; + } + } + if (method->is_not_compilable(comp_level)) { + return; + } + + // JVMTI -- post_compile_event requires jmethod_id() that may require + // a lock the compiling thread can not acquire. Prefetch it here. + if (JvmtiExport::should_post_compiled_method_load()) { + method->jmethod_id(); + } + + CompilerDirectiveMatcher matcher(method, comp_level); + bool is_blocking = is_blocking_compilation(matcher.directive_set(), compile_reason); + // CompileBroker::compile_method can trap and can have pending async exception. + compile_method_base(method, aot_code_entry, osr_bci, comp_level, hot_count, compile_reason, + is_blocking, THREAD); + } +} + nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, int comp_level, int hot_count, + AOTCodeEntry* aot_code_entry, CompileTask::CompileReason compile_reason, TRAPS) { // Do nothing if compilebroker is not initialized or compiles are submitted on level none @@ -1218,13 +1370,14 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, CompilerDirectiveMatcher matcher(method, comp_level); // CompileBroker::compile_method can trap and can have pending async exception. - nmethod* nm = CompileBroker::compile_method(method, osr_bci, comp_level, hot_count, compile_reason, matcher.directive_set(), THREAD); + nmethod* nm = CompileBroker::compile_method(method, osr_bci, comp_level, hot_count, aot_code_entry, compile_reason, matcher.directive_set(), THREAD); return nm; } nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, int comp_level, int hot_count, + AOTCodeEntry* aot_code_entry, CompileTask::CompileReason compile_reason, DirectiveSet* directive, TRAPS) { @@ -1233,8 +1386,15 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, assert(method->method_holder()->is_instance_klass(), "not an instance method"); assert(osr_bci == InvocationEntryBci || (0 <= osr_bci && osr_bci < method->code_size()), "bci out of range"); assert(!method->is_abstract() && (osr_bci == InvocationEntryBci || !method->is_native()), "cannot compile abstract/native methods"); - assert(!method->method_holder()->is_not_initialized(), "method holder must be initialized"); + assert(!method->method_holder()->is_not_initialized() || + compile_reason == CompileTask::Reason_AOTPreload || + CompileTask::reason_is_aot_compile(compile_reason), "method holder must be initialized"); // return quickly if possible + bool aot_compilation = CDSConfig::is_dumping_aot_code(); + if (aot_compilation && !CompileTask::reason_is_aot_compile(compile_reason)) { + // Skip normal compilations when compiling AOT code to speedup AOT compilation + return nullptr; + } // lock, make sure that the compilation // isn't prohibited in a straightforward way. @@ -1247,7 +1407,7 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, // standard compilation nmethod* method_code = method->code(); if (method_code != nullptr) { - if (compilation_is_complete(method, osr_bci, comp_level)) { + if (compilation_is_complete(method, osr_bci, comp_level, aot_code_entry, compile_reason)) { return method_code; } } @@ -1264,7 +1424,9 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, assert(!HAS_PENDING_EXCEPTION, "No exception should be present"); // some prerequisites that are compiler specific - if (comp->is_c2()) { + if (compile_reason != CompileTask::Reason_AOTPreload && + !CompileTask::reason_is_aot_compile(compile_reason) && + comp->is_c2()) { InternalOOMEMark iom(THREAD); method->constants()->resolve_string_constants(CHECK_AND_CLEAR_NONASYNC_NULL); // Resolve all classes seen in the signature of the method @@ -1319,8 +1481,8 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, if (!should_compile_new_jobs()) { return nullptr; } - bool is_blocking = !directive->BackgroundCompilationOption || ReplayCompiles; - compile_method_base(method, osr_bci, comp_level, hot_count, compile_reason, is_blocking, THREAD); + bool is_blocking = is_blocking_compilation(directive, compile_reason); + compile_method_base(method, aot_code_entry, osr_bci, comp_level, hot_count, compile_reason, is_blocking, THREAD); } // return requested nmethod @@ -1336,9 +1498,14 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, // CompileBroker::compilation_is_complete // // See if compilation of this method is already complete. -bool CompileBroker::compilation_is_complete(const methodHandle& method, - int osr_bci, - int comp_level) { +bool CompileBroker::compilation_is_complete(const methodHandle& method, + int osr_bci, + int comp_level, + AOTCodeEntry* aot_code_entry, + CompileTask::CompileReason compile_reason) { + if (CompileTask::reason_is_aot_compile(compile_reason)) { + return false; + } bool is_osr = (osr_bci != standard_entry_bci); if (is_osr) { if (method->is_not_osr_compilable(comp_level)) { @@ -1352,8 +1519,24 @@ bool CompileBroker::compilation_is_complete(const methodHandle& method, return true; } else { nmethod* result = method->code(); - if (result == nullptr) return false; - return comp_level == result->comp_level(); + if (result == nullptr) { + return false; + } + if (result->is_aot()) { + // See if an AOTCodeEntry has already been AOT-loaded by register_aot_method. + // Both this logic and register_aot_method run under MethodCompileQueue_lock. + // Note that CompilationPolicy::find_aot_code_entry also checks AOT-loading, + // but tentatively, not under the lock. + if (aot_code_entry == nullptr) { + return false; // Allow replace AOT code with normal JITed code + } else if (aot_code_entry == result->aot_code_entry()) { + return true; // Same code already AOT-loaded. + } else if (result->aot_preloaded()) { + // Allow replace preloaded AOT code with regular AOT code of the same level + return (comp_level != result->comp_level()); + } + } + return (comp_level == result->comp_level()); } } } @@ -1468,10 +1651,12 @@ CompileTask* CompileBroker::create_compile_task(CompileQueue* queue, int osr_bci, int comp_level, int hot_count, + AOTCodeEntry* aot_code_entry, CompileTask::CompileReason compile_reason, bool blocking) { CompileTask* new_task = new CompileTask(compile_id, method, osr_bci, comp_level, - hot_count, compile_reason, blocking); + hot_count, aot_code_entry, compile_reason, + blocking); queue->add(new_task); return new_task; } @@ -1490,40 +1675,13 @@ void CompileBroker::wait_for_completion(CompileTask* task) { JavaThread* thread = JavaThread::current(); methodHandle method(thread, task->method()); - bool free_task = true; { - // Wait until the task is complete or compilation is shut down. MonitorLocker ml(thread, CompileTaskWait_lock); - while (!task->is_complete() && !is_compilation_disabled_forever()) { + while (!task->is_complete()) { ml.wait(); } } - - // It is harmless to check this status without the lock, because - // completion is a stable property. - if (!task->is_complete()) { - // Task is not complete, likely because we are exiting for compilation - // shutdown. The task can still be reached through the queue, or executed - // by some compiler thread. There is no coordination with either MCQ lock - // holders or compilers, therefore we cannot delete the task. - // - // This will leave task allocated, which leaks it. At this (degraded) point, - // it is less risky to abandon the task, rather than attempting a more - // complicated deletion protocol. - free_task = false; - } - - if (free_task) { - assert(task->is_complete(), "Compilation should have completed"); - assert(task->next() == nullptr && task->prev() == nullptr, - "Completed task should not be in the queue"); - - // By convention, the waiter is responsible for deleting a - // blocking CompileTask. Since there is only one waiter ever - // waiting on a CompileTask, we know that no one else will - // be using this CompileTask; we can delete it. - delete task; - } + delete task; } void CompileBroker::wait_for_no_active_tasks() { @@ -1540,7 +1698,6 @@ bool CompileBroker::init_compiler_runtime() { AbstractCompiler* comp = thread->compiler(); // Final sanity check - the compiler object must exist guarantee(comp != nullptr, "Compiler object must exist"); - { // Must switch to native to allocate ci_env ThreadToNativeFromVM ttn(thread); @@ -1565,8 +1722,8 @@ bool CompileBroker::init_compiler_runtime() { return false; } - // C1 specific check - if (comp->is_c1() && (thread->get_buffer_blob() == nullptr)) { + // C1 specific check. AOT C1 thread does not use buffer to load AOT code. + if (comp->is_c1() && (thread->get_buffer_blob() == nullptr) && !thread->is_aot_thread()) { warning("Initialization of %s thread failed (no space to run compilers)", thread->name()); return false; } @@ -1591,7 +1748,7 @@ void CompileBroker::free_buffer_blob_if_allocated(CompilerThread* thread) { void CompileBroker::shutdown_compiler_runtime(AbstractCompiler* comp, CompilerThread* thread) { free_buffer_blob_if_allocated(thread); - if (comp->should_perform_shutdown()) { + if (comp->should_perform_shutdown(thread->is_aot_thread())) { // There are two reasons for shutting down the compiler // 1) compiler runtime initialization failed // 2) The code cache is full and the following flag is set: -XX:-UseCodeCacheFlushing @@ -1610,6 +1767,14 @@ void CompileBroker::shutdown_compiler_runtime(AbstractCompiler* comp, CompilerTh _c2_compile_queue->delete_all(); } + if (_ac1_compile_queue != nullptr) { + _ac1_compile_queue->delete_all(); + } + + if (_ac2_compile_queue != nullptr) { + _ac2_compile_queue->delete_all(); + } + // Set flags so that we continue execution with using interpreter only. UseCompiler = false; UseInterpreter = true; @@ -1627,13 +1792,25 @@ CompileLog* CompileBroker::get_log(CompilerThread* ct) { if (!LogCompilation) return nullptr; AbstractCompiler *compiler = ct->compiler(); - bool c1 = compiler->is_c1(); - jobject* compiler_objects = c1 ? _compiler1_objects : _compiler2_objects; - assert(compiler_objects != nullptr, "must be initialized at this point"); - CompileLog** logs = c1 ? _compiler1_logs : _compiler2_logs; - assert(logs != nullptr, "must be initialized at this point"); - int count = c1 ? _c1_count : _c2_count; - + jobject* compiler_objects = nullptr; + CompileLog** logs = nullptr; + int count = 0; + if (ct->queue() == _ac1_compile_queue) { + compiler_objects = _ac1_objects; + logs = _ac1_logs; + count = _ac1_count; + } else if (ct->queue() == _ac2_compile_queue) { + compiler_objects = _ac2_objects; + logs = _ac2_logs; + count = _ac2_count; + } else if (_c1_count + _c2_count > 0) { + bool c1 = compiler->is_c1(); + compiler_objects = c1 ? _compiler1_objects : _compiler2_objects; + assert(compiler_objects != nullptr, "must be initialized at this point"); + logs = c1 ? _compiler1_logs : _compiler2_logs; + assert(logs != nullptr, "must be initialized at this point"); + count = c1 ? _c1_count : _c2_count; + } // Find Compiler number by its threadObj. oop compiler_obj = ct->threadObj(); int compiler_number = 0; @@ -1670,6 +1847,8 @@ CompileLog* CompileBroker::get_log(CompilerThread* ct) { void CompileBroker::compiler_thread_loop() { CompilerThread* thread = CompilerThread::current(); CompileQueue* queue = thread->queue(); + precond(queue != nullptr); + // For the thread that initializes the ciObjectFactory // this resource mark holds all the shared objects ResourceMark rm; @@ -1756,7 +1935,9 @@ void CompileBroker::compiler_thread_loop() { task->set_failure_reason("breakpoints are present"); } - if (UseDynamicNumberOfCompilerThreads) { + // Don't use AOT compiler threads for dynamic C1 and C2 threads creation. + if (UseDynamicNumberOfCompilerThreads && + (queue == _c1_compile_queue || queue == _c2_compile_queue)) { possibly_add_compiler_threads(thread); assert(!thread->has_pending_exception(), "should have been handled"); } @@ -2038,19 +2219,19 @@ void CompileBroker::invoke_compiler_on_method(CompileTask* task) { if (WhiteBoxAPI && WhiteBox::compilation_locked) { whitebox_lock_compilation(); } - comp->compile_method(&ci_env, target, osr_bci, true, directive); + comp->compile_method(&ci_env, target, osr_bci, true /* install */, directive); /* Repeat compilation without installing code for profiling purposes */ - int repeat_compilation_count = directive->RepeatCompilationOption; + int repeat_compilation_count = task->is_aot_load() ? 0 : directive->RepeatCompilationOption; if (repeat_compilation_count > 0) { CHeapStringHolder failure_reason; - failure_reason.set(ci_env._failure_reason.get()); + failure_reason.set(ci_env.failure_reason()); while (repeat_compilation_count > 0) { ResourceMark rm(thread); task->print_ul("NO CODE INSTALLED"); thread->timeout()->reset(); ci_env._failure_reason.clear(); - comp->compile_method(&ci_env, target, osr_bci, false, directive); + comp->compile_method(&ci_env, target, osr_bci, false /* install */, directive); repeat_compilation_count--; } ci_env._failure_reason.set(failure_reason.get()); @@ -2058,11 +2239,18 @@ void CompileBroker::invoke_compiler_on_method(CompileTask* task) { } - if (!ci_env.failing() && !task->is_success()) { + if (!ci_env.failing() && !task->is_success() && !task->is_aot_compile()) { const char* reason = task->failure_reason(); assert(reason != nullptr, "compiler should always document failure"); - // Do not attempt further compilations of this method. - ci_env.record_method_not_compilable(reason != nullptr ? reason : "compile failed: reason unknown"); + if (reason == nullptr) { + reason = "compile failed: reason unknown"; + } + if (task->is_aot_load()) { + ci_env._failure_reason.set(reason); + } else { + // Do not attempt further compilations of this method. + ci_env.record_method_not_compilable(reason); + } } // Copy this bit to the enclosing block: @@ -2244,12 +2432,36 @@ void CompileBroker::update_compile_perf_data(CompilerThread* thread, const metho // // Collect statistics about the compilation. +static int stats_level(int comp_level, bool aot_preload) { + return aot_preload ? (CompLevel_full_optimization + 1) : comp_level; +} + +CompilerStatistics* CompileBroker::stats_for(int comp_level, bool aot_preload, bool is_aot) { + precond(!aot_preload || is_aot); + int level = stats_level(comp_level, aot_preload) - 1; + return is_aot ? &_aot_stats_per_level[level] : &_stats_per_level[level]; +} + void CompileBroker::collect_statistics(CompilerThread* thread, elapsedTimer time, CompileTask* task) { bool success = task->is_success(); methodHandle method (thread, task->method()); int compile_id = task->compile_id(); bool is_osr = (task->osr_bci() != standard_entry_bci); + AbstractCompiler* comp = task->compiler(); + assert(comp != nullptr, "Compiler object must exist"); const int comp_level = task->comp_level(); + assert(comp_level > CompLevel_none && comp_level <= CompLevel_full_optimization, + "CompilerStatistics object does not exist for compilation level %d", comp_level); + bool aot_load = task->is_aot_load(); + bool aot_preload = task->is_aot_preload(); + + nmethod* code = is_osr ? method->lookup_osr_nmethod_for(task->osr_bci(), comp_level, true /* match_level */) + : method->code(); + // With VerifyAOTCode AOT code is not installed. + bool verify_aot_code = task->is_aot_load() && VerifyAOTCode; + // Check if compiled code was invalidated already + bool invalidated = !verify_aot_code && (code == nullptr || code->compile_id() != compile_id); + CompilerCounters* counters = thread->counters(); MutexLocker locker(CompileStatistics_lock); @@ -2275,7 +2487,12 @@ void CompileBroker::collect_statistics(CompilerThread* thread, elapsedTimer time _perf_total_bailout_count->inc(); } _t_bailedout_compilation.add(time); - } else if (!task->is_success()) { + + if (CITime || log_is_enabled(Info, init)) { + CompilerStatistics* stats = stats_for(comp_level, aot_preload, aot_load); + stats->_bailout.update(time, 0); + } + } else if (invalidated) { if (UsePerfData) { _perf_last_invalidated_method->set_value(counters->current_method()); _perf_last_invalidated_type->set_value(counters->compile_type()); @@ -2283,9 +2500,14 @@ void CompileBroker::collect_statistics(CompilerThread* thread, elapsedTimer time } _total_invalidated_count++; _t_invalidated_compilation.add(time); + + if (CITime || log_is_enabled(Info, init)) { + CompilerStatistics* stats = stats_for(comp_level, aot_preload, aot_load); + stats->_invalidated.update(time, 0); + } } else { // Compilation succeeded - if (CITime) { + if (CITime || log_is_enabled(Info, init)) { int bytes_compiled = method->code_size() + task->num_inlined_bytecodes(); if (is_osr) { _t_osr_compilation.add(time); @@ -2296,8 +2518,15 @@ void CompileBroker::collect_statistics(CompilerThread* thread, elapsedTimer time } // Collect statistic per compilation level - if (comp_level > CompLevel_none && comp_level <= CompLevel_full_optimization) { - CompilerStatistics* stats = &_stats_per_level[comp_level-1]; + CompilerStatistics* stats = stats_for(comp_level, aot_preload, aot_load); + if (aot_load) { + _aot_stats._standard.update(time, bytes_compiled); + _aot_stats._nmethods_size += task->nm_total_size(); + _aot_stats._nmethods_code_size += task->nm_insts_size(); + stats->_standard.update(time, bytes_compiled); + stats->_nmethods_size += task->nm_total_size(); + stats->_nmethods_code_size += task->nm_insts_size(); + } else if (comp_level > CompLevel_none && comp_level <= CompLevel_full_optimization) { if (is_osr) { stats->_osr.update(time, bytes_compiled); } else { @@ -2306,12 +2535,11 @@ void CompileBroker::collect_statistics(CompilerThread* thread, elapsedTimer time stats->_nmethods_size += task->nm_total_size(); stats->_nmethods_code_size += task->nm_insts_size(); } else { - assert(false, "CompilerStatistics object does not exist for compilation level %d", comp_level); + ShouldNotReachHere(); } // Collect statistic per compiler - AbstractCompiler* comp = task->compiler(); - if (comp) { + if (!aot_load) { CompilerStatistics* stats = comp->stats(); if (is_osr) { stats->_osr.update(time, bytes_compiled); @@ -2320,8 +2548,6 @@ void CompileBroker::collect_statistics(CompilerThread* thread, elapsedTimer time } stats->_nmethods_size += task->nm_total_size(); stats->_nmethods_code_size += task->nm_insts_size(); - } else { // if (!comp) - assert(false, "Compiler object must exist"); } } @@ -2383,6 +2609,18 @@ jlong CompileBroker::total_compilation_ticks() { return _perf_total_compilation != nullptr ? _perf_total_compilation->get_value() : 0; } +void CompileBroker::log_not_entrant(nmethod* nm) { + _total_not_entrant_count++; + if (CITime || log_is_enabled(Info, init)) { + int level = nm->comp_level(); + if (level == CompLevel_none) { + return; // native wrapper + } + CompilerStatistics* stats = stats_for(level, nm->aot_preloaded(), nm->is_aot()); + stats->_made_not_entrant._count++; + } +} + void CompileBroker::print_times(const char* name, CompilerStatistics* stats) { tty->print_cr(" %s {speed: %6.3f bytes/s; standard: %6.3f s, %u bytes, %u methods; osr: %6.3f s, %u bytes, %u methods; nmethods_size: %u bytes; nmethods_code_size: %u bytes}", name, stats->bytes_per_second(), @@ -2391,11 +2629,101 @@ void CompileBroker::print_times(const char* name, CompilerStatistics* stats) { stats->_nmethods_size, stats->_nmethods_code_size); } +static void print_helper(outputStream* st, const char* name, CompilerStatistics::Data data, bool print_time = true) { + if (data._count > 0) { + st->print("; %s: %4u methods", name, data._count); + if (print_time) { + st->print(" (in %.3fs)", data._time.seconds()); + } + } +} + +static void print_tier_helper(outputStream* st, const char* prefix, int tier, CompilerStatistics* stats) { + st->print(" %s%d: %5u methods", prefix, tier, stats->_standard._count); + if (stats->_standard._count > 0) { + st->print(" (in %.3fs)", stats->_standard._time.seconds()); + } + print_helper(st, "osr", stats->_osr); + print_helper(st, "bailout", stats->_bailout); + print_helper(st, "invalid", stats->_invalidated); + print_helper(st, "not_entrant", stats->_made_not_entrant, false); + st->cr(); +} + +static void print_queue_info(outputStream* st, CompileQueue* queue) { + if (queue != nullptr) { + ThreadsListHandle tlh; + MutexLocker ml(MethodCompileQueue_lock); + + uint total_cnt = 0; + uint active_cnt = 0; + for (JavaThread* jt : tlh) { + guarantee(jt != nullptr, ""); + if (jt->is_Compiler_thread()) { + CompilerThread* ct = (CompilerThread*)jt; + + guarantee(ct != nullptr, ""); + if (ct->queue() == queue) { + ++total_cnt; + CompileTask* task = ct->task(); + if (task != nullptr) { + ++active_cnt; + } + } + } + } + + st->print(" %s (%d active / %d total threads): %u tasks", + queue->name(), active_cnt, total_cnt, queue->size()); + if (queue->size() > 0) { + uint counts[] = {0, 0, 0, 0, 0}; // T1 ... T5 + for (CompileTask* task = queue->first(); task != nullptr; task = task->next()) { + int tier = task->comp_level(); + if (task->is_aot_preload()) { + assert(tier == CompLevel_full_optimization, "%d", tier); + tier = CompLevel_full_optimization + 1; + } + counts[tier-1]++; + } + st->print(":"); + for (int tier = CompLevel_simple; tier <= CompilationPolicy::highest_compile_level() + 1; tier++) { + uint cnt = counts[tier-1]; + if (cnt > 0) { + st->print(" T%d: %u tasks;", tier, cnt); + } + } + } + st->cr(); + } +} +void CompileBroker::print_statistics_on(outputStream* st) { + st->print_cr(" Total: %u methods; %u bailouts, %u invalidated, %u non_entrant", + _total_compile_count, _total_bailout_count, _total_invalidated_count, _total_not_entrant_count); + for (int tier = CompLevel_simple; tier <= CompilationPolicy::highest_compile_level(); tier++) { + print_tier_helper(st, "Tier", tier, &_stats_per_level[tier-1]); + } + st->cr(); + + if (AOTCodeCaching) { + for (int tier = CompLevel_simple; tier <= CompilationPolicy::highest_compile_level() + 1; tier++) { + if (tier != CompLevel_full_profile) { + print_tier_helper(st, "AOT Code T", tier, &_aot_stats_per_level[tier - 1]); + } + } + st->cr(); + } + + print_queue_info(st, _c1_compile_queue); + print_queue_info(st, _c2_compile_queue); + print_queue_info(st, _ac1_compile_queue); + print_queue_info(st, _ac2_compile_queue); +} + void CompileBroker::print_times(bool per_compiler, bool aggregate) { if (per_compiler) { if (aggregate) { tty->cr(); - tty->print_cr("Individual compiler times (for compiled methods only)"); + tty->print_cr("[%dms] Individual compiler times (for compiled methods only)", (int)tty->time_stamp().milliseconds()); tty->print_cr("------------------------------------------------"); tty->cr(); } @@ -2405,6 +2733,9 @@ void CompileBroker::print_times(bool per_compiler, bool aggregate) { print_times(comp->name(), comp->stats()); } } + if (_aot_stats._standard._count > 0) { + print_times("AC", &_aot_stats); + } if (aggregate) { tty->cr(); tty->print_cr("Individual compilation Tier times (for compiled methods only)"); @@ -2417,6 +2748,13 @@ void CompileBroker::print_times(bool per_compiler, bool aggregate) { os::snprintf_checked(tier_name, sizeof(tier_name), "Tier%d", tier); print_times(tier_name, stats); } + for (int tier = CompLevel_simple; tier <= CompilationPolicy::highest_compile_level() + 1; tier++) { + CompilerStatistics* stats = &_aot_stats_per_level[tier-1]; + if (stats->_standard._bytes > 0) { + os::snprintf_checked(tier_name, sizeof(tier_name), "AOT Code T%d", tier); + print_times(tier_name, stats); + } + } } if (!aggregate) { @@ -2458,6 +2796,10 @@ void CompileBroker::print_times(bool per_compiler, bool aggregate) { CompileBroker::_t_invalidated_compilation.seconds(), total_invalidated_count == 0 ? 0.0 : CompileBroker::_t_invalidated_compilation.seconds() / total_invalidated_count); + if (AOTCodeCaching) { // Check flags because AOT code cache could be closed already + tty->cr(); + AOTCodeCache::print_timers_on(tty); + } AbstractCompiler *comp = compiler(CompLevel_simple); if (comp != nullptr) { tty->cr(); diff --git a/src/hotspot/share/compiler/compileBroker.hpp b/src/hotspot/share/compiler/compileBroker.hpp index db2e9069913..daecc48e642 100644 --- a/src/hotspot/share/compiler/compileBroker.hpp +++ b/src/hotspot/share/compiler/compileBroker.hpp @@ -184,13 +184,13 @@ class CompileBroker: AllStatic { static AbstractCompiler* _compilers[2]; // The maximum numbers of compiler threads to be determined during startup. - static int _c1_count, _c2_count; + static int _c1_count, _c2_count, _ac1_count, _ac2_count; // An array of compiler thread Java objects - static jobject *_compiler1_objects, *_compiler2_objects; + static jobject *_compiler1_objects, *_compiler2_objects, *_ac1_objects, *_ac2_objects; // An array of compiler logs - static CompileLog **_compiler1_logs, **_compiler2_logs; + static CompileLog **_compiler1_logs, **_compiler2_logs, **_ac1_logs, **_ac2_logs; // These counters are used for assigning id's to each compilation static volatile jint _compilation_id; @@ -199,6 +199,11 @@ class CompileBroker: AllStatic { static CompileQueue* _c2_compile_queue; static CompileQueue* _c1_compile_queue; + static CompileQueue* _ac1_compile_queue; + static CompileQueue* _ac2_compile_queue; + // Note: AOT nmethods are "virtually compiled" - copied in from the AOT cache. + // The dynamics of method replacement and dependency checks are common to AOT and JIT. + // See checks of is_aot_load and is_aot_compile for behavioral differences. // performance counters static PerfCounter* _perf_total_compilation; @@ -234,6 +239,7 @@ class CompileBroker: AllStatic { static uint _total_compile_count; static uint _total_bailout_count; static uint _total_invalidated_count; + static uint _total_not_entrant_count; static uint _total_native_compile_count; static uint _total_osr_compile_count; static uint _total_standard_compile_count; @@ -246,6 +252,8 @@ class CompileBroker: AllStatic { static jlong _peak_compilation_time; static CompilerStatistics _stats_per_level[]; + static CompilerStatistics _aot_stats; + static CompilerStatistics _aot_stats_per_level[]; static volatile int _print_compilation_warning; @@ -255,6 +263,7 @@ class CompileBroker: AllStatic { training_replay_t }; + static Handle create_thread_oop(const char* name, TRAPS); static JavaThread* make_thread(ThreadType type, jobject thread_oop, CompileQueue* queue, AbstractCompiler* comp, JavaThread* THREAD); static void init_compiler_threads(); static void init_training_replay(); @@ -267,9 +276,14 @@ class CompileBroker: AllStatic { int osr_bci, int comp_level, int hot_count, + AOTCodeEntry* aot_code_entry, CompileTask::CompileReason compile_reason, bool blocking); static void wait_for_completion(CompileTask* task); +public: + static void wait_for_no_active_tasks(); + +private: static void free_buffer_blob_if_allocated(CompilerThread* thread); static void invoke_compiler_on_method(CompileTask* task); @@ -277,9 +291,11 @@ class CompileBroker: AllStatic { int compilable, const char* failure_reason); static void update_compile_perf_data(CompilerThread *thread, const methodHandle& method, bool is_osr); + static CompilerStatistics* stats_for(int comp_level, bool aot_preload, bool is_aot); static void collect_statistics(CompilerThread* thread, elapsedTimer time, CompileTask* task); static void compile_method_base(const methodHandle& method, + AOTCodeEntry* aot_code_entry, int osr_bci, int comp_level, int hot_count, @@ -287,7 +303,7 @@ class CompileBroker: AllStatic { bool blocking, Thread* thread); - static CompileQueue* compile_queue(int comp_level); + static CompileQueue* compile_queue(int comp_level, bool is_aot); static bool init_compiler_runtime(); static void shutdown_compiler_runtime(AbstractCompiler* comp, CompilerThread* thread); @@ -303,11 +319,14 @@ class CompileBroker: AllStatic { return nullptr; } - static bool compilation_is_complete(const methodHandle& method, int osr_bci, int comp_level); + static bool initialized() { return _initialized; } + static bool compilation_is_complete(const methodHandle& method, int osr_bci, int comp_level, + AOTCodeEntry* aot_code_entry, + CompileTask::CompileReason compile_reason); static bool compilation_is_in_queue(const methodHandle& method); static void print_compile_queues(outputStream* st); - static int queue_size(int comp_level) { - CompileQueue *q = compile_queue(comp_level); + static int queue_size(int comp_level, bool is_aot = false) { + CompileQueue *q = compile_queue(comp_level, is_aot); return q != nullptr ? q->size() : 0; } static void compilation_init(JavaThread* THREAD); @@ -316,8 +335,12 @@ class CompileBroker: AllStatic { int osr_bci, int comp_level, int hot_count, + AOTCodeEntry* aot_code_entry, CompileTask::CompileReason compile_reason, TRAPS); + static void preload_aot_method(const methodHandle& method, + AOTCodeEntry* aot_code_entry, + TRAPS); static CompileQueue* c1_compile_queue(); static CompileQueue* c2_compile_queue(); @@ -326,6 +349,7 @@ class CompileBroker: AllStatic { int osr_bci, int comp_level, int hot_count, + AOTCodeEntry* aot_code_entry, CompileTask::CompileReason compile_reason, DirectiveSet* directive, TRAPS); @@ -376,8 +400,6 @@ class CompileBroker: AllStatic { return AtomicAccess::load(&_should_compile_new_jobs) == shutdown_compilation; } - static void wait_for_no_active_tasks(); - static void handle_full_code_cache(CodeBlobType code_blob_type); // Ensures that warning is only printed once. static bool should_print_compiler_warning() { @@ -436,9 +458,13 @@ class CompileBroker: AllStatic { static jlong get_peak_compilation_time() { return _peak_compilation_time; } static jlong get_total_compilation_time() { return _t_total_compilation.milliseconds(); } + static void log_not_entrant(nmethod* nm); + // Log that compilation profiling is skipped because metaspace is full. static void log_metaspace_failure(); + static void print_statistics_on(outputStream* st); + // CodeHeap State Analytics. static void print_info(outputStream *out); static void print_heapinfo(outputStream *out, const char* function, size_t granularity); diff --git a/src/hotspot/share/compiler/compileTask.cpp b/src/hotspot/share/compiler/compileTask.cpp index 6a5f706ec7a..9b0b68103b8 100644 --- a/src/hotspot/share/compiler/compileTask.cpp +++ b/src/hotspot/share/compiler/compileTask.cpp @@ -43,6 +43,7 @@ CompileTask::CompileTask(int compile_id, int osr_bci, int comp_level, int hot_count, + AOTCodeEntry* aot_code_entry, CompileReason compile_reason, bool is_blocking) : _compile_id(compile_id), @@ -57,6 +58,7 @@ CompileTask::CompileTask(int compile_id, _nm_insts_size(0), _comp_level(comp_level), _compiler(CompileBroker::compiler(comp_level)), + _aot_code_entry(aot_code_entry), _comp_directive_matcher(method, static_cast(comp_level)), _num_inlined_bytecodes(0), _next(nullptr), @@ -65,6 +67,8 @@ CompileTask::CompileTask(int compile_id, _time_queued(0), _time_started(0), _time_finished(0), + _aot_load_start(0), + _aot_load_finish(0), _hot_count(hot_count), _compile_reason(compile_reason), _failure_reason(nullptr), @@ -167,22 +171,26 @@ void CompileTask::print_tty() { void CompileTask::print_post(outputStream* st) { bool is_osr_method = osr_bci() != InvocationEntryBci; + bool is_aot = is_aot_load_or_compile(); + bool is_aot_preload = is_aot_preload_or_compile(); print_impl(st, is_unloaded() ? nullptr : method(), compile_id(), comp_level(), - is_osr_method, osr_bci(), is_blocking(), + is_osr_method, osr_bci(), is_blocking(), is_aot, is_aot_preload, compiler()->name(), nullptr, false /* short_form */, true /* cr */, true /* after_compile_details */, _num_inlined_bytecodes, _nm_total_size, _nm_insts_size, - _time_created, _time_queued, _time_started, _time_finished); + _time_created, _time_queued, _time_started, _time_finished, + _aot_load_start, _aot_load_finish); } // ------------------------------------------------------------------ // CompileTask::print_impl void CompileTask::print_impl(outputStream* st, Method* method, int compile_id, int comp_level, - bool is_osr_method, int osr_bci, bool is_blocking, + bool is_osr_method, int osr_bci, bool is_blocking, bool is_aot, bool is_aot_preload, const char* compiler_name, const char* msg, bool short_form, bool cr, bool after_compile_details, int inlined_bytecodes, int nm_total_size, int nm_insts_size, - jlong time_created, jlong time_queued, jlong time_started, jlong time_finished) { + jlong time_created, jlong time_queued, jlong time_started, jlong time_finished, + jlong aot_load_start, jlong aot_load_finish) { // Use stringStream to avoid breaking the line stringStream sst; if (after_compile_details) { @@ -212,6 +220,13 @@ void CompileTask::print_impl(outputStream* st, Method* method, int compile_id, i } sst.print("%7s ", ss.freeze()); } + { // Time to load from AOT code cache + stringStream ss; + if (aot_load_start != 0 && aot_load_finish != 0) { + ss.print("A%.1f", TimeHelper::counter_to_millis(aot_load_finish - aot_load_start)); + } + sst.print("%7s ", ss.freeze()); + } } else if (!short_form) { // Print current time sst.print(UINT64_FORMAT " ", (uint64_t) tty->time_stamp().milliseconds()); @@ -245,9 +260,11 @@ void CompileTask::print_impl(outputStream* st, Method* method, int compile_id, i const char exception_char = has_exception_handler ? '!' : ' '; const char blocking_char = is_blocking ? 'b' : ' '; const char native_char = is_native ? 'n' : ' '; + const char aot_char = is_aot ? 'A' : ' '; + const char aot_preload_char = is_aot_preload ? 'P' : ' '; // print method attributes - sst.print("%c%c%c%c%c ", compile_type, sync_char, exception_char, blocking_char, native_char); + sst.print("%c%c%c%c%c%c%c ", compile_type, sync_char, exception_char, blocking_char, native_char, aot_char, aot_preload_char); if (TieredCompilation) { if (comp_level != -1) sst.print("%d ", comp_level); @@ -290,8 +307,10 @@ void CompileTask::print_impl(outputStream* st, Method* method, int compile_id, i // CompileTask::print_compilation void CompileTask::print(outputStream* st, const char* msg, bool short_form, bool cr) { bool is_osr_method = osr_bci() != InvocationEntryBci; + bool is_aot = is_aot_load_or_compile(); + bool is_aot_preload = is_aot_preload_or_compile(); print_impl(st, is_unloaded() ? nullptr : method(), compile_id(), comp_level(), - is_osr_method, osr_bci(), is_blocking(), + is_osr_method, osr_bci(), is_blocking(), is_aot, is_aot_preload, compiler()->name(), msg, short_form, cr); } @@ -302,13 +321,20 @@ void CompileTask::log_task(xmlStream* log) { methodHandle method(thread, this->method()); ResourceMark rm(thread); + bool is_osr_method = osr_bci() != InvocationEntryBci; + bool is_aot = is_aot_load_or_compile(); + bool is_aot_preload = is_aot_preload_or_compile(); // log->print(" compile_id='%d'", _compile_id); - if (_osr_bci != CompileBroker::standard_entry_bci) { + if (is_osr_method) { log->print(" compile_kind='osr'"); // same as nmethod::compile_kind - } // else compile_kind='c2c' + } else if (is_aot_preload) { + log->print(" compile_kind='AP'"); + } else if (is_aot) { + log->print(" compile_kind='A'"); + } // else compile_kind='jit' if (!method.is_null()) log->method(method()); - if (_osr_bci != CompileBroker::standard_entry_bci) { + if (is_osr_method) { log->print(" osr_bci='%d'", _osr_bci); } if (_comp_level != CompilationPolicy::highest_compile_level()) { @@ -317,7 +343,6 @@ void CompileTask::log_task(xmlStream* log) { if (_is_blocking) { log->print(" blocking='1'"); } - log->stamp(); } // ------------------------------------------------------------------ @@ -335,15 +360,17 @@ void CompileTask::log_task_queued() { if (_hot_count != 0) { xtty->print(" hot_count='%d'", _hot_count); } + xtty->stamp(); xtty->end_elem(); } // ------------------------------------------------------------------ // CompileTask::log_task_start -void CompileTask::log_task_start(CompileLog* log) { +void CompileTask::log_task_start(CompileLog* log) { log->begin_head("task"); log_task(log); + log->stamp(); log->end_head(); } @@ -485,8 +512,8 @@ void CompileTask::print_ul(const nmethod* nm, const char* msg) { print_impl(&ls, nm->method(), nm->compile_id(), nm->comp_level(), nm->is_osr_method(), nm->is_osr_method() ? nm->osr_entry_bci() : -1, - /*is_blocking*/ false, - nm->compiler_name(), + /*is_blocking*/ false, nm->is_aot(), + nm->aot_preloaded(), nm->compiler_name(), msg, /* short form */ true, /* cr */ true); } } diff --git a/src/hotspot/share/compiler/compileTask.hpp b/src/hotspot/share/compiler/compileTask.hpp index afbff5b064e..2eaad96c2a8 100644 --- a/src/hotspot/share/compiler/compileTask.hpp +++ b/src/hotspot/share/compiler/compileTask.hpp @@ -33,7 +33,9 @@ #include "runtime/mutexLocker.hpp" #include "utilities/xmlstream.hpp" +class AOTCodeEntry; class CompileTrainingData; +class DirectiveSet; enum class InliningResult { SUCCESS, FAILURE }; @@ -60,10 +62,14 @@ class CompileTask : public CHeapObj { Reason_Replay, // ciReplay Reason_Whitebox, // Whitebox API Reason_MustBeCompiled, // Used for -Xcomp (see CompilationPolicy::must_be_compiled()) + Reason_AOTLoad, // "virtual compilation" - copy in normal AOT code + Reason_AOTPreload, // "virtual compilation" - copy in "preload" AOT code + Reason_AOTCompile, // create normal AOT code (assembly phase only) + Reason_AOTCompileForPreload, // create "preload" AOT code (assembly phase only) Reason_Count }; - static const char* reason_name(CompileTask::CompileReason compile_reason) { + static const char* reason_name(CompileReason compile_reason) { static const char* reason_names[] = { "no_reason", "count", @@ -71,12 +77,21 @@ class CompileTask : public CHeapObj { "tiered", "replay", "whitebox", - "must_be_compiled" + "must_be_compiled", + "aot_load", + "aot_preload", + "aot_compile", + "aot_compile_for_preload", }; static_assert(ARRAY_SIZE(reason_names) == Reason_Count); return reason_names[compile_reason]; } + static bool reason_is_aot_compile(CompileReason compile_reason) { + return (compile_reason == Reason_AOTCompile) || + (compile_reason == Reason_AOTCompileForPreload); + } + private: static int _active_tasks; int _compile_id; @@ -91,6 +106,7 @@ class CompileTask : public CHeapObj { CodeSection::csize_t _nm_insts_size; int _comp_level; AbstractCompiler* _compiler; + AOTCodeEntry* _aot_code_entry; CompilerDirectiveMatcher _comp_directive_matcher; int _num_inlined_bytecodes; CompileTask* _next; @@ -100,6 +116,8 @@ class CompileTask : public CHeapObj { jlong _time_queued; // time when task was enqueued jlong _time_started; // time when compilation started jlong _time_finished; // time when compilation finished + jlong _aot_load_start; + jlong _aot_load_finish; int _hot_count; // information about its invocation counter CompileReason _compile_reason; // more info about the task const char* _failure_reason; @@ -110,7 +128,9 @@ class CompileTask : public CHeapObj { public: CompileTask(int compile_id, const methodHandle& method, int osr_bci, int comp_level, - int hot_count, CompileReason compile_reason, bool is_blocking); + int hot_count, AOTCodeEntry* aot_code_entry, + CompileReason compile_reason, + bool is_blocking); ~CompileTask(); static void wait_for_no_active_tasks(); @@ -121,9 +141,13 @@ class CompileTask : public CHeapObj { bool is_complete() const { return _is_complete; } bool is_blocking() const { return _is_blocking; } bool is_success() const { return _is_success; } + bool is_aot_load() const { return _aot_code_entry != nullptr; } + bool is_aot_preload() const { return (_compile_reason == Reason_AOTPreload); } + AOTCodeEntry* aot_code_entry() const { return _aot_code_entry; } DirectiveSet* directive() const { return _comp_directive_matcher.directive_set(); } void transfer_directive(CompilerDirectiveMatcher& matcher) { _comp_directive_matcher.transfer_from(matcher); } CompileReason compile_reason() const { return _compile_reason; } + CodeSection::csize_t nm_content_size() { return _nm_content_size; } void set_nm_content_size(CodeSection::csize_t size) { _nm_content_size = size; } CodeSection::csize_t nm_insts_size() { return _nm_insts_size; } @@ -141,11 +165,25 @@ class CompileTask : public CHeapObj { } } + bool is_aot_compile() const { + return reason_is_aot_compile(compile_reason()); + } + + // Next two functions are mostly used to print "A" or "AP". + bool is_aot_load_or_compile() const { + return is_aot_load() || (_compile_reason == Reason_AOTCompile); + } + bool is_aot_preload_or_compile() const { + return is_aot_preload() || (_compile_reason == Reason_AOTCompileForPreload); + } + void mark_complete() { _is_complete = true; } void mark_success() { _is_success = true; } void mark_queued(jlong time) { _time_queued = time; } void mark_started(jlong time) { _time_started = time; } void mark_finished(jlong time) { _time_finished = time; } + void mark_aot_load_start(jlong time) { _aot_load_start = time; } + void mark_aot_load_finish(jlong time) { _aot_load_finish = time; } int comp_level() { return _comp_level;} void set_comp_level(int comp_level) { _comp_level = comp_level;} @@ -176,12 +214,14 @@ class CompileTask : public CHeapObj { private: static void print_impl(outputStream* st, Method* method, int compile_id, int comp_level, bool is_osr_method = false, int osr_bci = -1, bool is_blocking = false, + bool is_aot = false, bool is_preload = false, const char* compiler_name = nullptr, const char* msg = nullptr, bool short_form = false, bool cr = true, bool after_compile_details = false, int inlined_bytecodes = 0, int nm_total_size = 0, int nm_insts_size = 0, jlong time_created = 0, jlong time_queued = 0, - jlong time_started = 0, jlong time_finished = 0); + jlong time_started = 0, jlong time_finished = 0, + jlong aot_load_start = 0, jlong aot_load_finish = 0); public: void print(outputStream* st = tty, const char* msg = nullptr, bool short_form = false, bool cr = true); @@ -189,6 +229,7 @@ class CompileTask : public CHeapObj { static void print(outputStream* st, const nmethod* nm, const char* msg = nullptr, bool short_form = false, bool cr = true) { print_impl(st, nm->method(), nm->compile_id(), nm->comp_level(), nm->is_osr_method(), nm->is_osr_method() ? nm->osr_entry_bci() : -1, /*is_blocking*/ false, + nm->is_aot(), nm->aot_preloaded(), nm->compiler_name(), msg, short_form, cr); } static void print_ul(const nmethod* nm, const char* msg = nullptr); diff --git a/src/hotspot/share/compiler/compilerDefinitions.hpp b/src/hotspot/share/compiler/compilerDefinitions.hpp index e086fc8add6..2233fb6eced 100644 --- a/src/hotspot/share/compiler/compilerDefinitions.hpp +++ b/src/hotspot/share/compiler/compilerDefinitions.hpp @@ -61,6 +61,9 @@ enum CompLevel : s1 { CompLevel_full_optimization = 4, // C2 CompLevel_count = 5 }; +// The next CompLevel constant, defined for AOT only, is not part of the enum. +// TODO: consider moving it into the enum (but must refactor many uses) +const int CompLevel_preload_optimization = CompLevel_full_optimization + 1; // AP4 tier class CompilationModeFlag : AllStatic { enum class Mode { diff --git a/src/hotspot/share/compiler/compilerDirectives.cpp b/src/hotspot/share/compiler/compilerDirectives.cpp index 1b0bdc99f08..03649addc60 100644 --- a/src/hotspot/share/compiler/compilerDirectives.cpp +++ b/src/hotspot/share/compiler/compilerDirectives.cpp @@ -22,6 +22,7 @@ * */ +#include "cds/cdsConfig.hpp" #include "ci/ciMethod.hpp" #include "ci/ciUtilities.inline.hpp" #include "compiler/abstractCompiler.hpp" @@ -214,7 +215,14 @@ bool DirectiveSet::should_print_memstat() const { } size_t DirectiveSet::mem_limit() const { - return MemLimitOption < 0 ? -MemLimitOption : MemLimitOption; + size_t limit = MemLimitOption < 0 ? -MemLimitOption : MemLimitOption; +#if INCLUDE_CDS + if (CDSConfig::is_dumping_aot_code()) { + // Double limit for AOT compilation because it generates more code. + limit = limit * 2; + } +#endif + return limit; } bool DirectiveSet::should_crash_at_mem_limit() const { diff --git a/src/hotspot/share/compiler/compilerDirectives.hpp b/src/hotspot/share/compiler/compilerDirectives.hpp index ae814cdc491..ea709c6897c 100644 --- a/src/hotspot/share/compiler/compilerDirectives.hpp +++ b/src/hotspot/share/compiler/compilerDirectives.hpp @@ -52,7 +52,10 @@ cflags(DumpReplay, bool, false, DumpReplay) \ cflags(DumpInline, bool, false, DumpInline) \ cflags(CompilerDirectivesIgnoreCompileCommands, bool, CompilerDirectivesIgnoreCompileCommands, Unknown) \ - cflags(RepeatCompilation, intx, RepeatCompilation, RepeatCompilation) + cflags(RepeatCompilation, intx, RepeatCompilation, RepeatCompilation) \ + cflags(DontAOTCompile, bool, false, DontAOTCompile) \ + cflags(DontPreload, bool, false, DontPreload) \ + cflags(IgnoreAOTCompiled, bool, false, IgnoreAOTCompiled) #define compilerdirectives_common_string_flags(cflags) \ cflags(DisableIntrinsic, ccstrlist, DisableIntrinsic, DisableIntrinsic) \ cflags(ControlIntrinsic, ccstrlist, ControlIntrinsic, ControlIntrinsic) diff --git a/src/hotspot/share/compiler/compilerOracle.hpp b/src/hotspot/share/compiler/compilerOracle.hpp index bfed52f12e7..35cceea32cd 100644 --- a/src/hotspot/share/compiler/compilerOracle.hpp +++ b/src/hotspot/share/compiler/compilerOracle.hpp @@ -95,6 +95,9 @@ NOT_PRODUCT(option(TraceMergeStores, "TraceMergeStores", Ccstrlist)) \ option(CloneMapDebug, "CloneMapDebug", Bool) \ option(IncrementalInlineForceCleanup, "IncrementalInlineForceCleanup", Bool) \ option(MaxNodeLimit, "MaxNodeLimit", Intx) \ + option(DontAOTCompile, "DontAOTCompile", Bool) \ + option(DontPreload, "DontPreload", Bool) \ + option(IgnoreAOTCompiled, "IgnoreAOTCompiled", Bool) \ NOT_PRODUCT(option(TestOptionInt, "TestOptionInt", Intx)) \ NOT_PRODUCT(option(TestOptionUint, "TestOptionUint", Uintx)) \ NOT_PRODUCT(option(TestOptionBool, "TestOptionBool", Bool)) \ diff --git a/src/hotspot/share/compiler/compilerThread.cpp b/src/hotspot/share/compiler/compilerThread.cpp index 0139f864e12..5c215516894 100644 --- a/src/hotspot/share/compiler/compilerThread.cpp +++ b/src/hotspot/share/compiler/compilerThread.cpp @@ -40,7 +40,7 @@ CompilerThread::CompilerThread(CompileQueue* queue, _compiler = nullptr; _arena_stat = nullptr; _timeout = nullptr; - + _is_aot_thread = false; #ifndef PRODUCT _ideal_graph_printer = nullptr; #endif diff --git a/src/hotspot/share/compiler/compilerThread.hpp b/src/hotspot/share/compiler/compilerThread.hpp index 255b1dc0a37..96b16dfb817 100644 --- a/src/hotspot/share/compiler/compilerThread.hpp +++ b/src/hotspot/share/compiler/compilerThread.hpp @@ -81,6 +81,8 @@ class CompilerThread : public JavaThread { ArenaStatCounter* _arena_stat; Timeout* _timeout; + + bool _is_aot_thread; // Thread is used to load AOT code public: static CompilerThread* current() { @@ -104,6 +106,9 @@ class CompilerThread : public JavaThread { void set_compiler(AbstractCompiler* c); AbstractCompiler* compiler() const { return _compiler; } + bool is_aot_thread() const { return _is_aot_thread; } + void set_is_aot_thread() { _is_aot_thread = true; } + CompileQueue* queue() const { return _queue; } CompilerCounters* counters() const { return _counters; } ArenaStatCounter* arena_stat() const { return _arena_stat; } diff --git a/src/hotspot/share/compiler/compiler_globals.hpp b/src/hotspot/share/compiler/compiler_globals.hpp index 6c7c4a73c93..08fc126850f 100644 --- a/src/hotspot/share/compiler/compiler_globals.hpp +++ b/src/hotspot/share/compiler/compiler_globals.hpp @@ -389,6 +389,41 @@ "available.") \ range(0, max_juint) \ \ + /* AOT Code Caching flags */ \ + \ + product(uint, DisableAOTCode, 0, DIAGNOSTIC, \ + "Disable AOT code on some compilation levels " \ + "(T1=1; T2=10; T4=1000; T5/preload=10000) " \ + "(T3 is disabled regardless - AOT T3 code is not generated)") \ + constraint(DisableAOTCodeConstraintFunc, AtParse) \ + \ + product(uint, ClassInitBarrierMode, 0, DIAGNOSTIC, \ + "Produce AOT preload code which could be called on first " \ + "method invocation, add class initialization barriers, " \ + "other checks and constraints if needed " \ + "(0: no AOT preload code produced) " \ + "(1: produce AOT preload code with uncommon trap for barriers)") \ + range(0, 1) \ + \ + product(uint, AOTCodePreloadStart, 0, DIAGNOSTIC, \ + "The index of the first AOT code to preload") \ + \ + product(uint, AOTCodePreloadStop, max_jint, DIAGNOSTIC, \ + "The index of the last AOT code to preload") \ + \ + product(bool, AOTPreloadBlocking, false, DIAGNOSTIC, \ + "Preload code is processed with blocking. Startup would not " \ + "proceed until all preloaded code is done loading.") \ + \ + product(bool, VerifyAOTCode, false, DIAGNOSTIC, \ + "Load AOT code but not install into CodeCache") \ + \ + develop(bool, AOTAssertOnUnknownExternalAddress, false, \ + "Assert when external address is missing from the AOT Code " \ + "external address table. By default (false) AOT compilation " \ + "will be skipped for method which references such address.") \ + \ + // end of COMPILER_FLAGS DECLARE_FLAGS(COMPILER_FLAGS) diff --git a/src/hotspot/share/compiler/oopMap.cpp b/src/hotspot/share/compiler/oopMap.cpp index a447f59b2cd..8b4e7fdc45d 100644 --- a/src/hotspot/share/compiler/oopMap.cpp +++ b/src/hotspot/share/compiler/oopMap.cpp @@ -111,7 +111,6 @@ OopMap::OopMap(int frame_size, int arg_count) { #endif } - OopMap::OopMap(OopMap::DeepCopyToken, OopMap* source) { // This constructor does a deep copy // of the source OopMap. diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp index 3f064c3569b..ade3d9a7952 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp @@ -135,6 +135,32 @@ class ShenandoahBarrierSetC1 : public BarrierSetC1 { virtual bool generate_c1_runtime_stubs(BufferBlob* buffer_blob); + // Next methods are used by AOT code caching + CodeBlob* keepalive_barrier_c1_runtime_code_blob() const { + assert(_keepalive_barrier_c1_runtime_code_blob != nullptr, ""); + return _keepalive_barrier_c1_runtime_code_blob; + } + + CodeBlob* load_reference_barrier_strong_rt_code_blob() const { + assert(_load_reference_barrier_strong_rt_code_blob != nullptr, ""); + return _load_reference_barrier_strong_rt_code_blob; + } + + CodeBlob* load_reference_barrier_strong_native_rt_code_blob() const { + assert(_load_reference_barrier_strong_native_rt_code_blob != nullptr, ""); + return _load_reference_barrier_strong_native_rt_code_blob; + } + + CodeBlob* load_reference_barrier_weak_rt_code_blob() const { + assert(_load_reference_barrier_weak_rt_code_blob != nullptr, ""); + return _load_reference_barrier_weak_rt_code_blob; + } + + CodeBlob* load_reference_barrier_phantom_rt_code_blob() const { + assert(_load_reference_barrier_phantom_rt_code_blob != nullptr, ""); + return _load_reference_barrier_phantom_rt_code_blob; + } + protected: virtual void store_at_resolved(LIRAccess& access, LIR_Opr value); virtual LIR_Opr resolve_address(LIRAccess& access, bool resolve_in_register); diff --git a/src/hotspot/share/gc/z/c1/zBarrierSetC1.hpp b/src/hotspot/share/gc/z/c1/zBarrierSetC1.hpp index ec0881eec2e..58d7a7d2ae2 100644 --- a/src/hotspot/share/gc/z/c1/zBarrierSetC1.hpp +++ b/src/hotspot/share/gc/z/c1/zBarrierSetC1.hpp @@ -88,6 +88,7 @@ class ZStoreBarrierStubC1 : public CodeStub { }; class ZBarrierSetC1 : public BarrierSetC1 { + friend class AOTCodeAddressTable; private: address _load_barrier_on_oop_field_preloaded_runtime_stub; address _load_barrier_on_weak_oop_field_preloaded_runtime_stub; diff --git a/src/hotspot/share/include/cds.h b/src/hotspot/share/include/cds.h index 76de42db755..0068b9a1ec0 100644 --- a/src/hotspot/share/include/cds.h +++ b/src/hotspot/share/include/cds.h @@ -40,7 +40,7 @@ #define CDS_DYNAMIC_ARCHIVE_MAGIC 0xf00baba8 #define CDS_PREIMAGE_ARCHIVE_MAGIC 0xcafea07c #define CDS_GENERIC_HEADER_SUPPORTED_MIN_VERSION 13 -#define CURRENT_CDS_ARCHIVE_VERSION 20 +#define CURRENT_CDS_ARCHIVE_VERSION 21 typedef struct CDSFileMapRegion { int _crc; // CRC checksum of this region. diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 44208c22320..4560b2550dd 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -4159,6 +4159,10 @@ const char* InstanceKlass::init_state_name() const { return state_names[init_state()]; } +const char* InstanceKlass::state2name(ClassState s) { + return state_names[s]; +} + void InstanceKlass::print_class_flags(outputStream* st) const { AccessFlags flags(compute_modifier_flags()); if (flags.is_public ()) st->print("public "); @@ -4773,8 +4777,9 @@ void JNIid::verify(InstanceKlass* holder) { void InstanceKlass::set_init_state(ClassState state) { #ifdef ASSERT + // TODO enable: assert(state <= initialization_error, "only store known states"); bool good_state = in_aot_cache() ? (_init_state <= state) - : (_init_state < state); + : (_init_state < state); assert(good_state || state == allocated, "illegal state transition"); #endif assert(_init_thread == nullptr, "should be cleared before state change"); diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 1c9ddf84b76..0b20216aa49 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -601,6 +601,8 @@ class InstanceKlass: public Klass { const char* init_state_name() const; bool is_rewritten() const { return _misc_flags.rewritten(); } + static const char* state2name(ClassState state); + // is this a sealed class bool is_sealed() const; diff --git a/src/hotspot/share/oops/klass.cpp b/src/hotspot/share/oops/klass.cpp index 0bb1980333f..e0b76a9c640 100644 --- a/src/hotspot/share/oops/klass.cpp +++ b/src/hotspot/share/oops/klass.cpp @@ -23,6 +23,7 @@ */ #include "cds/cdsConfig.hpp" +#include "cds/cppVtables.hpp" #include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" @@ -1112,6 +1113,23 @@ bool Klass::is_valid(Klass* k) { return ClassLoaderDataGraph::is_valid(k->class_loader_data()); } +#if INCLUDE_CDS +// Check that this pointer is valid by checking that the vtbl pointer matches +bool Klass::is_valid_aot_klass(const Klass* k) { + if (k == nullptr) { + return false; + } else if (!is_aligned(k, sizeof(MetaWord))) { + // Quick sanity check on pointer. + return false; + } else if (!os::is_readable_range(k, k + 1)) { + return false; + } else if (!k->in_aot_cache()) { + return false; + } + return CppVtables::is_valid_shared_klass(k); +} +#endif + Method* Klass::method_at_vtable(int index) { #ifndef PRODUCT assert(index >= 0, "valid vtable index"); diff --git a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp index 88e12476983..f13daa8299d 100644 --- a/src/hotspot/share/oops/klass.hpp +++ b/src/hotspot/share/oops/klass.hpp @@ -814,6 +814,11 @@ class Klass : public Metadata { // for error reporting static bool is_valid(Klass* k); +#if INCLUDE_CDS + // For klass in AOT cache + static bool is_valid_aot_klass(const Klass* k); +#endif + static void on_secondary_supers_verification_failure(Klass* super, Klass* sub, bool linear_result, bool table_result, const char* msg); }; diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index ce76c1dd82f..51dc5700a6d 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -429,9 +429,15 @@ Symbol* Method::klass_name() const { } void Method::metaspace_pointers_do(MetaspaceClosure* it) { - log_trace(aot)("Iter(Method): %p", this); - - if (!method_holder()->is_rewritten() || Arguments::is_valhalla_enabled()) { + LogStreamHandle(Trace, aot) lsh; + if (lsh.is_enabled() && method_holder() != nullptr) { + lsh.print("Iter(Method): %p ", this); + print_external_name(&lsh); + lsh.cr(); + } + // holder is null for MH intrinsic methods + if ((method_holder() != nullptr && !method_holder()->is_rewritten()) || + Arguments::is_valhalla_enabled()) { it->push(&_constMethod, MetaspaceClosure::_writable); } else { it->push(&_constMethod); diff --git a/src/hotspot/share/oops/methodCounters.cpp b/src/hotspot/share/oops/methodCounters.cpp index 8735758819f..03accf0f8f7 100644 --- a/src/hotspot/share/oops/methodCounters.cpp +++ b/src/hotspot/share/oops/methodCounters.cpp @@ -23,6 +23,7 @@ */ #include "cds/cdsConfig.hpp" +#include "code/aotCodeCache.hpp" #include "compiler/compiler_globals.hpp" #include "compiler/compilerOracle.hpp" #include "memory/metaspaceClosure.hpp" @@ -40,6 +41,9 @@ MethodCounters::MethodCounters(const methodHandle& mh) : _highest_comp_level(0), _highest_osr_comp_level(0) { +#if INCLUDE_CDS + set_aot_preload_code_entry(nullptr); +#endif set_interpreter_throwout_count(0); JVMTI_ONLY(clear_number_of_breakpoints()); invocation_counter()->init(); @@ -73,6 +77,12 @@ MethodCounters* MethodCounters::allocate_with_exception(const methodHandle& mh, void MethodCounters::clear_counters() { invocation_counter()->reset(); backedge_counter()->reset(); +#if INCLUDE_CDS + if (aot_preload_code_entry() != nullptr) { + AOTCodeCache::invalidate(aot_preload_code_entry()); + set_aot_preload_code_entry(nullptr); + } +#endif set_interpreter_throwout_count(0); set_prev_time(0); set_prev_event_count(0); @@ -81,6 +91,15 @@ void MethodCounters::clear_counters() { set_highest_osr_comp_level(0); } +#if INCLUDE_CDS +void MethodCounters::deallocate_contents(ClassLoaderData* loader_data) { + if (aot_preload_code_entry() != nullptr) { + AOTCodeCache::invalidate(aot_preload_code_entry()); + set_aot_preload_code_entry(nullptr); + } +} +#endif + void MethodCounters::metaspace_pointers_do(MetaspaceClosure* it) { log_trace(aot, training)("Iter(MethodCounters): %p", this); it->push(&_method); @@ -88,6 +107,11 @@ void MethodCounters::metaspace_pointers_do(MetaspaceClosure* it) { } #if INCLUDE_CDS +void MethodCounters::set_aot_preload_code_entry(AOTCodeEntry* entry) { + precond(entry == nullptr || entry->for_preload()); + _aot_preload_code_entry = entry; +} + void MethodCounters::remove_unshareable_info() { } void MethodCounters::restore_unshareable_info(TRAPS) { diff --git a/src/hotspot/share/oops/methodCounters.hpp b/src/hotspot/share/oops/methodCounters.hpp index 4e5ce51b7c1..35f96baa6e9 100644 --- a/src/hotspot/share/oops/methodCounters.hpp +++ b/src/hotspot/share/oops/methodCounters.hpp @@ -30,6 +30,7 @@ #include "oops/metadata.hpp" #include "utilities/align.hpp" +class AOTCodeEntry; class MethodTrainingData; class MethodCounters : public Metadata { @@ -48,6 +49,15 @@ class MethodCounters : public Metadata { Method* _method; Metadata* _method_training_data; +#if INCLUDE_CDS + // After JDK-8380476 FIXME: Jamming in a code reference here is a code + // smell. Since access to this value is infrequent, it does not + // need to be a pointer. (See AOTCodeCache::invalidate_entry.) + // Suggestion: Have finish_write build a two-column binary-search + // table in the AOT cache, to map method ID to preload method index. + // Searchable static indexes are a core competency for AOT. + AOTCodeEntry* _aot_preload_code_entry; // AOT Code Cache entry for preload code +#endif jlong _prev_time; // Previous time the rate was acquired float _rate; // Events (invocation and backedge counter increments) per millisecond int _invoke_mask; // per-method Tier0InvokeNotifyFreqLog @@ -71,7 +81,7 @@ class MethodCounters : public Metadata { static MethodCounters* allocate_no_exception(const methodHandle& mh); static MethodCounters* allocate_with_exception(const methodHandle& mh, TRAPS); - void deallocate_contents(ClassLoaderData* loader_data) {} + void deallocate_contents(ClassLoaderData* loader_data) NOT_CDS_RETURN; static int method_counters_size() { return align_up((int)sizeof(MethodCounters), wordSize) / wordSize; @@ -165,6 +175,11 @@ class MethodCounters : public Metadata { } #if INCLUDE_CDS + void set_aot_preload_code_entry(AOTCodeEntry* entry); + AOTCodeEntry* aot_preload_code_entry() const { + return _aot_preload_code_entry; + } + void remove_unshareable_info(); void restore_unshareable_info(TRAPS); #endif diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index 6e35f05c9ce..f3bbacda4a8 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -1937,7 +1937,7 @@ void MethodData::remove_unshareable_info() { } void MethodData::restore_unshareable_info(TRAPS) { - //_extra_data_lock = new Mutex(Mutex::nosafepoint, "MDOExtraData_lock"); + // Do nothing. _extra_data_lock is created lazily. } #endif // INCLUDE_CDS diff --git a/src/hotspot/share/oops/trainingData.hpp b/src/hotspot/share/oops/trainingData.hpp index 8c94058c660..f778c64cbe8 100644 --- a/src/hotspot/share/oops/trainingData.hpp +++ b/src/hotspot/share/oops/trainingData.hpp @@ -535,10 +535,76 @@ class CompileTrainingData : public TrainingData { const bool _is_osr; const int _compile_id; + // Size of nmethod's inlineable instructions during training + int _inline_instructions_size; + // classes that should be initialized before this JIT task runs - DepList _init_deps; + DepList _init_deps; // set by JIT in TR, read by APH and PR // Number of uninitialized classes left, when it's 0, all deps are satisfied - volatile int _init_deps_left; + volatile int _init_deps_left; // set to deps length at AOT dump, counted down in PR + + // The deps list and counter are crucial to gating the readiness of any nmethod + // in which clinit (class initialization) barriers have been optimized away. + // That is the difference between AP4 "preload" code (which has clinit barriers) + // and A4 code (which runs much faster, because it doesn't have them). + // The CTD::_init_deps list is built by a JIT task in the training run (TR), + // is consulted by the AOT compiler in the assembly phase (APH), and eventually + // is used again in the production run (PR) to ensure that the nmethod (for + // this CTD) gets loaded only after all its dependency classes have been + // initialized. The _init_deps_left down-counter counts the number of remaining + // not-yet-initialized classes on the deps-list; when it reaches zero, the + // nmethod can be loaded (from its AOTCodeEntry) by the ac1 or ac2 loader task. + // + // Like other metadata, the CTD is built in the TR and used in both APH and PR. + // Events from the TR JIT task are posted via ciObjectFactory::notice_new_object + // which calls notice_jit_observation, to add classes to the deps-list for the + // CTD associated with the TR JIT task. An AOT compiler task later recapitulates + // the TR JIT task, calling ciEnv::compute_init_state_for_aot_compile to read the + // deps-list of the same CTD. As the AOT cache is dumped, the classes on each CTD + // deps-list are polled by compute_init_deps_left, so each _init_deps_left + // down-counter is initialized to the number of classes on the deps-list. + // + // After each clinit, CompilationPolicy::replay_training_at_init and then + // KlassTrainingData::notice_fully_initialized poll each CTD that depends on the + // newly-initialized class. This is fast because the KTDs have an inverse + // list (comp_deps) that indexes back to CTDs that refer to them as init deps. + // The two functions cooperate to serialize the CTD polling activity. + // The first enqueues the class on the TrainingReplayQueue, while the second + // runs inside the unique TrainingReplayThread. + // + // The poll decrements the down-counter, and when it reaches zero, invokes + // the CompilationPolicy method maybe_compile_early on that CTD's method. + // The compilation policy rechecks each candidate method for suitability, and + // enqueues a CompileTask to handle the load from the AOTCodeEntry for the CTD + // which is ready. CompilationPolicy handles AOT loading as well, as part of its + // responsibility for the full lifecycle of compiled code. + // + // The effect is as if the PR quickly fast-forwards the JIT activity from the TR, + // replaying each "virtual JIT" task (i.e., AOT load) as soon as the clinit events + // occur, which each loaded method depends on. (Thus the term "replay training".) + // In addition, AP4 "preload" code, is queued for loading at startup, since it + // does not need to wait for any down counter or clinit event. This helps the + // class initializations get done sooner, compared with interpreter-based + // initialization. + // + // Note that AOTCodeEntries and their CTDs do not poll clinit deps-lists for + // loading. Rather, the class clinit events push notifications to CTDs, leading + // to queueing of load requests. By the time the load request is vetted and + // queued, the nmethod code is known to be ready to use. There are some late + // load-time checks (such as verify_c2_state and read_klass), which are of + // relatively minor importance, although they can prevent loading. At worst, the + // JVM will call in the JIT to cover for an unloadable method. + // + // A few special classes, like MethodType, are initialized within the AOT cache. + // These are present on the clinit deps-lists of many CTDs, but they will never be + // initialized in the PR. They contribute to the down-counter state via a special + // pass at startup called replay_training_at_init_for_preloaded_classes. This + // pass finds such pre-initialized classes and calls replay_training_at_init for + // them, since they will never have a normal clinit. In effect, their class + // initialization actions, including CTD down-counts, are fast-forwarded at + // startup. See has_aot_initialized_mirror, which recognizes such classes. + // Whether a class is AOT-initialized or not, if it has CTDs depending on it, it + // polls them exactly once, gated by the latch has_init_deps_processed. public: // ciRecords is a generic meachanism to memoize CI responses to arbitary queries. For each function we're interested in we record @@ -652,7 +718,7 @@ class CompileTrainingData : public TrainingData { bool is_osr, int compile_id) : TrainingData(), // empty key - _method(mtd), _level(level), _is_osr(is_osr), _compile_id(compile_id), _init_deps_left(0) { } + _method(mtd), _level(level), _is_osr(is_osr), _compile_id(compile_id), _inline_instructions_size(0), _init_deps_left(0) { } public: ciRecords& ci_records() { return _ci_records; } static CompileTrainingData* make(CompileTask* task) NOT_CDS_RETURN_(nullptr); @@ -665,6 +731,9 @@ class CompileTrainingData : public TrainingData { bool is_osr() const { return _is_osr; } int compile_id() const { return _compile_id; } + int inline_instructions_size() const { return _inline_instructions_size; } + void set_inline_instructions_size(int size) { _inline_instructions_size = size; } + int init_dep_count() const { TrainingDataLocker::assert_locked(); return _init_deps.length(); @@ -794,6 +863,15 @@ class MethodTrainingData : public TrainingData { return nullptr; } + CompileTrainingData* compile_data_for_aot_code(int level) const { + CompileTrainingData* ctd = last_toplevel_compile(level); + if (ctd == nullptr && level == CompLevel_limited_profile) { + // We compile CompLevel_limited_profile AOT code for CompLevel_full_profile + ctd = _last_toplevel_compiles[CompLevel_full_profile - 1]; + } + return ctd; + } + void notice_compilation(int level, bool inlined = false) { if (!inlined) { _was_toplevel = true; diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 7741d420a46..083a3c94d48 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -935,6 +935,9 @@ "Use StoreStore barrier instead of Release barrier at the end " \ "of constructors") \ \ + product(bool, PreloadReduceTraps, true, DIAGNOSTIC, \ + "Preload code should avoid traps as much as possible.") \ + \ develop(bool, KillPathsReachableByDeadTypeNode, true, \ "When a Type node becomes top, make paths where the node is " \ "used dead by replacing them with a Halt node. Turning this off " \ @@ -993,6 +996,20 @@ product(uint, HotCodeCallLevel, 1, EXPERIMENTAL, \ "Number of levels of callees to relocate per candidate") \ range(0, max_juint) \ + \ + /* Next three AOT code flags are used only during assembly phase */ \ + /* for tier4 AOT compilation and they are ignored in other phases */ \ + \ + product(double, AOTCodeInvokeBase, 100.0, DIAGNOSTIC, \ + "AOT code invocation base limit") \ + range(1.0, 10000.0) \ + \ + product(double, AOTCodeInvokeScale, 1.0, DIAGNOSTIC, \ + "scale AOT code invocation limit") \ + range(0.001, 1000.0) \ + \ + product(bool, UseAOTCodeCounters, true, DIAGNOSTIC, \ + "Use AOT code counter to trigger JIT compilation") \ // end of C2_FLAGS diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index 580a4e72cbf..e836629cfbb 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -24,6 +24,7 @@ #include "classfile/vmClasses.hpp" #include "classfile/vmIntrinsics.hpp" +#include "code/aotCodeCache.hpp" #include "compiler/compilationMemoryStatistic.hpp" #include "compiler/compilerDefinitions.inline.hpp" #include "jfr/support/jfrIntrinsics.hpp" @@ -127,8 +128,15 @@ void C2Compiler::initialize() { void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, bool install_code, DirectiveSet* directive) { assert(is_initialized(), "Compiler thread must be initialized"); - CompilationMemoryStatisticMark cmsm(directive); + CompileTask* task = env->task(); + if (task->is_aot_load()) { + assert(install_code, "AOT code loading requires install_code"); + AOTCodeCache::load_nmethod(env, target, entry_bci, this); + // We want to go quickly through AOT code load requests + // instead of spending time on normal compilation. + return; + } bool subsume_loads = SubsumeLoads; bool do_escape_analysis = DoEscapeAnalysis; @@ -138,6 +146,8 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo bool do_locks_coarsening = EliminateLocks; bool do_superword = UseSuperWord; bool do_stringopts = OptimizeStringConcat; + bool for_aot_preload = (task->compile_reason() == CompileTask::Reason_AOTCompileForPreload); + assert(!for_aot_preload || (ClassInitBarrierMode > 0), "sanity"); while (!env->failing()) { ResourceMark rm; @@ -150,6 +160,7 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo do_locks_coarsening, do_superword, do_stringopts, + for_aot_preload, install_code); Compile C(env, target, entry_bci, options, directive); diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index b698172b801..b238fb91336 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -605,10 +605,15 @@ void Compile::print_compile_messages() { if( PrintOpto ) { if (is_osr_compilation()) { - tty->print("[OSR]%3d", _compile_id); - } else { - tty->print("%3d", _compile_id); + tty->print("[OSR]"); + } else if (env()->task()->is_aot_compile()) { + if (for_aot_preload()) { + tty->print("[PRE]"); + } else { + tty->print("[AOT]"); + } } + tty->print("%3d", _compile_id); } #endif } @@ -644,9 +649,13 @@ void Compile::print_ideal_ir(const char* compile_phase_name) const { NoSafepointVerifier nsv; ttyLocker ttyl; if (xtty != nullptr) { + CompileTask* task = env()->task(); + bool aot_comp = task->is_aot_load_or_compile(); + bool aot_preload_comp = task->is_aot_preload_or_compile(); xtty->head("ideal compile_id='%d'%s compile_phase='%s'", compile_id(), - is_osr_compilation() ? " compile_kind='osr'" : "", + is_osr_compilation() ? " compile_kind='osr'" : + (aot_preload_comp ? " compile_kind='AP'" : (aot_comp ? " compile_kind='A'" : "")), compile_phase_name); } @@ -693,6 +702,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _trace_opto_output(directive->TraceOptoOutputOption), #endif _clinit_barrier_on_entry(false), + _has_clinit_barriers(false), _comp_arena(mtCompiler, Arena::Tag::tag_comp), _barrier_set_state(BarrierSet::barrier_set()->barrier_set_c2()->create_barrier_state(comp_arena())), _env(ci_env), @@ -776,9 +786,14 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, set_print_intrinsics(directive->PrintIntrinsicsOption); set_has_irreducible_loop(true); // conservative until build_loop_tree() reset it - if (ProfileTraps) { + if ((ProfileTraps && !ci_env->is_aot_compile()) || + AOTCodeCache::is_using_code()) { // Make sure the method being compiled gets its own MDO, // so we can at least track the decompile_count(). + // Load recorded MDO from training run when AOT code + // is used - MDO may not be created yet in such case. + // No need for AOT compilation - it is not + // executed during assembly phase. method()->ensure_method_data(); } @@ -1003,6 +1018,7 @@ Compile::Compile(ciEnv* ci_env, _trace_opto_output(directive->TraceOptoOutputOption), #endif _clinit_barrier_on_entry(false), + _has_clinit_barriers(false), _comp_arena(mtCompiler, Arena::Tag::tag_comp), _barrier_set_state(BarrierSet::barrier_set()->barrier_set_c2()->create_barrier_state(comp_arena())), _env(ci_env), @@ -1181,8 +1197,12 @@ void Compile::Init(bool aliasing) { _max_node_limit = _directive->MaxNodeLimitOption; - if (VM_Version::supports_fast_class_init_checks() && has_method() && !is_osr_compilation() && method()->needs_clinit_barrier()) { + if (VM_Version::supports_fast_class_init_checks() && has_method() && !is_osr_compilation() && + (method()->needs_clinit_barrier() || (do_clinit_barriers() && method()->is_static()))) { set_clinit_barrier_on_entry(true); + if (do_clinit_barriers()) { + set_has_clinit_barriers(true); // Entry clinit barrier is in prolog code. + } } if (debug_info()->recording_non_safepoints()) { set_node_note_array(new(comp_arena()) GrowableArray @@ -4994,6 +5014,10 @@ bool Compile::final_graph_reshaping() { bool Compile::too_many_traps(ciMethod* method, int bci, Deoptimization::DeoptReason reason) { + if (PreloadReduceTraps && for_aot_preload()) { + // Preload code should not have traps, if possible. + return true; + } assert(reason > Deoptimization::Reason_none && reason <= Deoptimization::Reason_LIMIT, "invalid reason"); ciMethodData* md = method->method_data(); if (md->is_empty()) { @@ -5020,6 +5044,10 @@ bool Compile::too_many_traps(ciMethod* method, // Less-accurate variant which does not require a method and bci. bool Compile::too_many_traps(Deoptimization::DeoptReason reason, ciMethodData* logmd) { + if (PreloadReduceTraps && for_aot_preload()) { + // Preload code should not have traps, if possible. + return true; + } assert(reason > Deoptimization::Reason_none && reason <= Deoptimization::Reason_LIMIT, "invalid reason"); if (trap_count(reason) >= Deoptimization::per_method_trap_limit(reason)) { // Too many traps globally. @@ -5111,10 +5139,10 @@ bool Compile::needs_clinit_barrier(ciField* field, ciMethod* accessing_method) { } bool Compile::needs_clinit_barrier(ciInstanceKlass* holder, ciMethod* accessing_method) { - if (holder->is_initialized()) { + if (holder->is_initialized() && !do_clinit_barriers()) { return false; } - if (holder->is_being_initialized()) { + if (holder->is_being_initialized() || do_clinit_barriers()) { if (accessing_method->holder() == holder) { // Access inside a class. The barrier can be elided when access happens in , // , or a static method. In all those cases, there was an initialization diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 44077c9386f..008c6bd9a84 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -189,6 +189,8 @@ class Options { const bool _do_locks_coarsening; // Do locks coarsening const bool _do_superword; // Do SuperWord const bool _do_stringopts; // Do StringOpts + const bool _for_aot_preload; // Generate AOT code for preload (before Java method execution), + // include class init barriers const bool _install_code; // Install the code that was compiled public: Options(bool subsume_loads, @@ -199,6 +201,7 @@ class Options { bool do_locks_coarsening, bool do_superword, bool do_stringopts, + bool for_aot_preload, bool install_code) : _subsume_loads(subsume_loads), _do_escape_analysis(do_escape_analysis), @@ -208,6 +211,7 @@ class Options { _do_locks_coarsening(do_locks_coarsening), _do_superword(do_superword), _do_stringopts(do_stringopts), + _for_aot_preload(for_aot_preload), _install_code(install_code) { } @@ -221,6 +225,7 @@ class Options { /* do_lock_coarsening = */ false, /* do_superword = */ true, /* do_stringopts = */ false, + /* for_aot_preload = */ false, /* install_code = */ true ); } @@ -380,6 +385,7 @@ class Compile : public Phase { bool _has_monitors; // Metadata transfered to nmethod to enable Continuations lock-detection fastpath bool _has_scoped_access; // For shared scope closure bool _clinit_barrier_on_entry; // True if clinit barrier is needed on nmethod entry + bool _has_clinit_barriers; // True if compiled code has clinit barriers int _loop_opts_cnt; // loop opts round bool _has_flat_accesses; // Any known flat array accesses? bool _flat_accesses_share_alias; // Initially all flat array share a single slice @@ -606,6 +612,9 @@ class Compile : public Phase { bool do_superword() const { return _options._do_superword; } bool do_stringopts() const { return _options._do_stringopts; } + bool do_clinit_barriers() const { return _options._for_aot_preload; } + bool for_aot_preload() const { return _options._for_aot_preload; } + // Other fixed compilation parameters. ciMethod* method() const { return _method; } int entry_bci() const { return _entry_bci; } @@ -703,6 +712,8 @@ class Compile : public Phase { void set_has_monitors(bool v) { _has_monitors = v; } bool has_scoped_access() const { return _has_scoped_access; } void set_has_scoped_access(bool v) { _has_scoped_access = v; } + bool has_clinit_barriers()const { return _has_clinit_barriers; } + void set_has_clinit_barriers(bool z) { _has_clinit_barriers = z; } // check the CompilerOracle for special behaviours for this compile bool method_has_option(CompileCommandEnum option) const { diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 732a2f2fe2f..28a727202d2 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -27,6 +27,7 @@ #include "ci/ciSymbols.hpp" #include "classfile/vmIntrinsics.hpp" #include "classfile/vmSymbols.hpp" +#include "code/aotCodeCache.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compileLog.hpp" #include "interpreter/linkResolver.hpp" @@ -113,6 +114,14 @@ CallGenerator* Compile::call_generator(ciMethod* callee, int vtable_index, bool allow_inline = false; } + if (AOTCodeCache::is_using_code()) { + // During production run, when AOT code is used, + // C2 compilation could be requested without collecting + // profiling. Instead use profiling from training run. + // Make sure training data is loaded for callee. + callee->ensure_method_data(true /*training_data_only*/); + } + // Note: When we get profiling during stage-1 compiles, we want to pull // from more specific profile data which pertains to this inlining. // Right now, ignore the information in jvms->caller(), and do method[bci]. @@ -533,6 +542,10 @@ bool Parse::can_not_compile_call_site(ciMethod *dest_method, ciInstanceKlass* kl if (!holder_klass->is_being_initialized() && !holder_klass->is_initialized() && !holder_klass->is_interface()) { + if (C->env()->task()->is_aot_compile()) { + ResourceMark rm; + log_debug(aot, compilation)("Emitting uncommon trap (cannot compile call site) in AOT code for %s", holder_klass->name()->as_klass_external_name()); + } uncommon_trap(Deoptimization::Reason_uninitialized, Deoptimization::Action_reinterpret, holder_klass); @@ -599,6 +612,20 @@ void Parse::do_call() { return; } assert(holder_klass->is_loaded(), ""); + + if (bc() == Bytecodes::_invokestatic && C->do_clinit_barriers() && + C->needs_clinit_barrier(holder_klass, method())) { + // Generate class init barrier for static method in AOT "preload" code. + clinit_barrier(holder_klass, method()); + if (stopped()) { + // Constant folding of class initialization state check is not expected here. + // For AOT "preload" code all instance classes are considered initialized. + // See Reason_AOTCompileForPreload case in ciEnv::compute_init_state_for_aot_compile(). + // But check it anyway to be safe. + return; // MUST uncommon-trap? + } + } + //assert((bc_callee->is_static() || is_invokedynamic) == !has_receiver , "must match bc"); // XXX invokehandle (cur_bc_raw) // Note: this takes into account invokeinterface of methods declared in java/lang/Object, // which should be invokevirtuals but according to the VM spec may be invokeinterfaces diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index fd9f7090afd..54887860531 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -2512,6 +2512,15 @@ Node* GraphKit::uncommon_trap(int trap_request, trap_request), bci()); } + if (PreloadReduceTraps && C->for_aot_preload() && (action != Deoptimization::Action_none)) { + ResourceMark rm; + ciMethod* cim = C->method(); + log_debug(aot, codecache, deoptimization)("Uncommon trap in AOT preload code: reason=%s action=%s method=%s::%s bci=%d, %s", + Deoptimization::trap_reason_name(reason), Deoptimization::trap_action_name(action), + cim->holder()->name()->as_klass_external_name(), cim->name()->as_klass_external_name(), + bci(), comment); + } + CompileLog* log = C->log(); if (log != nullptr) { int kid = (klass == nullptr)? -1: log->identify(klass); @@ -3415,6 +3424,32 @@ bool GraphKit::seems_never_null(Node* obj, ciProfileData* data, bool& speculatin return false; } +void GraphKit::guard_klass_is_initialized(Node* klass) { + assert(C->do_clinit_barriers(), "should be called only for clinit barriers"); + if (ClassInitBarrierMode == 0) { + return; + } + precond(ClassInitBarrierMode == 1); // catch new value + int init_state_off = in_bytes(InstanceKlass::init_state_offset()); + Node* adr = basic_plus_adr(top(), klass, init_state_off); + Node* init_state = LoadNode::make(_gvn, nullptr, immutable_memory(), adr, + adr->bottom_type()->is_ptr(), TypeInt::BYTE, + T_BYTE, MemNode::acquire); + init_state = _gvn.transform(init_state); + + Node* initialized_state = makecon(TypeInt::make(InstanceKlass::fully_initialized)); + + Node* chk = _gvn.transform(new CmpINode(initialized_state, init_state)); + Node* tst = _gvn.transform(new BoolNode(chk, BoolTest::eq)); + + { // uncommon trap on slow path + BuildCutout unless(this, tst, PROB_MAX); + // Do not deoptimize this nmethod. Go to Interpreter to initialize class. + uncommon_trap(Deoptimization::Reason_uninitialized, Deoptimization::Action_none); + } + C->set_has_clinit_barriers(true); +} + void GraphKit::guard_klass_being_initialized(Node* klass) { int init_state_off = in_bytes(InstanceKlass::init_state_offset()); Node* adr = off_heap_plus_addr(klass, init_state_off); @@ -3453,9 +3488,14 @@ void GraphKit::guard_init_thread(Node* klass) { } void GraphKit::clinit_barrier(ciInstanceKlass* ik, ciMethod* context) { + if (C->do_clinit_barriers()) { + Node* klass = makecon(TypeKlassPtr::make(ik, Type::trust_interfaces)); + guard_klass_is_initialized(klass); + return; + } if (ik->is_being_initialized()) { if (C->needs_clinit_barrier(ik, context)) { - Node* klass = makecon(TypeKlassPtr::make(ik)); + Node* klass = makecon(TypeKlassPtr::make(ik, Type::trust_interfaces)); guard_klass_being_initialized(klass); guard_init_thread(klass); insert_mem_bar(Op_MemBarCPUOrder); @@ -3463,6 +3503,10 @@ void GraphKit::clinit_barrier(ciInstanceKlass* ik, ciMethod* context) { } else if (ik->is_initialized()) { return; // no barrier needed } else { + if (C->env()->task()->is_aot_compile()) { + ResourceMark rm; + log_debug(aot, compilation)("Emitting uncommon trap (clinit barrier) in AOT code for %s", ik->name()->as_klass_external_name()); + } uncommon_trap(Deoptimization::Reason_uninitialized, Deoptimization::Action_reinterpret, nullptr); diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 88cf7484446..ae5aa75f5e0 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -433,6 +433,7 @@ class GraphKit : public Phase { // Check the null_seen bit. bool seems_never_null(Node* obj, ciProfileData* data, bool& speculating); + void guard_klass_is_initialized(Node* klass); void guard_klass_being_initialized(Node* klass); void guard_init_thread(Node* klass); diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 72043317b37..9f30ff1c4f1 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -3256,7 +3256,10 @@ bool LibraryCallKit::inline_onspinwait() { return true; } -bool LibraryCallKit::klass_needs_init_guard(Node* kls) { +bool LibraryCallKit::klass_needs_init_guard(Node* kls) const { + if (C->do_clinit_barriers()) { + return true; // Generate guard in AOT preload code + } if (!kls->is_Con()) { return true; } diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 2978c3720e3..87ce134747d 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -264,7 +264,7 @@ class LibraryCallKit : public GraphKit { DecoratorSet mo_decorator_for_access_kind(AccessKind kind); bool inline_unsafe_access(bool is_store, BasicType type, AccessKind kind, bool is_unaligned); bool inline_unsafe_flat_access(bool is_store, AccessKind kind); - static bool klass_needs_init_guard(Node* kls); + bool klass_needs_init_guard(Node* kls) const; bool inline_unsafe_allocate(); bool inline_unsafe_newArray(bool uninitialized); bool inline_newArray(bool null_free, bool atomic); diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 48725ad405d..1bb72826531 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -29,6 +29,7 @@ #include "classfile/javaClasses.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/vmIntrinsics.hpp" +#include "code/aotCodeCache.hpp" #include "compiler/compileLog.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/c2/barrierSetC2.hpp" @@ -2290,7 +2291,9 @@ Node* LoadNode::Ideal_load_common(PhaseGVN* phase, bool can_reshape) { const Type* LoadNode::load_array_final_field(const TypeKlassPtr *tkls, ciKlass* klass) const { - assert(!UseCompactObjectHeaders || tkls->offset() != in_bytes(Klass::prototype_header_offset()), + assert(!UseCompactObjectHeaders || + AOTCodeCache::is_on_for_dump() || + tkls->offset() != in_bytes(Klass::prototype_header_offset()), "must not happen"); if (tkls->isa_instklassptr() && tkls->offset() == in_bytes(InstanceKlass::access_flags_offset())) { @@ -2512,10 +2515,14 @@ const Type* LoadNode::Value(PhaseGVN* phase) const { assert(Opcode() == Op_LoadI, "must load an int from _layout_kind"); return TypeInt::make(static_cast(klass->as_flat_array_klass()->layout_kind())); } - if (UseCompactObjectHeaders && tkls->offset() == in_bytes(Klass::prototype_header_offset())) { - // The field is Klass::_prototype_header. Return its (constant) value. - assert(this->Opcode() == Op_LoadX, "must load a proper type from _prototype_header"); - return TypeX::make(klass->prototype_header()); + // Class encoding and some class's values may change between runs. + // Force loading them when AOT code is generated. + if (!AOTCodeCache::is_on_for_dump()) { + if (UseCompactObjectHeaders && tkls->offset() == in_bytes(Klass::prototype_header_offset())) { + // The field is Klass::_prototype_header. Return its (constant) value. + assert(this->Opcode() == Op_LoadX, "must load a proper type from _prototype_header"); + return TypeX::make(klass->prototype_header()); + } } // Compute index into primary_supers array juint depth = (tkls->offset() - in_bytes(Klass::primary_supers_offset())) / sizeof(Klass*); diff --git a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp index de7dcbae771..6f480f75454 100644 --- a/src/hotspot/share/opto/output.cpp +++ b/src/hotspot/share/opto/output.cpp @@ -913,6 +913,7 @@ void PhaseOutput::FillLocArray( int idx, MachSafePointNode* sfpt, Node *local, } else if (t->base() == Type::VectorA || t->base() == Type::VectorS || t->base() == Type::VectorD || t->base() == Type::VectorX || t->base() == Type::VectorY || t->base() == Type::VectorZ) { + C->debug_info()->set_has_vectors(); array->append(new_loc_value( C->regalloc(), regnum, Location::vector )); } else if (C->regalloc()->is_oop(local)) { assert(t->base() == Type::OopPtr || t->base() == Type::InstPtr || @@ -1903,11 +1904,7 @@ void PhaseOutput::fill_buffer(C2_MacroAssembler* masm, uint* blk_starts) { #if defined(SUPPORT_OPTO_ASSEMBLY) // Dump the assembly code, including basic-block numbers if (C->print_assembly()) { - ttyLocker ttyl; // keep the following output all in one block - if (!VMThread::should_terminate()) { // test this under the tty lock - // print_metadata and dump_asm may safepoint which makes us loose the ttylock. - // We call them first and write to a stringStream, then we retake the lock to - // make sure the end tag is coherent, and that xmlStream->pop_tag is done thread safe. + if (!VMThread::should_terminate()) { ResourceMark rm; stringStream method_metadata_str; if (C->method() != nullptr) { @@ -1916,23 +1913,29 @@ void PhaseOutput::fill_buffer(C2_MacroAssembler* masm, uint* blk_starts) { stringStream dump_asm_str; dump_asm_on(&dump_asm_str, node_offsets, node_offset_limit); + // Make sure the end tag is coherent, and that xmlStream->pop_tag is done thread safe. NoSafepointVerifier nsv; - ttyLocker ttyl2; + ttyLocker ttyl; // This output goes directly to the tty, not the compiler log. // To enable tools to match it up with the compilation activity, // be sure to tag this tty output with the compile ID. if (xtty != nullptr) { + CompileTask* task = C->env()->task(); + bool aot_comp = task->is_aot_load_or_compile(); + bool aot_preload_comp = task->is_aot_preload_or_compile(); xtty->head("opto_assembly compile_id='%d'%s", C->compile_id(), - C->is_osr_compilation() ? " compile_kind='osr'" : ""); + C->is_osr_compilation() ? " compile_kind='osr'" : + (aot_preload_comp ? " compile_kind='AP'" : (aot_comp ? " compile_kind='A'" : ""))); } + const char* is_aot = C->env()->is_aot_compile() ? (C->for_aot_preload() ? "(AP) " : "(A) -") : "-----"; if (C->method() != nullptr) { - tty->print_cr("----------------------- MetaData before Compile_id = %d ------------------------", C->compile_id()); + tty->print_cr("----------------------- MetaData before Compile_id = %d %s-------------------", C->compile_id(), is_aot); tty->print_raw(method_metadata_str.freeze()); } else if (C->stub_name() != nullptr) { tty->print_cr("----------------------------- RuntimeStub %s -------------------------------", C->stub_name()); } tty->cr(); - tty->print_cr("------------------------ OptoAssembly for Compile_id = %d -----------------------", C->compile_id()); + tty->print_cr("------------------------ OptoAssembly for Compile_id = %d %s------------------", C->compile_id(), is_aot); tty->print_raw(dump_asm_str.freeze()); tty->print_cr("--------------------------------------------------------------------------------"); if (xtty != nullptr) { @@ -3305,9 +3308,7 @@ uint PhaseOutput::scratch_emit_size(const Node* n) { } void PhaseOutput::install() { - if (!C->should_install_code()) { - return; - } else if (C->stub_function() != nullptr) { + if (C->should_install_code() && C->stub_function() != nullptr) { install_stub(C->stub_name()); } else { install_code(C->method(), @@ -3360,15 +3361,19 @@ void PhaseOutput::install_code(ciMethod* target, &_handler_table, inc_table(), compiler, + C->has_clinit_barriers(), + C->for_aot_preload(), has_unsafe_access, SharedRuntime::is_wide_vector(C->max_vector_size()), C->has_monitors(), C->has_scoped_access(), - 0); + 0, + C->should_install_code()); if (C->log() != nullptr) { // Print code cache state into compiler log C->log()->code_cache_state(); } + assert(!C->has_clinit_barriers() || C->for_aot_preload(), "class init barriers should be only in preload code"); } } void PhaseOutput::install_stub(const char* stub_name) { diff --git a/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp index e7b4ed2f0b1..e2160955c17 100644 --- a/src/hotspot/share/opto/parse.hpp +++ b/src/hotspot/share/opto/parse.hpp @@ -608,6 +608,9 @@ class Parse : public GraphKit { // helper function for call statistics void count_compiled_calls(bool at_method_entry, bool is_inline) PRODUCT_RETURN; + // AOT compiled code invocations count + void count_aot_code_calls() NOT_CDS_RETURN; + Node_Notes* make_node_notes(Node_Notes* caller_nn); // Helper functions for handling normal and abnormal exits. @@ -638,8 +641,12 @@ class Parse : public GraphKit { void stress_trap(IfNode* orig_iff, Node* counter, Node* incr_store); // Increment counter used by StressUnstableIfTraps void increment_trap_stress_counter(Node*& counter, Node*& incr_store); + static volatile int _trap_stress_counter; public: + // Needed for AOT external address recording + static address trap_stress_counter_address() { return (address)&_trap_stress_counter; } + #ifndef PRODUCT // Handle PrintOpto, etc. void show_parse_info(); diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 08d5fdb8e22..984ad391dc0 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -26,9 +26,11 @@ #include "ci/ciSignature.hpp" #include "ci/ciTypeFlow.hpp" #include "compiler/compileLog.hpp" +#include "interpreter/invocationCounter.hpp" #include "interpreter/linkResolver.hpp" #include "memory/resourceArea.hpp" #include "oops/method.hpp" +#include "oops/trainingData.hpp" #include "opto/addnode.hpp" #include "opto/c2compiler.hpp" #include "opto/castnode.hpp" @@ -1226,9 +1228,11 @@ SafePointNode* Parse::create_entry_map() { _caller->map()->delete_replaced_nodes(); // If this is an inlined method, we may have to do a receiver null check. - if (_caller->has_method() && is_normal_parse() && !method()->is_static()) { + if (_caller->has_method() && is_normal_parse()) { GraphKit kit(_caller); - kit.null_check_receiver_before_call(method()); + if (!method()->is_static()) { + kit.null_check_receiver_before_call(method()); + } _caller = kit.transfer_exceptions_into_jvms(); if (kit.stopped()) { @@ -1297,6 +1301,149 @@ static bool is_auto_boxed_primitive(Node* n) { n->in(0)->as_CallJava()->method()->is_boxing_method()); } +#if INCLUDE_CDS +// Even the highest tier of AOT code (A4) incorporates an invocation +// counter on entry to an AOT nmethod. This is necessary because AOT +// optimizations are performed on less accurate information than JIT +// optimizations. A long-running JVM is likely to eventually +// recompile AOT code with the JIT compiler, so as to reoptimize with +// the fullest possible information. This helps AOT-assisted +// workloads attain the same peak performance as pure JIT-based +// workloads. +// +// One example of "less accurate" information for optimization is a +// static final field value which the AOT compiler refuses to fold as +// a constant, because many static finals cannot be predicted AOT. +// Another is the exact configuration of block and type profiles; the +// AOT compiler only sees profile information from the training run up +// to the point at which the AOT cache was built, while a production +// run may execute much longer, eventually adding very different +// information to the profile. +// +// The counter helps decide when to reoptimize. The counter limit +// chosen depends on the invocation count observed during the training +// run. The limit is an approximately linear function of the observed +// count, but at least about a hundred (see AOTCodeInvokeBase), and +// saturating out to about a million (times AOTCodeInvokeScale). +// +// The diagnostic flag UseAOTCodeCounters can be used to determine +// whether the counter overhead affected application performance. +// Note that the counter is only on the root method of any given +// nmethod. Inlined method calls are not counted. Counting overhead +// is therefore expected to be small. +// +// After JDK-8380476 TODO: The interpreter also uses a backedge counter +// to ensure eventual optimization of a method with a small invocation +// count that sits in a long-running loop. A4 methods could be given an +// equivalent counter, if they contain loops which may turn out to be +// long-running. It is help to deoptimize a long-running loop to the +// interpreter, without replacing the A4 method. The interpreter +// could be primed with a high backedge count, so that the loop will +// be reoptimized quickly by an OSR compilation. This whole concern +// is a corner case of a corner case, since OSR assumes low invocation +// count, while reoptimization to peak assumes a long-lived production +// run. + +static int scale_limit(int64_t limit) { + // To scale invocation limit a hyperbolic saturation curve formula + // is used with upper limit 1M. + return (int)(AOTCodeInvokeBase + limit / (1.0 + limit / (1000000.0 * AOTCodeInvokeScale))); +} + +void Parse::count_aot_code_calls() { + bool is_aot_compilation = C->env()->is_aot_compile(); + if (UseAOTCodeCounters && (depth() == 1) && is_aot_compilation) { + // Clear out dead values from the debug info in following runtime call + kill_dead_locals(); + + // Use method invocations count during training run and compare + // to invocations of AOT code during production run to trigger JIT + // compilation and replace AOT code with normal JITed code. + ciMetadata* mcp = method()->ensure_method_counters(); + assert(mcp != nullptr, "CompileBroker should create MethodCounters if it is missing"); + const TypePtr* mc_type = TypeMetadataPtr::make(TypePtr::Constant, mcp, Type::Offset(0)); + Node* mc = makecon(mc_type); + + assert(MethodTrainingData::have_data(), "TrainingData should be present for AOT compilation"); + methodHandle mh(Thread::current(), method()->get_Method()); + MethodTrainingData* mtd = MethodTrainingData::find_fast(mh); + assert(mtd != nullptr, "AOT compiled method should have MethodTrainingData"); + int64_t limit = mtd->invocation_count(); + int step = InvocationCounter::count_increment; + int scaled_limit = step * scale_limit(limit); + + // Count AOT compiled code invocations (use 32 bits because scaled limit fits into 32 bits) + intptr_t offset = in_bytes(MethodCounters::invocation_counter_offset()); + Node* cnt_adr = off_heap_plus_addr(mc, offset); + Node* ctrl = control(); + Node* cnt = make_load(ctrl, cnt_adr, TypeInt::INT, T_INT, MemNode::unordered); + Node* incr = _gvn.transform(new AddINode(cnt, intcon(step))); + store_to_memory(ctrl, cnt_adr, incr, T_INT, MemNode::unordered); + // Preserve memory for Phi node below + Node* st_mem = MergeMemNode::make(map()->memory()); + _gvn.set_type_bottom(st_mem); + + Node* lim = intcon(scaled_limit); + Node* chk = _gvn.transform( new CmpINode(incr, lim) ); + Node* tst = _gvn.transform( new BoolNode(chk, BoolTest::lt) ); + IfNode* iff = create_and_map_if(control(), tst, PROB_ALWAYS, (float)limit); + + RegionNode* result_rgn = new RegionNode(4); + record_for_igvn(result_rgn); + + Node* skip_call = _gvn.transform(new IfTrueNode(iff)); + result_rgn->init_req(1, skip_call); + + Node* in1_io = i_o(); + Node* in1_mem = st_mem; + // These two phis are pre-filled with copies of the fast IO and Memory + Node* io_phi = PhiNode::make(result_rgn, in1_io, Type::ABIO); + Node* mem_phi = PhiNode::make(result_rgn, in1_mem, Type::MEMORY, TypePtr::BOTTOM); + + Node* needs_call = _gvn.transform(new IfFalseNode(iff)); + set_control(needs_call); + + // InvocationCounter::carry_mask is private, calculate it from step + int carry_bits = step - 1; + Node* recomp_bit = intcon(carry_bits); + Node* rbit = _gvn.transform( new AndINode(incr, recomp_bit) ); + Node* chk2 = _gvn.transform( new CmpINode(rbit, intcon(0)) ); + Node* tst2 = _gvn.transform( new BoolNode(chk2, BoolTest::ne) ); + IfNode* iff2 = create_and_map_if(control(), tst2, PROB_FAIR, COUNT_UNKNOWN); + + Node* skip_call2 = _gvn.transform(new IfTrueNode(iff2)); + result_rgn->init_req(2, skip_call2); + + Node* needs_call2 = _gvn.transform(new IfFalseNode(iff2)); + set_control(needs_call2); + + Node* new_val = _gvn.transform(new OrINode(incr, recomp_bit)); + store_to_memory(control(), cnt_adr, new_val, T_INT, MemNode::unordered); + + const TypePtr* m_type = TypeMetadataPtr::make(method()); + Node* m = makecon(m_type); + make_runtime_call(RC_NO_LEAF | RC_UNCOMMON, + OptoRuntime::compile_method_Type(), + OptoRuntime::compile_method_Java(), + "compile_method", TypePtr::BOTTOM, m); + + // State before call + io_phi ->init_req(2, in1_io); + mem_phi->init_req(2, in1_mem); + + // State after call + result_rgn->init_req(3, control()); + io_phi ->init_req(3, i_o()); + mem_phi->init_req(3, reset_memory()); + + set_all_memory( _gvn.transform(mem_phi) ); + set_i_o( _gvn.transform(io_phi) ); + set_control( _gvn.transform(result_rgn) ); + } +} + +#endif // INCLUDE_CDS + //-----------------------------do_method_entry-------------------------------- // Emit any code needed in the pseudo-block before BCI zero. // The main thing to do is lock the receiver of a synchronized method. @@ -1455,6 +1602,8 @@ void Parse::do_method_entry() { } } } + + CDS_ONLY( count_aot_code_calls(); ) } //------------------------------init_blocks------------------------------------ @@ -1783,7 +1932,7 @@ void Parse::do_one_block() { if (failing()) return; assert(!have_se || stopped() || failing() || (sp() - pre_bc_sp) == depth, - "incorrect depth prediction: sp=%d, pre_bc_sp=%d, depth=%d", sp(), pre_bc_sp, depth); + "incorrect depth prediction: bc=%s bci=%d, sp=%d, pre_bc_sp=%d, depth=%d", Bytecodes::name(bc()), bci(), sp(), pre_bc_sp, depth); do_exceptions(); @@ -2489,6 +2638,9 @@ void Parse::call_register_finalizer() { // Add check to deoptimize once holder klass is fully initialized. void Parse::clinit_deopt() { + if (method()->holder()->is_initialized()) { + return; // in case do_clinit_barriers() is true + } assert(C->has_method(), "only for normal compilations"); assert(depth() == 1, "only for main compiled method"); assert(is_normal_parse(), "no barrier needed on osr entry"); diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index fc406cf0b1a..ff2e7c0b0e6 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1787,7 +1787,7 @@ inline int Parse::repush_if_args() { } // Used by StressUnstableIfTraps -static volatile int _trap_stress_counter = 0; +volatile int Parse::_trap_stress_counter = 0; void Parse::increment_trap_stress_counter(Node*& counter, Node*& incr_store) { Node* counter_addr = makecon(TypeRawPtr::make((address)&_trap_stress_counter)); diff --git a/src/hotspot/share/opto/rootnode.cpp b/src/hotspot/share/opto/rootnode.cpp index 60167c5436a..bdac33faa44 100644 --- a/src/hotspot/share/opto/rootnode.cpp +++ b/src/hotspot/share/opto/rootnode.cpp @@ -22,6 +22,7 @@ * */ +#include "code/aotCodeCache.hpp" #include "memory/allocation.inline.hpp" #include "opto/callnode.hpp" #include "opto/cfgnode.hpp" @@ -70,6 +71,7 @@ HaltNode::HaltNode(Node* ctrl, Node* frameptr, const char* halt_reason, bool rea init_req(TypeFunc::Memory, top); init_req(TypeFunc::FramePtr, frameptr ); init_req(TypeFunc::ReturnAdr,top); + AOTCodeCache::add_C_string(halt_reason); } const Type *HaltNode::bottom_type() const { return Type::BOTTOM; } diff --git a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp index f5095700a1f..4a0f53f42ae 100644 --- a/src/hotspot/share/opto/runtime.cpp +++ b/src/hotspot/share/opto/runtime.cpp @@ -200,6 +200,7 @@ const TypeFunc* OptoRuntime::_fast_arraycopy_Type = nullptr; const TypeFunc* OptoRuntime::_checkcast_arraycopy_Type = nullptr; const TypeFunc* OptoRuntime::_generic_arraycopy_Type = nullptr; const TypeFunc* OptoRuntime::_slow_arraycopy_Type = nullptr; +const TypeFunc* OptoRuntime::_compile_method_Type = nullptr; const TypeFunc* OptoRuntime::_unsafe_setmemory_Type = nullptr; const TypeFunc* OptoRuntime::_array_fill_Type = nullptr; const TypeFunc* OptoRuntime::_array_sort_Type = nullptr; @@ -296,6 +297,14 @@ void OptoRuntime::complete_monitor_locking_C(oopDesc* obj, BasicLock* lock, Java SharedRuntime::complete_monitor_locking_C(obj, lock, current); } +JRT_ENTRY(void, OptoRuntime::compile_method_C(Method* method, JavaThread* current)) + methodHandle m(current, method); + CompLevel level = CompLevel_full_optimization; + CompileBroker::compile_method(m, InvocationEntryBci, level, 0, nullptr, CompileTask::Reason_MustBeCompiled, current); + if (HAS_PENDING_EXCEPTION) { + CLEAR_PENDING_EXCEPTION; + } +JRT_END //============================================================================= // Opto compiler runtime routines @@ -711,6 +720,18 @@ static const TypeFunc* make_uncommon_trap_Type() { return TypeFunc::make(domain, range); } +static const TypeFunc* make_compile_method_Type() { + // create input type (domain) + const Type** fields = TypeTuple::fields(1); + fields[TypeFunc::Parms+0] = TypePtr::NOTNULL; // method to be compiled + const TypeTuple *domain = TypeTuple::make(TypeFunc::Parms+1, fields); + + // create result type (range) + fields = TypeTuple::fields(0); + const TypeTuple* range = TypeTuple::make(TypeFunc::Parms+0,fields); + return TypeFunc::make(domain, range); +} + //----------------------------------------------------------------------------- // Monitor Handling @@ -2378,6 +2399,7 @@ void OptoRuntime::initialize_types() { _checkcast_arraycopy_Type = make_arraycopy_Type(ac_checkcast); _generic_arraycopy_Type = make_arraycopy_Type(ac_generic); _slow_arraycopy_Type = make_arraycopy_Type(ac_slow); + _compile_method_Type = make_compile_method_Type(); _unsafe_setmemory_Type = make_setmemory_Type(); _array_fill_Type = make_array_fill_Type(); _array_sort_Type = make_array_sort_Type(); diff --git a/src/hotspot/share/opto/runtime.hpp b/src/hotspot/share/opto/runtime.hpp index a7c87fb43ab..8985a69f5e8 100644 --- a/src/hotspot/share/opto/runtime.hpp +++ b/src/hotspot/share/opto/runtime.hpp @@ -149,6 +149,7 @@ class OptoRuntime : public AllStatic { static const TypeFunc* _checkcast_arraycopy_Type; static const TypeFunc* _generic_arraycopy_Type; static const TypeFunc* _slow_arraycopy_Type; + static const TypeFunc* _compile_method_Type; static const TypeFunc* _unsafe_setmemory_Type; static const TypeFunc* _array_fill_Type; static const TypeFunc* _array_sort_Type; @@ -233,6 +234,7 @@ class OptoRuntime : public AllStatic { oopDesc* dest, jint dest_pos, jint length, JavaThread* thread); static void complete_monitor_locking_C(oopDesc* obj, BasicLock* lock, JavaThread* current); + static void compile_method_C(Method* method, JavaThread* current); public: static void monitor_notify_C(oopDesc* obj, JavaThread* current); @@ -299,8 +301,9 @@ class OptoRuntime : public AllStatic { static address slow_arraycopy_Java() { return _slow_arraycopy_Java; } static address register_finalizer_Java() { return _register_finalizer_Java; } - static address load_unknown_value_Java() { return _load_unknown_value_Java; } - static address store_unknown_value_Java() { return _store_unknown_value_Java; } + static address compile_method_Java() { return _compile_method_Java; } + static address load_unknown_value_Java() { return _load_unknown_value_Java; } + static address store_unknown_value_Java() { return _store_unknown_value_Java; } static address vthread_end_first_transition_Java() { return _vthread_end_first_transition_Java; } static address vthread_start_final_transition_Java() { return _vthread_start_final_transition_Java; } @@ -469,6 +472,11 @@ class OptoRuntime : public AllStatic { return _slow_arraycopy_Type; } // the full routine + static inline const TypeFunc* compile_method_Type() { + assert(_compile_method_Type != nullptr, "should be initialized"); + return _compile_method_Type; + } + static inline const TypeFunc* unsafe_setmemory_Type() { assert(_unsafe_setmemory_Type != nullptr, "should be initialized"); return _unsafe_setmemory_Type; diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 00d2bc57ff1..8a2a751fb70 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -1143,7 +1143,7 @@ bool WhiteBox::compile_method(Method* method, int comp_level, int bci, JavaThrea bool is_blocking = !matcher.directive_set()->BackgroundCompilationOption; // Compile method and check result - nmethod* nm = CompileBroker::compile_method(mh, bci, comp_level, mh->invocation_count(), CompileTask::Reason_Whitebox, CHECK_false); + nmethod* nm = CompileBroker::compile_method(mh, bci, comp_level, mh->invocation_count(), nullptr, CompileTask::Reason_Whitebox, CHECK_false); MutexLocker mu(THREAD, Compile_lock); bool is_queued = mh->queued_for_compilation(); if ((!is_blocking && is_queued) || nm != nullptr) { diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index b661acf488c..348727c439e 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -26,6 +26,7 @@ #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" +#include "code/aotCodeCache.hpp" #include "code/codeCache.hpp" #include "code/debugInfoRec.hpp" #include "code/nmethod.hpp" @@ -1829,8 +1830,11 @@ static void log_uncommon_trap(nmethod* nm, Method* tm, intptr_t pc, frame& fr, i if (class_name != nullptr) { ls.print("%s class_name=%s ", unresolved ? "unresolved" : "", class_name); } - ls.print("cid=%4d %s level=%d ", - nm->compile_id(), (is_osr ? "osr" : ""), nm->comp_level()); + ls.print("cid=%4d %s%s%s level=%d ", + nm->compile_id(), (is_osr ? "osr" : " "), + (nm->is_aot() ? "aot " : ""), + (nm->aot_preloaded() ? "preload " : ""), + nm->comp_level()); ls.print_cr("pc=" INTPTR_FORMAT " relative_pc=" INTPTR_FORMAT, pc, fr.pc() - nm->code_begin()); @@ -1881,7 +1885,7 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* current, jint tr gather_statistics(reason, action, trap_bc); // Ensure that we can record deopt. history: - bool create_if_missing = ProfileTraps; + bool create_if_missing = ProfileTraps || AOTCodeCache::is_using_code(); methodHandle profiled_method; profiled_method = trap_method; diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp index 24d55c2d52d..d30734ac4c1 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp @@ -453,3 +453,24 @@ JVMFlag::Error ControlIntrinsicConstraintFunc(ccstrlist value, bool verbose) { return JVMFlag::SUCCESS; } +JVMFlag::Error DisableAOTCodeConstraintFunc(uint value, bool verbose) { + const int max_modes = 5; + uint original_value = value; + for (int i = 0; i < max_modes; i++) { + if (value % 10 > 1) { + JVMFlag::printError(verbose, + "Invalid value (" UINT32_FORMAT ") " + "in DisableAOTCode at position %d\n", value, i); + return JVMFlag::VIOLATES_CONSTRAINT; + } + value = value / 10; + } + if (value != 0) { + JVMFlag::printError(verbose, + "Invalid value (" UINT32_FORMAT ") " + "for DisableAOTCode: maximal %d digits\n", original_value, max_modes); + return JVMFlag::VIOLATES_CONSTRAINT; + } + return JVMFlag::SUCCESS; +} + diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.hpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.hpp index 45e91058e0b..3eaa31f20e6 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.hpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.hpp @@ -52,6 +52,7 @@ f(intx, InitArrayShortSizeConstraintFunc) \ f(ccstrlist, DisableIntrinsicConstraintFunc) \ f(ccstrlist, ControlIntrinsicConstraintFunc) \ + f(uint, DisableAOTCodeConstraintFunc) \ COMPILER2_PRESENT( \ f(intx, InteriorEntryAlignmentConstraintFunc) \ f(intx, NodeLimitFudgeFactorConstraintFunc) \ diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index 2d1191e8eb0..d5fbd76666e 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -689,6 +689,8 @@ void frame::print_C_frame(outputStream* st, char* buf, int buflen, address pc) { // First letter indicates type of the frame: // J: Java frame (compiled) // j: Java frame (interpreted) +// A: Java frame (AOT compiled and loaded after class initialization) +// P: Java frame (AOT compiled and preloaded before class initialization) // V: VM frame (C/C++) // v: Other frames running VM generated code (e.g. stubs, adapters, etc.) // C: C/C++ frame @@ -729,7 +731,8 @@ void frame::print_on_error(outputStream* st, char* buf, int buflen, bool verbose nmethod* nm = _cb->as_nmethod(); Method* m = nm->method(); if (m != nullptr) { - st->print("J %d%s", nm->compile_id(), (nm->is_osr_method() ? "%" : "")); + st->print("%s", (nm->aot_preloaded() ? "P" : (nm->is_aot() ? "A" : "J"))); + st->print(" %d%s", nm->compile_id(), (nm->is_osr_method() ? "%" : "")); st->print(" %s", nm->compiler_name()); m->name_and_sig_as_C_string(buf, buflen); st->print(" %s", buf); diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 703b0776cad..31bb9fcc5d5 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -126,7 +126,11 @@ const size_t minimumSymbolTableSize = 1024; \ product(bool, AOTCompatibleOopCompression, false, DIAGNOSTIC, \ "Always use HeapBasedNarrowOop mode, so that AOT code will " \ - "always work regardless of runtime heap range") \ + "always work regardless of runtime heap range; this is set " \ + "by default when assembling an AOT cache which contains code, " \ + "and by default in a production run that uses that AOT cache; " \ + "to diagnose possible effects on performance, you can assemble " \ + "an AOT cache by disabling either AOTCodeCaching or this flag") \ \ product(bool, UseCompactObjectHeaders, true, \ "Use compact 64-bit object headers in 64-bit VM") \ @@ -995,7 +999,8 @@ const int ObjectAlignmentInBytes = 8; "Use population count instruction") \ \ develop(bool, TraceMethodReplacement, false, \ - "Print when methods are replaced do to recompilation") \ + "Print when AOT or tiered JIT methods are replaced due to " \ + "recompilation") \ \ product(intx, MinPassesBeforeFlush, 10, DIAGNOSTIC, \ "Minimum number of sweeper passes before an nmethod " \ diff --git a/src/hotspot/share/runtime/init.cpp b/src/hotspot/share/runtime/init.cpp index 8130bfbc1eb..03d478c2a45 100644 --- a/src/hotspot/share/runtime/init.cpp +++ b/src/hotspot/share/runtime/init.cpp @@ -22,6 +22,7 @@ * */ +#include "cds/aotMetaspace.hpp" #include "classfile/stringTable.hpp" #include "classfile/symbolTable.hpp" #include "code/aotCodeCache.hpp" @@ -129,6 +130,7 @@ jint init_globals() { bytecodes_init(); classLoader_init1(); compilationPolicy_init(); + AOTMetaspace::get_aot_code_region_size(); codeCache_init(); VM_Version_init(); // depends on codeCache_init for emitting code VMRegImpl::set_regName(); // need this before generate_stubs (for printing oop maps). diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index d854db4f131..4ec55fb052f 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -34,6 +34,7 @@ #include "classfile/stringTable.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" +#include "code/aotCodeCache.hpp" #include "code/codeCache.hpp" #include "compiler/compilationMemoryStatistic.hpp" #include "compiler/compilationPolicy.hpp" @@ -250,9 +251,12 @@ static void print_bytecode_count() {} // General statistics printing (profiling ...) -void print_statistics() { +void print_statistics_before_exit() { + LogStreamHandle(Info, aot, codecache, stats) aot_log; if (CITime) { CompileBroker::print_times(); + } else if (aot_log.is_enabled()) { + AOTCodeCache::print_timers_on(&aot_log); } #ifdef COMPILER1 @@ -285,6 +289,9 @@ void print_statistics() { if (PrintNMethodStatistics) { nmethod::print_statistics(); + AOTCodeCache::print_statistics_on(tty); + } else if (aot_log.is_enabled()) { + AOTCodeCache::print_statistics_on(&aot_log); } if (CountCompiledCalls) { print_method_invocation_histogram(); @@ -505,7 +512,7 @@ void before_exit(JavaThread* thread, bool halt) { } #endif - print_statistics(); + print_statistics_before_exit(); { MutexLocker ml(BeforeExit_lock); _before_exit_status = BEFORE_EXIT_DONE; diff --git a/src/hotspot/share/runtime/java.hpp b/src/hotspot/share/runtime/java.hpp index 2eb8d6b8ead..db2f44f9622 100644 --- a/src/hotspot/share/runtime/java.hpp +++ b/src/hotspot/share/runtime/java.hpp @@ -58,6 +58,8 @@ extern void vm_shutdown_during_initialization(const char* error, const char* mes extern void vm_exit_during_cds_dumping(const char* error, const char* message = nullptr); +extern void print_statistics_before_exit(); + // This is defined in linkType.cpp due to linking restraints extern bool is_vm_statically_linked(); diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index b0fa8af184d..9e9ac9c65cd 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -3247,7 +3247,7 @@ void AdapterHandlerLibrary::lookup_aot_cache(AdapterHandlerEntry* handler) { const char* name = AdapterHandlerLibrary::name(handler); const uint32_t id = AdapterHandlerLibrary::id(handler); - CodeBlob* blob = AOTCodeCache::load_code_blob(AOTCodeEntry::Adapter, id, name); + CodeBlob* blob = AOTCodeCache::load_adapter(id, name); if (blob != nullptr) { handler->set_adapter_blob(blob->as_adapter_blob()); } @@ -3361,7 +3361,7 @@ bool AdapterHandlerLibrary::generate_adapter_code(AdapterHandlerEntry* handler, // try to save generated code const char* name = AdapterHandlerLibrary::name(handler); const uint32_t id = AdapterHandlerLibrary::id(handler); - bool success = AOTCodeCache::store_code_blob(*adapter_blob, AOTCodeEntry::Adapter, id, name); + bool success = AOTCodeCache::store_adapter(*adapter_blob, id, name); assert(success || !AOTCodeCache::is_dumping_adapter(), "caching of adapter must be disabled"); } #endif // ZERO diff --git a/src/hotspot/share/runtime/stubDeclarations.hpp b/src/hotspot/share/runtime/stubDeclarations.hpp index cc7f8130982..c8f215082e3 100644 --- a/src/hotspot/share/runtime/stubDeclarations.hpp +++ b/src/hotspot/share/runtime/stubDeclarations.hpp @@ -238,6 +238,7 @@ do_stub(monitor_notifyAll, 0, false, false) \ do_stub(rethrow, 2, true, true) \ do_stub(slow_arraycopy, 0, false, false) \ + do_stub(compile_method, 0, true, false) \ do_stub(register_finalizer, 0, false, false) \ do_stub(load_unknown_value, 0, true, false) \ do_stub(store_unknown_value, 0, true, false) \ diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 0e1b8519fc9..601c64c8c63 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -35,6 +35,7 @@ #include "classfile/systemDictionary.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" +#include "code/aotCodeCache.hpp" #include "compiler/compileBroker.hpp" #include "compiler/compilerThread.hpp" #include "compiler/compileTask.hpp" @@ -819,6 +820,9 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { initialize_class(vmSymbols::jdk_internal_vm_Continuation(), CHECK_JNI_ERR); } + // Pre-load AOT compiled methods + AOTCodeCache::preload_code(CHECK_JNI_ERR); + if (NativeHeapTrimmer::enabled()) { NativeHeapTrimmer::initialize(); } diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index 68f61f08098..d6b26f209f6 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -29,6 +29,7 @@ #include "classfile/javaThreadStatus.hpp" #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" +#include "code/aotCodeCache.hpp" #include "code/codeBlob.hpp" #include "code/codeCache.hpp" #include "code/compiledIC.hpp" @@ -544,8 +545,11 @@ nonstatic_field(nmethod, _immutable_data_size, int) \ nonstatic_field(nmethod, _compile_id, int) \ nonstatic_field(nmethod, _comp_level, CompLevel) \ + nonstatic_field(nmethod, _aot_code_entry, AOTCodeEntry*) \ volatile_nonstatic_field(nmethod, _exception_cache, ExceptionCache*) \ \ + nonstatic_field(AOTCodeEntry, _for_preload, bool) \ + \ nonstatic_field(Deoptimization::UnrollBlock, _size_of_deoptimized_frame, int) \ nonstatic_field(Deoptimization::UnrollBlock, _caller_adjustment, int) \ nonstatic_field(Deoptimization::UnrollBlock, _number_of_frames, int) \ @@ -1107,6 +1111,7 @@ declare_toplevel_type(CompileTask) \ declare_toplevel_type(Deoptimization) \ declare_toplevel_type(Deoptimization::UnrollBlock) \ + declare_toplevel_type(AOTCodeEntry) \ \ /************************/ \ /* ImmutableOopMap */ \ diff --git a/src/hotspot/share/utilities/nativeStackPrinter.cpp b/src/hotspot/share/utilities/nativeStackPrinter.cpp index b243791c933..f1fa9bdb7a3 100644 --- a/src/hotspot/share/utilities/nativeStackPrinter.cpp +++ b/src/hotspot/share/utilities/nativeStackPrinter.cpp @@ -45,7 +45,7 @@ void NativeStackPrinter::print_stack_from_frame(outputStream* st, frame fr, bool print_source_info, int max_frames) { // see if it's a valid frame if (fr.pc()) { - st->print_cr("Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)"); + st->print_cr("Native frames: (J=compiled Java code, A=AOT compiled, P=AOT preloaded, j=interpreted, Vv=VM code, C=native code)"); const int limit = max_frames == -1 ? StackPrintLimit : MIN2(max_frames, StackPrintLimit); int count = 0; diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index 477155e75a7..8461f1edf6a 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -319,7 +319,7 @@ void VMError::print_stack_trace(outputStream* st, JavaThread* jt, } #else if (jt->has_last_Java_frame()) { - st->print_cr("Java frames: (J=compiled Java code, j=interpreted, Vv=VM code)"); + st->print_cr("Java frames: (J=compiled Java code, A=AOT compiled, P=AOT preloaded, j=interpreted, Vv=VM code)"); for (StackFrameStream sfs(jt, true /* update */, true /* process_frames */); !sfs.is_done(); sfs.next()) { sfs.current()->print_on_error(st, buf, buflen, verbose); st->cr(); diff --git a/test/hotspot/jtreg/ProblemList-AotJdk.txt b/test/hotspot/jtreg/ProblemList-AotJdk.txt index bb5793cd4a3..e772230bc31 100644 --- a/test/hotspot/jtreg/ProblemList-AotJdk.txt +++ b/test/hotspot/jtreg/ProblemList-AotJdk.txt @@ -53,10 +53,21 @@ compiler/intrinsics/klass/TestIsPrimitive.java 0000000 generic-all # However when AOTClassLinking is enabled, Integer is always resolved at JVM start-up. compiler/ciReplay/TestInliningProtectionDomain.java 0000000 generic-all +# The test is very sensetive to profiling date for String class and fail +# when use training data from AOT cache created from training with +# different Java application: TestSetupAOT.java +compiler/stringopts/TestStringConcatIR.java 0000000 generic-all + # These tests fail often with AotJdk due to JDK-8323727 compiler/arguments/TestStressReflectiveCode.java 8323727 generic-all compiler/arraycopy/TestCloneWithStressReflectiveCode.java 8323727 generic-all +serviceability/sa/TestJhsdbJstackMixedWithXComp.java#xcomp-disable-tiered-compilation 8382548 linux-aarch64 + +compiler/calls/NativeCalls.java 8383780 generic-all + +vmTestbase/vm/mlvm/indy/stress/jdi/breakpointInCompiledCode/Test.java 8383781 generic-all + ############################################################################# # Preview project specific failures go here at the end of the file. diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 54b5d12a484..05af7904a06 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -553,10 +553,15 @@ tier1_runtime_appcds = \ runtime/cds/appcds/aotCache/HelloAOTCache.java \ runtime/cds/appcds/aotCode \ runtime/cds/appcds/HelloTest.java \ - -runtime/cds/appcds/aotCode/AOTCodeFlags.java + -runtime/cds/appcds/aotCode/AOTCodeFlags.java \ + -runtime/cds/appcds/aotCode/TestAOTCodeCounters.java \ + -runtime/cds/appcds/aotCode/TestAOTCodeFeatures.java tier1_runtime_appcds_exclude = \ runtime/cds/appcds/ \ + runtime/cds/appcds/aotCode/AOTCodeFlags.java \ + runtime/cds/appcds/aotCode/TestAOTCodeCounters.java \ + runtime/cds/appcds/aotCode/TestAOTCodeFeatures.java \ -:tier1_runtime_appcds tier1_runtime_no_cds = \ diff --git a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java index 340c55ef99c..1f4562defa5 100644 --- a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java +++ b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java @@ -165,12 +165,12 @@ static class Runner { private static final Pattern reCompileCommand = Pattern.compile( "CompileCommand: (.*)"); private static final Pattern reCompilation = Pattern.compile( - "(\\d+) (C1|C2|no compiler): *(\\d+) ([ %][ s][ !][ b][ n]) ([-0-4 ]) +([^ ]+).*"); + "(\\d+) (C1|C2|no compiler): *(\\d+) ([ %][ s][ !][ b][ n][ A][ P]) ([-0-4 ]) +([^ ]+).*"); private static final Pattern reExcludeCompile = Pattern.compile( ".*made not compilable on level (\\d) +([^ ]+) .* excluded by CompileCommand"); private static final Pattern reCompiledMethod = Pattern.compile( ".*-{35} Assembly -{35}\\n(?:\\[[0-9.]+s]\\[warning]\\[os] Loading hsdis library failed\\n)?\\nCompiled method \\((?:c1|c2)\\) (\\d+) ([Cc][12]): *" - + "(\\d+) ([ %][ s][ !][ b][ n]) ([-0-4 ]) +([^ ]+) +(@ -?[0-9]+ +)?\\(\\d+ bytes\\)", Pattern.DOTALL); + + "(\\d+) ([ %][ s][ !][ b][ n][ A][ P]) ([-0-4 ]) +([^ ]+) +(@ -?[0-9]+ +)?\\(\\d+ bytes\\)", Pattern.DOTALL); private static final Pattern reMethodData = Pattern.compile( ".*-{72}\\nstatic ([^\\n]+)\\n *interpreter_invocation_count: *\\d+\\n *invocation_counter: *\\d+", Pattern.DOTALL); private static final Pattern reEndOfLog = Pattern.compile(""); diff --git a/test/hotspot/jtreg/compiler/inlining/LateInlinePrinting.java b/test/hotspot/jtreg/compiler/inlining/LateInlinePrinting.java index 88b483076e3..0d1a284a4d4 100644 --- a/test/hotspot/jtreg/compiler/inlining/LateInlinePrinting.java +++ b/test/hotspot/jtreg/compiler/inlining/LateInlinePrinting.java @@ -84,19 +84,16 @@ public static void main(String[] args) throws Exception { OutputAnalyzer analyzer = new OutputAnalyzer(pb.start()); analyzer.shouldHaveExitValue(0); - analyzer.shouldContain(""" -compiler.inlining.LateInlinePrinting$TestLateInlining::test2 (7 bytes) - @ 0 compiler.inlining.LateInlinePrinting$TestLateInlining::inlined1 (1 bytes) inline (hot) late inline succeeded - @ 3 compiler.inlining.LateInlinePrinting$TestLateInlining::inlined2 (1 bytes) inline (hot) late inline succeeded - """); - analyzer.shouldContain(""" -compiler.inlining.LateInlinePrinting$TestLateInlining::test1 (13 bytes) - @ 0 compiler.inlining.LateInlinePrinting$TestLateInlining::test3 (1 bytes) inline (hot) late inline succeeded - @ 3 compiler.inlining.LateInlinePrinting$TestLateInlining::testFailInline (1 bytes) failed to inline: disallowed by CompileCommand - @ 6 compiler.inlining.LateInlinePrinting$TestLateInlining::testFailInline (1 bytes) failed to inline: disallowed by CompileCommand - @ 9 compiler.inlining.LateInlinePrinting$TestLateInlining::test2 (7 bytes) inline (hot) late inline succeeded - @ 0 compiler.inlining.LateInlinePrinting$TestLateInlining::inlined1 (1 bytes) inline (hot) late inline succeeded - @ 3 compiler.inlining.LateInlinePrinting$TestLateInlining::inlined2 (1 bytes) inline (hot) late inline succeeded - """); + analyzer.shouldContain("compiler.inlining.LateInlinePrinting$TestLateInlining::test2 (7 bytes)"); + analyzer.shouldContain("@ 0 compiler.inlining.LateInlinePrinting$TestLateInlining::inlined1 (1 bytes) inline (hot) late inline succeeded"); + analyzer.shouldContain("@ 3 compiler.inlining.LateInlinePrinting$TestLateInlining::inlined2 (1 bytes) inline (hot) late inline succeeded"); + + analyzer.shouldContain("compiler.inlining.LateInlinePrinting$TestLateInlining::test1 (13 bytes)"); + analyzer.shouldContain("@ 0 compiler.inlining.LateInlinePrinting$TestLateInlining::test3 (1 bytes) inline (hot) late inline succeeded"); + analyzer.shouldContain("@ 3 compiler.inlining.LateInlinePrinting$TestLateInlining::testFailInline (1 bytes) failed to inline: disallowed by CompileCommand"); + analyzer.shouldContain("@ 6 compiler.inlining.LateInlinePrinting$TestLateInlining::testFailInline (1 bytes) failed to inline: disallowed by CompileCommand"); + analyzer.shouldContain("@ 9 compiler.inlining.LateInlinePrinting$TestLateInlining::test2 (7 bytes) inline (hot) late inline succeeded"); + analyzer.shouldContain("@ 0 compiler.inlining.LateInlinePrinting$TestLateInlining::inlined1 (1 bytes) inline (hot) late inline succeeded"); + analyzer.shouldContain("@ 3 compiler.inlining.LateInlinePrinting$TestLateInlining::inlined2 (1 bytes) inline (hot) late inline succeeded"); } } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java index 6fe4b31b9ef..1b74232a81f 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java @@ -146,6 +146,7 @@ public class TestFramework { "LogCompilation", "UseCompactObjectHeaders", "UseFMA", + "AOTCache", // Riscv "UseRVV", "UseZbb", diff --git a/test/hotspot/jtreg/compiler/profiling/spectrapredefineclass/Launcher.java b/test/hotspot/jtreg/compiler/profiling/spectrapredefineclass/Launcher.java index 297c0312069..f525c14694b 100644 --- a/test/hotspot/jtreg/compiler/profiling/spectrapredefineclass/Launcher.java +++ b/test/hotspot/jtreg/compiler/profiling/spectrapredefineclass/Launcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -29,6 +29,8 @@ * java.instrument * @requires vm.jvmti & (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4) * @requires vm.compMode != "Xcomp" + * @comment Can't use AOT cache because -XX:TypeProfileLevel=222 invalidates training data + * @requires !vm.cds.supports.aot.code.caching | vm.opt.AOTCache == null * @build compiler.profiling.spectrapredefineclass.Agent * @run driver jdk.test.lib.helpers.ClassFileInstaller compiler.profiling.spectrapredefineclass.Agent * @run driver compiler.profiling.spectrapredefineclass.Launcher diff --git a/test/hotspot/jtreg/compiler/profiling/spectrapredefineclass_classloaders/Launcher.java b/test/hotspot/jtreg/compiler/profiling/spectrapredefineclass_classloaders/Launcher.java index e7f5736889c..ee77e86e3ba 100644 --- a/test/hotspot/jtreg/compiler/profiling/spectrapredefineclass_classloaders/Launcher.java +++ b/test/hotspot/jtreg/compiler/profiling/spectrapredefineclass_classloaders/Launcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -29,6 +29,8 @@ * java.instrument * @requires vm.jvmti & (vm.opt.TieredStopAtLevel == null | vm.opt.TieredStopAtLevel == 4) * @requires vm.compMode != "Xcomp" + * @comment Can't use AOT cache because -XX:TypeProfileLevel=222 invalidates training data + * @requires !vm.cds.supports.aot.code.caching | vm.opt.AOTCache == null * @build compiler.profiling.spectrapredefineclass_classloaders.Agent * compiler.profiling.spectrapredefineclass_classloaders.Test * compiler.profiling.spectrapredefineclass_classloaders.A diff --git a/test/hotspot/jtreg/compiler/runtime/safepoints/TestMachTempsAcrossSafepoints.java b/test/hotspot/jtreg/compiler/runtime/safepoints/TestMachTempsAcrossSafepoints.java index ecd8f58c5ed..126bd79f5af 100644 --- a/test/hotspot/jtreg/compiler/runtime/safepoints/TestMachTempsAcrossSafepoints.java +++ b/test/hotspot/jtreg/compiler/runtime/safepoints/TestMachTempsAcrossSafepoints.java @@ -34,6 +34,8 @@ * @key randomness * @library /test/lib / * @requires vm.gc.G1 & vm.bits == 64 & vm.opt.final.UseCompressedOops == true + * @comment Can't use AOT cache because training data may affect shape of test's code + * @requires !vm.cds.supports.aot.code.caching | vm.opt.AOTCache == null * @run driver compiler.runtime.safepoints.TestMachTempsAcrossSafepoints */ diff --git a/test/hotspot/jtreg/runtime/cds/MetaspaceAllocGaps.java b/test/hotspot/jtreg/runtime/cds/MetaspaceAllocGaps.java index df297ddbaa3..ce9610d089f 100644 --- a/test/hotspot/jtreg/runtime/cds/MetaspaceAllocGaps.java +++ b/test/hotspot/jtreg/runtime/cds/MetaspaceAllocGaps.java @@ -42,9 +42,12 @@ public class MetaspaceAllocGaps { public static void main(String[] args) throws Exception { String appJar = ClassFileInstaller.getJarPath("hello.jar"); for (int i = 0; i < 2; i++) { + // Exclude AOT training data for consistency. String compressedOops = "-XX:" + (i == 0 ? "-" : "+") + "UseCompressedOops"; SimpleCDSAppTester.of("MetaspaceAllocGaps" + i) .addVmArgs("-Xlog:aot=debug,aot+alloc=trace", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:-AOTRecordTraining", "-XX:+UseCompactObjectHeaders") .classpath(appJar) .appCommandLine("Hello") diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java index 7d557a3b1c7..0aaf2f5fb77 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java @@ -26,7 +26,7 @@ /* * @test * @summary Sanity test for -XX:+AOTCompatibleOopCompression - * @requires vm.cds.supports.aot.class.linking + * @requires vm.cds.supports.aot.code.caching * @requires vm.bits == 64 & vm.opt.final.UseCompressedOops == true * @library /test/lib * @build AOTCompatibleOopCompression diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompileEagerly.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompileEagerly.java index c740ecd96f5..76f7faf75f2 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompileEagerly.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompileEagerly.java @@ -83,7 +83,7 @@ public static void main(String[] args) throws Exception { System.out.println("Production Run with AOTCache and eager compilation explicitly ON"); pb = ProcessTools.createLimitedTestJavaProcessBuilder( "-XX:AOTCache=" + aotCacheFile, - "-XX:+UnlockExperimentalVMOptions", + "-XX:+UnlockDiagnosticVMOptions", "-XX:+AOTCompileEagerly", "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod-eager-on"); @@ -93,7 +93,7 @@ public static void main(String[] args) throws Exception { System.out.println("Production Run with AOTCache and eager compilation explicitly OFF"); pb = ProcessTools.createLimitedTestJavaProcessBuilder( "-XX:AOTCache=" + aotCacheFile, - "-XX:+UnlockExperimentalVMOptions", + "-XX:+UnlockDiagnosticVMOptions", "-XX:-AOTCompileEagerly", "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod-eager-off"); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/TestSetupAOTTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/TestSetupAOTTest.java index 118b4c6af1d..71aabc710ee 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/TestSetupAOTTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/TestSetupAOTTest.java @@ -27,7 +27,7 @@ * @summary This is a test case for creating an AOT cache using the setup_aot/TestSetupAOT.java program, which * is used for running HotSpot tests in the "AOT mode" * (E.g., make test JTREG=AOT_JDK=true TEST=open/test/hotspot/jtreg/runtime/invokedynamic) - * @requires vm.cds + * @requires vm.cds.supports.aot.class.linking * @library /test/lib /test/setup_aot * @build TestSetupAOTTest JavacBenchApp TestSetupAOT * @run driver jdk.test.lib.helpers.ClassFileInstaller diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCPUFeatureIncompatibilityTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCPUFeatureIncompatibilityTest.java index 990a5e81e12..6e2cbd54c16 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCPUFeatureIncompatibilityTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCPUFeatureIncompatibilityTest.java @@ -90,11 +90,11 @@ public static void testIncompatibleFeature(String vmOption, String featureName, public String[] vmArgs(RunMode runMode) { if (runMode == RunMode.ASSEMBLY) { if (mode == IncompatibilityMode.MISSING) { - return new String[] {"-Xlog:aot+codecache*=debug"}; + return new String[] {"-Xlog:aot+codecache+exit=debug"}; } else { // UnlockDiagnosticVMOptions must precede vmOption because // some tested CPU feature flags are diagnostic (e.g. UseZba on riscv). - return new String[] {"-XX:+UnlockDiagnosticVMOptions", vmOption, "-Xlog:aot+codecache*=debug"}; + return new String[] {"-XX:+UnlockDiagnosticVMOptions", vmOption, "-Xlog:aot+codecache+exit=debug"}; } } else if (runMode == RunMode.PRODUCTION) { if (mode == IncompatibilityMode.MISSING) { @@ -102,12 +102,12 @@ public String[] vmArgs(RunMode runMode) { vmOption, // Prevent exiting VM on failure "-XX:-AbortVMOnAOTCodeFailure", - "-Xlog:aot+codecache*=debug"}; + "-Xlog:aot+codecache+init=debug"}; } else { return new String[] {"-XX:+UnlockDiagnosticVMOptions", // Prevent exiting VM on failure "-XX:-AbortVMOnAOTCodeFailure", - "-Xlog:aot+codecache*=debug"}; + "-Xlog:aot+codecache+init=debug"}; } } return new String[]{}; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java index b0d65fe48be..3d1c3dac962 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java @@ -26,7 +26,7 @@ * @test * @summary Sanity test of AOT Code Cache with compressed oops configurations * @requires vm.cds.supports.aot.code.caching - * @requires vm.compMode != "Xcomp" + * @requires vm.compMode != "Xcomp" & vm.compMode != "Xint" * @requires vm.bits == 64 * @requires vm.opt.final.UseCompressedOops * @comment The test verifies AOT checks during VM startup and not code generation. @@ -183,7 +183,7 @@ public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception line = list.get(i+2); Matcher m = p.matcher(line); if (!m.find()) { - throw new RuntimeException("Pattern \"" + p + "\" not found in the output"); + throw new RuntimeException("Pattern \"" + p + "\" not found in the output. Got \"" + line + "\""); } aotCacheBase = Long.valueOf(m.group(1), 16); aotCacheShift = Integer.valueOf(m.group(2)); @@ -191,7 +191,7 @@ public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception line = list.get(i+6); m = p.matcher(line); if (!m.find()) { - throw new RuntimeException("Pattern \"" + p + "\" not found in the output"); + throw new RuntimeException("Pattern \"" + p + "\" not found in the output. Got \"" + line + "\""); } currentBase = Long.valueOf(m.group(1), 16); currentShift = Integer.valueOf(m.group(2)); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeFlags.java index d7ff823734c..aa857b0167f 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeFlags.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeFlags.java @@ -27,11 +27,14 @@ * @requires vm.gc != "Z" * @summary Sanity test of combinations of the AOT Code Caching diagnostic flags * @requires vm.cds.supports.aot.code.caching + * @requires vm.compMode != "Xcomp" & vm.compMode != "Xint" + * @comment It runs for long time (hours) with -Xcomp and AOT code caching enabled + * on (virtual) machine with small number of cores * @requires vm.compiler1.enabled & vm.compiler2.enabled * @comment Both C1 and C2 JIT compilers are required because the test verifies * compiler's runtime blobs generation. * @library /test/lib /test/setup_aot - * @build AOTCodeFlags + * @build AOTCodeFlags AOTCodeSimpleTestApp * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar * AOTCodeSimpleTestApp * @run driver/timeout=1500 AOTCodeFlags @@ -41,11 +44,14 @@ * @requires vm.gc.Z * @summary Sanity test of combinations of the AOT Code Caching diagnostic flags * @requires vm.cds.supports.aot.code.caching + * @requires vm.compMode != "Xcomp" & vm.compMode != "Xint" + * @comment It runs for long time (hours) with -Xcomp and AOT code caching enabled + * on (virtual) machine with small number of cores * @requires vm.compiler1.enabled & vm.compiler2.enabled * @comment Both C1 and C2 JIT compilers are required because the test verifies * compiler's runtime blobs generation. * @library /test/lib /test/setup_aot - * @build AOTCodeFlags + * @build AOTCodeFlags AOTCodeSimpleTestApp * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar * AOTCodeSimpleTestApp * @run driver/timeout=1500 AOTCodeFlags Z @@ -55,11 +61,14 @@ * @requires vm.gc.Shenandoah * @summary Sanity test of combinations of the AOT Code Caching diagnostic flags * @requires vm.cds.supports.aot.code.caching + * @requires vm.compMode != "Xcomp" & vm.compMode != "Xint" + * @comment It runs for long time (hours) with -Xcomp and AOT code caching enabled + * on (virtual) machine with small number of cores * @requires vm.compiler1.enabled & vm.compiler2.enabled * @comment Both C1 and C2 JIT compilers are required because the test verifies * compiler's runtime blobs generation. * @library /test/lib /test/setup_aot - * @build AOTCodeFlags + * @build AOTCodeFlags AOTCodeSimpleTestApp * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar * AOTCodeSimpleTestApp * @run driver/timeout=1500 AOTCodeFlags Shenandoah @@ -69,11 +78,14 @@ * @requires vm.gc.Parallel * @summary Sanity test of combinations of the AOT Code Caching diagnostic flags * @requires vm.cds.supports.aot.code.caching + * @requires vm.compMode != "Xcomp" & vm.compMode != "Xint" + * @comment It runs for long time (hours) with -Xcomp and AOT code caching enabled + * on (virtual) machine with small number of cores * @requires vm.compiler1.enabled & vm.compiler2.enabled * @comment Both C1 and C2 JIT compilers are required because the test verifies * compiler's runtime blobs generation. * @library /test/lib /test/setup_aot - * @build AOTCodeFlags + * @build AOTCodeFlags AOTCodeSimpleTestApp * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar * AOTCodeSimpleTestApp * @run driver/timeout=1500 AOTCodeFlags Parallel @@ -90,10 +102,10 @@ public class AOTCodeFlags { private static String gcName = null; public static void main(String... args) throws Exception { Tester t = new Tester(args.length == 0 ? null : args[0]); - // mode bits 0 and 1 encode AOTAdapterCaching and AOTStubCaching settings + // mode bits 0,1,2 encode AOTAdapterCaching, AOTStubCaching and AOTCodeCaching settings // aMode is used for assembly run, pMode for production run - for (int aMode = 0; aMode < 4; aMode++) { - for (int pMode = 0; pMode < 4; pMode++) { + for (int aMode = 0; aMode < 8; aMode++) { + for (int pMode = 0; pMode < 8; pMode++) { t.setTestMode(aMode, pMode); t.run(new String[] {"AOT", "--two-step-training"}); } @@ -118,6 +130,10 @@ boolean isStubCachingOn(int mode) { return (mode & 0x2) != 0; } + boolean isCodeCachingOn(int mode) { + return (mode & 0x4) != 0; + } + public void setTestMode(int aMode, int pMode) { this.aMode = aMode; this.pMode = pMode; @@ -128,6 +144,7 @@ public List getVMArgsForTestMode(int mode) { list.add("-XX:+UnlockDiagnosticVMOptions"); list.add(isAdapterCachingOn(mode) ? "-XX:+AOTAdapterCaching" : "-XX:-AOTAdapterCaching"); list.add(isStubCachingOn(mode) ? "-XX:+AOTStubCaching" : "-XX:-AOTStubCaching"); + list.add(isCodeCachingOn(mode) ? "-XX:+AOTCodeCaching" : "-XX:-AOTCodeCaching"); return list; } @@ -159,6 +176,7 @@ public String[] vmArgs(RunMode runMode) { List args = getGCArgs(); args.addAll(List.of("-Xlog:aot+codecache+init=debug", "-Xlog:aot+codecache+exit=debug", + "-Xlog:aot+codecache+nmethod=info", "-Xlog:aot+codecache+stubs=debug")); // Ensure compilations are finished before the JVM exits. @@ -185,61 +203,84 @@ public String[] appCommandLine(RunMode runMode) { @Override public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { if (runMode == RunMode.ASSEMBLY) { - if (!isAdapterCachingOn(aMode) && !isStubCachingOn(aMode)) { // this is equivalent to completely disable AOT code cache - out.shouldNotMatch("Adapters:\\s+total"); - out.shouldNotMatch("Shared Blobs:\\s+total"); - out.shouldNotMatch("C1 Blobs:\\s+total"); - out.shouldNotMatch("C2 Blobs:\\s+total"); + if (!isAdapterCachingOn(aMode) && !isStubCachingOn(aMode) && !isCodeCachingOn(aMode)) { + // this is equivalent to completely disable AOT code cache + out.shouldNotMatch("Adapter:\\s+total"); + out.shouldNotMatch("SharedBlob:\\s+total"); + out.shouldNotMatch("C1Blob:\\s+total"); + out.shouldNotMatch("C2Blob:\\s+total"); + out.shouldNotMatch("StubGenBlob:\\s+total"); + out.shouldNotMatch("Nmethod:\\s+total"); } else { if (isAdapterCachingOn(aMode)) { - // AOTAdapterCaching is on, non-zero adapters should be stored - out.shouldMatch("Adapters:\\s+total=[1-9][0-9]+"); + // AOTAdapterCaching is on, adapters should be stored + out.shouldMatch("Adapter:\\s+total=[1-9][0-9]+"); } else { // AOTAdapterCaching is off, no adapters should be stored - out.shouldMatch("Adapters:\\s+total=0"); + out.shouldMatch("Adapter:\\s+total=0"); } if (isStubCachingOn(aMode)) { - // AOTStubCaching is on, non-zero stubs should be stored - out.shouldMatch("Shared Blobs:\\s+total=[1-9][0-9]+"); - out.shouldMatch("C1 Blobs:\\s+total=[1-9][0-9]+"); - out.shouldMatch("C2 Blobs:\\s+total=[1-9][0-9]+"); + // AOTStubCaching is on, stubs should be stored + out.shouldMatch("SharedBlob:\\s+total=[1-9][0-9]+"); + out.shouldMatch("C1Blob:\\s+total=[1-9][0-9]+"); + out.shouldMatch("C2Blob:\\s+total=[1-9][0-9]+"); + out.shouldMatch("StubGenBlob:\\s+total=[1-9]+"); } else { // AOTStubCaching is off, no stubs should be stored - out.shouldMatch("Shared Blobs:\\s+total=0"); - out.shouldMatch("C1 Blobs:\\s+total=0"); - out.shouldMatch("C2 Blobs:\\s+total=0"); + out.shouldMatch("SharedBlob:\\s+total=0"); + out.shouldMatch("C1Blob:\\s+total=0"); + out.shouldMatch("C2Blob:\\s+total=0"); + out.shouldMatch("StubGenBlob:\\s+total=0"); + } + if (isCodeCachingOn(aMode)) { + // AOTCodeCaching is on, nmethods should be stored + out.shouldMatch("Nmethod:\\s+total=[1-9][0-9]+"); + } else { + // AOTCodeCaching is off, no nmethods should be stored + out.shouldMatch("Nmethod:\\s+total=0"); } } } else if (runMode == RunMode.PRODUCTION) { - // Irrespective of assembly run mode, if both adapter and stub caching is disabled + // Irrespective of assembly run mode, if all types of code caching is disabled // in production run, then it is equivalent to completely disabling AOT code cache - if (!isAdapterCachingOn(pMode) && !isStubCachingOn(pMode)) { - out.shouldNotMatch("Adapters:\\s+total"); - out.shouldNotMatch("Shared Blobs:\\s+total"); - out.shouldNotMatch("C1 Blobs:\\s+total"); - out.shouldNotMatch("C2 Blobs:\\s+total"); + if (!isAdapterCachingOn(pMode) && !isStubCachingOn(pMode) && !isCodeCachingOn(pMode)) { + out.shouldNotMatch("Adapter:\\s+total"); + out.shouldNotMatch("SharedBlob:\\s+total"); + out.shouldNotMatch("C1Blob:\\s+total"); + out.shouldNotMatch("C2Blob:\\s+total"); + out.shouldNotMatch("StubGenBlob:\\s+total"); + out.shouldNotMatch("Nmethod:\\s+total"); } else { // If AOT code cache is effectively disabled in the assembly run, then production run // would emit empty code cache message. - if (!isAdapterCachingOn(aMode) && !isStubCachingOn(aMode)) { - if (isAdapterCachingOn(pMode) || isStubCachingOn(pMode)) { + if (!isAdapterCachingOn(aMode) && !isStubCachingOn(aMode) && !isCodeCachingOn(aMode)) { + if (isAdapterCachingOn(pMode) || isStubCachingOn(pMode) || isCodeCachingOn(pMode)) { out.shouldMatch("AOT Code Cache is empty"); } } else { if (isAdapterCachingOn(aMode)) { if (isAdapterCachingOn(pMode)) { - out.shouldMatch("Read blob.*kind=Adapter.*"); + out.shouldMatch("Loaded blob.*kind=Adapter.*"); } else { - out.shouldNotMatch("Read blob.*kind=Adapter.*"); + out.shouldNotMatch("Loaded blob.*kind=Adapter.*"); } } if (isStubCachingOn(aMode)) { if (isStubCachingOn(pMode)) { - out.shouldMatch("Read blob.*kind=SharedBlob.*"); - out.shouldMatch("Read blob.*kind=C1Blob.*"); + out.shouldMatch("Loaded blob.*kind=SharedBlob.*"); + out.shouldMatch("Loaded blob.*kind=C1Blob.*"); + out.shouldMatch("Loaded blob.*kind=StubGenBlob.*"); + } else { + out.shouldNotMatch("Loaded blob.*kind=SharedBlob.*"); + out.shouldNotMatch("Loaded blob.*kind=C1Blob.*"); + out.shouldNotMatch("Loaded blob.*kind=StubGenBlob.*"); + } + } + if (isCodeCachingOn(aMode)) { + if (isCodeCachingOn(pMode)) { + out.shouldMatch("Loaded nmethod .*"); } else { - out.shouldNotMatch("Read blob.*kind=SharedBlob.*"); - out.shouldNotMatch("Read blob.*kind=C1Blob.*"); + out.shouldNotMatch("Loaded nmethod .*"); } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTest.java index a27711b716b..2ff084d2c6e 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTest.java @@ -189,10 +189,10 @@ public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception } else if (runMode == RunMode.PRODUCTION) { out.shouldMatch("aot,codecache,init.*\\s+Loaded [1-9]\\d+ AOT code entries from AOT Code Cache"); if (!withVerifyOops) { // Adapters are not cached for VerifyOops+Valhalla - out.shouldMatch("aot,codecache,stubs.*\\s+Read blob.*kind=Adapter.*"); + out.shouldMatch("aot,codecache,stubs.*\\s+Loaded blob.*kind=Adapter.*"); } - out.shouldMatch("aot,codecache,stubs.*\\s+Read blob.*kind=SharedBlob.*"); - out.shouldMatch("aot,codecache,stubs.*\\s+Read blob.*kind=C1Blob.*"); + out.shouldMatch("aot,codecache,stubs.*\\s+Loaded blob.*kind=SharedBlob.*"); + out.shouldMatch("aot,codecache,stubs.*\\s+Loaded blob.*kind=C1Blob.*"); } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java new file mode 100644 index 00000000000..6ee6b8e4168 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java @@ -0,0 +1,156 @@ +/* + * 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 8391718 + * @summary Test AOTAssertOnUnknownExternalAddress flag with unknown external address + * @requires vm.flagless + * @requires vm.debug + * @comment the flag is debug + * @requires vm.cds.supports.aot.code.caching + * @requires vm.compiler2.enabled + * @library /test/lib /test/setup_aot + * @build ${test.main.class} + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * TestUnknownAddress + * @run driver/timeout=480 ${test.main.class} + */ + +import java.lang.reflect.*; +import sun.misc.*; + +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + + +public class AOTCodeTestUnknownExternAddress { + + public static void main(String... args) throws Exception { + String appJar = ClassFileInstaller.getJarPath("app.jar"); + String aotConfigFile = "app.aotconfig"; + String aotCacheFile = "app.aot"; + String appClass = "TestUnknownAddress"; + + ProcessBuilder pb; + OutputAnalyzer out; + + // first make sure we have a valid aotConfigFile + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:CompileCommand=compileonly,TestUnknownAddress::test*", + "-Xcomp", "-XX:-TieredCompilation", + "-Xlog:aot", + "-XX:AOTMode=record", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, appClass); + + out = CDSTestUtils.executeAndLog(pb, "train"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:CompileCommand=compileonly,TestUnknownAddress::test*", + "-Xcomp", "-XX:-TieredCompilation", + "-Xlog:aot+codecache=debug", + "-Xlog:aot+codecache+exit=debug", + "-Xlog:aot+codecache+nmethod", + "-XX:-AOTAssertOnUnknownExternalAddress", + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar); + + out = CDSTestUtils.executeAndLog(pb, "assemble skip assert"); + out.shouldNotContain("Address 0xffffffffffffffff is missing in AOT Code Cache addresses table"); + out.shouldNotMatch("'TestUnknownAddress\\.test1\\(Z\\)V' AOT.*is skipped: store to AOT Code Cache failed"); + out.shouldMatch("'TestUnknownAddress\\.test1\\(Z\\)V' AOT.*: wrote to AOT Code Cache"); + out.shouldMatch("'TestUnknownAddress\\.test2\\(Z\\)V' AOT.*is skipped: store to AOT Code Cache failed"); + out.shouldMatch("aot,codecache,exit.*Wrote [1-9]\\d* AOT code entries to AOT Code Cache"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:CompileCommand=compileonly,TestUnknownAddress::test*", + "-Xcomp", "-XX:-TieredCompilation", + "-Xlog:aot+codecache+init=debug", + "-XX:AOTMode=on", + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar, appClass); + + out = CDSTestUtils.executeAndLog(pb, "production"); + out.shouldMatch("aot,codecache,init.*\\s+Loaded [1-9]\\d* AOT code entries from AOT Code Cache"); + out.shouldHaveExitValue(0); + + // Assembly run with assert + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:CompileCommand=compileonly,TestUnknownAddress::test*", + "-Xcomp", "-XX:-TieredCompilation", + "-XX:-CreateCoredumpOnCrash", + "-Xlog:aot+codecache=debug", + "-Xlog:aot+codecache+exit=debug", + "-Xlog:aot+codecache+nmethod", + "-XX:+AOTAssertOnUnknownExternalAddress", + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar); + + out = ProcessTools.executeProcess(pb); + out.shouldContain("for is missing in AOT Code Cache addresses table"); + out.shouldNotHaveExitValue(0); + } +} + +class TestUnknownAddress { + private static Unsafe unsafe; + private static final long UNKNOWN_ADDRESS = 0x1234_5678_9000L; + private static final long MINUS_1_ADDRESS = -1L; + + static { + try { + Field field = Unsafe.class.getDeclaredField("theUnsafe"); + field.setAccessible(true); + unsafe = (Unsafe)field.get(null); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void test1(boolean b) { + if (b) { + unsafe.putLong(MINUS_1_ADDRESS, 42); + } + } + + public static void test2(boolean b) { + if (b) { + unsafe.putLong(UNKNOWN_ADDRESS, 42); + } + } + + public static void main(String[] args) { + test1(false); + test2(false); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestAOTCodeCounters.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestAOTCodeCounters.java new file mode 100644 index 00000000000..0aaa8a66ddf --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestAOTCodeCounters.java @@ -0,0 +1,124 @@ +/* + * 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 + * @summary Test AOT code invocation counters in C2 compiled AOT code + * @requires vm.cds.supports.aot.code.caching + * @requires vm.compiler2.enabled + * @comment C2 JIT compiler is required because the test verifies + * compiled code generation. + * @library /test/lib /test/setup_aot + * @build ${test.main.class} AOTCodeSimpleTestApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * AOTCodeSimpleTestApp + * @run driver/timeout=480 ${test.main.class} + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.Utils; + +public class TestAOTCodeCounters { + private static final Random RANDOM = Utils.getRandomInstance(); + + // Test default (Base=100. Scale=1.) and limit values. + static String randomBase = String.valueOf(RANDOM.nextDouble(1.0, 10000.0)); + static String randomScale = String.valueOf(RANDOM.nextDouble(0.001, 1000.0)); + static String[][] flags = { + {"-XX:AOTCodeInvokeBase=1.0", "-XX:AOTCodeInvokeScale=0.001"}, + {"-XX:AOTCodeInvokeBase=1.0", "-XX:AOTCodeInvokeScale=1.0"}, + {"-XX:AOTCodeInvokeBase=1.0", "-XX:AOTCodeInvokeScale=1000.0"}, + {"-XX:AOTCodeInvokeBase=100.0", "-XX:AOTCodeInvokeScale=0.001"}, + {"-XX:AOTCodeInvokeBase=100.0", "-XX:AOTCodeInvokeScale=1.0"}, + {"-XX:AOTCodeInvokeBase=100.0", "-XX:AOTCodeInvokeScale=1000.0"}, + {"-XX:AOTCodeInvokeBase=10000.0", "-XX:AOTCodeInvokeScale=0.001"}, + {"-XX:AOTCodeInvokeBase=10000.0", "-XX:AOTCodeInvokeScale=1.0"}, + {"-XX:AOTCodeInvokeBase=10000.0", "-XX:AOTCodeInvokeScale=1000.0"}, + {"-XX:AOTCodeInvokeBase=" + randomBase, "-XX:AOTCodeInvokeScale=" + randomScale} + }; + + public static void main(String... args) throws Exception { + System.out.println("Random: -XX:AOTCodeInvokeBase=" + randomBase + " -XX:AOTCodeInvokeScale=" + randomScale); + for (int i = 0; i < flags.length; i++) { + Tester t = new Tester(flags[i]); + t.run(new String[] {"AOT", "--two-step-training"}); + } + } + + static class Tester extends CDSAppTester { + private String[] Counterflags; + + public Tester(String[] flags) { + super("TestAOTCodeCounters"); + Counterflags = flags; + } + + @Override + public String classpath(RunMode runMode) { + return "app.jar"; + } + + @Override + public String[] vmArgs(RunMode runMode) { + List args = new ArrayList(); + + // Ensure compilations are finished before the JVM exits. + args.add("-Xbatch"); + + // Add flags for logs + args.addAll(List.of("-Xlog:aot+codecache+init=debug", + "-Xlog:aot+codecache+nmethod=info", + "-Xlog:aot+codecache+exit=debug")); + // Add diagnostic flags + args.addAll(List.of("-XX:+UnlockDiagnosticVMOptions", + "-XX:+UseAOTCodeCounters", + "-XX:+AbortVMOnAOTCodeFailure")); + // Add feature flag + args.addAll(Arrays.asList(Counterflags)); + return args.toArray(new String[args.size()]); + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { "AOTCodeSimpleTestApp" }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (runMode == RunMode.ASSEMBLY) { + out.shouldMatch("aot,codecache,exit.*\\s+AOT code cache size: [1-9]\\d+ bytes"); + out.shouldMatch("Nmethod:\\s+total=[1-9][0-9]+"); + } else if (runMode == RunMode.PRODUCTION) { + out.shouldMatch("aot,codecache,init.*\\s+Loaded [1-9]\\d+ AOT code entries from AOT Code Cache"); + out.shouldMatch("Loaded nmethod .*"); + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestAOTCodeFeatures.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestAOTCodeFeatures.java new file mode 100644 index 00000000000..00a75baa157 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestAOTCodeFeatures.java @@ -0,0 +1,110 @@ +/* + * 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 + * @summary Sanity test AOTCodeCache flags + * @requires vm.cds.supports.aot.code.caching + * @requires vm.compiler1.enabled & vm.compiler2.enabled + * @comment Both C1 and C2 JIT compilers are required because the test verifies + * compiled code generation. + * @library /test/lib /test/setup_aot + * @build TestAOTCodeFeatures JavacBenchApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * JavacBenchApp + * JavacBenchApp$ClassFile + * JavacBenchApp$FileManager + * JavacBenchApp$SourceFile + * @run driver/timeout=480 ${test.main.class} + */ + +import java.util.ArrayList; +import java.util.List; + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.process.OutputAnalyzer; + +public class TestAOTCodeFeatures { + + // Too long to test both, default and opposite, values. + // Only test opposite. + static String[] flags = { + "-XX:-AOTCompileEagerly", + "-XX:ClassInitBarrierMode=0", + "-XX:-UseAOTCodeCounters", + "-XX:-PreloadReduceTraps", + "-XX:+AOTPreloadBlocking", + "-XX:AOTCodePreloadStart=2G", + "-XX:AOTCodePreloadStop=0" + }; + + public static void main(String... args) throws Exception { + for (int i = 0; i < flags.length; i++) { + Tester t = new Tester(flags[i]); + t.run(new String[] {"AOT", "--two-step-training"}); + } + } + + static class Tester extends CDSAppTester { + private String flagName; + + public Tester(String name) { + super("TestAOTCodeFeatures"); + flagName = name; + } + + @Override + public String classpath(RunMode runMode) { + return "app.jar"; + } + + @Override + public String[] vmArgs(RunMode runMode) { + List args = new ArrayList(); + // Add flags for logs + args.addAll(List.of("-Xlog:aot+codecache+init=debug", + "-Xlog:aot+codecache+exit=debug")); + // Add diagnostic flags + args.addAll(List.of("-XX:+UnlockDiagnosticVMOptions", + "-XX:+AbortVMOnAOTCodeFailure")); + // Add feature flag + args.add(flagName); + return args.toArray(new String[args.size()]); + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { "JavacBenchApp", "1" }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (runMode == RunMode.ASSEMBLY) { + out.shouldMatch("aot,codecache,exit.*\\s+AOT code cache size: [1-9]\\d+ bytes"); + } else if (runMode == RunMode.PRODUCTION) { + out.shouldMatch("aot,codecache,init.*\\s+Loaded [1-9]\\d+ AOT code entries from AOT Code Cache"); + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestDisableAOTCode.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestDisableAOTCode.java new file mode 100644 index 00000000000..cc295b7231c --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestDisableAOTCode.java @@ -0,0 +1,219 @@ +/* + * 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 + * @summary Sanity test of the diagnostic flag -XX:DisableAOTCode=n + * @requires vm.cds.supports.aot.code.caching + * @requires vm.flagless + * @requires vm.compiler1.enabled & vm.compiler2.enabled + * @comment Both C1 and C2 JIT compilers are required because the test verifies + * all AOT code tiers generation (except tier 3). + * @library /test/lib /test/setup_aot + * @build TestDisableAOTCode JavacBenchApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * JavacBenchApp + * JavacBenchApp$ClassFile + * JavacBenchApp$FileManager + * JavacBenchApp$SourceFile + * @run driver/timeout=480 TestDisableAOTCode + */ + +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestDisableAOTCode { + + public static void main(String... args) throws Exception { + String appJar = ClassFileInstaller.getJarPath("app.jar"); + String aotConfigFile = "app.aotconfig"; + String aotCacheFile = "app.aot"; + String appClass = "JavacBenchApp"; + String appArgs = "1"; + + ProcessBuilder pb; + OutputAnalyzer out; + + // first make sure we have a valid aotConfigFile + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot", + "-XX:AOTMode=record", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "train"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+exit", + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableAOTCode=1", + "-cp", appJar); + + out = CDSTestUtils.executeAndLog(pb, "assemble_no_AOT_T1"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 1: total=0"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 2:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 3: total=0"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 4:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 5:.*"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+exit", + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableAOTCode=10000", + "-cp", appJar); + + out = CDSTestUtils.executeAndLog(pb, "assemble_no_AOT_T5"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 1:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 2:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 3: total=0"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 4:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 5: total=0"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+exit", + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableAOTCode=11111", + "-cp", appJar); + + out = CDSTestUtils.executeAndLog(pb, "assemble_no_AOT_all"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 1: total=0"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 2: total=0"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 3: total=0"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 4: total=0"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 5: total=0"); + out.shouldHaveExitValue(0); + + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+exit", + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar); + + out = CDSTestUtils.executeAndLog(pb, "assemble_default"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 1:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 2:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 3: total=0"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 4:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 5:.*"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+init", + "-XX:AOTCache=" + aotCacheFile, + "-XX:+CITime", + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "production_default"); + out.shouldMatch(" AOT Code T1.*"); + out.shouldMatch(" AOT Code T2.*"); + out.shouldMatch(" AOT Code T4.*"); + out.shouldMatch(" AOT Code T5.*"); + // Tier 3 AOT code generation is not implemented + out.shouldNotMatch("\\s+AOT Code T3.*"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+init", + "-XX:AOTCache=" + aotCacheFile, + "-XX:+CITime", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableAOTCode=1", + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "production_no_AOT_T1"); + out.shouldNotMatch(" AOT Code T1.*"); + out.shouldMatch(" AOT Code T2.*"); + out.shouldMatch(" AOT Code T4.*"); + out.shouldMatch(" AOT Code T5.*"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+init", + "-XX:AOTCache=" + aotCacheFile, + "-XX:+CITime", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableAOTCode=10000", + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "production_no_AOT_T5"); + out.shouldMatch(" AOT Code T1.*"); + out.shouldMatch(" AOT Code T2.*"); + out.shouldMatch(" AOT Code T4.*"); + out.shouldNotMatch(" AOT Code T5.*"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+init", + "-XX:AOTCache=" + aotCacheFile, + "-XX:+CITime", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableAOTCode=11111", + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "production_no_AOT_all"); + out.shouldNotMatch(" AOT Code T1.*"); + out.shouldNotMatch(" AOT Code T2.*"); + out.shouldNotMatch(" AOT Code T4.*"); + out.shouldNotMatch(" AOT Code T5.*"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+init", + "-XX:AOTCache=" + aotCacheFile, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableAOTCode=15", + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "production_wrong_value"); + out.shouldContain("Improperly specified VM option 'DisableAOTCode=15'"); + out.shouldHaveExitValue(1); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+init", + "-XX:AOTCache=" + aotCacheFile, + "-XX:+UnlockDiagnosticVMOptions", + "-XX:DisableAOTCode=111111", + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "production_wrong_digits"); + out.shouldContain("Improperly specified VM option 'DisableAOTCode=111111'"); + out.shouldHaveExitValue(1); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestVerifyAOTCode.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestVerifyAOTCode.java new file mode 100644 index 00000000000..3c88ca3651d --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/TestVerifyAOTCode.java @@ -0,0 +1,120 @@ +/* + * 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 + * @summary Sanity test of the diagnostic flag -XX:+VerifyAOTCode + * @requires vm.cds.supports.aot.code.caching + * @requires vm.flagless + * @requires vm.compiler1.enabled & vm.compiler2.enabled + * @comment Both C1 and C2 JIT compilers are required because the test verifies + * all AOT code tiers generation (except tier 3). + * @library /test/lib /test/setup_aot + * @build TestVerifyAOTCode JavacBenchApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * JavacBenchApp + * JavacBenchApp$ClassFile + * JavacBenchApp$FileManager + * JavacBenchApp$SourceFile + * @run driver/timeout=480 TestVerifyAOTCode + */ + +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestVerifyAOTCode { + + public static void main(String... args) throws Exception { + String appJar = ClassFileInstaller.getJarPath("app.jar"); + String aotConfigFile = "app.aotconfig"; + String aotCacheFile = "app.aot"; + String appClass = "JavacBenchApp"; + String appArgs = "10"; + + ProcessBuilder pb; + OutputAnalyzer out; + + // first make sure we have a valid aotConfigFile + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot", + "-XX:AOTMode=record", + "-XX:AOTConfiguration=" + aotConfigFile, + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "train"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+exit", + "-XX:AOTMode=create", + "-XX:AOTConfiguration=" + aotConfigFile, + "-XX:AOTCache=" + aotCacheFile, + "-cp", appJar); + + out = CDSTestUtils.executeAndLog(pb, "assemble"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 1:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 2:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 3:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 4:.*"); + out.shouldMatch("aot,codecache,exit.*\\s+Tier 5:.*"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+init", + "-Xlog:aot+codecache+nmethod", + "-XX:AOTCache=" + aotCacheFile, + "-XX:+CITime", + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "production_default"); + out.shouldMatch("Loaded nmethod from AOT Code Cache"); + out.shouldMatch(" AOT Code T1.*"); + out.shouldMatch(" AOT Code T2.*"); + out.shouldMatch(" AOT Code T4.*"); + out.shouldMatch(" AOT Code T5.*"); + // Tier 3 AOT code generation is not implemented + out.shouldNotMatch("\\s+AOT Code T3.*"); + out.shouldHaveExitValue(0); + + pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot+codecache+init", + "-Xlog:aot+codecache+nmethod", + "-XX:AOTCache=" + aotCacheFile, + "-XX:+CITime", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+VerifyAOTCode", + "-cp", appJar, appClass, appArgs); + + out = CDSTestUtils.executeAndLog(pb, "production_VerifyAOTCode"); + out.shouldMatch("Verified nmethod from AOT Code Cache"); + out.shouldNotMatch("Loaded nmethod from AOT Code Cache"); + out.shouldMatch(" AOT Code T1.*"); + out.shouldMatch(" AOT Code T2.*"); + out.shouldMatch(" AOT Code T4.*"); + out.shouldMatch(" AOT Code T5.*"); + out.shouldHaveExitValue(0); + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java index fc24f341ff3..9564e619ecd 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java @@ -25,7 +25,7 @@ /* * @test * @summary "AOT" aliases for traditional CDS command-line options - * @requires vm.cds + * @requires vm.cds.supports.aot.code.caching * @requires vm.flagless * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes * @build Hello @@ -52,6 +52,11 @@ public static void main(String[] args) throws Exception { } static void positiveTests() throws Exception { + String hasTrainingDataPattern = "MethodTrainingData *= *[1-9]"; + String hasAOTCodePattern = "Shared file region .ac. .: *[1-9]"; + String noAOTCodePattern = "Shared file region .ac. .: *0"; + String hasMappedAOTCodePattern = "Mapped [0-9]+ bytes at address 0x[0-9a-f]+ from AOT Code Cache"; + //---------------------------------------------------------------------- printTestCase("Training Run"); ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( @@ -63,6 +68,8 @@ static void positiveTests() throws Exception { OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "train"); out.shouldContain("Hello World"); out.shouldContain("AOTConfiguration recorded: " + aotConfigFile); + out.shouldMatch(hasTrainingDataPattern); + out.shouldMatch(noAOTCodePattern); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- @@ -78,6 +85,8 @@ static void positiveTests() throws Exception { out = CDSTestUtils.executeAndLog(pb, "asm"); out.shouldContain("AOTCache creation is complete"); out.shouldMatch("hello[.]aot"); + out.shouldMatch(hasTrainingDataPattern); + out.shouldMatch(hasAOTCodePattern); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- @@ -85,11 +94,13 @@ static void positiveTests() throws Exception { pb = ProcessTools.createLimitedTestJavaProcessBuilder( "-XX:AOTCache=" + aotCacheFile, "-Xlog:aot", + "-Xlog:aot+codecache*", "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod"); out.shouldContain("Using AOT-linked classes: true (static archive: has aot-linked classes)"); out.shouldContain("Opened AOT cache hello.aot."); out.shouldContain("Hello World"); + out.shouldMatch(hasMappedAOTCodePattern); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- @@ -143,7 +154,7 @@ static void positiveTests() throws Exception { "-XX:-AOTClassLinking", "-XX:AOTConfiguration=" + aotConfigFile, "-XX:AOTCache=" + aotCacheFile, - "-Xlog:aot", + "-Xlog:aot=debug", "-XX:+UnlockDiagnosticVMOptions", "-XX:+AOTCompatibleOopCompression", // avoid production run failure due to incompatible CompressedOops::base "-cp", appJar); @@ -151,6 +162,8 @@ static void positiveTests() throws Exception { out.shouldContain("AOTClassLinking is updated to true (the same as when AOT configuration file"); out.shouldContain("AOTCache creation is complete"); out.shouldMatch("hello[.]aot"); + out.shouldMatch(hasTrainingDataPattern); + out.shouldMatch(hasAOTCodePattern); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- @@ -158,11 +171,13 @@ static void positiveTests() throws Exception { pb = ProcessTools.createLimitedTestJavaProcessBuilder( "-XX:AOTCache=" + aotCacheFile, "-Xlog:aot", + "-Xlog:aot+codecache*", "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod"); out.shouldContain("Using AOT-linked classes: true"); out.shouldContain("Opened AOT cache hello.aot."); out.shouldContain("Hello World"); + out.shouldMatch(hasMappedAOTCodePattern); out.shouldHaveExitValue(0); //---------------------------------------------------------------------- diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotProfile/AOTProfileFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/aotProfile/AOTProfileFlags.java index 4ad42cb715b..7564f8880ec 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotProfile/AOTProfileFlags.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotProfile/AOTProfileFlags.java @@ -74,6 +74,7 @@ static void trainAndRun(String testName, String trainingFlags, String production // first make sure we have a valid aotConfigFile with default value of TypeProfileLevel pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot", "-XX:AOTMode=record", "-XX:AOTConfiguration=" + aotConfigFile, "-XX:+UnlockExperimentalVMOptions", @@ -84,6 +85,7 @@ static void trainAndRun(String testName, String trainingFlags, String production out.shouldHaveExitValue(0); pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot", "-XX:AOTMode=create", "-XX:AOTConfiguration=" + aotConfigFile, "-XX:AOTCache=" + aotCacheFile, @@ -95,6 +97,7 @@ static void trainAndRun(String testName, String trainingFlags, String production out.shouldHaveExitValue(0); pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot", "-XX:AOTCache=" + aotCacheFile, "-XX:+UnlockExperimentalVMOptions", trainingFlags, @@ -105,6 +108,7 @@ static void trainAndRun(String testName, String trainingFlags, String production out.shouldHaveExitValue(0); pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xlog:aot", "-XX:AOTCache=" + aotCacheFile, "-XX:+UnlockExperimentalVMOptions", productionFlags, diff --git a/test/jdk/ProblemList-AotJdk.txt b/test/jdk/ProblemList-AotJdk.txt index 37d7a80fa56..cb26a58a1e6 100644 --- a/test/jdk/ProblemList-AotJdk.txt +++ b/test/jdk/ProblemList-AotJdk.txt @@ -44,6 +44,16 @@ java/lang/invoke/DumpMethodHandleInternals.java 0000000 generic- # this message will not be printed in the production run. java/util/Locale/UseOldISOCodesTest.java 0000000 generic-all +# These vector tests hit MemLimit during C2 compilation on Aarch64 +jdk/incubator/vector/Vector128ConversionTests.java 8337281 linux-aarch64,macosx-aarch64 +jdk/incubator/vector/Vector256ConversionTests.java 8337281 linux-aarch64,macosx-aarch64 +jdk/incubator/vector/Vector512ConversionTests.java 8337281 linux-aarch64,macosx-aarch64 +jdk/incubator/vector/Vector64ConversionTests.java#id0 8337281 linux-aarch64,macosx-aarch64 +jdk/incubator/vector/VectorMaxConversionTests.java#id0 8337281 linux-aarch64,macosx-aarch64 + +java/math/BigInteger/SerializationTests.java 8393482 generic-all +java/util/concurrent/locks/Lock/OOMEInAQS.java 8393490 generic-all + ############################################################################# # Preview project specific failures go here at the end of the file. diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index 185d967487c..c47b786e48b 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -487,6 +487,7 @@ protected String vmCDSSupportsAOTClassLinking() { */ protected String vmCDSSupportsAOTCodeCaching() { if ("true".equals(vmCDSSupportsAOTClassLinking()) && + !"true".equals(isPreviewEnabled()) && !"zero".equals(vmFlavor()) && (Platform.isX64() || Platform.isAArch64() || Platform.isRISCV64())) { return "true"; diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java index 4f395321a43..d434e1c1fae 100644 --- a/test/lib/jdk/test/lib/cds/CDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -25,6 +25,7 @@ import java.io.File; import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.Platform; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.StringArrayUtils; @@ -243,8 +244,13 @@ private String[] addCommonVMArgs(RunMode runMode) { // In one-step workflow ASSEMBLY phase is not executed separately. // Therefore AOTCompatibleOopCompression needs to be passed to the TRAINING phase, // so that it can be propagated to the ASSEMBLY phase. - if (runMode == RunMode.TRAINING || runMode == RunMode.ASSEMBLY) { + if (isAOTWorkflow() && isDumping(runMode)) { cmdLine = StringArrayUtils.concat(cmdLine, "-XX:+UnlockDiagnosticVMOptions", "-XX:+AOTCompatibleOopCompression"); + if (Platform.isDebugBuild()) { + // Always assert when AOT test code references new unknown external address. + // AOTAssertOnUnknownExternalAddress is debug flag not available in product VM. + cmdLine = StringArrayUtils.concat(cmdLine, "-XX:+AOTAssertOnUnknownExternalAddress"); + } } return cmdLine; } diff --git a/test/setup_aot/TestSetupAOT.java b/test/setup_aot/TestSetupAOT.java index 0ab17d6ca99..0a63ae48b14 100644 --- a/test/setup_aot/TestSetupAOT.java +++ b/test/setup_aot/TestSetupAOT.java @@ -52,7 +52,7 @@ // This is a larger set of classes than those stored in the JDK's default CDS archive. // This customized cache can also have additional optimizations that are not // enabled in the default CDS archive. For example, AOT-linked classes and lambda -// expressions. In the future, it can also contain AOT profiles and AOT compiled methods. +// expressions. // // We can use this customized AOT cache to run various HotSpot tests to improve // coverage on AOT. From 1b0a0558c2f457a835970e76e128173e4b29a6b0 Mon Sep 17 00:00:00 2001 From: Rajan Halade Date: Thu, 8 Oct 2026 22:02:12 +0000 Subject: [PATCH 011/101] 8385585: EmSignRootG2CA.java test fails with: couldn't determine EE certificate status Reviewed-by: mdonovan, mullan --- .../certification/EmSignRootG2CA.java | 170 ++++++++++-------- 1 file changed, 91 insertions(+), 79 deletions(-) diff --git a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/EmSignRootG2CA.java b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/EmSignRootG2CA.java index 14e48a6e78f..60c2066e134 100644 --- a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/EmSignRootG2CA.java +++ b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/EmSignRootG2CA.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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8319187 + * @bug 8319187 8385585 * @summary Interoperability tests with eMudhra emSign Root CA G2 CS root * @build ValidatePathWithParams * @run main/othervm/manual -Djava.security.debug=certpath EmSignRootG2CA OCSP @@ -74,86 +74,98 @@ public class EmSignRootG2CA { "EJK8NSNmK3FNblQfOyFM7VE0uOGHOUwpMGVM2A==\n" + "-----END CERTIFICATE-----"; - // Owner: CN=test, OU=test, O=test, L=test, ST=test, C=IN - // Issuer: CN=emSign CS CA - G2, O=eMudhra Technologies Limited, OU=emSign PKI, C=IN - // Serial number: 7c9ade672c0ad1b6 - // Valid from: Wed Aug 30 05:39:25 PDT 2023 until: Sat Aug 30 05:39:25 PDT 2025 + // Owner: CN=eMudhra Technologies Limited, O=eMudhra Technologies Limited, + // L=Bangalore, ST=Karnataka, C=IN + // Issuer: CN=emSign CS CA - G2, O=eMudhra Technologies Limited, + // OU=emSign PKI, C=IN + // Serial number: e66c2d81f1cf97f024897e285626ad + // Valid from: Fri Sep 04 00:59:47 PDT 2026 until: Sat Sep 04 00:59:47 + // PDT 2027 private static final String VALID = "-----BEGIN CERTIFICATE-----\n" + - "MIIGNjCCBB6gAwIBAgIIfJreZywK0bYwDQYJKoZIhvcNAQEMBQAwZTELMAkGA1UE\n" + - "BhMCSU4xEzARBgNVBAsTCmVtU2lnbiBQS0kxJTAjBgNVBAoTHGVNdWRocmEgVGVj\n" + - "aG5vbG9naWVzIExpbWl0ZWQxGjAYBgNVBAMTEWVtU2lnbiBDUyBDQSAtIEcyMB4X\n" + - "DTIzMDgzMDEyMzkyNVoXDTI1MDgzMDEyMzkyNVowWDELMAkGA1UEBhMCSU4xDTAL\n" + - "BgNVBAgTBHRlc3QxDTALBgNVBAcTBHRlc3QxDTALBgNVBAoMBHRlc3QxDTALBgNV\n" + - "BAsTBHRlc3QxDTALBgNVBAMTBHRlc3QwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAw\n" + - "ggGKAoIBgQC04pOiSFbl7Bd4wFYXzzyukKh+EmwIq8xRGQDkuYH+C6Zao36VAV+k\n" + - "xGw7lmM3rf4YUcArgZYHfrxgPJNBbGrCi/YnEPYQTNwSrBAePUx1tt13LVBxHfNu\n" + - "cQQT+kqE7064WsYfmfr/uzJZemqVH7lG82DN23+8E/235AIh3lz/pn7T9ByLj7TV\n" + - "zWP40oT0UfQXQvWUpFevPONu/RksRP+NiKV3ji6/wYpvrfodzkrGxw2DPfOh4Iam\n" + - "j6bBH2rkTMToH853plsQGr2ji8OndePfvDdk+5c33Jz1knCNPZSlYQIIp8scyz4z\n" + - "jaUGdoC140FjEA1SMA2WzpRJoE7xjAidLv7jiV596/bTwrIM+IZhzBc8SKRmkdZ6\n" + - "lYjPYJHPqRosRtfxcQne3pY6F4s1aOUtuGJaQS/AJkkykZoOx27plWM5SjtmlrL+\n" + - "7g2/ihWT9CEagYuo44tqk9Tmp3P37+ADAmiXxP0zUxYIv77DSabdArrZ+AB5XUol\n" + - "V8sxE1V6h0UCAwEAAaOCAXUwggFxMB8GA1UdIwQYMBaAFBWGyrZ0lhdIWDSCLM3S\n" + - "4XWer0S3MB0GA1UdDgQWBBQ2k0TE2p46sYwI5M/a1XJ8M5Oc8DAOBgNVHQ8BAf8E\n" + - "BAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwNwYDVR0fBDAwLjAsoCqgKIYmaHR0\n" + - "cDovL2NybC5lbXNpZ24uY29tP2VtU2lnbkNTQ0FHMi5jcmwwTgYDVR0gBEcwRTA5\n" + - "BgsrBgEEAYOOIQEAATAqMCgGCCsGAQUFBwIBFhxodHRwOi8vcmVwb3NpdG9yeS5l\n" + - "bVNpZ24uY29tMAgGBmeBDAEEATBzBggrBgEFBQcBAQRnMGUwIgYIKwYBBQUHMAGG\n" + - "Fmh0dHA6Ly9vY3NwLmVtU2lnbi5jb20wPwYIKwYBBQUHMAKGM2h0dHA6Ly9yZXBv\n" + - "c2l0b3J5LmVtc2lnbi5jb20vY2VydHMvZW1TaWduQ1NDQUcyLmNydDAMBgNVHRMB\n" + - "Af8EAjAAMA0GCSqGSIb3DQEBDAUAA4ICAQBKLa7j8fNpcnWNv7NegrMKTRy7gycI\n" + - "qrMK848wISX6jl2wg6b275sWQHzQRxA6rbB76bF2HXLFcpITJPaz+vjetYOVQd4v\n" + - "l8iZN52OpN6Pwrheiz7JhdLiHisN+2NKMmF899bH7w1l2Sr/FQl5vqk41gwwWMen\n" + - "99Waf4Bp6p3lvBArK2BbabTs8+16xvmkHEK3d3l3Bu6qTEbQRgUI5XsVXmXXn8Pg\n" + - "IANliTEsbsN9CMWrJ56ciEujU7w2L+IBfvKhl10N1AQNHwpQzwfFyz2BUbACN75o\n" + - "feIUBarM3ssNzpnt7idgkCTwWVrdEL1NHyW967aEMWyVwaRrtkjFOW/0xuSr2rEI\n" + - "jBpPj5RPdP6ZEaqnmg5PIgSrJ8FBjx6JmvVgZH/XEl5MZ7PsvJFfIMun6RxXtGn7\n" + - "QP0+ipkRrI6USNFS84H53Q0WJhQWZUgd3cdm37wpFGvxOVEskIgJNW9SbOgiT9sB\n" + - "zTIy3ceOK2onmUkDM2Q2+Hbc7A4BmNIlW4fpYXvZlM7IXSl9U3Voks92Hi45azgz\n" + - "StWZv9+Ronmmp+b7JKCe7MZXIBHfj0JhAVNJiYTZ9BqkY2VRvuQPVUdKxske9fQ6\n" + - "ciFJ5a6RDOhce6pFloaQu39ci2XCY1N4mIR3vFzpmBNkttlEXviK07XNTv9cnQt6\n" + - "3CW5aMAsfTbmOw==\n" + + "MIIG6zCCBNOgAwIBAgIQAOZsLYHxz5fwJIl+KFYmrTANBgkqhkiG9w0BAQwFADBl\n" + + "MQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11\n" + + "ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEaMBgGA1UEAxMRZW1TaWduIENTIENB\n" + + "IC0gRzIwHhcNMjYwOTA0MDc1OTQ3WhcNMjcwOTA0MDc1OTQ3WjCBgzELMAkGA1UE\n" + + "BhMCSU4xEjAQBgNVBAgTCUthcm5hdGFrYTESMBAGA1UEBxMJQmFuZ2Fsb3JlMSUw\n" + + "IwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMSUwIwYDVQQDExxl\n" + + "TXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMIICIjANBgkqhkiG9w0BAQEFAAOC\n" + + "Ag8AMIICCgKCAgEA5Mh6duhdJ4xfZw3Iwnvy7KIauGVXCpX/EOS3ueDOKPCQ8XG+\n" + + "pWHP+bs1OioHxWwRAo0AVV/hYr9c+nAyAq0Y0RsZGeBDqZUVK0oECYdWvssPP/0o\n" + + "ZUcmiDvDI3NDYKXE8XqPrxd6uHZ0mFkJn+GQHUrTx7gOtNCscijmkeMip9jyLq8B\n" + + "m8c9B6TzkSXD6Q2tB76cRuHGLL7FHDzBut6GZElJicsoeI/AQtLfakz02Taea8Ue\n" + + "ILyLCnpq5jgij2zqk6ozMDd8lpHYvJCwZ7tFHfuxQBcWy9R2qUAeXBUX+Iymq+Me\n" + + "gpv8m3Ov1382p+iP6wpxp/LsDlr+Cg3uil19aFG2GO7WIxiIyDbSY3t6VjhZWFWP\n" + + "YbemMXhiKCMWZmX8bKVb8wJYjxUaBDpCoIq8+gj/XglUjq7vpXN8oARLW69z1fJi\n" + + "Yt0QAlEDLMcM0Rh3wmEr/sqtu7mkBU7Lp1nnqNx4bPXDf69SalbTnM3ukN7QYpgz\n" + + "d/cxzACOQioTwoCDFSHuOBhLuyo81xho9UKzpMR50r7FcePmiLiAtkgl7fZ02U1C\n" + + "DincqL+bLtpUxGcSmxNtI3NF+OqpcG+DUjUFDge7k+WgbFxvzahcxH5i5YdzIRf0\n" + + "y3NbodvHIYmVPcH0vMv1ntywwP1jy9VuAgT24cj+Es5rmPYy8xogrZa95kkCAwEA\n" + + "AaOCAXYwggFyMB8GA1UdIwQYMBaAFBWGyrZ0lhdIWDSCLM3S4XWer0S3MB0GA1Ud\n" + + "DgQWBBQy1M8ziJmPioriHY7mJB7+beUk5zAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0l\n" + + "BAwwCgYIKwYBBQUHAwMwNwYDVR0fBDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNp\n" + + "Z24uY29tP2VtU2lnbkNTQ0FHMi5jcmwwTwYDVR0gBEgwRjA6BgwrBgEEAYOOIQEA\n" + + "AQIwKjAoBggrBgEFBQcCARYcaHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbTAI\n" + + "BgZngQwBBAEwcwYIKwYBBQUHAQEEZzBlMCIGCCsGAQUFBzABhhZodHRwOi8vb2Nz\n" + + "cC5lbVNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVwb3NpdG9yeS5lbXNp\n" + + "Z24uY29tL2NlcnRzL2VtU2lnbkNTQ0FHMi5jcnQwDAYDVR0TAQH/BAIwADANBgkq\n" + + "hkiG9w0BAQwFAAOCAgEAL9uz2lWq/Dx1P9jot/DUvDS3u7wKf3qw5XH191kQ1B+S\n" + + "m9/uhkEptH4lanpXoB/QorvTSkSvu2D2LxQOjHbXdYzLJqDO4NNbFw3S7H5jRVWT\n" + + "dRBrb/2VREX5LJ2NBVgxETCpOMyXKgpmefcvVYxZ1jKa80H6Jqd7YzPpFhwsHglX\n" + + "/YQX+jYUWjdxkWSo48jlAbi81vUj5snn+AbrFnq8RRy1r22Z5k3zSFNGz2W9iWzS\n" + + "UjpSQoaz7e4/L+2flScEydmmv1F2MH9boP5XIZmNXKp6sTr55jZQmQiWysiERPJ2\n" + + "0bHhM9Q8qr+Sp8NWVZs5tIYnWJSyBasOctkVvl2ADdrIofR9cmWaP1/EdB5Hl1cx\n" + + "Aqo3FKfXK5ZNRHt7B7v11PoBiwzYqk45L03AABNmuZcNC3HkG5emfBrlY0Ud6QuK\n" + + "/8Oi4+G01H/6DFCqMqEFR/AFQtzPDnlaEL3XvoZyJcIJARP14XTyXXlUJt5fI0yq\n" + + "E6F0KsJCeCRcGKZcjLMWyN+5mPsNdT9EdQOpYJ8dXLtB5r2uN0xsjcOPpFTAIwRt\n" + + "fUW5MUFiZZvWqqadhI1EhKhWDTyTn8vJGWNiqXKbbfedck/k/B5Kc75A5nfuBB/u\n" + + "awVsnhMuAU7yVUOH37dGv4b4tKRnkW/L+owhEOfbWbiRrtmQ4z/u5+84yxddNiY=\n" + "-----END CERTIFICATE-----"; - // Owner: CN=test, OU=test, O=test, L=test, ST=test, C=IN - // Issuer: CN=emSign CS CA - G2, O=eMudhra Technologies Limited, OU=emSign PKI, C=IN - // Serial number: cf02dedd03d2f509 - // Valid from: Thu Oct 05 22:38:51 PDT 2023 until: Sun Oct 05 22:38:51 PDT 2025 + // Owner: CN=eMudhra Technologies Limited, O=eMudhra Technologies Limited, + // L=Bangalore, ST=Karnataka, C=IN + // Issuer: CN=emSign CS CA - G2, O=eMudhra Technologies Limited, + // OU=emSign PKI, C=IN + // Serial number: 399515f0e9db1ec53b695df8a4216 + // Valid from: Sat Sep 05 10:12:31 PDT 2026 until: Sun Sep 05 10:12:31 + // PDT 2027 private static final String REVOKED = "-----BEGIN CERTIFICATE-----\n" + - "MIIGNzCCBB+gAwIBAgIJAM8C3t0D0vUJMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNV\n" + - "BAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRl\n" + - "Y2hub2xvZ2llcyBMaW1pdGVkMRowGAYDVQQDExFlbVNpZ24gQ1MgQ0EgLSBHMjAe\n" + - "Fw0yMzEwMDYwNTM4NTFaFw0yNTEwMDYwNTM4NTFaMFgxCzAJBgNVBAYTAklOMQ0w\n" + - "CwYDVQQIEwR0ZXN0MQ0wCwYDVQQHEwR0ZXN0MQ0wCwYDVQQKDAR0ZXN0MQ0wCwYD\n" + - "VQQLEwR0ZXN0MQ0wCwYDVQQDEwR0ZXN0MIIBojANBgkqhkiG9w0BAQEFAAOCAY8A\n" + - "MIIBigKCAYEAmUSghjvjUvVgYguH2PMLwW4TwtYsNDpAuGPqux53lI9v9S5u4oAv\n" + - "m1Sa3MW7CeEnhHNAIFu/AKvNXSfkvnJpTozWstZMjd93DcNacteBG0fBKTkIq+5k\n" + - "A8qIBiXWk8NORlbjV5bXnoW2pO7wbrALDK3FGf2JAQjuYWXE1mlVk0+SJewUSN+F\n" + - "XTl63V3tcaqjxhoViY8/dCWc7pNTPgQ/f+Rmnm1bpE0hxVPpQ29+60lyoNtKiOWj\n" + - "InKRKBV8jYkR/xI13bKWguaxZnswpf2MrophQTvO9ivPHADWhZlNYYjYYEMl4tbi\n" + - "rG2EquJ7g8Jdo+aL3BggLv5gFkpfoEcaveNuUWy7ggUl7MNhvgDdWdoi6VY7R8Fi\n" + - "F52+JqPByGpHkZKi0wPa3BaI7guGGyCn3TMe66kNTMS4ADxHktqQlpNSaYYl/84G\n" + - "lnr2WxQt/W+sXoorlKc/Kh0ubbm6eDzPE8kkIDV2uIxUEgSL7SJQ95yf5XgRihoH\n" + - "KoBA45iR5vCtAgMBAAGjggF1MIIBcTAfBgNVHSMEGDAWgBQVhsq2dJYXSFg0gizN\n" + - "0uF1nq9EtzAdBgNVHQ4EFgQUDs5dk74eElzdEKdxIlkzISoWSFkwDgYDVR0PAQH/\n" + - "BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDcGA1UdHwQwMC4wLKAqoCiGJmh0\n" + - "dHA6Ly9jcmwuZW1zaWduLmNvbT9lbVNpZ25DU0NBRzIuY3JsME4GA1UdIARHMEUw\n" + - "OQYLKwYBBAGDjiEBAAEwKjAoBggrBgEFBQcCARYcaHR0cDovL3JlcG9zaXRvcnku\n" + - "ZW1TaWduLmNvbTAIBgZngQwBBAEwcwYIKwYBBQUHAQEEZzBlMCIGCCsGAQUFBzAB\n" + - "hhZodHRwOi8vb2NzcC5lbVNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw\n" + - "b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL2VtU2lnbkNTQ0FHMi5jcnQwDAYDVR0T\n" + - "AQH/BAIwADANBgkqhkiG9w0BAQwFAAOCAgEAGa2XSoRkoIkHHHGXrdzTBCf/+KgK\n" + - "FlHhqlBOk5rwLDX1sfNlmsaz10I69phE90Ac8Coa/xCrBaFrTYqRvmkY9gU19jkn\n" + - "FdVcwQEHNku7Ro/Z/mbyi+aTBzHMTy0Vl4HqVnQInjV891n64SerUuAB7wNVOOho\n" + - "GoBfpf6lzDzzuEmetFokHYv1tWGQqPF/dHLARQraUlQpWjsnOx0QcZ5cM79REONE\n" + - "y6uzXT2vaatT3ns8Mtx8zooq+t8pnZlXJqlrwNTcnPad9gSsVu6vfsnWhLhz0VLG\n" + - "sYPXcWIssLbBQW3v5z0l1Isj7vy2UFfbn8AmZ0PanPo3v3C2sk19DK+Zlc9xBAXc\n" + - "KKwc4m8le6QkP/EB2wUA7ey5Cf29hjNDJpZznquEaWl9aKbBRdJDKsK88IBJjzK0\n" + - "Gbpw9fYJ3txuGA7Q27gyaZAeGAIrFvOtRY0XFbr20qSh2GBBYN57+lBPh4UKqgy8\n" + - "Z2Kk/2jK9k+nm41JYCmwVZHg3Va9RRfW8FkeE95gAUFPDWjeV+GvcimCbcB3DwaZ\n" + - "9fy1qfV4xsduhC3ei6f7Ask8LbAEWaEIXmgK10YbIfhzomCyCzlA+E+gwkq/bmkv\n" + - "B8hh27KWA6IRt7URI51MZlh0e8fULyXlOZcoJA/IPX9RdePa2RHFuPSypBHjoN7z\n" + - "6bCML1XZ2xnHIAg=\n" + + "MIIG6jCCBNKgAwIBAgIPA5lRXw6dsexTtpXfikIWMA0GCSqGSIb3DQEBDAUAMGUx\n" + + "CzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk\n" + + "aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRowGAYDVQQDExFlbVNpZ24gQ1MgQ0Eg\n" + + "LSBHMjAeFw0yNjA5MDUxNzEyMzFaFw0yNzA5MDUxNzEyMzFaMIGDMQswCQYDVQQG\n" + + "EwJJTjESMBAGA1UECBMJS2FybmF0YWthMRIwEAYDVQQHEwlCYW5nYWxvcmUxJTAj\n" + + "BgNVBAoMHGVNdWRocmEgVGVjaG5vbG9naWVzIExpbWl0ZWQxJTAjBgNVBAMTHGVN\n" + + "dWRocmEgVGVjaG5vbG9naWVzIExpbWl0ZWQwggIiMA0GCSqGSIb3DQEBAQUAA4IC\n" + + "DwAwggIKAoICAQDamKtV3CD/F8V6OpeyMvQZakL/CLsYZVSlSHN34u2nxtpG5RIt\n" + + "buEMQfZl1lsLQlV39TgoCfrf7kY2S9xLZcMapUGMwT0HoP+rRnyVJpfhgq/ULlX/\n" + + "L2KgGxJyUP6XpVcdrlyrHOAvErFhdhdg9L4/tTwOoarHUZCNBB1Wx3Rl1sgCMVbl\n" + + "UcbzgJZulYhMJ+deEnHwJTPLO/GTFHek7hGy7bYoD2jhwUgG6Yjae5rZWHnLaKRg\n" + + "zACpgQl2sUAtJ7l8Zh0Ig14BTzM0OTfQutPaB3JYr81wZj4awhk39MDf4ob6kcEm\n" + + "8KqqYqlARhhFe8XOCZ5IrmQEzie2am0EZICk6g2nBvIcMUgPN9yFOGQdUaSSQe5+\n" + + "lA6skGA/ZADVCBf9E1j/tqABdcFdYiPvpInlRxXCK/xxbspupQhffyamqUtj7PT+\n" + + "tIdAaruiFV/PIx6MvmNpBhzyPt01QJlAy5GU//5/EnQ7HW/np9jjHr0QdZFbnis+\n" + + "O+VloqNSP7UW9eWo1mMwQWLGr/PQHNERGGsPb6gofxUJdKgp4kbg9rqfBkLdYLBV\n" + + "jVYAKIpOlP7b93eta5qr0DOImBLor5fvZOs7bljltp68S1mmVxMsvezmSJIcvLvG\n" + + "R0G4YWDvobhcCVmEk1nV580+/3OZEMbBk3R4oOB2MUv4eiCYrlpWI0xmjQIDAQAB\n" + + "o4IBdjCCAXIwHwYDVR0jBBgwFoAUFYbKtnSWF0hYNIIszdLhdZ6vRLcwHQYDVR0O\n" + + "BBYEFMQn0uMfp84zx4vz2EYeBnzyyh9fMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE\n" + + "DDAKBggrBgEFBQcDAzA3BgNVHR8EMDAuMCygKqAohiZodHRwOi8vY3JsLmVtc2ln\n" + + "bi5jb20/ZW1TaWduQ1NDQUcyLmNybDBPBgNVHSAESDBGMDoGDCsGAQQBg44hAQAB\n" + + "AjAqMCgGCCsGAQUFBwIBFhxodHRwOi8vcmVwb3NpdG9yeS5lbXNpZ24uY29tMAgG\n" + + "BmeBDAEEATBzBggrBgEFBQcBAQRnMGUwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3Nw\n" + + "LmVtU2lnbi5jb20wPwYIKwYBBQUHMAKGM2h0dHA6Ly9yZXBvc2l0b3J5LmVtc2ln\n" + + "bi5jb20vY2VydHMvZW1TaWduQ1NDQUcyLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqG\n" + + "SIb3DQEBDAUAA4ICAQBy0UYUXy/AwPgRmUWu1B0xeq9hU2vFwoFmGbOkd2sJqOOi\n" + + "KdIbpjvIdLtPchIasENu+oc55ENgmbD4u5WDxGspBjL18sbE6jmzmGY1dsbCgF17\n" + + "Pi9KvlX3LRtD8qTD2oJ5y/YJ7fMKW4j+h4ssNrmA2NboXhgX3e+idQeZ2ItAgV0k\n" + + "s3STGRkoOKdsuwpeD/+69nXVIIYusVSgOuYXZb/ViWcL3cMq75cZdR/WYixxU9ZV\n" + + "Z55NQKikOGVTuKBpDv9BEa5yqAK32RcajUl7R7bT7AuY+LnTRHtX0P4sCkohj1kP\n" + + "9JGoFWN1NIJeCou5GSRmuWLYpnMfi9oDvMPt2e7GUqKkNF+OXTxWfq5A0xtkLWGN\n" + + "3qEr9xD34ZnFP5h0c6sE8hzee65erhqSo6LqlQMGINP2gL+fUG+smdX0JQYbX9hP\n" + + "raKmp53+aHwolzn0R3t9otgZ6yVdAZLScNLXqDd55/gr/TDrfu4XzRTHK9RCrMzT\n" + + "trvbKKtqTuwEp1vQBoced60CI1ZjG41tipF1irhU+pyHk/Kt9YiExp6QZGoBznTY\n" + + "9Rvw5No0XoVQHLC56JbsGUot8bVVpGNXzF8PtWBmskqttsw1XTgSUOsnoopfb+Eb\n" + + "fpPUdqTDs47FRowITPbj4cgYzYRKWEc7xh8fozZ18lN8yAP6hA2OfDN3j4HrCA==\n" + "-----END CERTIFICATE-----"; public static void main(String[] args) throws Exception { @@ -174,6 +186,6 @@ public static void main(String[] args) throws Exception { // Validate Revoked pathValidator.validate(new String[]{REVOKED, INT}, ValidatePathWithParams.Status.REVOKED, - "Thu Oct 05 22:51:36 PDT 2023", System.out); + "Sat Sep 05 10:32:30 PDT 2026", System.out); } } From 5ec0824750e3c7576c1e02feb95602a32e426e49 Mon Sep 17 00:00:00 2001 From: huanglb Date: Fri, 9 Oct 2026 02:22:48 +0000 Subject: [PATCH 012/101] 8391598: RISC-V: Avoid call-patching I-cache flushes with Ziccid Reviewed-by: gli, fyang, gcao --- src/hotspot/cpu/riscv/globals_riscv.hpp | 2 ++ src/hotspot/cpu/riscv/nativeInst_riscv.cpp | 17 ++++++++++++++--- src/hotspot/cpu/riscv/vm_version_riscv.hpp | 2 ++ .../linux_riscv/vm_version_linux_riscv.cpp | 3 ++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/hotspot/cpu/riscv/globals_riscv.hpp b/src/hotspot/cpu/riscv/globals_riscv.hpp index cb2be598762..50bbc7e2e99 100644 --- a/src/hotspot/cpu/riscv/globals_riscv.hpp +++ b/src/hotspot/cpu/riscv/globals_riscv.hpp @@ -119,6 +119,8 @@ define_pd_global(bool, ValueTypeReturnedAsFields, false); product(bool, UseZicbom, false, EXPERIMENTAL, "Use Zicbom instructions") \ product(bool, UseZicbop, false, EXPERIMENTAL, "Use Zicbop instructions") \ product(bool, UseZicboz, false, EXPERIMENTAL, "Use Zicboz instructions") \ + product(bool, UseZiccid, false, EXPERIMENTAL, \ + "Use Ziccid to omit icache flushes for atomic call patching") \ product(bool, UseZicond, false, DIAGNOSTIC, "Use Zicond instructions") \ product(bool, UseZihintpause, false, EXPERIMENTAL, \ "Use Zihintpause instructions") \ diff --git a/src/hotspot/cpu/riscv/nativeInst_riscv.cpp b/src/hotspot/cpu/riscv/nativeInst_riscv.cpp index 1f757f805b6..abd9423efa6 100644 --- a/src/hotspot/cpu/riscv/nativeInst_riscv.cpp +++ b/src/hotspot/cpu/riscv/nativeInst_riscv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2020, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -85,8 +85,19 @@ void NativeCall::optimize_call(address dest, bool mt_safe) { } // We changed instruction stream if (mt_safe) { - // IC invalidate provides a leading full fence, it thus happens after we changed the instruction stream. - ICache::invalidate_range(jmp_ins_pc, NativeInstruction::instruction_size); + if (UseZiccid) { + // Ziccid provides in-order instruction fetches and eventual store visibility + // without FENCE.I for cacheable, coherent memory. It requires Ziccif, which + // guarantees atomic, naturally aligned power-of-two instruction fetches up + // to min(ILEN, XLEN) bits. This aligned atomic 32-bit jal/jalr replacement + // satisfies Ziccif's patching rules, and old/new calls can safely execute + // concurrently, so ICache::invalidate_range can be omitted. + // Preserve the full data fence otherwise provided by ICache::invalidate_range. + OrderAccess::fence(); + } else { + // IC invalidate provides a leading full fence after the instruction store. + ICache::invalidate_range(jmp_ins_pc, NativeInstruction::instruction_size); + } } } diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 5c6e1b30943..58901ae4d36 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -281,6 +281,8 @@ class VM_Version : public Abstract_VM_Version { decl(Zicbop , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbop)) \ /* Zicboz Cache Block Zero Operations */ \ decl(Zicboz , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicboz)) \ + /* Ziccid Instruction/Data Coherence and Consistency */ \ + decl(Ziccid , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZiccid)) \ /* Base Counters and Timers */ \ decl(Zicntr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ /* Zicond Conditional operations */ \ diff --git a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp index 4c154f3ea40..2b8ac1edb89 100644 --- a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp +++ b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Huawei Technologies Co., Ltd. All rights reserved. * Copyright (c) 2023, Rivos Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -378,6 +378,7 @@ void VM_Version::xuantie_features() { ext_Zicbom.enable_feature(); ext_Zicbop.enable_feature(); ext_Zicboz.enable_feature(); + ext_Ziccid.enable_feature(); ext_Zicond.enable_feature(); ext_Zicntr.enable_feature(); ext_Zicsr.enable_feature(); From 1fd948de21cf42c6962ad244299c2f1d257133da Mon Sep 17 00:00:00 2001 From: huanglb Date: Fri, 9 Oct 2026 02:23:28 +0000 Subject: [PATCH 013/101] 8392347: RISC-V: Use 32-byte code entry alignment on Xuantie Reviewed-by: gcao, gli, fyang --- src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp index 2b8ac1edb89..cc814b958e4 100644 --- a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp +++ b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp @@ -393,4 +393,8 @@ void VM_Version::xuantie_features() { unaligned_scalar.enable_feature(MISALIGNED_SCALAR_FAST); unaligned_vector.enable_feature(MISALIGNED_VECTOR_FAST); + + if (FLAG_IS_DEFAULT(CodeEntryAlignment)) { + FLAG_SET_DEFAULT(CodeEntryAlignment, 32); + } } From 952037afbf540c30a77f0ad284f98e7f368d8c9c Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 9 Oct 2026 07:27:26 +0000 Subject: [PATCH 014/101] 8393678: [windows] DefaultProxySelector.c leaks handle 'session' if initJavaClass fails Reviewed-by: dfuchs --- src/java.base/windows/native/libnet/DefaultProxySelector.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/java.base/windows/native/libnet/DefaultProxySelector.c b/src/java.base/windows/native/libnet/DefaultProxySelector.c index 06bc70114f3..f1cc50e4ce7 100644 --- a/src/java.base/windows/native/libnet/DefaultProxySelector.c +++ b/src/java.base/windows/native/libnet/DefaultProxySelector.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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,6 +65,8 @@ Java_sun_net_spi_DefaultProxySelector_init(JNIEnv *env, jclass clazz) { } if (!initJavaClass(env)) { + WinHttpCloseHandle(session); + session = NULL; return JNI_FALSE; } From ee1a2c955eef8638a30d392f1a8ebd93aa49be1e Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Fri, 9 Oct 2026 07:55:36 +0000 Subject: [PATCH 015/101] 8393061: java/nio/channels/Channels/Available.java failing (lnx) Reviewed-by: alanb, haosun --- .../java/nio/channels/Channels/Available.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/test/jdk/java/nio/channels/Channels/Available.java b/test/jdk/java/nio/channels/Channels/Available.java index 6f5c19d783a..9e37598bbb6 100644 --- a/test/jdk/java/nio/channels/Channels/Available.java +++ b/test/jdk/java/nio/channels/Channels/Available.java @@ -55,7 +55,7 @@ class Available { /** * Test InputStream.available when input stream reads from a SeekableByteChannel. */ - void testSeekableByteChannel(SeekableByteChannel ch) throws Exception { + private static void testSeekableByteChannel(SeekableByteChannel ch) throws Exception { InputStream in = Channels.newInputStream(ch); long size = ch.size(); long[] positions = new long[] { @@ -68,20 +68,16 @@ void testSeekableByteChannel(SeekableByteChannel ch) throws Exception { Integer.MAX_VALUE - size + 1L, Integer.MAX_VALUE - 1L, Integer.MAX_VALUE, - Integer.MAX_VALUE + 1L, - Long.MAX_VALUE - size - 1L, - Long.MAX_VALUE - size, - Long.MAX_VALUE - size + 1L, - Long.MAX_VALUE - 1L, - Long.MAX_VALUE + Integer.MAX_VALUE + 1L }; for (long pos : positions) { if (pos >= 0) { - ch.position(pos); int expectedAvailable = Math.clamp(size - pos, 0, Integer.MAX_VALUE); - int available = in.available(); - System.err.format(" position = %d, available = %d%n", pos, available); - assertEquals(expectedAvailable, available); + System.err.format( + " size = %d, position = %d, expected available = %d%n", + size, pos, expectedAvailable); + ch.position(pos); + assertEquals(expectedAvailable, in.available()); } } } From f2b6aba29e9f24769844dc862294ecc001f77119 Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Fri, 9 Oct 2026 07:56:09 +0000 Subject: [PATCH 016/101] 8377972: Provide a way for an application to opt-in to legacy Desktop.browse() behavior Reviewed-by: prr, kizune --- .../sun/lwawt/macosx/CDesktopPeer.java | 5 ++-- .../native/libawt_lwawt/awt/CDesktopPeer.m | 8 ++++--- .../classes/java/awt/peer/DesktopPeer.java | 24 ++++++++++++++++++- .../classes/sun/awt/windows/WDesktopPeer.java | 9 +++++-- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CDesktopPeer.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CDesktopPeer.java index cc0e253f23b..9e062c8197d 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CDesktopPeer.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CDesktopPeer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -39,7 +39,6 @@ import java.nio.file.Files; import java.nio.file.Path; - /** * Concrete implementation of the interface {@code DesktopPeer} for MacOS X * @@ -80,7 +79,7 @@ public void mail(URI uri) throws IOException { @Override public void browse(URI uri) throws IOException { - this.lsOpen(uri, BROWSE); + this.lsOpen(uri, isBrowseInsecureAllowed(uri) ? OPEN : BROWSE); } @Override diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m index faacef5adea..65a1d2bc213 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m @@ -42,16 +42,18 @@ NSURL *urlToOpen = [NSURL URLWithString:JavaStringToNSString(env, uri)]; NSURL *appURI = nil; + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; - if (action == sun_lwawt_macosx_CDesktopPeer_BROWSE) { + if (action == sun_lwawt_macosx_CDesktopPeer_OPEN) { + // Open with the app associated with the URI scheme + appURI = [workspace URLForApplicationToOpenURL:urlToOpen]; + } else if (action == sun_lwawt_macosx_CDesktopPeer_BROWSE) { // To get the defaultBrowser NSURL *httpsURL = [NSURL URLWithString:@"https://"]; - NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; appURI = [workspace URLForApplicationToOpenURL:httpsURL]; } else if (action == sun_lwawt_macosx_CDesktopPeer_MAIL) { // To get the default mailer NSURL *mailtoURL = [NSURL URLWithString:@"mailto://"]; - NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; appURI = [workspace URLForApplicationToOpenURL:mailtoURL]; } diff --git a/src/java.desktop/share/classes/java/awt/peer/DesktopPeer.java b/src/java.desktop/share/classes/java/awt/peer/DesktopPeer.java index c1b13a4e8f3..61b595c04a6 100644 --- a/src/java.desktop/share/classes/java/awt/peer/DesktopPeer.java +++ b/src/java.desktop/share/classes/java/awt/peer/DesktopPeer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -38,6 +38,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.util.Arrays; import javax.swing.JMenuBar; @@ -281,4 +282,25 @@ default boolean moveToTrash(File file) { return false; } + /** + * Returns whether or not Desktop.browse() will open URI in browser or + * in associated allowed app + * + * @param uri URI to be browsed + * @return whether or not associated app will be opened + */ + default boolean isBrowseInsecureAllowed(URI uri) { + String allowList = System.getProperty("awt.desktop.browse_insecure"); + String scheme = uri.getScheme(); + + // For http,https scheme URL should always open in browser + // For other schemes, it will depend on whether scheme is in allowList + + return scheme != null && !(scheme.equals("http") || scheme.equals("https")) + && allowList != null + && (allowList.equals("*") + || Arrays.stream(allowList.split(",")) + .map(String::trim) + .anyMatch(allowed -> allowed.equalsIgnoreCase(scheme))); + } } diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WDesktopPeer.java b/src/java.desktop/windows/classes/sun/awt/windows/WDesktopPeer.java index e5b628dd74b..06df8223931 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WDesktopPeer.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WDesktopPeer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 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 @@ -105,7 +105,12 @@ public void mail(URI uri) throws IOException { @Override public void browse(URI uri) throws IOException { - this.launchUriInBrowser(uri); + if (isBrowseInsecureAllowed(uri)) { + this.ShellExecute(uri, ACTION_OPEN_VERB); + } else { + // Fall back to opening the URI in browser + this.launchUriInBrowser(uri); + } } private void ShellExecute(File file, String verb) throws IOException { From 118ef3b70780bb95f266c1a88c23c492ce439c7c Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Fri, 9 Oct 2026 08:19:49 +0000 Subject: [PATCH 017/101] 8393866: C2: TypeInstPtr with klass_is_exact should not have abstract instance_klass Reviewed-by: dlong, chagedorn, vlivanov --- src/hotspot/share/opto/compile.cpp | 24 +++++++++++++++--------- src/hotspot/share/opto/type.cpp | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index b238fb91336..ad3ce015257 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -1576,9 +1576,13 @@ const TypePtr *Compile::flatten_alias_type( const TypePtr *tj ) const { if (xk && ik->equals(canonical_holder)) { assert(tj == TypeInstPtr::make(to->ptr(), canonical_holder, is_known_inst, nullptr, Type::Offset(offset), instance_id, TypePtr::MaybeFlat), "exact type should be canonical type"); + } else if (is_known_inst) { + assert(xk, "Known instance should be exact type"); + // Keep the type for known instances + tj = to = TypeInstPtr::make(to->ptr(), to->instance_klass(), true, nullptr, Type::Offset(offset), instance_id, + TypePtr::MaybeFlat); } else { - assert(xk || !is_known_inst, "Known instance should be exact type"); - tj = to = TypeInstPtr::make(to->ptr(), canonical_holder, is_known_inst, nullptr, Type::Offset(offset), instance_id, + tj = to = TypeInstPtr::make(to->ptr(), canonical_holder, false, nullptr, Type::Offset(offset), instance_id, TypePtr::MaybeFlat); } } @@ -1762,13 +1766,15 @@ Compile::AliasType* Compile::find_alias_type(const TypePtr* adr_type, bool no_cr Type::str(adr_type), Type::str(flat), Type::str(flatten_alias_type(flat))); assert(flat != TypePtr::BOTTOM, "cannot alias-analyze an untyped ptr: adr_type = %s", Type::str(adr_type)); - if (flat->isa_instptr()) { - const TypeOopPtr* foop = flat->is_oopptr(); - // Scalarizable allocations have exact klass always. - bool exact = !foop->klass_is_exact() || foop->is_known_instance(); - const TypePtr* xoop = foop->cast_to_exactness(exact)->is_ptr(); - assert(foop == flatten_alias_type(xoop), "exactness must not affect alias type: foop = %s; xoop = %s", - Type::str(foop), Type::str(xoop)); + if (const TypeInstPtr* finst = flat->isa_instptr(); finst != nullptr) { + bool check_exact = !finst->klass_is_exact(); + // Try casting the type to the other exactness if possible + if ((check_exact && finst->instance_klass()->is_loaded() && !finst->instance_klass()->is_abstract()) || + (!check_exact && !finst->is_known_instance())) { + const TypePtr* xinst = finst->cast_to_exactness(check_exact)->is_ptr(); + assert(finst == flatten_alias_type(xinst), "exactness must not affect alias type: finst = %s; xinst = %s", + Type::str(finst), Type::str(xinst)); + } } } #endif diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index 2f318b2c3ce..51d7ede5a5b 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -4367,7 +4367,7 @@ TypeInstPtr::TypeInstPtr(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, assert(k != nullptr && (k->is_loaded() || o == nullptr), "cannot have constants with non-loaded klass"); - assert(!xk || k->is_loaded(), "pointer to an oop of an exact type must be loaded"); + assert(!xk || (k->is_loaded() && !k->is_abstract()), "pointer to an oop of an exact type must be concrete"); assert(!xk || interfaces->eq(k->as_instance_klass()), "inconsistency between k and interfaces"); }; From 850620f960d873475a4661a751e484b94378511c Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 9 Oct 2026 08:22:59 +0000 Subject: [PATCH 018/101] 8393706: [windows] DefaultProxySelector.c loop continues after proxy_array was set to NULL Reviewed-by: clanger, azeller --- src/java.base/windows/native/libnet/DefaultProxySelector.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/java.base/windows/native/libnet/DefaultProxySelector.c b/src/java.base/windows/native/libnet/DefaultProxySelector.c index f1cc50e4ce7..56bae4386d0 100644 --- a/src/java.base/windows/native/libnet/DefaultProxySelector.c +++ b/src/java.base/windows/native/libnet/DefaultProxySelector.c @@ -332,20 +332,24 @@ Java_sun_net_spi_DefaultProxySelector_getSystemProxies(JNIEnv *env, jhost = (*env)->NewString(env, current->host, (jsize)wcslen(current->host)); if (jhost == NULL || (*env)->ExceptionCheck(env)) { proxy_array = NULL; + goto noproxy; } isa = (*env)->CallStaticObjectMethod(env, isaddr_class, isaddr_createUnresolvedID, jhost, current->port); if (isa == NULL || (*env)->ExceptionCheck(env)) { proxy_array = NULL; + goto noproxy; } proxy = (*env)->NewObject(env, proxy_class, proxy_ctrID, type_proxy, isa); if (proxy == NULL || (*env)->ExceptionCheck(env)) { proxy_array = NULL; + goto noproxy; } (*env)->SetObjectArrayElement(env, proxy_array, index, proxy); if ((*env)->ExceptionCheck(env)) { proxy_array = NULL; + goto noproxy; } index++; } From 8e48fff3fbdeb0d61ac2d8c7c94a653559f62671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Fri, 9 Oct 2026 09:46:15 +0000 Subject: [PATCH 019/101] 8377548: Implement dark theme for other specs and tool guides Reviewed-by: nbenalla, erikj --- make/Docs.gmk | 20 +- .../docs-resources/resources/jdk-default.css | 256 ++++++++++++++++-- .../docs-resources/resources/jdk-default.js | 181 +++++++++++++ make/data/docs-resources/resources/link.svg | 31 +++ .../classes/build/tools/fixuppandoc/Main.java | 33 ++- .../classes/build/tools/jdwpgen/RootNode.java | 8 +- .../doclets/formats/html/Navigation.java | 42 +-- .../formats/html/resources/script.js.template | 234 +++++++++------- .../testNavigation/TestModuleNavigation.java | 18 +- .../doclet/testNavigation/TestNavigation.java | 39 +-- .../doclet/testSpecTag/TestSpecTag.java | 3 +- 11 files changed, 636 insertions(+), 229 deletions(-) create mode 100644 make/data/docs-resources/resources/jdk-default.js create mode 100644 make/data/docs-resources/resources/link.svg diff --git a/make/Docs.gmk b/make/Docs.gmk index a4a4bc60363..b9d4a033450 100644 --- a/make/Docs.gmk +++ b/make/Docs.gmk @@ -705,22 +705,25 @@ endif SPEC_HEADER_BLOCK := \
\ - \
JDWP_PROTOCOL := $(SUPPORT_OUTPUTDIR)/gensrc/jdk.jdi/jdwp-protocol.html ifneq ($(call ApplySpecFilter, $(JDWP_PROTOCOL)), ) - JDWP_HEADER_BLOCK := $(subst PATH_TO_SPECS,..,$(SPEC_HEADER_BLOCK)) + JDWP_HEADER_BLOCK := $(subst THEME_SUPPORT,, \ + $(subst PATH_TO_SPECS,..,$(SPEC_HEADER_BLOCK))) $(eval $(call SetupTextFileProcessing, PROCESS_JDWP_PROTOCOL, \ SOURCE_FILES := $(JDWP_PROTOCOL), \ OUTPUT_DIR := $(DOCS_OUTPUTDIR)/specs/jdwp, \ REPLACEMENTS := \ - @@ -196,22 +209,30 @@
-
+
-
-

+ +

-

+

+
+
-

+

+
+
-

+

+
+
-

+

+
+
-

-

-
+
+
+

Constant Index

@@ -220,17 +241,20 @@ +
-

-


-

- -

- +
+

+ +

+ +
-

+


+
+
@@ -248,8 +272,6 @@ -
-

@@ -287,89 +309,88 @@ -

+


+
-

-
-

- functions: -
    - -
- - - function types: -
    - -
-
- - - types: +

+ functions: -
- - - flags and constants: - + + + + flags and constants: + + + + +
-
+
+
- - - -
- - -
-      jvmtiError
+    
+    
+ + +
+        jvmtiError
 
-      (jvmtiEnv* env)
-
- - - - - - + (jvmtiEnv* env)
+
+ + + + + + +
@@ -420,7 +441,7 @@
- SetEventNotificationMode(JVMTI_ENABLE, + SetEventNotificationMode(JVMTI_ENABLE, , NULL)
@@ -523,35 +544,36 @@ -
+
+
- - -
-
-
-      typedef 
-      
-       (JNICALL *
-      
-      )
+    
+    
+
+
+        typedef 
+        
+         (JNICALL *
+        
+        )
     (
-      
-        
-        
+        
+          
+          
           ,
      
-          
+            
+          
         
-      
-      );
-    
-
- - -
+ ); +
+
+ + +
+
@@ -559,8 +581,6 @@ -
-

@@ -580,7 +600,6 @@ typedef struct { -

Event Index

Supported devkit targets
x86_64-linux-gnu
aarch64-linux-gnu
arm-linux-gnueabihf
ppc64le-linux-gnu
riscv64-linux-gnu
s390x-linux-gnu
Target Debian tree Debian arch
x86 buster i386 i386-linux-gnu (all)
arm buster armhf arm-linux-gnueabihf (all)
aarch64 buster arm64 aarch64-linux-gnu (all)
ppc64le buster ppc64el powerpc64le-linux-gnu (all)
s390x buster s390x s390x-linux-gnu (all)
mipsle buster mipsel mipsel-linux-gnu zero
mips64le buster mips64el mips64el-linux-gnueabi64 zero
armel buster arm arm-linux-gnueabi zero
ppc sid powerpc powerpc-linux-gnu zero
ppc64be sid ppc64 powerpc64-linux-gnu (all)
m68k sid m68k m68k-linux-gnu zero
alpha sid alpha alpha-linux-gnu zero
sh4 sid sh4 sh4-linux-gnu zero
riscv64 sid riscv64
Capabilities
@@ -1227,7 +1242,6 @@ typedef struct { -
@@ -1621,28 +1635,19 @@ typedef struct { -
-

Errors

-
-
-
-
-

Data Types

-
-
@@ -1653,7 +1658,6 @@ typedef struct {
Structure Type Definitions
-
@@ -1664,7 +1668,6 @@ typedef struct {
Function Type Definitions
-
@@ -1675,7 +1678,6 @@ typedef struct {
Enumeration Definitions
-
@@ -1688,7 +1690,6 @@ typedef struct {
Function Table Layout
-
@@ -1762,11 +1763,9 @@ typedef struct { -
-
@@ -1780,7 +1779,6 @@ typedef struct {
-
@@ -1793,14 +1791,10 @@ typedef struct { -
-

Change History

Last update:
Version: -
-
@@ -1865,7 +1859,7 @@ typedef struct { -

+

Rationale:

@@ -1873,7 +1867,7 @@ typedef struct { -

+

To do:

@@ -1891,7 +1885,6 @@ typedef struct {
- @@ -1960,14 +1953,13 @@ typedef struct { + -
+ -
- -
+
@@ -2024,5 +2016,4 @@ typedef struct { JVM TI - From 7996ac76cb6be620cceddb48f03fab4038d94d0c Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Mon, 12 Oct 2026 14:04:11 +0000 Subject: [PATCH 045/101] 8388519: Add APIs to get supported and negotiated named groups Reviewed-by: mullan --- .../javax/net/ssl/ExtendedSSLSession.java | 26 +- .../classes/javax/net/ssl/SSLEngine.java | 31 +- .../javax/net/ssl/SSLServerSocket.java | 31 +- .../classes/javax/net/ssl/SSLSocket.java | 31 +- .../classes/sun/security/ssl/Finished.java | 57 ++-- .../sun/security/ssl/HandshakeContext.java | 10 + .../classes/sun/security/ssl/NamedGroup.java | 50 ++- .../sun/security/ssl/SSLContextImpl.java | 6 + .../sun/security/ssl/SSLEngineImpl.java | 7 +- .../sun/security/ssl/SSLServerSocketImpl.java | 7 +- .../sun/security/ssl/SSLSessionImpl.java | 10 + .../sun/security/ssl/SSLSocketImpl.java | 5 + .../common/ImmutableExtendedSSLSession.java | 6 +- .../httpclient/ImmutableSSLSessionTest.java | 7 +- .../NamedGroupTestData.java | 103 +++++++ ...SLEngineNegotiatedSupportedNamedGroup.java | 286 ++++++++++++++++++ ...SLSocketNegotiatedSupportedNamedGroup.java | 283 +++++++++++++++++ 17 files changed, 886 insertions(+), 70 deletions(-) create mode 100644 test/jdk/javax/net/ssl/ExtendedSSLSession/NamedGroupTestData.java create mode 100644 test/jdk/javax/net/ssl/ExtendedSSLSession/SSLEngineNegotiatedSupportedNamedGroup.java create mode 100644 test/jdk/javax/net/ssl/ExtendedSSLSession/SSLSocketNegotiatedSupportedNamedGroup.java diff --git a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java index 980ff77d83a..6237c55df3f 100644 --- a/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java +++ b/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, 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 @@ -94,6 +94,30 @@ public ExtendedSSLSession() {} */ public abstract String[] getPeerSupportedSignatureAlgorithms(); + /** + * Returns the key exchange named group negotiated for this session. + *

+ * The standard named groups are defined in the + * + * Named Groups section of the Java Security Standard Algorithm Names + * Specification. Other named groups supported by the provider can also be + * returned. + * + * @implSpec The default implementation throws + * {@code UnsupportedOperationException}. + * @return the negotiated key exchange named group, or {@code null} + * if no named group was negotiated or the handshake has not + * completed + * @throws UnsupportedOperationException if the underlying provider does + * not implement the operation + * @spec security/standard-names.html Java Security Standard Algorithm Names + * @since 28 + */ + public String getNamedGroup() { + throw new UnsupportedOperationException( + "Underlying provider does not implement the method"); + } + /** * Obtains a {@link List} containing all {@link SNIServerName}s * of the requested Server Name Indication (SNI) extension. diff --git a/src/java.base/share/classes/javax/net/ssl/SSLEngine.java b/src/java.base/share/classes/javax/net/ssl/SSLEngine.java index 9376b7d1876..e707ef3fc6d 100644 --- a/src/java.base/share/classes/javax/net/ssl/SSLEngine.java +++ b/src/java.base/share/classes/javax/net/ssl/SSLEngine.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 @@ -1484,4 +1484,33 @@ public void setHandshakeApplicationProtocolSelector( getHandshakeApplicationProtocolSelector() { throw new UnsupportedOperationException(); } + + /** + * Returns the names of the named groups which could be enabled for use + * on this engine. + *

+ * The returned array includes named groups from the list of standard + * named group names in the + * + * Named Groups section of the Java Security Standard Algorithm Names + * Specification, and may also include other named groups that the provider + * supports. + *

+ * To get the names of the named groups which are currently enabled for use + * on this engine, call {@link SSLParameters#getNamedGroups()}, + * accessible through {@link #getSSLParameters()}. + * + * @implSpec The implementation in this class throws + * {@code UnsupportedOperationException} and performs no other + * action. + * @return an array of named group names + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation. + * @spec security/standard-names.html Java Security Standard Algorithm Names + * @since 28 + */ + public String [] getSupportedNamedGroups() { + throw new UnsupportedOperationException( + "Underlying provider does not implement the method"); + } } diff --git a/src/java.base/share/classes/javax/net/ssl/SSLServerSocket.java b/src/java.base/share/classes/javax/net/ssl/SSLServerSocket.java index 71cb304edbc..0e4d0651e49 100644 --- a/src/java.base/share/classes/javax/net/ssl/SSLServerSocket.java +++ b/src/java.base/share/classes/javax/net/ssl/SSLServerSocket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -529,4 +529,33 @@ public void setSSLParameters(SSLParameters params) { } } + /** + * Returns the names of the named groups which could be enabled for use + * on an SSL connection. + *

+ * The returned array includes named groups from the list of standard + * named group names in the + * + * Named Groups section of the Java Security Standard Algorithm Names + * Specification, and may also include other named groups that the provider + * supports. + *

+ * To get the names of the named groups which are currently enabled for use + * by newly accepted connections, + * call {@link SSLParameters#getNamedGroups()}, accessible through + * {@link #getSSLParameters()}. + * + * @implSpec The implementation in this class throws + * {@code UnsupportedOperationException} and performs no other + * action. + * @return an array of named group names + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation. + * @spec security/standard-names.html Java Security Standard Algorithm Names + * @since 28 + */ + public String [] getSupportedNamedGroups() { + throw new UnsupportedOperationException( + "Underlying provider does not implement the method"); + } } diff --git a/src/java.base/share/classes/javax/net/ssl/SSLSocket.java b/src/java.base/share/classes/javax/net/ssl/SSLSocket.java index 117f2de9bbc..3ae7cd72c67 100644 --- a/src/java.base/share/classes/javax/net/ssl/SSLSocket.java +++ b/src/java.base/share/classes/javax/net/ssl/SSLSocket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -872,4 +872,33 @@ public void setHandshakeApplicationProtocolSelector( getHandshakeApplicationProtocolSelector() { throw new UnsupportedOperationException(); } + + /** + * Returns the names of the named groups which could be enabled for use + * on this connection. + *

+ * The returned array includes named groups from the list of standard + * named group names in the + * + * Named Groups section of the Java Security Standard Algorithm Names + * Specification, and may also include other named groups that the provider + * supports. + *

+ * To get the names of the named groups which are currently enabled for use + * on this connection, call {@link SSLParameters#getNamedGroups()}, + * accessible through {@link #getSSLParameters()}. + * + * @implSpec The implementation in this class throws + * {@code UnsupportedOperationException} and performs no other + * action. + * @return an array of named group names + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation. + * @spec security/standard-names.html Java Security Standard Algorithm Names + * @since 28 + */ + public String [] getSupportedNamedGroups() { + throw new UnsupportedOperationException( + "Underlying provider does not implement the method"); + } } diff --git a/src/java.base/share/classes/sun/security/ssl/Finished.java b/src/java.base/share/classes/sun/security/ssl/Finished.java index 918664a6037..a44a7258114 100644 --- a/src/java.base/share/classes/sun/security/ssl/Finished.java +++ b/src/java.base/share/classes/sun/security/ssl/Finished.java @@ -423,16 +423,14 @@ private byte[] onProduceFinished(ClientHandshakeContext chc, engineGetClientSessionContext()).put( chc.handshakeSession); } - chc.conContext.conSession = chc.handshakeSession.finish(); - chc.conContext.protocolVersion = chc.negotiatedProtocol; - // handshake context cleanup. - chc.handshakeFinished = true; + completeHandshakeState(chc); // May need to retransmit the last flight for DTLS. if (!chc.sslContext.isDTLS()) { chc.conContext.finishHandshake(); } + recordEvent(chc); } @@ -490,16 +488,14 @@ private byte[] onProduceFinished(ServerHandshakeContext shc, shc.handshakeSession); } } - shc.conContext.conSession = shc.handshakeSession.finish(); - shc.conContext.protocolVersion = shc.negotiatedProtocol; - // handshake context cleanup. - shc.handshakeFinished = true; + completeHandshakeState(shc); // May need to retransmit the last flight for DTLS. if (!shc.sslContext.isDTLS()) { shc.conContext.finishHandshake(); } + recordEvent(shc); } @@ -559,16 +555,14 @@ private void onConsumeFinished(ClientHandshakeContext chc, engineGetClientSessionContext()).put( chc.handshakeSession); } - chc.conContext.conSession = chc.handshakeSession.finish(); - chc.conContext.protocolVersion = chc.negotiatedProtocol; - // handshake context cleanup. - chc.handshakeFinished = true; + completeHandshakeState(chc); // May need to retransmit the last flight for DTLS. if (!chc.sslContext.isDTLS()) { chc.conContext.finishHandshake(); } + recordEvent(chc); } else { chc.handshakeProducers.put(SSLHandshake.FINISHED.id, @@ -620,16 +614,14 @@ private void onConsumeFinished(ServerHandshakeContext shc, engineGetServerSessionContext()).put( shc.handshakeSession); } - shc.conContext.conSession = shc.handshakeSession.finish(); - shc.conContext.protocolVersion = shc.negotiatedProtocol; - // handshake context cleanup. - shc.handshakeFinished = true; + completeHandshakeState(shc); // May need to retransmit the last flight for DTLS. if (!shc.sslContext.isDTLS()) { shc.conContext.finishHandshake(); } + recordEvent(shc); } else { shc.handshakeProducers.put(SSLHandshake.FINISHED.id, @@ -761,15 +753,10 @@ private byte[] onProduceFinished(ClientHandshakeContext chc, chc.handshakeSession.setResumptionMasterSecret( resumptionMasterSecret); - chc.conContext.conSession = chc.handshakeSession.finish(); - chc.conContext.protocolVersion = chc.negotiatedProtocol; - - // handshake context cleanup. - chc.handshakeFinished = true; + completeHandshakeState(chc); chc.conContext.finishHandshake(); recordEvent(chc); - // The handshake message has been delivered. return null; } @@ -1155,17 +1142,13 @@ private void onConsumeFinished(ServerHandshakeContext shc, "Failure to derive application secrets", gse); } - // update connection context - shc.conContext.conSession = shc.handshakeSession.finish(); - shc.conContext.protocolVersion = shc.negotiatedProtocol; - - // handshake context cleanup. - shc.handshakeFinished = true; + completeHandshakeState(shc); // May need to retransmit the last flight for DTLS. if (!shc.sslContext.isDTLS()) { shc.conContext.finishHandshake(); } + recordEvent(shc); // @@ -1176,6 +1159,13 @@ private void onConsumeFinished(ServerHandshakeContext shc, } } + private static void completeHandshakeState(HandshakeContext hc) { + hc.conContext.conSession = hc.handshakeSession.finish(); + hc.conContext.protocolVersion = hc.negotiatedProtocol; + hc.conContext.conSession.setNamedGroup(hc.getNegotiatedNamedGroup()); + hc.handshakeFinished = true; + } + private static void recordEvent(HandshakeContext hc) { TLSHandshakeEvent event = new TLSHandshakeEvent(); if (event.shouldCommit() || EventHelper.isLoggingSecurity()) { @@ -1190,13 +1180,12 @@ private static void recordEvent(HandshakeContext hc) { // not verified msg } long peerCertificateId = Integer.toUnsignedLong(hash); - String namedGroup = "N/A"; - for (SSLCredentials cred : hc.handshakeCredentials) { - if (cred instanceof NamedGroupCredentials ngCred) { - namedGroup = ngCred.getNamedGroup().name; - break; - } + + String namedGroup = session.getNamedGroup(); + if (namedGroup == null) { + namedGroup = "N/A"; } + if (event.shouldCommit()) { event.peerHost = session.getPeerHost(); event.peerPort = session.getPeerPort(); diff --git a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java index fbf2c00bbb4..842310426c3 100644 --- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java +++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java @@ -598,5 +598,15 @@ List getRequestedServerNames() { return Objects.requireNonNullElse(requestedServerNames, Collections.emptyList()); } + + NamedGroup getNegotiatedNamedGroup() { + for (SSLCredentials credentials : handshakeCredentials) { + if (credentials instanceof NamedGroupCredentials ngCredentials) { + return ngCredentials.getNamedGroup(); + } + } + + return null; + } } diff --git a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java index 924ad14451e..094020602be 100644 --- a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java +++ b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java @@ -33,8 +33,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.LinkedHashSet; import java.util.List; -import java.util.ArrayList; import java.util.Objects; import java.util.Set; import javax.crypto.KeyAgreement; @@ -433,33 +433,8 @@ static String nameOf(int id) { return "UNDEFINED-NAMED-GROUP(" + id + ")"; } - public static List namesOf(String[] namedGroups) { - if (namedGroups == null) { - return null; - } - - if (namedGroups.length == 0) { - return List.of(); - } - - List ngs = new ArrayList<>(namedGroups.length); - for (String ss : namedGroups) { - NamedGroup ng = NamedGroup.nameOf(ss); - if (ng == null || !ng.isAvailable) { - if (SSLLogger.isOn() && - SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE_VERBOSE)) { - SSLLogger.finest( - "Ignore the named group (" + ss - + "), unsupported or unavailable"); - } - - continue; - } - - ngs.add(ng); - } - - return Collections.unmodifiableList(ngs); + static String[] namesOf(List groups) { + return groups.stream().map(ng -> ng.name).toArray(String[]::new); } // Is there any supported group permitted by the constraints? @@ -919,6 +894,25 @@ static final class SupportedGroups { } } + // Returns all supported named groups for the specified protocols and + // cipher suites. Default named groups are returned first. + static List getSupportedGroups(List protocols, + List cipherSuites) { + + LinkedHashSet supportedGroups = new LinkedHashSet<>(); + supportedGroups.addAll(Arrays.asList(defaultGroups)); + supportedGroups.addAll(Arrays.asList(NamedGroup.values())); + + return supportedGroups.stream() + .filter(ng -> ng.isAvailable(protocols)) + .filter(ng -> ng.isSupported(cipherSuites)) + .filter(ng -> SSLConfiguration.enableFFDHE || + ng.spec != NamedGroupSpec.NAMED_GROUP_FFDHE) + .filter(ng -> ng.isPermitted( + SSLAlgorithmConstraints.DEFAULT)) + .toList(); + } + // Avoid the group lookup for default and customized groups. static NamedGroup[] getGroupsFromConfig(SSLConfiguration sslConfig) { if (sslConfig.namedGroups == defaultNames) { diff --git a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java index fdeb94bb496..c65256da3ce 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java @@ -293,6 +293,12 @@ StatusResponseManager getStatusResponseManager() { // Is the context for DTLS protocols? abstract boolean isDTLS(); + // Get supported Named Groups. + List getSupportedNamedGroups() { + return NamedGroup.SupportedGroups.getSupportedGroups( + getSupportedProtocolVersions(), getSupportedCipherSuites()); + } + // Get default protocols. List getDefaultProtocolVersions(boolean roleIsServer) { return roleIsServer ? getServerDefaultProtocolVersions() diff --git a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java index 5052f9bc9f4..77ea04e4ad4 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.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 @@ -1082,6 +1082,11 @@ public void setHandshakeApplicationProtocolSelector( } } + @Override + public String [] getSupportedNamedGroups() { + return NamedGroup.namesOf(sslContext.getSupportedNamedGroups()); + } + @Override public boolean useDelegatedTask() { return true; diff --git a/src/java.base/share/classes/sun/security/ssl/SSLServerSocketImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLServerSocketImpl.java index 1b959f982b9..9f7e5884ce3 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLServerSocketImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLServerSocketImpl.java @@ -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 @@ -264,6 +264,11 @@ public void setSSLParameters(SSLParameters params) { } } + @Override + public String [] getSupportedNamedGroups() { + return NamedGroup.namesOf(sslContext.getSupportedNamedGroups()); + } + @Override public Socket accept() throws IOException { SSLSocketImpl s = new SSLSocketImpl(sslContext, sslConfig); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java index 45882a79fd7..bd83d0703fc 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java @@ -102,6 +102,7 @@ final class SSLSessionImpl extends ExtendedSSLSession { private final Collection localSupportedSignAlgs; private Collection peerSupportedSignAlgs; //for certificate private boolean useDefaultPeerSignAlgs = false; + private NamedGroup namedGroup; private List statusResponses; private SecretKey exporterMasterSecret; // TLSv1.3+ exporter info private RandomCookie clientRandom; // TLSv1.2- exporter info @@ -1413,6 +1414,15 @@ Collection getPeerSupportedSignatureSchemes() { return peerSupportedSignAlgs; } + @Override + public String getNamedGroup() { + return namedGroup == null ? null : namedGroup.name; + } + + void setNamedGroup(NamedGroup namedGroup) { + this.namedGroup = namedGroup; + } + /** * Obtains a List containing all {@link SNIServerName}s * of the requested Server Name Indication (SNI) extension. diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java index cef2f43526a..8ab583a2e38 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java @@ -1415,6 +1415,11 @@ public void setHandshakeApplicationProtocolSelector( } } + @Override + public String [] getSupportedNamedGroups() { + return NamedGroup.namesOf(sslContext.getSupportedNamedGroups()); + } + /** * Read the initial handshake records. */ diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/ImmutableExtendedSSLSession.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/ImmutableExtendedSSLSession.java index 2a5f125c0c4..b50e99decd4 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/ImmutableExtendedSSLSession.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/ImmutableExtendedSSLSession.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, 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 @@ -132,6 +132,10 @@ public String[] getPeerSupportedSignatureAlgorithms() { return delegate.getPeerSupportedSignatureAlgorithms(); } + public String getNamedGroup() { + return delegate.getNamedGroup(); + } + public List getRequestedServerNames() { return delegate.getRequestedServerNames(); } diff --git a/test/jdk/java/net/httpclient/ImmutableSSLSessionTest.java b/test/jdk/java/net/httpclient/ImmutableSSLSessionTest.java index a7c9deddac7..7545ecd1f62 100644 --- a/test/jdk/java/net/httpclient/ImmutableSSLSessionTest.java +++ b/test/jdk/java/net/httpclient/ImmutableSSLSessionTest.java @@ -60,7 +60,6 @@ import static java.net.http.HttpClient.Version.HTTP_2; import static java.net.http.HttpClient.Version.HTTP_3; import static java.net.http.HttpOption.H3_DISCOVERY; -import static java.net.http.HttpOption.Http3DiscoveryMode.ALT_SVC; import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY; import static java.nio.charset.StandardCharsets.US_ASCII; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -242,6 +241,7 @@ private void checkDummySession(SSLSession session) throws Exception { assertEquals(List.of("foo", "bar"), Arrays.asList(ext.getLocalSupportedSignatureAlgorithms())); assertEquals(List.of(new SNIHostName("localhost")), ((ExtendedSSLSession) session).getRequestedServerNames()); + assertEquals("MyNamedGroup", ((ExtendedSSLSession) session).getNamedGroup()); List status = ext.getStatusResponses(); assertEquals(1, status.size()); assertEquals("42", new String(status.get(0), US_ASCII)); @@ -363,6 +363,11 @@ public String[] getPeerSupportedSignatureAlgorithms() { return new String[] {"bar", "foo"}; } + @Override + public String getNamedGroup() { + return "MyNamedGroup"; + } + @Override public List getRequestedServerNames() { return List.of(new SNIHostName("localhost")); diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/NamedGroupTestData.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/NamedGroupTestData.java new file mode 100644 index 00000000000..0065d802beb --- /dev/null +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/NamedGroupTestData.java @@ -0,0 +1,103 @@ +/* + * 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 data class, not a test. +final class NamedGroupTestData { + + static final String[] DEFAULT_SUPPORTED_NG = new String[]{ + "X25519MLKEM768", + "x25519", + "secp256r1", + "secp384r1", + "secp521r1", + "x448", + "ffdhe2048", + "ffdhe3072", + "ffdhe4096", + "sect233k1", + "sect233r1", + "sect239k1", + "sect283k1", + "sect283r1", + "sect409k1", + "sect409r1", + "sect571k1", + "sect571r1", + "secp224k1", + "secp224r1", + "secp256k1", + "ffdhe6144", + "ffdhe8192", + "SecP256r1MLKEM768", + "SecP384r1MLKEM1024" + }; + + // X25519MLKEM768, SecP256r1MLKEM768 and SecP384r1MLKEM1024 are not + // supported in DTLSv1.2 + static final String[] DTLS12_SUPPORTED_NG = new String[]{ + "x25519", + "secp256r1", + "secp384r1", + "secp521r1", + "x448", + "ffdhe2048", + "ffdhe3072", + "ffdhe4096", + "sect233k1", + "sect233r1", + "sect239k1", + "sect283k1", + "sect283r1", + "sect409k1", + "sect409r1", + "sect571k1", + "sect571r1", + "secp224k1", + "secp224r1", + "secp256k1", + "ffdhe6144", + "ffdhe8192" + }; + + static final String[][] TEST_VALUES = new String[][]{ + /* + * Test values format as follows: + * Requested Named Group(s), Negotiated Named Group, Protocol + */ + + // Use default named group + {null, "X25519MLKEM768", "TLSv1.3"}, + {null, "x25519", "TLSv1.2"}, + {null, "x25519", "DTLSv1.2"}, + + // Request a single named group + {"secp384r1", "secp384r1", "TLSv1.3"}, + {"secp384r1", "secp384r1", "TLSv1.2"}, + {"secp384r1", "secp384r1", "DTLSv1.2"}, + + // Request multiple named groups + {"secp256r1,secp384r1", "secp256r1", "TLSv1.3"}, + {"secp256r1,secp384r1", "secp256r1", "TLSv1.2"}, + {"secp256r1,secp384r1", "secp256r1", "DTLSv1.2"}, + }; +} diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/SSLEngineNegotiatedSupportedNamedGroup.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/SSLEngineNegotiatedSupportedNamedGroup.java new file mode 100644 index 00000000000..fe8846b4a02 --- /dev/null +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/SSLEngineNegotiatedSupportedNamedGroup.java @@ -0,0 +1,286 @@ +/* + * 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertTrue; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import javax.net.ssl.ExtendedSSLSession; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; + +/* + * @test + * @bug 8388519 + * @summary Verify that ExtendedSSLSession reports the negotiated named group. + * Verify that SSLEngine reports the supported named groups. + * @library /javax/net/ssl/templates + * /test/lib + * @build NamedGroupTestData + * @run main SSLEngineNegotiatedSupportedNamedGroup + */ + +public class SSLEngineNegotiatedSupportedNamedGroup extends SSLEngineTemplate { + private static String protocol; + + private final String[] inputNamedGroups; + private final String negotiatedNamedGroup; + + protected SSLEngineNegotiatedSupportedNamedGroup(String[] inputNamedGroups, + String negotiatedNamedGroup) throws Exception { + this.inputNamedGroups = inputNamedGroups; + this.negotiatedNamedGroup = negotiatedNamedGroup; + } + + @Override + protected ContextParameters getClientContextParameters() { + return new ContextParameters(protocol, "PKIX", "SunX509"); + } + + @Override + protected ContextParameters getServerContextParameters() { + return new ContextParameters(protocol, "PKIX", "SunX509"); + } + + public static void main(String[] args) throws Exception { + // Run through test values + for (String[] v : NamedGroupTestData.TEST_VALUES) { + System.out.println("Running with test parameters: " + + Arrays.toString(v)); + protocol = v[2]; + new SSLEngineNegotiatedSupportedNamedGroup( + v[0] != null ? v[0].split(",") : null, + v[1]).run(); + } + } + + private void run() throws Exception { + for (SSLEngine engine : new SSLEngine[]{serverEngine, clientEngine}) { + SSLParameters params = engine.getSSLParameters(); + params.setProtocols(new String[]{protocol}); + params.setNamedGroups(inputNamedGroups); + engine.setSSLParameters(params); + } + + // Complete handshake. + initialSession(clientEngine, serverEngine); + + checkNamedGroup(serverEngine); + checkNamedGroup(clientEngine); + } + + private void checkNamedGroup(SSLEngine engine) { + // Check SSLEngine.getSupportedNamedGroups() call + assertTrue(Arrays.equals(getDefaultSupportedGroups(), + engine.getSupportedNamedGroups()), "Expected: " + + Arrays.toString(getDefaultSupportedGroups()) + + "; Received: " + + Arrays.toString(engine.getSupportedNamedGroups())); + + // Check ExtendedSSLSession.getNamedGroup() call + ExtendedSSLSession session = + (ExtendedSSLSession) engine.getSession(); + assertEquals(negotiatedNamedGroup, session.getNamedGroup()); + } + + private static String[] getDefaultSupportedGroups() { + if (protocol.equals("DTLSv1.2")) { + return NamedGroupTestData.DTLS12_SUPPORTED_NG; + } else { + return NamedGroupTestData.DEFAULT_SUPPORTED_NG; + } + } + + // Works for both TLS and DTLS. + private static void initialSession(SSLEngine clientEngine, + SSLEngine serverEngine) throws SSLException { + boolean clientDone = false; + boolean serverDone = false; + boolean cliDataReady = false; + boolean servDataReady = false; + SSLEngineResult clientResult; + SSLEngineResult serverResult; + SSLSession session = clientEngine.getSession(); + int appBufferMax = session.getApplicationBufferSize(); + int netBufferMax = session.getPacketBufferSize(); + ByteBuffer clientIn = ByteBuffer.allocate(appBufferMax + 50); + ByteBuffer serverIn = ByteBuffer.allocate(appBufferMax + 50); + ByteBuffer cTOs = ByteBuffer.allocateDirect(netBufferMax); + ByteBuffer sTOc = ByteBuffer.allocateDirect(netBufferMax); + HandshakeStatus hsStat; + final ByteBuffer clientOut = ByteBuffer.wrap( + "Hi Server, I'm Client".getBytes()); + final ByteBuffer serverOut = ByteBuffer.wrap( + "Hello Client, I'm Server".getBytes()); + + clientEngine.beginHandshake(); + serverEngine.beginHandshake(); + + while (!clientDone && !serverDone) { + // Client processing + hsStat = clientEngine.getHandshakeStatus(); + log("Client HS Stat: " + hsStat); + switch (hsStat) { + case NOT_HANDSHAKING: + log("Closing client engine"); + clientEngine.closeOutbound(); + clientDone = true; + break; + case NEED_WRAP: + log(String.format("CTOS: p:%d, l:%d, c:%d", cTOs.position(), + cTOs.limit(), cTOs.capacity())); + clientResult = clientEngine.wrap(clientOut, cTOs); + log("client wrap: ", clientResult); + if (clientResult.getStatus() + == SSLEngineResult.Status.BUFFER_OVERFLOW) { + // Get a larger buffer and try again + int updateSize = 2 * netBufferMax; + log("Resizing buffer to " + updateSize + " bytes"); + cTOs = ByteBuffer.allocate(updateSize); + clientResult = clientEngine.wrap(clientOut, cTOs); + log("client wrap (resized): ", clientResult); + } + runDelegatedTasks(clientResult, clientEngine); + cTOs.flip(); + cliDataReady = true; + break; + case NEED_UNWRAP: + if (servDataReady) { + log(String.format("STOC: p:%d, l:%d, c:%d", + sTOc.position(), + sTOc.limit(), sTOc.capacity())); + clientResult = clientEngine.unwrap(sTOc, clientIn); + log("client unwrap: ", clientResult); + runDelegatedTasks(clientResult, clientEngine); + servDataReady = sTOc.hasRemaining(); + sTOc.compact(); + } else { + log("Server-to-client data not ready, skipping client" + + " unwrap"); + } + break; + case NEED_UNWRAP_AGAIN: + clientResult = clientEngine.unwrap(ByteBuffer.allocate(0), + clientIn); + log("client unwrap (again): ", clientResult); + runDelegatedTasks(clientResult, clientEngine); + break; + } + + // Server processing + hsStat = serverEngine.getHandshakeStatus(); + log("Server HS Stat: " + hsStat); + switch (hsStat) { + case NEED_WRAP: + log(String.format("STOC: p:%d, l:%d, c:%d", sTOc.position(), + sTOc.limit(), sTOc.capacity())); + serverResult = serverEngine.wrap(serverOut, sTOc); + log("server wrap: ", serverResult); + if (serverResult.getStatus() + == SSLEngineResult.Status.BUFFER_OVERFLOW) { + // Get a new buffer and try again + int updateSize = 2 * netBufferMax; + log("Resizing buffer to " + updateSize + " bytes"); + sTOc = ByteBuffer.allocate(updateSize); + serverResult = serverEngine.wrap(clientOut, sTOc); + log("server wrap (resized): ", serverResult); + } + runDelegatedTasks(serverResult, serverEngine); + sTOc.flip(); + servDataReady = true; + break; + case NOT_HANDSHAKING: + log("Closing server engine"); + serverEngine.closeOutbound(); + serverDone = true; + break; + case NEED_UNWRAP: + if (cliDataReady) { + log(String.format("CTOS: p:%d, l:%d, c:%d", + cTOs.position(), + cTOs.limit(), cTOs.capacity())); + serverResult = serverEngine.unwrap(cTOs, serverIn); + log("server unwrap: ", serverResult); + runDelegatedTasks(serverResult, serverEngine); + cliDataReady = cTOs.hasRemaining(); + cTOs.compact(); + } else { + log("Client-to-server data not ready, skipping server" + + " unwrap"); + } + break; + case NEED_UNWRAP_AGAIN: + serverResult = serverEngine.unwrap(ByteBuffer.allocate(0), + serverIn); + log("server unwrap (again): ", serverResult); + runDelegatedTasks(serverResult, serverEngine); + break; + } + } + } + + private static void log(String str) { + System.out.println(str); + } + + private static void log(String str, SSLEngineResult result) { + System.out.println("The format of the SSLEngineResult is: \n" + + "\t\"getStatus() / getHandshakeStatus()\" +\n" + + "\t\"bytesConsumed() / bytesProduced()\"\n"); + + HandshakeStatus hsStatus = result.getHandshakeStatus(); + + log(str + + result.getStatus() + "/" + hsStatus + ", " + + result.bytesConsumed() + "/" + result.bytesProduced() + + " bytes"); + + if (hsStatus == HandshakeStatus.FINISHED) { + log("\t...ready for application data"); + } + } + + private static void runDelegatedTasks(SSLEngineResult result, + SSLEngine engine) { + HandshakeStatus hsStatus = result.getHandshakeStatus(); + + if (hsStatus == HandshakeStatus.NEED_TASK) { + Runnable runnable; + while ((runnable = engine.getDelegatedTask()) != null) { + log("\trunning delegated task..."); + runnable.run(); + } + hsStatus = engine.getHandshakeStatus(); + if (hsStatus == HandshakeStatus.NEED_TASK) { + throw new RuntimeException( + "handshake shouldn't need additional tasks"); + } + log("\tnew HandshakeStatus: " + hsStatus); + } + } +} diff --git a/test/jdk/javax/net/ssl/ExtendedSSLSession/SSLSocketNegotiatedSupportedNamedGroup.java b/test/jdk/javax/net/ssl/ExtendedSSLSession/SSLSocketNegotiatedSupportedNamedGroup.java new file mode 100644 index 00000000000..b9f9af655a5 --- /dev/null +++ b/test/jdk/javax/net/ssl/ExtendedSSLSession/SSLSocketNegotiatedSupportedNamedGroup.java @@ -0,0 +1,283 @@ +/* + * 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertTrue; + +import java.net.InetSocketAddress; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import javax.net.ssl.ExtendedSSLSession; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; + +/* + * @test + * @bug 8388519 + * @summary Verify that ExtendedSSLSession reports the negotiated named group. + * Verify that SSLSocket reports the supported named groups. + * @library /javax/net/ssl/templates + * /test/lib + * @build NamedGroupTestData + * + * @comment Full Handshake + * @run main SSLSocketNegotiatedSupportedNamedGroup + * + * @comment Stateless Resumption + * @run main SSLSocketNegotiatedSupportedNamedGroup resume TLSv1.3 + * @run main SSLSocketNegotiatedSupportedNamedGroup resume TLSv1.2 + * + * @comment Stateful Resumption + * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false + * SSLSocketNegotiatedSupportedNamedGroup resume TLSv1.3 + * @run main/othervm -Djdk.tls.server.enableSessionTicketExtension=false + * SSLSocketNegotiatedSupportedNamedGroup resume TLSv1.2 + */ + +public class SSLSocketNegotiatedSupportedNamedGroup extends SSLSocketTemplate { + + private static final String INITIAL_GROUP = "secp256r1"; + private static final String RESUMED_GROUP = "secp384r1"; + private static final int TIMEOUT = 20000; + + private final String[] inputNamedGroups; + private final String negotiatedNamedGroup; + private final String protocol; + + public SSLSocketNegotiatedSupportedNamedGroup(String[] inputNamedGroups, + String negotiatedNamedGroup, String protocol) { + this.inputNamedGroups = inputNamedGroups; + this.negotiatedNamedGroup = negotiatedNamedGroup; + this.protocol = protocol; + } + + @Override + protected ContextParameters getClientContextParameters() { + return new ContextParameters(protocol, "PKIX", "SunX509"); + } + + @Override + protected ContextParameters getServerContextParameters() { + return new ContextParameters(protocol, "PKIX", "SunX509"); + } + + public static void main(String[] args) throws Exception { + if (args.length > 0 && args[0].equals("resume")) { + new SSLSocketNegotiatedSupportedNamedGroup( + null, null, args[1]).testResumption(); + return; + } + + runFullHandshakeTests(); + } + + private static void runFullHandshakeTests() throws Exception { + // Check SSLServerSocket.getSupportedNamedGroups() call with default + // configuration. + SSLSocketTemplate defaultTest = new SSLSocketTemplate(); + SSLContext context = defaultTest.createServerSSLContext(); + SSLServerSocketFactory sslssf = context.getServerSocketFactory(); + try (SSLServerSocket sslServerSocket = + (SSLServerSocket) sslssf.createServerSocket( + defaultTest.serverPort, 0, defaultTest.serverAddress)) { + assertTrue(Arrays.equals(NamedGroupTestData.DEFAULT_SUPPORTED_NG, + sslServerSocket.getSupportedNamedGroups()), + Arrays.toString(sslServerSocket.getSupportedNamedGroups())); + } + + // Run through test values + for (String[] v : NamedGroupTestData.TEST_VALUES) { + // SSLSocket doesn't support DTLS + if (!v[2].startsWith("DTLS")) { + System.out.println("Running with test parameters: " + + Arrays.toString(v)); + new SSLSocketNegotiatedSupportedNamedGroup( + v[0] != null ? v[0].split(",") : null, + v[1], v[2]).run(); + } + } + } + + @Override + protected void configureServerSocket(SSLServerSocket socket) { + SSLParameters params = socket.getSSLParameters(); + params.setProtocols(new String[]{protocol}); + params.setNamedGroups(inputNamedGroups); + socket.setSSLParameters(params); + } + + @Override + protected void configureClientSocket(SSLSocket socket) { + SSLParameters params = socket.getSSLParameters(); + params.setProtocols(new String[]{protocol}); + params.setNamedGroups(inputNamedGroups); + socket.setSSLParameters(params); + } + + @Override + protected void runServerApplication(SSLSocket socket) throws Exception { + super.runServerApplication(socket); + checkNamedGroup(socket); + } + + @Override + protected void runClientApplication(SSLSocket socket) throws Exception { + super.runClientApplication(socket); + checkNamedGroup(socket); + } + + private void checkNamedGroup(SSLSocket socket) { + // Check SSLSocket.getSupportedNamedGroups() call + assertTrue(Arrays.equals(NamedGroupTestData.DEFAULT_SUPPORTED_NG, + socket.getSupportedNamedGroups()), "Expected: " + + Arrays.toString(NamedGroupTestData.DEFAULT_SUPPORTED_NG) + + "; Received: " + + Arrays.toString(socket.getSupportedNamedGroups())); + + // Check ExtendedSSLSession.getNamedGroup() call + ExtendedSSLSession session = + (ExtendedSSLSession) socket.getSession(); + assertEquals(negotiatedNamedGroup, session.getNamedGroup()); + } + + // Resumption test entry point. + private void testResumption() throws Exception { + // Key exchange is not performed in TLSv1.2 resumption unlike in TLSv1.3, + // so getNamedGroup() returns null for a resumed TLSv1.2 session. + String resumedNamedGroup = protocol.equals("TLSv1.3") ? + RESUMED_GROUP : null; + + SSLContext serverContext = createServerSSLContext(); + SSLContext clientContext = createClientSSLContext(); + + try (SSLServerSocket serverSocket = (SSLServerSocket) + serverContext.getServerSocketFactory().createServerSocket( + 0, 0, serverAddress); + ExecutorService executor = Executors.newSingleThreadExecutor()) { + + configureResumptionServerSocket(serverSocket); + serverSocket.setSoTimeout(TIMEOUT); + + // Start server. + Future serverFuture = executor.submit(() -> { + runServer(serverSocket, resumedNamedGroup); + return null; + }); + + // Run initial client. + long initialCreationTime = runClient( + clientContext, serverSocket.getLocalPort(), + INITIAL_GROUP, INITIAL_GROUP, false); + + // Run resumption client. + long resumedCreationTime = runClient( + clientContext, serverSocket.getLocalPort(), + RESUMED_GROUP, resumedNamedGroup, true); + + assertTrue(initialCreationTime == resumedCreationTime, + "Client session was not resumed"); + serverFuture.get(); + } + } + + private void configureResumptionServerSocket(SSLServerSocket socket) { + SSLParameters params = socket.getSSLParameters(); + params.setProtocols(new String[]{protocol}); + params.setNamedGroups(new String[]{INITIAL_GROUP, RESUMED_GROUP}); + socket.setSSLParameters(params); + } + + private void runServer(SSLServerSocket serverSocket, + String resumedNamedGroup) throws Exception { + + long initialCreationTime = 0; + String[] expectedNamedGroups = { + INITIAL_GROUP, resumedNamedGroup}; + + for (int i = 0; i < expectedNamedGroups.length; i++) { + try (SSLSocket socket = (SSLSocket) serverSocket.accept()) { + socket.setSoTimeout(TIMEOUT); + + // Don't create a new session on 2nd connection. + if (i == 1) { + socket.setEnableSessionCreation(false); + } + + socket.startHandshake(); + ExtendedSSLSession session = + (ExtendedSSLSession) socket.getSession(); + + assertEquals(expectedNamedGroups[i], + session.getNamedGroup()); + + if (i == 0) { + initialCreationTime = session.getCreationTime(); + } else { + assertTrue(initialCreationTime == session.getCreationTime(), + "Server session was not resumed"); + } + + socket.getInputStream().read(); + socket.getOutputStream().write(280); + socket.getOutputStream().flush(); + } + } + } + + private long runClient(SSLContext context, int serverPort, + String namedGroup, String expectedNamedGroup, + boolean resume) throws Exception { + + try (SSLSocket socket = + (SSLSocket) context.getSocketFactory().createSocket()) { + + SSLParameters params = socket.getSSLParameters(); + params.setProtocols(new String[]{protocol}); + params.setNamedGroups(new String[]{namedGroup}); + socket.setSSLParameters(params); + socket.setSoTimeout(TIMEOUT); + + if (resume) { + socket.setEnableSessionCreation(false); + } + + socket.connect(new InetSocketAddress(serverAddress, serverPort), + TIMEOUT); + socket.startHandshake(); + + ExtendedSSLSession session = (ExtendedSSLSession) socket.getSession(); + assertEquals(expectedNamedGroup, session.getNamedGroup()); + + socket.getOutputStream().write(85); + socket.getOutputStream().flush(); + socket.getInputStream().read(); + + return session.getCreationTime(); + } + } +} From 2ce50fab5a3068b80b78fbf3fbf39f0f849f8aa9 Mon Sep 17 00:00:00 2001 From: Evgeny Astigeevich Date: Mon, 12 Oct 2026 14:23:35 +0000 Subject: [PATCH 046/101] 8390358: HotCodeCollector can relocate never-sampled nmethod Reviewed-by: crakoczy, kvn --- .../share/runtime/hotCodeCollector.cpp | 42 ++++++---- .../share/runtime/hotCodeCollector.hpp | 2 +- src/hotspot/share/runtime/hotCodeSampler.cpp | 77 +++++++++++-------- src/hotspot/share/runtime/hotCodeSampler.hpp | 50 ++++++++++-- 4 files changed, 113 insertions(+), 58 deletions(-) diff --git a/src/hotspot/share/runtime/hotCodeCollector.cpp b/src/hotspot/share/runtime/hotCodeCollector.cpp index bb848582c46..819894a0874 100644 --- a/src/hotspot/share/runtime/hotCodeCollector.cpp +++ b/src/hotspot/share/runtime/hotCodeCollector.cpp @@ -112,6 +112,14 @@ void HotCodeCollector::thread_entry(JavaThread* thread, TRAPS) { } } +static nmethod* find_nmethod(void* addr) { + if (addr == nullptr) { + return nullptr; + } + CodeBlob* blob = CodeCache::find_blob(addr); + return blob == nullptr ? nullptr : blob->as_nmethod_or_null(); +} + void HotCodeCollector::do_grouping(Candidates& candidates) { // Number of nmethods relocated (candidate + callees) int num_relocated = 0; @@ -131,14 +139,25 @@ void HotCodeCollector::do_grouping(Candidates& candidates) { break; } - nmethod* candidate = candidates.get_candidate(); + Candidate candidate = candidates.get_candidate(); MutexLocker ml_Compile_lock(Compile_lock); MutexLocker ml_CompiledIC_lock(CompiledIC_lock, Mutex::_no_safepoint_check_flag); MutexLocker ml_CodeCache_lock(CodeCache_lock, Mutex::_no_safepoint_check_flag); - switch (do_relocation(candidate, 0, &num_relocated)) { + nmethod* nm = find_nmethod(candidate.get_nmethod()); + if (nm == nullptr || + nm != candidate.get_nmethod() || + nm->compile_id() != candidate.get_compile_id()) { + log_debug(hotcode)("Skipped stale candidate: address=%p, compile_id=%d, samples=%d", + candidate.get_nmethod(), candidate.get_compile_id(), candidate.get_sample_count()); + num_skipped++; + continue; + } + + switch (do_relocation(nm, 0, &num_relocated)) { case nmethod::RelocationResult::SUCCESS: + candidates.move_samples_to_hot(candidate.get_sample_count()); break; case nmethod::RelocationResult::FAILED_NO_SPACE_IN_CODE_HEAP: { CodeHeap* heap = CodeCache::get_code_heap(CodeBlobType::MethodHot); @@ -153,6 +172,9 @@ void HotCodeCollector::do_grouping(Candidates& candidates) { break; } case nmethod::RelocationResult::FAILED_NOT_RELOCATABLE_NMETHOD: + if (nm->method() != nullptr) { + log_debug(hotcode)("Skipped not relocatable nmethod: %s", nm->method()->name_and_sig_as_C_string()); + } num_skipped++; break; case nmethod::RelocationResult::FAILED_INVALIDATED_NMETHOD: @@ -165,21 +187,9 @@ void HotCodeCollector::do_grouping(Candidates& candidates) { "Skipped %d candidates. %d candidates invalidated during relocation.", num_relocated, num_skipped, num_invalidated); } -nmethod::RelocationResult HotCodeCollector::do_relocation(void* candidate, uint call_level, int* num_relocated) { +nmethod::RelocationResult HotCodeCollector::do_relocation(nmethod* nm, uint call_level, int* num_relocated) { assert(num_relocated != nullptr, "num_relocated must be provided"); - if (candidate == nullptr) { - return nmethod::RelocationResult::FAILED_NOT_RELOCATABLE_NMETHOD; - } - - // Verify that address still points to CodeBlob - CodeBlob* blob = CodeCache::find_blob(candidate); - if (blob == nullptr) { - return nmethod::RelocationResult::FAILED_NOT_RELOCATABLE_NMETHOD; - } - - // Verify that blob is nmethod - nmethod* nm = blob->as_nmethod_or_null(); if (nm == nullptr || nm->method() == nullptr) { return nmethod::RelocationResult::FAILED_NOT_RELOCATABLE_NMETHOD; } @@ -235,7 +245,7 @@ nmethod::RelocationResult HotCodeCollector::do_relocation(void* candidate, uint address dest = ((CallRelocation*) reloc)->destination(); // Recursively relocate callees - if (do_relocation(dest, call_level + 1, num_relocated) == nmethod::RelocationResult::FAILED_NO_SPACE_IN_CODE_HEAP) { + if (do_relocation(find_nmethod(dest), call_level + 1, num_relocated) == nmethod::RelocationResult::FAILED_NO_SPACE_IN_CODE_HEAP) { return nmethod::RelocationResult::FAILED_NO_SPACE_IN_CODE_HEAP; } } diff --git a/src/hotspot/share/runtime/hotCodeCollector.hpp b/src/hotspot/share/runtime/hotCodeCollector.hpp index 0c4e853730e..d6efee3360a 100644 --- a/src/hotspot/share/runtime/hotCodeCollector.hpp +++ b/src/hotspot/share/runtime/hotCodeCollector.hpp @@ -42,7 +42,7 @@ class HotCodeCollector : public JavaThread { static void do_grouping(Candidates& candidates); - static nmethod::RelocationResult do_relocation(void* candidate, uint call_level, int* num_relocated); + static nmethod::RelocationResult do_relocation(nmethod* candidate, uint call_level, int* num_relocated); public: static void initialize(); diff --git a/src/hotspot/share/runtime/hotCodeSampler.cpp b/src/hotspot/share/runtime/hotCodeSampler.cpp index e033765c1f2..33579d1b268 100644 --- a/src/hotspot/share/runtime/hotCodeSampler.cpp +++ b/src/hotspot/share/runtime/hotCodeSampler.cpp @@ -25,6 +25,7 @@ #ifdef COMPILER2 #include "code/codeCache.hpp" +#include "code/nmethod.hpp" #include "logging/log.hpp" #include "runtime/hotCodeSampler.hpp" #include "runtime/javaThread.inline.hpp" @@ -62,50 +63,63 @@ bool ThreadSampler::sample_all_java_threads() { } CodeBlob* cb = CodeCache::find_blob(pc); - if (cb != nullptr && cb->is_nmethod()) { - bool created = false; - int *count = _samples.put_if_absent(cb->as_nmethod(), 0, &created); - (*count)++; - if (created) { - _samples.maybe_grow(); - } + if (cb == nullptr || !cb->is_nmethod()) { + continue; + } + + nmethod* nm = cb->as_nmethod(); + + // We can dereference the nmethod pointer here because we are sampling from a JavaThread and + // the code blob cannot be purged while the thread does not reach a safepoint. + assert(Thread::current()->is_Java_thread(), "ThreadSampler should only be called from a JavaThread"); + int compile_id = nm->compile_id(); + CodeBlobType code_blob_type = CodeCache::get_code_blob_type(nm); + + if (code_blob_type == CodeBlobType::MethodHot) { + _hot_sample_count++; + continue; + } + + if (code_blob_type != CodeBlobType::MethodNonProfiled) { + continue; + } + + _non_profiled_sample_count++; + + bool created = false; + Pair* sampled_nm = _samples.put_if_absent(compile_id, Pair(nm, 0), &created); + sampled_nm->second++; + if (created) { + _samples.maybe_grow(); } } return true; } +#define NMETHOD_PTR(sampled_nm) (sampled_nm.first) +#define SAMPLE_COUNT(sampled_nm) (sampled_nm.second) + Candidates::Candidates(ThreadSampler& sampler) - : _hot_sample_count(0), _non_profiled_sample_count(0) { - auto func = [&](nmethod* nm, int count) { - if (CodeCache::get_code_blob_type(nm) == CodeBlobType::MethodNonProfiled) { - _candidates.append(Pair(nm, count)); - add_non_profiled_sample_count(count); - } else if (CodeCache::get_code_blob_type(nm) == CodeBlobType::MethodHot) { - add_hot_sample_count(count); - } + : _hot_sample_count(sampler.hot_sample_count()), + _non_profiled_sample_count(sampler.non_profiled_sample_count()) { + auto func = [&](int compile_id, const Pair sampled_nm) { + _candidates.append(Candidate(NMETHOD_PTR(sampled_nm), compile_id, SAMPLE_COUNT(sampled_nm))); }; sampler.iterate_samples(func); log_info(hotcode)("Generated candidate list from %d samples corresponding to %d nmethods", _non_profiled_sample_count + _hot_sample_count, _candidates.length()); } -void Candidates::add_candidate(nmethod* nm, int count) { - _candidates.append(Pair(nm, count)); -} - -void Candidates::add_hot_sample_count(int count) { +void Candidates::move_samples_to_hot(int count) { _hot_sample_count += count; -} - -void Candidates::add_non_profiled_sample_count(int count) { - _non_profiled_sample_count += count; + _non_profiled_sample_count -= count; } void Candidates::sort() { _candidates.sort( - [](Pair* a, Pair* b) { - if (a->second > b->second) return 1; - if (a->second < b->second) return -1; + [](Candidate* a, Candidate* b) { + if (a->get_sample_count() > b->get_sample_count()) return 1; + if (a->get_sample_count() < b->get_sample_count()) return -1; return 0; } ); @@ -115,14 +129,9 @@ bool Candidates::has_candidates() { return !_candidates.is_empty(); } -nmethod* Candidates::get_candidate() { +Candidate Candidates::get_candidate() { assert(has_candidates(), "must not be empty"); - Pair candidate = _candidates.pop(); - - _hot_sample_count += candidate.second; - _non_profiled_sample_count -= candidate.second; - - return candidate.first; + return _candidates.pop(); } double Candidates::get_hot_sample_percent() { diff --git a/src/hotspot/share/runtime/hotCodeSampler.hpp b/src/hotspot/share/runtime/hotCodeSampler.hpp index e6c2474b068..83f396f7a7b 100644 --- a/src/hotspot/share/runtime/hotCodeSampler.hpp +++ b/src/hotspot/share/runtime/hotCodeSampler.hpp @@ -40,23 +40,46 @@ static inline uint rand_sampling_period_ms() { } class ThreadSampler; +class nmethod; + +class Candidate : public StackObj { + private: + nmethod* _nm; + int _compile_id; + int _sample_count; + + public: + Candidate() : _nm(nullptr), _compile_id(0), _sample_count(0) {} + Candidate(nmethod* nm, int compile_id, int samples_count) : _nm(nm), + _compile_id(compile_id), _sample_count(samples_count) {} + + nmethod* get_nmethod() const { + return _nm; + } + + int get_compile_id() const { + return _compile_id; + } + + int get_sample_count() const { + return _sample_count; + } +}; class Candidates : public StackObj { private: - GrowableArray> _candidates; + GrowableArray _candidates; int _hot_sample_count; int _non_profiled_sample_count; public: Candidates(ThreadSampler& sampler); - void add_candidate(nmethod* nm, int count); - void add_hot_sample_count(int count); - void add_non_profiled_sample_count(int count); + void move_samples_to_hot(int count); void sort(); bool has_candidates(); - nmethod* get_candidate(); + Candidate get_candidate(); double get_hot_sample_percent(); }; @@ -85,10 +108,15 @@ class ThreadSampler : public StackObj { static const int INITIAL_TABLE_SIZE = 109; // Table of nmethods found during profiling with sample count - ResizeableHashTable _samples; + // hash table: key = compile_id, value = Pair + ResizeableHashTable, AnyObj::C_HEAP, mtInternal> _samples; + + int _hot_sample_count; + int _non_profiled_sample_count; public: - ThreadSampler() : _samples(INITIAL_TABLE_SIZE, HotCodeSampleSeconds * 1000 / HotCodeMaxSamplingMs) {} + ThreadSampler() : _samples(INITIAL_TABLE_SIZE, HotCodeSampleSeconds * 1000 / HotCodeMaxSamplingMs), _hot_sample_count(0), + _non_profiled_sample_count(0) {} // Iterate over and sample all Java threads. Return false if sampling was interrupted by JFR sampling. bool sample_all_java_threads(); @@ -98,6 +126,14 @@ class ThreadSampler : public StackObj { void iterate_samples(Function func) { _samples.iterate_all(func); } + + int hot_sample_count() const { + return _hot_sample_count; + } + + int non_profiled_sample_count() const { + return _non_profiled_sample_count; + } }; #endif // SHARE_RUNTIME_HOTCODESAMPLER_HPP From 52c1cf3a15d8aebbb25cc9c0096bf5773ecaf2c6 Mon Sep 17 00:00:00 2001 From: Joe Darcy Date: Mon, 12 Oct 2026 14:55:51 +0000 Subject: [PATCH 047/101] 8393682: BigDecimal.{toBigInteger(), toBigIntegerExact()} could special-case small values Reviewed-by: psandoz --- .../share/classes/java/math/BigDecimal.java | 17 +- .../math/BigDecimal/ToBigIntegerTests.java | 236 ++++++++++++++++++ 2 files changed, 249 insertions(+), 4 deletions(-) create mode 100644 test/jdk/java/math/BigDecimal/ToBigIntegerTests.java diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index a99ccd2fc99..0a7ed29bc0e 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -3599,8 +3599,12 @@ boolean isInteger() { * @jls 5.1.3 Narrowing Primitive Conversion */ public BigInteger toBigInteger() { - // force to an integer, quietly - return this.setScale(0, ROUND_DOWN).inflated(); + if (signum() == 0 || // Numerical zero + (scale() >= precision()) ) { // Only fractional digits + return BigInteger.ZERO; + } else { // force to an integer, quietly + return this.setScale(0, ROUND_DOWN).inflated(); + } } /** @@ -3614,8 +3618,13 @@ public BigInteger toBigInteger() { * @since 1.5 */ public BigInteger toBigIntegerExact() { - // round to an integer, with Exception if decimal part non-0 - return this.setScale(0, ROUND_UNNECESSARY).inflated(); + if (signum() == 0) { // Numerical zero + return BigInteger.ZERO; + } else if ((scale() >= precision()) ) { // Quick check for only fractional digits + throw new ArithmeticException("Rounding necessary; only fractional digits"); + } else { // round to an integer, with Exception if fractional part non-0 + return this.setScale(0, ROUND_UNNECESSARY).inflated(); + } } /** diff --git a/test/jdk/java/math/BigDecimal/ToBigIntegerTests.java b/test/jdk/java/math/BigDecimal/ToBigIntegerTests.java new file mode 100644 index 00000000000..18faa55ac54 --- /dev/null +++ b/test/jdk/java/math/BigDecimal/ToBigIntegerTests.java @@ -0,0 +1,236 @@ +/* + * 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 8393682 + * @summary Tests of BigDecimal.{toBigInteger, toBigIntegerExact} + */ + +import java.math.*; + +public class ToBigIntegerTests { + public static void main(String... args) { + int failures = 0; + + failures += zeroTestCases(); + failures += toBigIntegerTests(); + failures += toBigIntegerExactTests(); + + if (failures > 0) { + throw new RuntimeException("Incurred " + failures + + " failures while testing toBigInteger/toBigIntegerExact."); + } + } + + private static record TbiTestCase(BigDecimal input, BigInteger expected) { + static TbiTestCase of(BigDecimal input, BigInteger expected) { + return new TbiTestCase(input, expected); + } + + static TbiTestCase of(String input, BigInteger expected) { + return new TbiTestCase(new BigDecimal(input), expected); + } + } + + private static TbiTestCase zeroTestCases[] = { + TbiTestCase.of(new BigDecimal(BigInteger.ZERO, + Integer.MIN_VALUE), BigInteger.ZERO), + TbiTestCase.of(new BigDecimal(BigInteger.ZERO, -1), BigInteger.ZERO), + TbiTestCase.of(BigDecimal.ZERO, BigInteger.ZERO), + TbiTestCase.of(new BigDecimal(BigInteger.ZERO, +1), BigInteger.ZERO), + TbiTestCase.of(new BigDecimal(BigInteger.ZERO, + Integer.MAX_VALUE), BigInteger.ZERO), + }; + + private static int zeroTestCases() { + int failures = 0; + + for(var zeroTestCase : zeroTestCases) { + BigDecimal input = zeroTestCase.input; + BigInteger expected = zeroTestCase.expected; + + if (!input.toBigInteger().equals(expected)) { + failures++; + System.out.println(input + " did not have expected toBigInteger result of " + expected); + } + + if (!input.toBigIntegerExact().equals(expected)) { + failures++; + System.out.println(input + " did not have toBigIntegerExact result of " + expected); + } + } + + return failures; + } + + private static TbiTestCase tbiTestCases[] = { + // nonzero constant values in the API + TbiTestCase.of(BigDecimal.ONE, BigInteger.ONE), + TbiTestCase.of(BigDecimal.TWO, BigInteger.TWO), + TbiTestCase.of(BigDecimal.TEN, BigInteger.TEN), + + // Negative scale values with (precision == 1) + TbiTestCase.of( "1e1", BigInteger.TEN), + TbiTestCase.of( "1e2", BigInteger.valueOf( 100)), + TbiTestCase.of( "1e3", BigInteger.valueOf( 1_000)), + TbiTestCase.of( "1e6", BigInteger.valueOf(1_000_000)), + + // Scale == 0 values, varying precision + TbiTestCase.of(BigDecimal.valueOf( 12, 0), BigInteger.valueOf( 12)), + TbiTestCase.of(BigDecimal.valueOf( 123, 0), BigInteger.valueOf( 123)), + TbiTestCase.of(BigDecimal.valueOf(1234, 0), BigInteger.valueOf(1234)), + + // Positive scale, varying precision + TbiTestCase.of(BigDecimal.valueOf( 12, 1), BigInteger.valueOf( 1)), + TbiTestCase.of(BigDecimal.valueOf( 123, 1), BigInteger.valueOf( 12)), + TbiTestCase.of(BigDecimal.valueOf(1234, 1), BigInteger.valueOf(123)), + + TbiTestCase.of(BigDecimal.valueOf( 12, 2), BigInteger.valueOf( 0)), + TbiTestCase.of(BigDecimal.valueOf( 123, 2), BigInteger.valueOf( 1)), + TbiTestCase.of(BigDecimal.valueOf(1234, 2), BigInteger.valueOf(12)), + + TbiTestCase.of(BigDecimal.valueOf( 12, 3), BigInteger.valueOf(0)), + TbiTestCase.of(BigDecimal.valueOf( 123, 3), BigInteger.valueOf(0)), + TbiTestCase.of(BigDecimal.valueOf(1234, 3), BigInteger.valueOf(1)), + + // More fractional values + TbiTestCase.of("-0.5", BigInteger.ZERO), + TbiTestCase.of( "0.5", BigInteger.ZERO), + + TbiTestCase.of("-0.5001", BigInteger.ZERO), + TbiTestCase.of( "0.5001", BigInteger.ZERO), + + TbiTestCase.of("-0.9999", BigInteger.ZERO), + TbiTestCase.of( "0.9999", BigInteger.ZERO), + + TbiTestCase.of( "0.0001", BigInteger.ZERO), + TbiTestCase.of("-0.0001", BigInteger.ZERO), + + TbiTestCase.of( "1e-15", BigInteger.ZERO), + TbiTestCase.of("-1e-15", BigInteger.ZERO), + + // Test rounding to 1 + TbiTestCase.of("-1.5", BigInteger.ONE.negate()), + TbiTestCase.of( "1.5", BigInteger.ONE), + + TbiTestCase.of("-1.5001", BigInteger.ONE.negate()), + TbiTestCase.of( "1.5001", BigInteger.ONE), + + TbiTestCase.of("-1.9999", BigInteger.ONE.negate()), + TbiTestCase.of( "1.9999", BigInteger.ONE), + + TbiTestCase.of( "1.0001", BigInteger.ONE), + TbiTestCase.of("-1.0001", BigInteger.ONE.negate()), + }; + + private static int toBigIntegerTests() { + int failures = 0; + + for(var tbiTestCase : tbiTestCases) { + BigDecimal input = tbiTestCase.input; + BigInteger expected = tbiTestCase.expected; + + if (!input.toBigInteger().equals(expected)) { + failures++; + System.out.println(input + " did not have expected toBigInteger of result of " + expected); + } + } + + return failures; + } + + private static TbiTestCase tbiExactTestCases[] = { + // nonzero constant value in the API + TbiTestCase.of(BigDecimal.ONE, BigInteger.ONE), + TbiTestCase.of(BigDecimal.TWO, BigInteger.TWO), + TbiTestCase.of(BigDecimal.TEN, BigInteger.TEN), + + // Negative scale values with (precision == 1) + TbiTestCase.of( "1e1", BigInteger.TEN), + TbiTestCase.of( "1e2", BigInteger.valueOf( 100)), + TbiTestCase.of( "1e3", BigInteger.valueOf( 1_000)), + TbiTestCase.of( "1e6", BigInteger.valueOf(1_000_000)), + + // Scale == 0 values, varying precision + TbiTestCase.of(BigDecimal.valueOf( 12, 0), BigInteger.valueOf( 12)), + TbiTestCase.of(BigDecimal.valueOf( 123, 0), BigInteger.valueOf( 123)), + TbiTestCase.of(BigDecimal.valueOf(1234, 0), BigInteger.valueOf(1234)), + + // Positive scale, varying precision + TbiTestCase.of(BigDecimal.valueOf( 12, 1), null), + TbiTestCase.of(BigDecimal.valueOf( 123, 1), null), + TbiTestCase.of(BigDecimal.valueOf(1234, 1), null), + + TbiTestCase.of(BigDecimal.valueOf( 12, 2), null), + TbiTestCase.of(BigDecimal.valueOf( 123, 2), null), + TbiTestCase.of(BigDecimal.valueOf(1234, 2), null), + + TbiTestCase.of(BigDecimal.valueOf( 12, 3), null), + TbiTestCase.of(BigDecimal.valueOf( 123, 3), null), + TbiTestCase.of(BigDecimal.valueOf(1234, 3), null), + + // More fractional values + TbiTestCase.of("-0.9999", null), + TbiTestCase.of( "0.9999", null), + + TbiTestCase.of( "0.0001", null), + TbiTestCase.of("-0.0001", null), + + + TbiTestCase.of( "1e-15", null), + TbiTestCase.of("-1e-15", null), + }; + + private static int toBigIntegerExactTests() { + int failures = 0; + + for(var tbiTestCase : tbiExactTestCases) { + BigDecimal input = tbiTestCase.input; + BigInteger expected = tbiTestCase.expected; + BigInteger result = null; + + if (expected == null) { // ArithmeticException expected + try { + result = input.toBigIntegerExact(); + failures++; + System.out.println("Got unexpected non-exceptionsl result of toBigIntegerExact on " + input); + } catch(ArithmeticException ae) { + ; // Expected + } + } else { + try { + result = input.toBigIntegerExact(); + if (!result.equals(expected)) { + failures++; + System.out.println(input + " did not have expected toBigIntegerExact of " + expected); + } + } catch(ArithmeticException ae) { + failures++; + System.out.println("Unexpected exception computing toBigIntegerExact of " + input); + } + } + } + return failures; + } +} From 444fccb4b38cb83adb3830528227630f16891a70 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 12 Oct 2026 16:30:18 +0000 Subject: [PATCH 048/101] 8393744: JFR: Remove unused classes, methods, fields and imports Reviewed-by: mgronlun, apangin --- src/hotspot/share/jfr/jni/jfrJniMethod.cpp | 4 -- src/hotspot/share/jfr/jni/jfrJniMethod.hpp | 2 - .../jfr/jni/jfrJniMethodRegistration.cpp | 1 - .../share/classes/jdk/jfr/EventSettings.java | 9 +--- .../classes/jdk/jfr/ValueDescriptor.java | 13 +---- .../classes/jdk/jfr/internal/Control.java | 6 +-- .../jdk/jfr/internal/EventControl.java | 3 +- .../classes/jdk/jfr/internal/JDKEvents.java | 3 +- .../share/classes/jdk/jfr/internal/JVM.java | 32 ------------ .../jdk/jfr/internal/MetadataLoader.java | 4 +- .../jdk/jfr/internal/MetadataReader.java | 2 +- .../jdk/jfr/internal/MetadataRepository.java | 4 +- .../jdk/jfr/internal/PlatformRecorder.java | 1 - .../jdk/jfr/internal/PlatformRecording.java | 14 ------ .../jdk/jfr/internal/PrivateAccess.java | 4 +- .../share/classes/jdk/jfr/internal/Type.java | 6 +-- .../classes/jdk/jfr/internal/TypeLibrary.java | 17 +++---- .../jfr/internal/consumer/EventParser.java | 6 +-- .../jfr/internal/consumer/OngoingStream.java | 1 - .../jfr/internal/consumer/ParserFactory.java | 4 +- .../jdk/jfr/internal/dcmd/AbstractDCmd.java | 10 +--- .../jdk/jfr/internal/event/EventWriter.java | 6 +-- .../jdk/jfr/internal/jfc/model/JFCModel.java | 6 +-- .../classes/jdk/jfr/internal/query/Query.java | 5 +- .../jdk/jfr/internal/query/TableCell.java | 7 +-- .../jdk/jfr/internal/settings/Throttler.java | 4 +- .../jdk/jfr/internal/tool/Assemble.java | 2 - .../jdk/jfr/internal/tool/PrettyWriter.java | 3 +- .../jfr/internal/tool/UserDataException.java | 49 ------------------- .../internal/tool/UserSyntaxException.java | 45 ----------------- .../classes/jdk/jfr/internal/util/Utils.java | 10 +--- .../jdk/jfr/internal/util/ValueParser.java | 9 +--- .../jdk/management/jfr/DiskRepository.java | 12 +---- .../jfr/FlightRecorderMXBeanImpl.java | 3 +- .../management/jfr/RemoteRecordingStream.java | 9 +--- 35 files changed, 36 insertions(+), 280 deletions(-) delete mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java delete mode 100644 src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 45cb2b6541e..a74b5453b8c 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -87,10 +87,6 @@ NO_TRANSITION(jstring, jfr_get_pid(JNIEnv* env, jclass jvm)) return pid_string; // exception pending if null NO_TRANSITION_END -NO_TRANSITION(jlong, jfr_elapsed_frequency(JNIEnv* env, jclass jvm)) - return JfrTime::frequency(); -NO_TRANSITION_END - NO_TRANSITION(jlong, jfr_elapsed_counter(JNIEnv* env, jclass jvm)) return JfrTicks::now(); NO_TRANSITION_END diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp index 2d17e5cd3f8..99629bd8144 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp @@ -59,8 +59,6 @@ jstring JNICALL jfr_get_pid(JNIEnv* env, jclass jvm); jlong JNICALL jfr_stacktrace_id(JNIEnv* env, jclass jvm, jint skip, jlong stack_filter_id); -jlong JNICALL jfr_elapsed_frequency(JNIEnv* env, jclass jvm); - void JNICALL jfr_subscribe_log_level(JNIEnv* env, jclass jvm, jobject log_tag, jint id); void JNICALL jfr_log(JNIEnv* env, jclass jvm, jint tag_set, jint level, jstring message); diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp index e37f786117d..594addc5954 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp @@ -49,7 +49,6 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) { (char*)"getPid", (char*)"()Ljava/lang/String;", (void*)jfr_get_pid, (char*)"getStackTraceId", (char*)"(IJ)J", (void*)jfr_stacktrace_id, (char*)"getThreadId", (char*)"(Ljava/lang/Thread;)J", (void*)jfr_id_for_thread, - (char*)"getTicksFrequency", (char*)"()J", (void*)jfr_elapsed_frequency, (char*)"subscribeLogLevel", (char*)"(Ljdk/jfr/internal/LogTag;I)V", (void*)jfr_subscribe_log_level, (char*)"log", (char*)"(IILjava/lang/String;)V", (void*)jfr_log, (char*)"logEvent", (char*)"(I[Ljava/lang/String;Z)V", (void*)jfr_log_event, diff --git a/src/jdk.jfr/share/classes/jdk/jfr/EventSettings.java b/src/jdk.jfr/share/classes/jdk/jfr/EventSettings.java index a8de9592140..de2f28d7614 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/EventSettings.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/EventSettings.java @@ -96,8 +96,8 @@ public AnnotationElement newAnnotation(Type annotationType, List values, } @Override - public ValueDescriptor newValueDescriptor(String name, Type fieldType, List annos, int dimension, boolean constantPool, String fieldName) { - return new ValueDescriptor(fieldType, name, annos, dimension, constantPool, fieldName); + public ValueDescriptor newValueDescriptor(String name, Type fieldType, List annos, int dimension, boolean constantPool) { + return new ValueDescriptor(fieldType, name, annos, dimension, constantPool); } @Override @@ -125,11 +125,6 @@ public void setAnnotations(SettingDescriptor s, List a) { s.setAnnotations(a); } - @Override - public String getFieldName(ValueDescriptor v) { - return v.getJavaFieldName(); - } - @Override public ValueDescriptor newValueDescriptor(Class type, String name) { return new ValueDescriptor(type, name, List.of(), true); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java b/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java index 9994b689d09..6fa1cf169fa 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/ValueDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -51,12 +51,11 @@ public final class ValueDescriptor { private final String name; private final boolean isArray; private final boolean constantPool; - private final String javaFieldName; private String label = UNKNOWN; private String contentType = UNKNOWN; // package private, invoked by jdk.internal. - ValueDescriptor(Type type, String name, List annotations, int dimension, boolean constantPool, String fieldName) { + ValueDescriptor(Type type, String name, List annotations, int dimension, boolean constantPool) { Objects.requireNonNull(annotations); if (dimension < 0) { throw new IllegalArgumentException("Dimension must be positive"); @@ -66,7 +65,6 @@ public final class ValueDescriptor { this.isArray = dimension > 0; this.constantPool = constantPool; this.annotationConstruct = new AnnotationConstruct(annotations); - this.javaFieldName = fieldName; } /** @@ -137,7 +135,6 @@ public ValueDescriptor(Class type, String name, List annot this(type, name, List.copyOf(annotations), false); } - ValueDescriptor(Class type, String name, List annotations, boolean allowArray) { Objects.requireNonNull(type, "type"); Objects.requireNonNull(name, "name"); @@ -151,7 +148,6 @@ public ValueDescriptor(Class type, String name, List annot Utils.ensureJavaIdentifier(name); this.type = Objects.requireNonNull(Utils.getValidType(Objects.requireNonNull(type), Objects.requireNonNull(name))); this.annotationConstruct = new AnnotationConstruct(annotations); - this.javaFieldName = name; // Needed for dynamic events this.isArray = type.isArray(); // Assume we always want to store String and Thread in constant pool this.constantPool = type == Class.class || type == Thread.class; @@ -321,11 +317,6 @@ boolean isConstantPool() { return constantPool; } - // package private - String getJavaFieldName() { - return javaFieldName; - } - // package private boolean isUnsigned() { return annotationConstruct.hasUnsigned(); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java index b21b539cab8..a784e21b692 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Control.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -115,10 +115,6 @@ final String getLastValue() { return lastValue; } - final SettingControl getSettingControl() { - return delegate; - } - boolean isVisible(boolean hasEventHook) { if (isType(ThresholdSetting.class)) { return !hasEventHook; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java index 621c879f712..3391a945433 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -66,7 +66,6 @@ public final class EventControl { record NamedControl(String name, Control control) { } - static final String FIELD_SETTING_PREFIX = "setting"; private static final Type TYPE_ENABLED = TypeLibrary.createType(EnabledSetting.class); private static final Type TYPE_THRESHOLD = TypeLibrary.createType(ThresholdSetting.class); private static final Type TYPE_STACK_TRACE = TypeLibrary.createType(StackTraceSetting.class); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java index 79bc614545e..d859c796ee6 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JDKEvents.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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,7 +47,6 @@ import jdk.jfr.internal.periodic.PeriodicEvents; import jdk.jfr.internal.settings.MethodSetting; import jdk.jfr.internal.tracing.PlatformTracer; -import jdk.jfr.tracing.MethodTracer; public final class JDKEvents { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java index d3f50567c23..c2dbffd31a9 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java @@ -39,8 +39,6 @@ * */ public final class JVM { - private static final JVM jvm = new JVM(); - static final long RESERVED_CLASS_ID_LIMIT = 500; /* @@ -165,13 +163,6 @@ public final class JVM { */ public static native long getThreadId(Thread t); - /** - * Frequency, ticks per second - * - * @return frequency - */ - public static native long getTicksFrequency(); - /** * Returns the same clock that sets the start time of a chunk (in nanos). */ @@ -326,16 +317,6 @@ public final class JVM { */ public static native void setForceInstrumentation(boolean force); - /** - * Turn on/off compressed integers. - * - * @param compressed true if compressed integers should be used, false - * otherwise. - * - * @throws IllegalStateException if state can't be changed. - */ - public static native void setCompressedIntegers(boolean compressed) throws IllegalStateException; - /** * Set stack depth. * @@ -523,19 +504,6 @@ public final class JVM { public static native void uncaughtException(Thread thread, Throwable t); - /** - * Sets cutoff for event. - * - * Determines how long the event should be allowed to run. - * - * Long.MAXIMUM_VALUE = no limit - * - * @param eventTypeId the id of the event type - * @param cutoffTicks cutoff in ticks, - * @return true, if it could be set - */ - public static native boolean setCutoff(long eventTypeId, long cutoffTicks); - /** * Sets the event emission rate in event sample size per time unit. * diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java index 2d1332aed06..d5a4cc34fcf 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -265,7 +265,7 @@ private void addFields(Map lookup, Map if (!"package".equals(f.name) && !"java.lang.Class".equals(te.name)) { Utils.ensureJavaIdentifier(f.name); } - type.add(PrivateAccess.getInstance().newValueDescriptor(f.name, fieldType, aes, f.array ? 1 : 0, f.constantPool, null)); + type.add(PrivateAccess.getInstance().newValueDescriptor(f.name, fieldType, aes, f.array ? 1 : 0, f.constantPool)); } } } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java index dd0a68e0ee0..40712dee67b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java @@ -242,7 +242,7 @@ private void defineTypes(Element metadata) { boolean constantPool = fieldElement.attribute(ATTRIBUTE_CONSTANT_POOL) != null; // Add annotation later, because they may refer to undefined // types at this stage - t.add(PrivateAccess.getInstance().newValueDescriptor(name, fieldType, new ArrayList<>(), (int) dimension, constantPool, null)); + t.add(PrivateAccess.getInstance().newValueDescriptor(name, fieldType, new ArrayList<>(), (int) dimension, constantPool)); } t.trimFields(); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java index 22d55e6e99f..4047bb6c898 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -28,8 +28,6 @@ import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java index 8d13026d389..f48a3836e73 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java @@ -333,7 +333,6 @@ synchronized void stop(PlatformRecording recording) { } } OldObjectSample.emit(recording); - recording.setFinalStartnanos(JVMSupport.getChunkStartNanos()); if (endPhysical) { PeriodicEvents.doChunkEnd(); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java index ee30e2ceca4..8fbb52ef751 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java @@ -53,7 +53,6 @@ import java.util.TimerTask; import java.util.TreeMap; -import jdk.jfr.Configuration; import jdk.jfr.FlightRecorderListener; import jdk.jfr.Recording; import jdk.jfr.RecordingState; @@ -91,7 +90,6 @@ public final class PlatformRecording implements AutoCloseable { private TimerTask startTask; private boolean shouldWriteActiveRecordingEvent = true; private Duration flushInterval = Duration.ofSeconds(1); - private long finalStartChunkNanos = Long.MIN_VALUE; private long startNanos = -1; PlatformRecording(PlatformRecorder recorder, long id) { @@ -551,10 +549,6 @@ public String toString() { return getName() + " (id=" + getId() + ") " + getState(); } - public void setConfiguration(Configuration c) { - setSettings(c.getSettings()); - } - public void setMaxAge(Duration maxAge) { synchronized (recorder) { if (getState() == RecordingState.CLOSED) { @@ -884,14 +878,6 @@ public long getStartNanos() { return startNanos; } - public long getFinalChunkStartNanos() { - return finalStartChunkNanos; - } - - public void setFinalStartnanos(long chunkStartNanos) { - this.finalStartChunkNanos = chunkStartNanos; - } - public void removeBefore(Instant timestamp) { synchronized (recorder) { while (!chunks.isEmpty()) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java index b30e50ff4f3..b5c31c8d31f 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java @@ -74,7 +74,7 @@ public static void setPrivateAccess(PrivateAccess pa) { public abstract AnnotationElement newAnnotation(Type annotationType, List values, boolean boot); - public abstract ValueDescriptor newValueDescriptor(String name, Type fieldType, List annotations, int dimension, boolean constantPool, String fieldName); + public abstract ValueDescriptor newValueDescriptor(String name, Type fieldType, List annotations, int dimension, boolean constantPool); public abstract PlatformRecording getPlatformRecording(Recording r); @@ -82,8 +82,6 @@ public static void setPrivateAccess(PrivateAccess pa) { public abstract boolean isConstantPool(ValueDescriptor v); - public abstract String getFieldName(ValueDescriptor v); - public abstract ValueDescriptor newValueDescriptor(Class type, String name); public abstract SettingDescriptor newSettingDescriptor(Type type, String name, String def, List aes); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java index 80edba97e59..d3fdc745331 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Type.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -214,10 +214,6 @@ private boolean calculateSimpleType() { return superType == null; } - public boolean isDefinedByJVM() { - return id < JVM.RESERVED_CLASS_ID_LIMIT; - } - public void setFields(List fields) { this.fields = List.copyOf(fields); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java index aabd9a094bc..42d12d098f2 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java @@ -31,7 +31,6 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -77,26 +76,23 @@ private TypeLibrary() { private static ValueDescriptor createStartTimeField() { var annos = createStandardAnnotations("Start Time", null); annos.add(new jdk.jfr.AnnotationElement(Timestamp.class, Timestamp.TICKS)); - return PrivateAccess.getInstance().newValueDescriptor(ImplicitFields.START_TIME, Type.LONG, annos, 0, false, - ImplicitFields.START_TIME); + return PrivateAccess.getInstance().newValueDescriptor(ImplicitFields.START_TIME, Type.LONG, annos, 0, false); } private static ValueDescriptor createStackTraceField() { var annos = createStandardAnnotations("Stack Trace", "Stack Trace starting from the method the event was committed in"); - return PrivateAccess.getInstance().newValueDescriptor(ImplicitFields.STACK_TRACE, Type.STACK_TRACE, annos, 0, true, - ImplicitFields.STACK_TRACE); + return PrivateAccess.getInstance().newValueDescriptor(ImplicitFields.STACK_TRACE, Type.STACK_TRACE, annos, 0, true); } private static ValueDescriptor createThreadField() { var annos = createStandardAnnotations("Event Thread", "Thread in which event was committed in"); - return PrivateAccess.getInstance().newValueDescriptor(ImplicitFields.EVENT_THREAD, Type.THREAD, annos, 0, true, - ImplicitFields.EVENT_THREAD); + return PrivateAccess.getInstance().newValueDescriptor(ImplicitFields.EVENT_THREAD, Type.THREAD, annos, 0, true); } private static ValueDescriptor createDurationField() { var annos = createStandardAnnotations("Duration", null); annos.add(new jdk.jfr.AnnotationElement(Timespan.class, Timespan.TICKS)); - return PrivateAccess.getInstance().newValueDescriptor(ImplicitFields.DURATION, Type.LONG, annos, 0, false, ImplicitFields.DURATION); + return PrivateAccess.getInstance().newValueDescriptor(ImplicitFields.DURATION, Type.LONG, annos, 0, false); } public static synchronized void initialize() { @@ -378,9 +374,8 @@ private static ValueDescriptor createField(Field field) { Class fieldType = field.getType(); boolean constantPool = Thread.class == fieldType || fieldType == Class.class; Type type = createType(fieldType); - String fieldName = field.getName(); Name name = field.getAnnotation(Name.class); - String useName = fieldName; + String useName = field.getName(); if (name != null) { useName = Utils.validJavaIdentifier(name.value(), useName); } @@ -391,7 +386,7 @@ private static ValueDescriptor createField(Field field) { ans.add(ae); } } - return PrivateAccess.getInstance().newValueDescriptor(useName, type, ans, 0, constantPool, fieldName); + return PrivateAccess.getInstance().newValueDescriptor(useName, type, ans, 0, constantPool); } private static List resolveRepeatedAnnotations(Annotation[] annotations) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java index 57dacb95503..ce611904402 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -104,10 +104,6 @@ public void setEnabled(boolean enabled) { this.enabled = enabled; } - public boolean isEnabled() { - return enabled; - } - @Override public RecordedEvent parse(RecordingInput input) throws IOException { if (!enabled) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/OngoingStream.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/OngoingStream.java index a60117fe316..3cac3105b07 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/OngoingStream.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/OngoingStream.java @@ -31,7 +31,6 @@ import jdk.jfr.Recording; import jdk.jfr.RecordingState; -import jdk.jfr.internal.SecuritySupport; import jdk.jfr.internal.management.EventByteStream; import jdk.jfr.internal.management.HiddenWait; import jdk.jfr.internal.management.ManagementSupport; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java index c973dadb3b7..134afc03d53 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -90,7 +90,7 @@ private Parser createParser(ValueDescriptor v, boolean event) throws IOException boolean constantPool = PrivateAccess.getInstance().isConstantPool(v); if (v.isArray()) { Type valueType = PrivateAccess.getInstance().getType(v); - ValueDescriptor element = PrivateAccess.getInstance().newValueDescriptor(v.getName(), valueType, v.getAnnotationElements(), 0, constantPool, null); + ValueDescriptor element = PrivateAccess.getInstance().newValueDescriptor(v.getName(), valueType, v.getAnnotationElements(), 0, constantPool); return new ArrayParser(createParser(element, event)); } long id = v.getTypeId(); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java index 701bc0396c6..edeccb77a11 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -253,14 +253,6 @@ private Recording findRecordingByName(String name) throws DCmdException { throw new DCmdException("Could not find %s.\n\nUse JFR.check without options to see list of all available recordings.", name); } - protected final String exampleRepository() { - if ("\r\n".equals(System.lineSeparator())) { - return "C:\\Repositories"; - } else { - return "/Repositories"; - } - } - protected final String exampleFilename() { if ("\r\n".equals(System.lineSeparator())) { return "C:\\Users\\user\\recording.jfr"; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java index 8dbac99067a..f35e297c39d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/event/EventWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -224,10 +224,6 @@ private int usedSize() { return (int) (currentPosition - startPosition); } - private void flush() { - flush(usedSize(), 0); - } - private void flush(int usedSize, int requestedSize) { JVM.flush(this, usedSize, requestedSize); } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java index e0b5fb4a6f3..78d915f509e 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/jfc/model/JFCModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -140,10 +140,6 @@ public List getInputs() { return inputs; } - public XmlConfiguration getConfiguration() { - return configuration; - } - public LinkedHashMap getSettings() { LinkedHashMap result = new LinkedHashMap<>(); for (XmlEvent event : configuration.getEvents()) { diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/Query.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/Query.java index de316aad354..d12d536892b 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/Query.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/Query.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 @@ -41,9 +41,6 @@ record Source(String name, Optional alias) { record Condition(String field, String value) { } - record Where(List conditions) { - } - record Property(String name, Consumer style) { } diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/TableCell.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/TableCell.java index 7928eae2f4c..62b10075e45 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/TableCell.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/TableCell.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 @@ -68,6 +68,7 @@ public void setPreferredWidth(int width) { public int getPreferredWidth() { return preferredWidth; } + public void addLine(String text) { int contentWidth = getContentWidth(); if (text.length() > contentWidth) { @@ -113,10 +114,6 @@ public int getContentSize() { return cellHeight * getContentWidth(); } - public List getLines() { - return lines; - } - private String truncate(String text, int size) { if (size < ELLIPSIS_LENGTH) { return ELLIPSIS.substring(0, ELLIPSIS_LENGTH - size); diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java index e5093de781b..814fb420bae 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/Throttler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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,7 +26,7 @@ import java.util.Random; import java.util.concurrent.locks.ReentrantLock; -import jdk.jfr.internal.PlatformEventType; + public final class Throttler { private static final ThrottlerParameters DISABLED_PARAMETERS = new ThrottlerParameters(0, 0, 0); private static final long MILLIUNITS = 1000; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java index 36eff5b4106..c7299eb57e4 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Assemble.java @@ -28,14 +28,12 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.List; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java index 44aecf6413c..c53c155d864 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/PrettyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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 @@ -27,7 +27,6 @@ import java.io.IOException; import java.io.PrintWriter; -import java.lang.reflect.Array; import java.nio.file.Path; import java.time.Duration; import java.time.Instant; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java deleted file mode 100644 index a21ae95f671..00000000000 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserDataException.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2018, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 jdk.jfr.internal.tool; - -/** - * Exception that is thrown if there is something wrong with the input, for instance - * a file that can't be read or a numerical value that is out of range. - *

- * When this exception is thrown, a user will typically not want to see the - * command line syntax, but instead information about what was wrong with the - * input. - */ -final class UserDataException extends Exception { - private static final long serialVersionUID = 6656457380115167810L; - /** - * The error message. - * - * The first letter should not be capitalized, so a context can be printed prior - * to the error message. - * - * @param errorMessage - */ - public UserDataException(String errorMessage) { - super(errorMessage); - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java deleted file mode 100644 index 0606bb37b58..00000000000 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/UserSyntaxException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2018, 2020, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 jdk.jfr.internal.tool; - -/** - * Exception that is thrown if options don't follow the syntax of the command. - */ -final class UserSyntaxException extends Exception { - private static final long serialVersionUID = 3437009454344160933L; - - /** - * The error message. - * - * The first letter should not be capitalized, so a context can be printed prior - * to the error message. - * - * @param errorMessage - */ - public UserSyntaxException(String errorMessage) { - super(errorMessage); - } -} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java index db832626ded..c770610bff0 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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,9 +26,6 @@ package jdk.jfr.internal.util; import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; import java.lang.annotation.Annotation; import java.lang.annotation.Repeatable; import java.lang.reflect.Array; @@ -135,11 +132,6 @@ private static Method getValueMethod(Class annotated) { } } - public static void touch(Path dumpFile) throws IOException { - RandomAccessFile raf = new RandomAccessFile(dumpFile.toFile(), "rw"); - raf.close(); - } - public static Class unboxType(Class t) { if (t == Integer.class) { return int.class; diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/ValueParser.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/ValueParser.java index cf112d3beb5..d004447f49d 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/util/ValueParser.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/ValueParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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,13 +24,6 @@ */ package jdk.jfr.internal.util; -import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.HOURS; -import static java.util.concurrent.TimeUnit.MICROSECONDS; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.SECONDS; - public final class ValueParser { public static final String INFINITY = "infinity"; public static final long MISSING = Long.MIN_VALUE; diff --git a/src/jdk.management.jfr/share/classes/jdk/management/jfr/DiskRepository.java b/src/jdk.management.jfr/share/classes/jdk/management/jfr/DiskRepository.java index 1ed2a2a05ae..898e17a0463 100644 --- a/src/jdk.management.jfr/share/classes/jdk/management/jfr/DiskRepository.java +++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/DiskRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -28,24 +28,18 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; import java.util.Deque; import java.util.Iterator; -import java.util.List; import java.util.Objects; -import java.util.Queue; import jdk.jfr.internal.management.ChunkFilename; import jdk.jfr.internal.management.ManagementSupport; @@ -92,10 +86,6 @@ private void destroy() { } } - public boolean isDead() { - return referenceCount == 0; - } - public Path path() { return path; } diff --git a/src/jdk.management.jfr/share/classes/jdk/management/jfr/FlightRecorderMXBeanImpl.java b/src/jdk.management.jfr/share/classes/jdk/management/jfr/FlightRecorderMXBeanImpl.java index 467ff05e513..019e176bd87 100644 --- a/src/jdk.management.jfr/share/classes/jdk/management/jfr/FlightRecorderMXBeanImpl.java +++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/FlightRecorderMXBeanImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 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,7 +61,6 @@ import javax.management.StandardEmitterMBean; import jdk.jfr.Configuration; -import jdk.jfr.EventType; import jdk.jfr.FlightRecorder; import jdk.jfr.FlightRecorderListener; import jdk.jfr.Recording; diff --git a/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java b/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java index a8752e159ad..8ef8ef4a4ec 100644 --- a/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java +++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/RemoteRecordingStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -27,11 +27,8 @@ import java.io.IOException; import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -40,7 +37,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.Future; import java.util.function.Consumer; import javax.management.JMX; import javax.management.MBeanServerConnection; @@ -50,15 +46,12 @@ import jdk.jfr.EventSettings; import jdk.jfr.EventType; import jdk.jfr.Recording; -import jdk.jfr.RecordingState; import jdk.jfr.consumer.EventStream; import jdk.jfr.consumer.MetadataEvent; import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordingStream; import jdk.jfr.internal.management.EventSettingsModifier; import jdk.jfr.internal.management.ManagementSupport; import jdk.jfr.internal.management.StreamBarrier; -import jdk.management.jfr.DiskRepository.DiskChunk; import jdk.jfr.internal.management.EventByteStream; /** From 0d1b5e73ec2c2cd0f9991aa7ed2dffb92209c600 Mon Sep 17 00:00:00 2001 From: Rajan Halade Date: Mon, 12 Oct 2026 16:31:52 +0000 Subject: [PATCH 049/101] 8392771: Add 2 Certum roots Reviewed-by: mullan --- .../share/data/cacerts/certumec384ca | 22 ++++++++++ .../share/data/cacerts/certumtrustedrootca | 40 +++++++++++++++++++ .../certification/CAInterop.java | 35 ++++++++++++++++ .../security/lib/cacerts/VerifyCACerts.java | 10 +++-- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/java.base/share/data/cacerts/certumec384ca create mode 100644 src/java.base/share/data/cacerts/certumtrustedrootca diff --git a/src/java.base/share/data/cacerts/certumec384ca b/src/java.base/share/data/cacerts/certumec384ca new file mode 100644 index 00000000000..47a276e915a --- /dev/null +++ b/src/java.base/share/data/cacerts/certumec384ca @@ -0,0 +1,22 @@ +Owner: CN=Certum EC-384 CA, OU=Certum Certification Authority, O=Asseco Data Systems S.A., C=PL +Issuer: CN=Certum EC-384 CA, OU=Certum Certification Authority, O=Asseco Data Systems S.A., C=PL +Serial number: 788f275c81125220a504d02dddba73f4 +Valid from: Mon Mar 26 07:24:54 GMT 2018 until: Thu Mar 26 07:24:54 GMT 2043 +Signature algorithm name: SHA384withECDSA +Subject Public Key Algorithm: 384-bit EC (secp384r1) key +Version: 3 +-----BEGIN CERTIFICATE----- +MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw +CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw +JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT +EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 +WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT +LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX +BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE +KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm +Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 +EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J +UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn +nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= +-----END CERTIFICATE----- diff --git a/src/java.base/share/data/cacerts/certumtrustedrootca b/src/java.base/share/data/cacerts/certumtrustedrootca new file mode 100644 index 00000000000..fb1dc35f62c --- /dev/null +++ b/src/java.base/share/data/cacerts/certumtrustedrootca @@ -0,0 +1,40 @@ +Owner: CN=Certum Trusted Root CA, OU=Certum Certification Authority, O=Asseco Data Systems S.A., C=PL +Issuer: CN=Certum Trusted Root CA, OU=Certum Certification Authority, O=Asseco Data Systems S.A., C=PL +Serial number: 1ebf5950b8c980374c06f7eb554fb5ed +Valid from: Fri Mar 16 12:10:13 GMT 2018 until: Mon Mar 16 12:10:13 GMT 2043 +Signature algorithm name: SHA512withRSA +Subject Public Key Algorithm: 4096-bit RSA key +Version: 3 +-----BEGIN CERTIFICATE----- +MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 +MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu +MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV +BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw +MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg +U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo +b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ +n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q +p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq +NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF +8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 +HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa +mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi +7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF +ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P +qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ +v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 +Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 +vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD +ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 +WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo +zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR +5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ +GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf +5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq +0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D +P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM +qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP +0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf +E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb +-----END CERTIFICATE----- diff --git a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java index 32b8661379f..65a534f1286 100644 --- a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java +++ b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java @@ -559,6 +559,34 @@ * wisekeyglobalrootgcca CRL */ +/* + * @test id=certumtrustedrootca + * @bug 8392771 + * @summary Interoperability tests with Certum Trusted Root CA + * @library /test/lib + * @build jtreg.SkippedException ValidatePathWithURL CAInterop + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp CAInterop + * certumtrustedrootca OCSP + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp + * -Dcom.sun.security.ocsp.useget=false CAInterop certumtrustedrootca OCSP + * @run main/othervm/manual -Djava.security.debug=certpath CAInterop + * certumtrustedrootca CRL + */ + +/* + * @test id=certumec384ca + * @bug 8392771 + * @summary Interoperability tests with Certum EC-384 CA + * @library /test/lib + * @build jtreg.SkippedException ValidatePathWithURL CAInterop + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp CAInterop + * certumec384ca OCSP + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp + * -Dcom.sun.security.ocsp.useget=false CAInterop certumec384ca OCSP + * @run main/othervm/manual -Djava.security.debug=certpath CAInterop + * certumec384ca CRL + */ + /** * Collection of certificate validation tests for interoperability with external CAs. * These tests are marked as manual as they depend on external infrastructure and may fail @@ -741,6 +769,13 @@ private CATestURLs getTestURLs(String alias) { new CATestURLs("https://gcvalidssl.hightrusted.com", "https://gcrevokedssl.hightrusted.com"); + case "certumtrustedrootca" -> + new CATestURLs("https://valid-ctrca.certificates.certum.pl", + "https://revoked-ctrca.certificates.certum.pl"); + case "certumec384ca" -> + new CATestURLs("https://valid-cec384ca.certificates.certum.pl", + "https://revoked-cec384ca.certificates.certum.pl"); + default -> throw new RuntimeException("No test setup found for: " + alias); }; } diff --git a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java index db6d9a66f88..611f21ae1cb 100644 --- a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java +++ b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java @@ -29,7 +29,7 @@ * 8243559 8225072 8258630 8259312 8256421 8225081 8225082 8225083 8245654 * 8305975 8304760 8307134 8295894 8314960 8317373 8317374 8318759 8319187 * 8321408 8316138 8341057 8303770 8350498 8359170 8361212 8372351 8387123 - * 8386371 + * 8386371 8392771 * @summary Check root CA entries in cacerts file */ import java.io.ByteArrayInputStream; @@ -48,12 +48,12 @@ public class VerifyCACerts { + File.separator + "security" + File.separator + "cacerts"; // The numbers of certs now. - private static final int COUNT = 109; + private static final int COUNT = 111; // SHA-256 of cacerts, can be generated with // shasum -a 256 cacerts | sed -e 's/../&:/g' | tr '[:lower:]' '[:upper:]' | cut -c1-95 private static final String CHECKSUM - = "BB:0F:A9:B7:21:8E:8E:66:95:2B:1A:85:2F:6A:EC:F0:81:69:09:A9:56:70:F3:E4:A8:B9:AD:0D:BE:43:69:11"; + = "5C:83:D7:C3:1A:F2:FE:79:22:93:F3:CA:CB:01:B6:79:F0:A3:1C:EF:7D:F8:FC:54:61:FE:AE:3B:11:32:D8:CB"; // Hex formatter to upper case with ":" delimiter private static final HexFormat HEX = HexFormat.ofDelimiter(":").withUpperCase(); @@ -280,6 +280,10 @@ public class VerifyCACerts { "6B:9C:08:E8:6E:B0:F7:67:CF:AD:65:CD:98:B6:21:49:E5:49:4A:67:F5:84:5E:7B:D1:ED:01:9F:27:B8:6B:D6"); put("wisekeyglobalrootgcca [jdk]", "85:60:F9:1C:36:24:DA:BA:95:70:B5:FE:A0:DB:E3:6F:F1:1A:83:23:BE:94:86:85:4F:B3:F3:4A:55:71:19:8D"); + put("certumtrustedrootca [jdk]", + "FE:76:96:57:38:55:77:3E:37:A9:5E:7A:D4:D9:CC:96:C3:01:57:C1:5D:31:76:5B:A9:B1:57:04:E1:AE:78:FD"); + put("certumec384ca [jdk]", + "6B:32:80:85:62:53:18:AA:50:D1:73:C9:8D:8B:DA:09:D5:7E:27:41:3D:11:4C:F7:87:A0:F5:D0:6C:03:0C:F6"); } }; From b74cc9bb27febc8e566614e34f650b19069e2e15 Mon Sep 17 00:00:00 2001 From: Stuart Marks Date: Mon, 12 Oct 2026 18:23:54 +0000 Subject: [PATCH 050/101] 8394084: Minor enhancements to ObjectStreamPrinter Reviewed-by: bchristi --- .../test/lib/hexdump/ObjectStreamPrinter.java | 82 ++++++++++++++++--- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/test/lib/jdk/test/lib/hexdump/ObjectStreamPrinter.java b/test/lib/jdk/test/lib/hexdump/ObjectStreamPrinter.java index 110840256d3..856319a745c 100644 --- a/test/lib/jdk/test/lib/hexdump/ObjectStreamPrinter.java +++ b/test/lib/jdk/test/lib/hexdump/ObjectStreamPrinter.java @@ -28,6 +28,8 @@ import java.io.InputStream; import java.io.IOException; import java.io.ObjectStreamConstants; +import java.io.OutputStream; +import java.io.PrintStream; import java.io.PrintWriter; import java.io.UTFDataFormatException; import java.nio.file.Files; @@ -876,25 +878,81 @@ public String toString() { return sb.toString(); } } + + static HexPrinter getHexPrinter(OutputStream os) { + return HexPrinter.simple() + .dest(os instanceof PrintStream ps ? ps : new PrintStream(os)) + .formatter(ObjectStreamPrinter.formatter(), "; ", 100); + } + + // ===== programmatic entry points ===== + /** - * Simple utility to open and print contents of one or more files as a serialized object stream. + * Decodes and prints serialized data read from {@code i} and sends decoded output + * to {@code o}. + * + * @param i the InputStream to decode + * @param o the output destination + */ + public static void print(InputStream i, OutputStream o) { + getHexPrinter(o).format(i); + } + + /** + * Decodes and prints serialized data read from {@code i} and sends decoded output + * to System.out. + * + * @param i the InputStream to decode + */ + public static void print(InputStream i) { + print(i, System.out); + } + + /** + * Decodes and prints serialized data from {@code ba} and sends decoded output + * to {@code o}. + * + * @param ba byte array + * @param o the output destination + */ + public static void print(byte[] ba, OutputStream o) { + getHexPrinter(o).format(ba); + } + + /** + * Decodes and prints serialized data from {@code ba} and sends decoded output + * to System.out. + * + * @param ba byte array + */ + public static void print(byte[] ba) { + print(ba, System.out); + } + + /** + * Command line utility to open and print contents of one or more files as a + * serialized object stream. + * * @param args file names */ public static void main(String[] args) { if (args.length < 1) { - System.out.println("Usage: "); - return; + System.out.println("Usage: object-stream-file ..."); + System.out.println(" use - for stdin"); + System.exit(1); } - ObjectStreamPrinter fmt = ObjectStreamPrinter.formatter(); + for (String file : args) { - System.out.printf("%s%n", file); - try (InputStream is = Files.newInputStream(Path.of(file))) { - - DataInputStream dis = new DataInputStream(is); - HexPrinter p = HexPrinter.simple() - .dest(System.out) - .formatter(ObjectStreamPrinter.formatter(), "; ", 100); - p.format(dis); + boolean useStdin = "-".equals(file); + + if (args.length > 1) { + System.out.printf("%n>>> %s%n%n", useStdin ? "(stdin)" : file); + } + + // NOTE: if stdin is used, this closes stdin after processing. Maybe this + // is ok since this is main() and we exit afterward. + try (InputStream is = useStdin ? System.in : Files.newInputStream(Path.of(file))) { + getHexPrinter(System.out).format(is); } catch (EOFException eof) { System.out.println(); } catch (IOException ioe) { From 18ba1e4af562fdd09f3848669b241bd0471fdb8a Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 12 Oct 2026 22:03:54 +0000 Subject: [PATCH 051/101] 8393473: Remove unused medialib ConvolveOp code for float/double data Reviewed-by: serb, azvegint --- .../native/libmlib_image/mlib_ImageConv.h | 18 - .../mlib_ImageConvClearEdge_Fp.c | 149 ----- .../native/libmlib_image/mlib_ImageConvEdge.h | 8 - .../native/libmlib_image/mlib_ImageConvMxN.c | 13 +- .../libmlib_image/mlib_ImageConvMxN_Fp.c | 103 ---- .../libmlib_image/mlib_ImageConv_D64nw.c | 582 ------------------ .../libmlib_image/mlib_ImageConv_F32nw.c | 579 ----------------- .../libmlib_image/mlib_c_ImageConvClearEdge.c | 2 +- .../native/libmlib_image/mlib_image_proto.h | 29 - 9 files changed, 2 insertions(+), 1481 deletions(-) delete mode 100644 src/java.desktop/share/native/libmlib_image/mlib_ImageConvClearEdge_Fp.c delete mode 100644 src/java.desktop/share/native/libmlib_image/mlib_ImageConvMxN_Fp.c delete mode 100644 src/java.desktop/share/native/libmlib_image/mlib_ImageConv_D64nw.c delete mode 100644 src/java.desktop/share/native/libmlib_image/mlib_ImageConv_F32nw.c diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageConv.h b/src/java.desktop/share/native/libmlib_image/mlib_ImageConv.h index b3e7a2b3d5e..cd87e8f0614 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageConv.h +++ b/src/java.desktop/share/native/libmlib_image/mlib_ImageConv.h @@ -39,24 +39,6 @@ if (k != akernel) mlib_free(k); \ return status #endif /* FREE_AND_RETURN_STATUS */ -mlib_status mlib_convMxNnw_d64(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *ker, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask); - -mlib_status mlib_convMxNnw_f32(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *ker, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask); - mlib_status mlib_convMxNnw_s16(mlib_image *dst, const mlib_image *src, const mlib_s32 *kernel, diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageConvClearEdge_Fp.c b/src/java.desktop/share/native/libmlib_image/mlib_ImageConvClearEdge_Fp.c deleted file mode 100644 index e26ed8389ce..00000000000 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageConvClearEdge_Fp.c +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2003, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - - -/* - * FUNCTIONS - * mlib_c_ImageConvClearEdge - Set edge of an image to a specific - * color. (for float-point image) - * - * SYNOPSIS - * mlib_status mlib_c_ImageConvClearEdge_Fp(mlib_image *img, - * mlib_s32 dx_l, - * mlib_s32 dx_r, - * mlib_s32 dy_t, - * mlib_s32 dy_b, - * const mlib_d64 *color, - * mlib_s32 cmask) - * - * ARGUMENT - * img Pointer to an image. - * dx_l Number of columns on the left side of the - * image to be cleared. - * dx_r Number of columns on the right side of the - * image to be cleared. - * dy_t Number of rows on the top edge of the - * image to be cleared. - * dy_b Number of rows on the top edge of the - * image to be cleared. - * color Pointer to the color that the edges are set to. - * cmask Channel mask to indicate the channels to be convolved. - * Each bit of which represents a channel in the image. The - * channels corresponded to 1 bits are those to be processed. - * - * RESTRICTION - * img can have 1, 2, 3 or 4 channels of MLIB_FLOAT or MLIB_DOUBLE - * data type. - * - * DESCRIPTION - * Set edge of an image to a specific color. - * The unselected channels are not overwritten. - * If src and dst have just one channel, - * cmask is ignored. - */ - -#include "mlib_image.h" -#include "mlib_ImageConvEdge.h" - -/***************************************************************/ -#define EDGES(chan, type, mask) \ -{ \ - type *pimg = (type *) mlib_ImageGetData(img); \ - type color_i; \ - mlib_s32 img_stride = mlib_ImageGetStride(img) / sizeof(type); \ - mlib_s32 i, j, l; \ - mlib_s32 testchan; \ - \ - testchan = 1; \ - for (l = chan - 1; l >= 0; l--) { \ - if ((mask & testchan) == 0) { \ - testchan <<= 1; \ - continue; \ - } \ - testchan <<= 1; \ - color_i = (type) color[l]; \ - for (j = 0; j < dx_l; j++) { \ - for (i = dy_t; i < (img_height - dy_b); i++) { \ - pimg[i * img_stride + l + j * chan] = color_i; \ - } \ - } \ - for (j = 0; j < dx_r; j++) { \ - for (i = dy_t; i < (img_height - dy_b); i++) { \ - pimg[i * img_stride + l + (img_width - 1 - j) * chan] = color_i; \ - } \ - } \ - for (i = 0; i < dy_t; i++) { \ - for (j = 0; j < img_width; j++) { \ - pimg[i * img_stride + l + j * chan] = color_i; \ - } \ - } \ - for (i = 0; i < dy_b; i++) { \ - for (j = 0; j < img_width; j++) { \ - pimg[(img_height - 1 - i) * img_stride + l + j * chan] = color_i; \ - } \ - } \ - } \ -} - -/***************************************************************/ -mlib_status mlib_ImageConvClearEdge_Fp(mlib_image *img, - mlib_s32 dx_l, - mlib_s32 dx_r, - mlib_s32 dy_t, - mlib_s32 dy_b, - const mlib_d64 *color, - mlib_s32 cmask) -{ - mlib_s32 img_width = mlib_ImageGetWidth(img); - mlib_s32 img_height = mlib_ImageGetHeight(img); - mlib_s32 channel = mlib_ImageGetChannels(img); - - if (dx_l + dx_r > img_width) { - dx_l = img_width; - dx_r = 0; - } - - if (dy_t + dy_b > img_height) { - dy_t = img_height; - dy_b = 0; - } - - if (channel == 1) cmask = 1; - - switch (mlib_ImageGetType(img)) { - case MLIB_FLOAT: - EDGES(channel,mlib_f32, cmask); - break; - case MLIB_DOUBLE: - EDGES(channel,mlib_d64, cmask); - break; - default: - return MLIB_FAILURE; - } - - return MLIB_SUCCESS; -} - -/***************************************************************/ diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageConvEdge.h b/src/java.desktop/share/native/libmlib_image/mlib_ImageConvEdge.h index 7aaabfcc20b..0bd8ecfd240 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageConvEdge.h +++ b/src/java.desktop/share/native/libmlib_image/mlib_ImageConvEdge.h @@ -47,14 +47,6 @@ mlib_status mlib_ImageConvClearEdge(mlib_image *dst, const mlib_s32 *color, mlib_s32 cmask); -mlib_status mlib_ImageConvClearEdge_Fp(mlib_image *img, - mlib_s32 dx_l, - mlib_s32 dx_r, - mlib_s32 dy_t, - mlib_s32 dy_b, - const mlib_d64 *color, - mlib_s32 cmask); - mlib_status mlib_ImageConvZeroEdge(mlib_image *dst, mlib_s32 dx_l, mlib_s32 dx_r, diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageConvMxN.c b/src/java.desktop/share/native/libmlib_image/mlib_ImageConvMxN.c index 7723b3c6191..8554d78e5e6 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageConvMxN.c +++ b/src/java.desktop/share/native/libmlib_image/mlib_ImageConvMxN.c @@ -196,19 +196,8 @@ mlib_status mlib_ImageConvMxN_f(mlib_image *dst, case MLIB_INT: ret = mlib_convMxNnw_s32(dst_i, src_i, kernel, m, n, dm, dn, scale, cmask); break; - case MLIB_FLOAT: - ret = mlib_convMxNnw_f32(dst_i, src_i, kernel, m, n, dm, dn, cmask); - break; - case MLIB_DOUBLE: - ret = mlib_convMxNnw_d64(dst_i, src_i, kernel, m, n, dm, dn, cmask); - break; - default: - /* For some reasons, there is no convolution routine for type MLIB_BIT. - * For now, we silently ignore it (because this image type is not used by java), - * but probably we have to report an error. - */ - break; + return MLIB_FAILURE; } } diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageConvMxN_Fp.c b/src/java.desktop/share/native/libmlib_image/mlib_ImageConvMxN_Fp.c deleted file mode 100644 index 11d7e9d3387..00000000000 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageConvMxN_Fp.c +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2003, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - - -/* - * FUNCTION - * mlib_ImageConvMxN_Fp - image convolution with edge condition - * - * SYNOPSIS - * mlib_status mlib_ImageConvMxN_Fp(mlib_image *dst, - * const mlib_image *src, - * const mlib_d64 *kernel, - * mlib_s32 m, - * mlib_s32 n, - * mlib_s32 dm, - * mlib_s32 dn, - * mlib_s32 cmask, - * mlib_edge edge) - * - * ARGUMENTS - * dst Pointer to destination image. - * src Pointer to source image. - * m Kernel width (m must be not less than 1). - * n Kernel height (n must be not less than 1). - * dm, dn Position of key element in convolution kernel. - * kernel Pointer to convolution kernel. - * cmask Channel mask to indicate the channels to be convolved. - * Each bit of which represents a channel in the image. The - * channels corresponded to 1 bits are those to be processed. - * edge Type of edge condition. - * - * DESCRIPTION - * 2-D convolution, MxN kernel. - * - * The center of the source image is mapped to the center of the - * destination image. - * The unselected channels are not overwritten. If both src and dst have - * just one channel, cmask is ignored. - * - * The edge condition can be one of the following: - * MLIB_EDGE_DST_NO_WRITE (default) - * MLIB_EDGE_DST_FILL_ZERO - * MLIB_EDGE_DST_COPY_SRC - * MLIB_EDGE_SRC_EXTEND - * - * RESTRICTION - * The src and the dst must be the same type and have same number - * of channels (1, 2, 3, or 4). - * m >= 1, n >= 1, - * 0 <= dm < m, 0 <= dn < n. - */ - -#include "mlib_image.h" -#include "mlib_ImageCheck.h" -#include "mlib_SysMath.h" -#include "mlib_ImageConv.h" -#include "safe_math.h" - -/***************************************************************/ -mlib_status mlib_ImageConvMxN_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge) -{ - mlib_type type; - - MLIB_IMAGE_CHECK(dst); - type = mlib_ImageGetType(dst); - - if (type != MLIB_FLOAT && type != MLIB_DOUBLE) - return MLIB_FAILURE; - - return mlib_ImageConvMxN_f(dst, src, kernel, m, n, dm, dn, 0, cmask, edge); -} - -/***************************************************************/ diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageConv_D64nw.c b/src/java.desktop/share/native/libmlib_image/mlib_ImageConv_D64nw.c deleted file mode 100644 index 389c1b52c1e..00000000000 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageConv_D64nw.c +++ /dev/null @@ -1,582 +0,0 @@ -/* - * 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - - -/* - * FUNCTION - * Internal functions for mlib_ImageConv* on D64/F32 type and - * MLIB_EDGE_DST_NO_WRITE mask - * - */ - -#include "mlib_image.h" -#include "mlib_ImageConv.h" - -/***************************************************************/ -/* - This define switches between functions of MLIB_DOUBLE and MLIB_FLOAT types: - Files mlib_ImageConv_D64nw.c and mlib_ImageConv_F32nw.c -*/ - -#define TYPE_DOUBLE - -/***************************************************************/ -#ifdef TYPE_DOUBLE - -#define CONV_FUNC(KERN) mlib_conv##KERN##nw_d64 - -#define DTYPE mlib_d64 - -#else - -#define CONV_FUNC(KERN) mlib_conv##KERN##nw_f32 - -#define DTYPE mlib_f32 - -#endif /* TYPE_DOUBLE */ - -/***************************************************************/ -#define GET_SRC_DST_PARAMETERS(type) \ - mlib_s32 hgt = mlib_ImageGetHeight(src); \ - mlib_s32 wid = mlib_ImageGetWidth(src); \ - mlib_s32 sll = mlib_ImageGetStride(src) / sizeof(type); \ - mlib_s32 dll = mlib_ImageGetStride(dst) / sizeof(type); \ - type* adr_src = mlib_ImageGetData(src); \ - type* adr_dst = mlib_ImageGetData(dst); \ - mlib_s32 chan1 = mlib_ImageGetChannels(src) - -/***************************************************************/ -#define DEF_VARS(type) \ - GET_SRC_DST_PARAMETERS(type); \ - type *sl; \ - type *dl, *dp = NULL; \ - mlib_s32 i = 0, j, c - -/***************************************************************/ -#define BUFF_SIZE 1600 - -#define CACHE_SIZE (64*1024) - -static mlib_status mlib_ImageConv1xN(mlib_image *dst, - const mlib_image *src, - const DTYPE *k, - mlib_s32 n, - mlib_s32 dn, - mlib_s32 cmask) -{ - DTYPE buff[BUFF_SIZE], *pbuff = buff; - const DTYPE *pk; - DTYPE k0, k1, k2, k3; - DTYPE p0, p1, p2, p3, p4; - DTYPE *sp, *sl_c, *dl_c, *sl0; - DEF_VARS(DTYPE); - mlib_s32 off, kh; - mlib_s32 l, hsize, max_hsize; - - hgt -= (n - 1); - adr_dst += dn*dll; - - max_hsize = (CACHE_SIZE/sizeof(DTYPE))/sll; - - if (!max_hsize) max_hsize = 1; - - if (max_hsize > BUFF_SIZE) { - pbuff = mlib_malloc(sizeof(DTYPE)*max_hsize); - } - - sl_c = adr_src; - dl_c = adr_dst; - - for (l = 0; l < hgt; l += hsize) { - hsize = hgt - l; - - if (hsize > max_hsize) hsize = max_hsize; - - for (c = 0; c < chan1; c++) { - if (!(cmask & (1 << (chan1 - 1 - c)))) continue; - - sl = sl_c + c; - dl = dl_c + c; - - for (j = 0; j < hsize; j++) pbuff[j] = 0.0; - - for (i = 0; i < wid; i++) { - sl0 = sl; - - for (off = 0; off < (n - 4); off += 4) { - pk = k + off; - sp = sl0; - - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - p2 = sp[0]; p3 = sp[sll]; p4 = sp[2*sll]; - sp += 3*sll; - - for (j = 0; j < hsize; j += 2) { - p0 = p2; p1 = p3; p2 = p4; - p3 = sp[0]; - p4 = sp[sll]; - - pbuff[j ] += p0*k0 + p1*k1 + p2*k2 + p3*k3; - pbuff[j + 1] += p1*k0 + p2*k1 + p3*k2 + p4*k3; - - sp += 2*sll; - } - - sl0 += 4*sll; - } - - pk = k + off; - sp = sl0; - - dp = dl; - kh = n - off; - - if (kh == 4) { - - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - p2 = sp[0]; p3 = sp[sll]; p4 = sp[2*sll]; - - sp += 3*sll; - - for (j = 0; j <= (hsize - 2); j += 2) { - p0 = p2; p1 = p3; p2 = p4; - p3 = sp[0]; - p4 = sp[sll]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + pbuff[j]; - dp[dll] = p1*k0 + p2*k1 + p3*k2 + p4*k3 + pbuff[j + 1]; - - pbuff[j] = 0; - pbuff[j + 1] = 0; - - sp += 2*sll; - dp += 2*dll; - } - - if (j < hsize) { - p0 = p2; p1 = p3; p2 = p4; - p3 = sp[0]; - - dp[0] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + pbuff[j]; - - pbuff[j] = 0; - } - - } else if (kh == 3) { - - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; - p2 = sp[0]; p3 = sp[sll]; - - sp += 2*sll; - - for (j = 0; j <= (hsize - 2); j += 2) { - p0 = p2; p1 = p3; - p2 = sp[0]; - p3 = sp[sll]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + pbuff[j]; - dp[dll] = p1*k0 + p2*k1 + p3*k2 + pbuff[j + 1]; - - pbuff[j] = 0; - pbuff[j + 1] = 0; - - sp += 2*sll; - dp += 2*dll; - } - - if (j < hsize) { - p0 = p2; p1 = p3; - p2 = sp[0]; - - dp[0] = p0*k0 + p1*k1 + p2*k2 + pbuff[j]; - - pbuff[j] = 0; - } - - } else if (kh == 2) { - - k0 = pk[0]; k1 = pk[1]; - p2 = sp[0]; - - sp += sll; - - for (j = 0; j <= (hsize - 2); j += 2) { - p0 = p2; - p1 = sp[0]; - p2 = sp[sll]; - - dp[0 ] = p0*k0 + p1*k1 + pbuff[j]; - dp[dll] = p1*k0 + p2*k1 + pbuff[j + 1]; - - pbuff[j] = 0; - pbuff[j + 1] = 0; - - sp += 2*sll; - dp += 2*dll; - } - - if (j < hsize) { - p0 = p2; - p1 = sp[0]; - - dp[0] = p0*k0 + p1*k1 + pbuff[j]; - - pbuff[j] = 0; - } - - } else /* if (kh == 1) */ { - - k0 = pk[0]; - - for (j = 0; j < hsize; j++) { - p0 = sp[0]; - - dp[0] = p0*k0 + pbuff[j]; - - pbuff[j] = 0; - - sp += sll; - dp += dll; - } - } - - sl += chan1; - dl += chan1; - } - } - - sl_c += max_hsize*sll; - dl_c += max_hsize*dll; - } - - if (pbuff != buff) mlib_free(pbuff); - - return MLIB_SUCCESS; -} - -/***************************************************************/ -#define MAX_KER 7 -#define MAX_NM 81 - -mlib_status CONV_FUNC(MxN)(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *ker, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask) -{ - DTYPE k0, k1, k2, k3, k4, k5, k6, *sp; - DTYPE p0, p1, p2, p3, p4, p5, p6, p7; - mlib_s32 l, off, kw; - DEF_VARS(DTYPE); - mlib_s32 chan2 = chan1 + chan1; - mlib_s32 chan3 = chan1 + chan2; - -#ifdef TYPE_DOUBLE - const mlib_d64 *k = ker; -#else - mlib_f32 k_arr[MAX_NM], *k = k_arr; - - if (n*m > MAX_NM) { - k = mlib_malloc(n*m*sizeof(mlib_f32)); - - if (k == NULL) return MLIB_FAILURE; - } - - for (i = 0; i < n*m; i++) k[i] = (mlib_f32)ker[i]; -#endif /* TYPE_DOUBLE */ - - if (m == 1) return mlib_ImageConv1xN(dst, src, k, n, dn, cmask); - - wid -= (m - 1); - hgt -= (n - 1); - adr_dst += dn*dll + dm*chan1; - - for (c = 0; c < chan1; c++) { - if (!(cmask & (1 << (chan1 - 1 - c)))) continue; - - sl = adr_src + c; - dl = adr_dst + c; - - for (j = 0; j < hgt; j++) { - const DTYPE *pk = k; - - for (l = 0; l < n; l++) { - DTYPE *sp0 = sl + l*sll; - - for (off = 0; off < m; off += kw, pk += kw, sp0 += chan1) { - kw = m - off; - - if (kw > 2*MAX_KER) kw = MAX_KER; else - if (kw > MAX_KER) kw = kw/2; - - dp = dl; - - if (kw == 7) { - - p2 = sp0[0]; p3 = sp0[chan1]; p4 = sp0[chan2]; - sp0 += chan3; - p5 = sp0[0]; p6 = sp0[chan1]; p7 = sp0[chan2]; - sp = sp0 += chan3; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - k4 = pk[4]; k5 = pk[5]; k6 = pk[6]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; p4 = p6; - - p5 = sp[- chan1]; p6 = sp[0]; p7 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4 + p5*k5 + p6*k6; - dp[chan1] = p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4 + p6*k5 + p7*k6; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; p4 = p6; - - p5 = sp[- chan1]; p6 = sp[0]; p7 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4 + p5*k5 + p6*k6; - dp[chan1] += p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4 + p6*k5 + p7*k6; - - sp += chan2; - dp += chan2; - } - } - - } else if (kw == 6) { - - p2 = sp0[0]; p3 = sp0[chan1]; p4 = sp0[chan2]; - sp0 += chan3; - p5 = sp0[0]; p6 = sp0[chan1]; - sp = sp0 += chan2; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - k4 = pk[4]; k5 = pk[5]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; p4 = p6; - - p5 = sp[0]; p6 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4 + p5*k5; - dp[chan1] = p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4 + p6*k5; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; p4 = p6; - - p5 = sp[0]; p6 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4 + p5*k5; - dp[chan1] += p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4 + p6*k5; - - sp += chan2; - dp += chan2; - } - } - - } else if (kw == 5) { - - p2 = sp0[0]; p3 = sp0[chan1]; p4 = sp0[chan2]; - sp0 += chan3; - p5 = sp0[0]; - sp = sp0 += chan1; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - k4 = pk[4]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; - - p4 = sp[0]; p5 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4; - dp[chan1] = p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; - - p4 = sp[0]; p5 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4; - dp[chan1] += p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4; - - sp += chan2; - dp += chan2; - } - } - - } else if (kw == 4) { - - p2 = sp0[0]; p3 = sp0[chan1]; p4 = sp0[chan2]; - sp0 += chan3; - sp = sp0; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; - - p3 = sp[0]; p4 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3; - dp[chan1] = p1*k0 + p2*k1 + p3*k2 + p4*k3; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; - - p3 = sp[0]; p4 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2 + p3*k3; - dp[chan1] += p1*k0 + p2*k1 + p3*k2 + p4*k3; - - sp += chan2; - dp += chan2; - } - } - - } else if (kw == 3) { - - p2 = sp0[0]; p3 = sp0[chan1]; - sp0 += chan3; - sp = sp0 -= chan1; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; - - p2 = sp[0]; p3 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2; - dp[chan1] = p1*k0 + p2*k1 + p3*k2; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; - - p2 = sp[0]; p3 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2; - dp[chan1] += p1*k0 + p2*k1 + p3*k2; - - sp += chan2; - dp += chan2; - } - } - - } else { /* kw == 2 */ - - p2 = sp0[0]; - sp0 += chan3; - sp = sp0 -= chan2; - k0 = pk[0]; k1 = pk[1]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; - - p1 = sp[0]; p2 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1; - dp[chan1] = p1*k0 + p2*k1; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; - - p1 = sp[0]; p2 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1; - dp[chan1] += p1*k0 + p2*k1; - - sp += chan2; - dp += chan2; - } - } - } - } - } - - /* last pixels */ - - if (wid & 1) { - DTYPE *sp0 = sl + i*chan1, s = 0; - const DTYPE *pk = k; - mlib_s32 x; - - for (l = 0; l < n; l++) { - DTYPE *sp = sp0 + l*sll; - - for (x = 0; x < m; x++) s += sp[x*chan1] * (*pk++); - } - - dp[0] = s; - } - - /* next line */ - sl += sll; - dl += dll; - } - } - -#ifndef TYPE_DOUBLE - - if (k != k_arr) mlib_free(k); -#endif /* TYPE_DOUBLE */ - - return MLIB_SUCCESS; -} - -/***************************************************************/ diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageConv_F32nw.c b/src/java.desktop/share/native/libmlib_image/mlib_ImageConv_F32nw.c deleted file mode 100644 index e6c68de5054..00000000000 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageConv_F32nw.c +++ /dev/null @@ -1,579 +0,0 @@ -/* - * 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 - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - - -/* - * FUNCTION - * Internal functions for mlib_ImageConv* on D64/F32 type and - * MLIB_EDGE_DST_NO_WRITE mask - * - */ - -#include "mlib_image.h" -#include "mlib_ImageConv.h" - -/***************************************************************/ -/* - This define switches between functions of MLIB_DOUBLE and MLIB_FLOAT types: - Files mlib_ImageConv_D64nw.c and mlib_ImageConv_F32nw.c -*/ - -/* #define TYPE_DOUBLE */ - -/***************************************************************/ -#ifdef TYPE_DOUBLE - -#define CONV_FUNC(KERN) mlib_conv##KERN##nw_d64 - -#define DTYPE mlib_d64 - -#else - -#define CONV_FUNC(KERN) mlib_conv##KERN##nw_f32 - -#define DTYPE mlib_f32 - -#endif /* TYPE_DOUBLE */ - -/***************************************************************/ -#define GET_SRC_DST_PARAMETERS(type) \ - mlib_s32 hgt = mlib_ImageGetHeight(src); \ - mlib_s32 wid = mlib_ImageGetWidth(src); \ - mlib_s32 sll = mlib_ImageGetStride(src) / sizeof(type); \ - mlib_s32 dll = mlib_ImageGetStride(dst) / sizeof(type); \ - type* adr_src = mlib_ImageGetData(src); \ - type* adr_dst = mlib_ImageGetData(dst); \ - mlib_s32 chan1 = mlib_ImageGetChannels(src) - -/***************************************************************/ -#define DEF_VARS(type) \ - GET_SRC_DST_PARAMETERS(type); \ - type *sl; \ - type *dl, *dp = NULL; \ - mlib_s32 i, j, c - -/***************************************************************/ -#define BUFF_SIZE 1600 - -#define CACHE_SIZE (64*1024) - -static mlib_status mlib_ImageConv1xN(mlib_image *dst, - const mlib_image *src, - const DTYPE *k, - mlib_s32 n, - mlib_s32 dn, - mlib_s32 cmask) -{ - DTYPE buff[BUFF_SIZE], *pbuff = buff; - const DTYPE *pk; - DTYPE k0, k1, k2, k3; - DTYPE p0, p1, p2, p3, p4; - DTYPE *sp, *sl_c, *dl_c, *sl0; - DEF_VARS(DTYPE); - mlib_s32 off, kh; - mlib_s32 l, hsize, max_hsize; - - hgt -= (n - 1); - adr_dst += dn*dll; - - max_hsize = (CACHE_SIZE/sizeof(DTYPE))/sll; - - if (!max_hsize) max_hsize = 1; - - if (max_hsize > BUFF_SIZE) { - pbuff = mlib_malloc(sizeof(DTYPE)*max_hsize); - } - - sl_c = adr_src; - dl_c = adr_dst; - - for (l = 0; l < hgt; l += hsize) { - hsize = hgt - l; - - if (hsize > max_hsize) hsize = max_hsize; - - for (c = 0; c < chan1; c++) { - if (!(cmask & (1 << (chan1 - 1 - c)))) continue; - - sl = sl_c + c; - dl = dl_c + c; - - for (j = 0; j < hsize; j++) pbuff[j] = 0.0; - - for (i = 0; i < wid; i++) { - sl0 = sl; - - for (off = 0; off < (n - 4); off += 4) { - pk = k + off; - sp = sl0; - - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - p2 = sp[0]; p3 = sp[sll]; p4 = sp[2*sll]; - sp += 3*sll; - - for (j = 0; j < hsize; j += 2) { - p0 = p2; p1 = p3; p2 = p4; - p3 = sp[0]; - p4 = sp[sll]; - - pbuff[j ] += p0*k0 + p1*k1 + p2*k2 + p3*k3; - pbuff[j + 1] += p1*k0 + p2*k1 + p3*k2 + p4*k3; - - sp += 2*sll; - } - - sl0 += 4*sll; - } - - pk = k + off; - sp = sl0; - - dp = dl; - kh = n - off; - - if (kh == 4) { - - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - p2 = sp[0]; p3 = sp[sll]; p4 = sp[2*sll]; - - sp += 3*sll; - - for (j = 0; j <= (hsize - 2); j += 2) { - p0 = p2; p1 = p3; p2 = p4; - p3 = sp[0]; - p4 = sp[sll]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + pbuff[j]; - dp[dll] = p1*k0 + p2*k1 + p3*k2 + p4*k3 + pbuff[j + 1]; - - pbuff[j] = 0; - pbuff[j + 1] = 0; - - sp += 2*sll; - dp += 2*dll; - } - - if (j < hsize) { - p0 = p2; p1 = p3; p2 = p4; - p3 = sp[0]; - - dp[0] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + pbuff[j]; - - pbuff[j] = 0; - } - - } else if (kh == 3) { - - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; - p2 = sp[0]; p3 = sp[sll]; - - sp += 2*sll; - - for (j = 0; j <= (hsize - 2); j += 2) { - p0 = p2; p1 = p3; - p2 = sp[0]; - p3 = sp[sll]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + pbuff[j]; - dp[dll] = p1*k0 + p2*k1 + p3*k2 + pbuff[j + 1]; - - pbuff[j] = 0; - pbuff[j + 1] = 0; - - sp += 2*sll; - dp += 2*dll; - } - - if (j < hsize) { - p0 = p2; p1 = p3; - p2 = sp[0]; - - dp[0] = p0*k0 + p1*k1 + p2*k2 + pbuff[j]; - - pbuff[j] = 0; - } - - } else if (kh == 2) { - - k0 = pk[0]; k1 = pk[1]; - p2 = sp[0]; - - sp += sll; - - for (j = 0; j <= (hsize - 2); j += 2) { - p0 = p2; - p1 = sp[0]; - p2 = sp[sll]; - - dp[0 ] = p0*k0 + p1*k1 + pbuff[j]; - dp[dll] = p1*k0 + p2*k1 + pbuff[j + 1]; - - pbuff[j] = 0; - pbuff[j + 1] = 0; - - sp += 2*sll; - dp += 2*dll; - } - - if (j < hsize) { - p0 = p2; - p1 = sp[0]; - - dp[0] = p0*k0 + p1*k1 + pbuff[j]; - - pbuff[j] = 0; - } - - } else /* if (kh == 1) */ { - - k0 = pk[0]; - - for (j = 0; j < hsize; j++) { - p0 = sp[0]; - - dp[0] = p0*k0 + pbuff[j]; - - pbuff[j] = 0; - - sp += sll; - dp += dll; - } - } - - sl += chan1; - dl += chan1; - } - } - - sl_c += max_hsize*sll; - dl_c += max_hsize*dll; - } - - if (pbuff != buff) mlib_free(pbuff); - - return MLIB_SUCCESS; -} - -/***************************************************************/ -#define MAX_KER 7 -#define MAX_NM 81 - -mlib_status CONV_FUNC(MxN)(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *ker, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask) -{ - DTYPE k0, k1, k2, k3, k4, k5, k6, *sp; - DTYPE p0, p1, p2, p3, p4, p5, p6, p7; - mlib_s32 l, off, kw; - DEF_VARS(DTYPE); - mlib_s32 chan2 = chan1 + chan1; - mlib_s32 chan3 = chan1 + chan2; - -#ifdef TYPE_DOUBLE - const mlib_d64 *k = ker; -#else - mlib_f32 k_arr[MAX_NM], *k = k_arr; - - if (n*m > MAX_NM) { - k = mlib_malloc(n*m*sizeof(mlib_f32)); - - if (k == NULL) return MLIB_FAILURE; - } - - for (i = 0; i < n*m; i++) k[i] = (mlib_f32)ker[i]; -#endif /* TYPE_DOUBLE */ - - if (m == 1) return mlib_ImageConv1xN(dst, src, k, n, dn, cmask); - - wid -= (m - 1); - hgt -= (n - 1); - adr_dst += dn*dll + dm*chan1; - - for (c = 0; c < chan1; c++) { - if (!(cmask & (1 << (chan1 - 1 - c)))) continue; - - sl = adr_src + c; - dl = adr_dst + c; - - for (j = 0; j < hgt; j++) { - const DTYPE *pk = k; - - for (l = 0; l < n; l++) { - DTYPE *sp0 = sl + l*sll; - - for (off = 0; off < m; off += kw, pk += kw, sp0 += chan1) { - kw = m - off; - - if (kw > 2*MAX_KER) kw = MAX_KER; else - if (kw > MAX_KER) kw = kw/2; - - dp = dl; - - if (kw == 7) { - p2 = sp0[0]; p3 = sp0[chan1]; p4 = sp0[chan2]; - sp0 += chan3; - p5 = sp0[0]; p6 = sp0[chan1]; p7 = sp0[chan2]; - sp = sp0 += chan3; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - k4 = pk[4]; k5 = pk[5]; k6 = pk[6]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; p4 = p6; - - p5 = sp[- chan1]; p6 = sp[0]; p7 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4 + p5*k5 + p6*k6; - dp[chan1] = p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4 + p6*k5 + p7*k6; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; p4 = p6; - - p5 = sp[- chan1]; p6 = sp[0]; p7 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4 + p5*k5 + p6*k6; - dp[chan1] += p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4 + p6*k5 + p7*k6; - - sp += chan2; - dp += chan2; - } - } - - } else if (kw == 6) { - p2 = sp0[0]; p3 = sp0[chan1]; p4 = sp0[chan2]; - sp0 += chan3; - p5 = sp0[0]; p6 = sp0[chan1]; - sp = sp0 += chan2; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - k4 = pk[4]; k5 = pk[5]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; p4 = p6; - - p5 = sp[0]; p6 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4 + p5*k5; - dp[chan1] = p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4 + p6*k5; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; p4 = p6; - - p5 = sp[0]; p6 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4 + p5*k5; - dp[chan1] += p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4 + p6*k5; - - sp += chan2; - dp += chan2; - } - } - - } else if (kw == 5) { - p2 = sp0[0]; p3 = sp0[chan1]; p4 = sp0[chan2]; - sp0 += chan3; - p5 = sp0[0]; - sp = sp0 += chan1; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - k4 = pk[4]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; - - p4 = sp[0]; p5 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4; - dp[chan1] = p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; p3 = p5; - - p4 = sp[0]; p5 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2 + p3*k3 + p4*k4; - dp[chan1] += p1*k0 + p2*k1 + p3*k2 + p4*k3 + p5*k4; - - sp += chan2; - dp += chan2; - } - } - - } else if (kw == 4) { - - p2 = sp0[0]; p3 = sp0[chan1]; p4 = sp0[chan2]; - sp0 += chan3; - sp = sp0; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; k3 = pk[3]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; - - p3 = sp[0]; p4 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2 + p3*k3; - dp[chan1] = p1*k0 + p2*k1 + p3*k2 + p4*k3; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; p2 = p4; - - p3 = sp[0]; p4 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2 + p3*k3; - dp[chan1] += p1*k0 + p2*k1 + p3*k2 + p4*k3; - - sp += chan2; - dp += chan2; - } - } - - } else if (kw == 3) { - - p2 = sp0[0]; p3 = sp0[chan1]; - sp0 += chan3; - sp = sp0 -= chan1; - k0 = pk[0]; k1 = pk[1]; k2 = pk[2]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; - - p2 = sp[0]; p3 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1 + p2*k2; - dp[chan1] = p1*k0 + p2*k1 + p3*k2; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; p1 = p3; - - p2 = sp[0]; p3 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1 + p2*k2; - dp[chan1] += p1*k0 + p2*k1 + p3*k2; - - sp += chan2; - dp += chan2; - } - } - - } else { /* kw == 2 */ - - p2 = sp0[0]; - sp0 += chan3; - sp = sp0 -= chan2; - k0 = pk[0]; k1 = pk[1]; - - if (pk == k) { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; - - p1 = sp[0]; p2 = sp[chan1]; - - dp[0 ] = p0*k0 + p1*k1; - dp[chan1] = p1*k0 + p2*k1; - - sp += chan2; - dp += chan2; - } - - } else { - for (i = 0; i <= (wid - 2); i += 2) { - p0 = p2; - - p1 = sp[0]; p2 = sp[chan1]; - - dp[0 ] += p0*k0 + p1*k1; - dp[chan1] += p1*k0 + p2*k1; - - sp += chan2; - dp += chan2; - } - } - } - } - } - - /* last pixels */ - - if (wid & 1) { - DTYPE *sp0 = sl + i*chan1, s = 0; - const DTYPE *pk = k; - mlib_s32 x; - - for (l = 0; l < n; l++) { - DTYPE *sp = sp0 + l*sll; - - for (x = 0; x < m; x++) s += sp[x*chan1] * (*pk++); - } - - dp[0] = s; - } - - /* next line */ - sl += sll; - dl += dll; - } - } - -#ifndef TYPE_DOUBLE - - if (k != k_arr) mlib_free(k); -#endif /* TYPE_DOUBLE */ - - return MLIB_SUCCESS; -} - -/***************************************************************/ diff --git a/src/java.desktop/share/native/libmlib_image/mlib_c_ImageConvClearEdge.c b/src/java.desktop/share/native/libmlib_image/mlib_c_ImageConvClearEdge.c index 08644f9d7dc..9832f1c6424 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_c_ImageConvClearEdge.c +++ b/src/java.desktop/share/native/libmlib_image/mlib_c_ImageConvClearEdge.c @@ -164,7 +164,7 @@ mlib_status mlib_ImageConvZeroEdge(mlib_image *dst, mlib_type type = mlib_ImageGetType(dst); if (type == MLIB_FLOAT || type == MLIB_DOUBLE) { - return mlib_ImageConvClearEdge_Fp(dst, dx_l, dx_r, dy_t, dy_b, zero, cmask); + return MLIB_FAILURE; } else { return mlib_ImageConvClearEdge(dst, dx_l, dx_r, dy_t, dy_b, (mlib_s32 *) zero, cmask); diff --git a/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h b/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h index 8155d73e205..a8ad65e7c4b 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h +++ b/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h @@ -2651,20 +2651,6 @@ __mlib_ImageConvMxN(mlib_image *dst, mlib_edge edge); -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConvMxN_Fp mlib_ImageConvMxN_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConvMxN_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge); - - #if defined ( __MEDIALIB_OLD_NAMES ) #define __mlib_ImageConvolveMxN mlib_ImageConvolveMxN #endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ @@ -2678,21 +2664,6 @@ mlib_status __mlib_ImageConvolveMxN(mlib_image *dst, mlib_s32 cmask, mlib_edge edge); - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConvolveMxN_Fp mlib_ImageConvolveMxN_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConvolveMxN_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge); - - #if defined ( __MEDIALIB_OLD_NAMES ) #define __mlib_ImageDilate4 mlib_ImageDilate4 #endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ From 26582dad4cff32d18a79377e5b60fb23f9f24328 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Mon, 12 Oct 2026 23:28:36 +0000 Subject: [PATCH 052/101] 8393554: Shenandoah: Remove ShenandoahImplicitGCInvokesConcurrent option Reviewed-by: kdnilsen, pfontanilla, shade --- src/hotspot/share/code/aotCodeCache.hpp | 1 - .../shenandoahCompactHeuristics.cpp | 1 - .../heuristics/shenandoahStaticHeuristics.cpp | 1 - .../mode/shenandoahGenerationalMode.cpp | 1 - .../gc/shenandoah/mode/shenandoahMode.hpp | 1 + .../shenandoah/mode/shenandoahPassiveMode.cpp | 1 - .../shenandoah/mode/shenandoahPassiveMode.hpp | 11 +++-- .../gc/shenandoah/mode/shenandoahSATBMode.cpp | 1 - .../shenandoah/shenandoahCollectorPolicy.cpp | 46 ++++--------------- .../shenandoah/shenandoahCollectorPolicy.hpp | 1 - .../gc/shenandoah/shenandoahControlThread.cpp | 3 +- .../shenandoahGenerationalControlThread.cpp | 14 ++---- .../share/gc/shenandoah/shenandoahHeap.hpp | 2 +- .../gc/shenandoah/shenandoah_globals.hpp | 6 --- 14 files changed, 22 insertions(+), 68 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.hpp b/src/hotspot/share/code/aotCodeCache.hpp index 6fe864f1f71..ac536eb1cfc 100644 --- a/src/hotspot/share/code/aotCodeCache.hpp +++ b/src/hotspot/share/code/aotCodeCache.hpp @@ -470,7 +470,6 @@ class AOTStubData : public StackObj { #if INCLUDE_SHENANDOAHGC #define AOTCODECACHE_CONFIGS_SHENANDOAHGC_DO(do_var, do_fun) \ do_var(bool, ExplicitGCInvokesConcurrent) \ - do_var(bool, ShenandoahImplicitGCInvokesConcurrent) \ do_var(bool, ShenandoahLoadRefBarrier) \ do_var(bool, ShenandoahSATBBarrier) \ do_var(bool, ShenandoahCloneBarrier) \ diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp index a11820e5a80..783c1f0709c 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp @@ -35,7 +35,6 @@ ShenandoahCompactHeuristics::ShenandoahCompactHeuristics(ShenandoahSpaceInfo* sp ShenandoahHeuristics(space_info), _bytes_used_at_end_of_gc(0) { SHENANDOAH_ERGO_ENABLE_FLAG(ExplicitGCInvokesConcurrent); - SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahImplicitGCInvokesConcurrent); SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahUncommit); SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahAlwaysClearSoftRefs); SHENANDOAH_ERGO_ENABLE_FLAG(UseStringDeduplication); diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.cpp index 0ceab0460cf..a376ac83b1e 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.cpp @@ -34,7 +34,6 @@ ShenandoahStaticHeuristics::ShenandoahStaticHeuristics(ShenandoahSpaceInfo* space_info) : ShenandoahHeuristics(space_info) { SHENANDOAH_ERGO_ENABLE_FLAG(ExplicitGCInvokesConcurrent); - SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahImplicitGCInvokesConcurrent); } bool ShenandoahStaticHeuristics::should_start_gc() { diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahGenerationalMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahGenerationalMode.cpp index 5a21ded6d29..15c161df417 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahGenerationalMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahGenerationalMode.cpp @@ -49,7 +49,6 @@ void ShenandoahGenerationalMode::initialize_flags() const { SHENANDOAH_ERGO_OVERRIDE_DEFAULT(GCTimeRatio, 70); SHENANDOAH_ERGO_ENABLE_FLAG(ExplicitGCInvokesConcurrent); - SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahImplicitGCInvokesConcurrent); // This helps most multi-core hardware hosts, enable by default SHENANDOAH_ERGO_ENABLE_FLAG(UseCondCardMark); diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahMode.hpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahMode.hpp index f3e98d92b2e..c212e2b883b 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahMode.hpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahMode.hpp @@ -58,6 +58,7 @@ class ShenandoahMode : public CHeapObj { virtual bool is_diagnostic() = 0; virtual bool is_experimental() = 0; virtual bool is_generational() { return false; } + virtual bool is_concurrent() { return true; } }; #endif // SHARE_GC_SHENANDOAH_MODE_SHENANDOAHMODE_HPP diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp index 31ffcd623fd..a880baccdf7 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp @@ -35,7 +35,6 @@ void ShenandoahPassiveMode::initialize_flags() const { // Do not allow concurrent cycles. FLAG_SET_DEFAULT(ExplicitGCInvokesConcurrent, false); - FLAG_SET_DEFAULT(ShenandoahImplicitGCInvokesConcurrent, false); // No need for evacuation reserve with Full GC, only for Degenerated GC. if (!ShenandoahDegeneratedGC) { diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.hpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.hpp index 032092a70ec..d6b1054064c 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.hpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.hpp @@ -29,11 +29,12 @@ class ShenandoahPassiveMode : public ShenandoahMode { public: - virtual void initialize_flags() const; - virtual ShenandoahHeuristics* initialize_heuristics(ShenandoahSpaceInfo* space_info) const; - virtual const char* name() { return "Passive"; } - virtual bool is_diagnostic() { return true; } - virtual bool is_experimental() { return false; } + void initialize_flags() const override; + ShenandoahHeuristics* initialize_heuristics(ShenandoahSpaceInfo* space_info) const override; + const char* name() override { return "Passive"; } + bool is_diagnostic() override { return true; } + bool is_experimental() override { return false; } + bool is_concurrent() override { return false; } }; #endif // SHARE_GC_SHENANDOAH_MODE_SHENANDOAHPASSIVEMODE_HPP diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp index 842c7841b9c..a92d152edf7 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp @@ -35,7 +35,6 @@ void ShenandoahSATBMode::initialize_flags() const { } SHENANDOAH_ERGO_ENABLE_FLAG(ExplicitGCInvokesConcurrent); - SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahImplicitGCInvokesConcurrent); // Final configuration checks SHENANDOAH_CHECK_FLAG_SET(ShenandoahLoadRefBarrier); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp index e3267517e05..0eaf91f38ff 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2013, 2021, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -45,7 +45,6 @@ ShenandoahCollectorPolicy::ShenandoahCollectorPolicy() : _alloc_failure_degenerated(0), _alloc_failure_degenerated_upgrade_to_full(0), _alloc_failure_full(0) { - Copy::zero_to_bytes(_degen_point_counts, sizeof(size_t) * ShenandoahGC::_DEGENERATED_LIMIT); Copy::zero_to_bytes(_collection_cause_counts, sizeof(size_t) * GCCause::_last_gc_cause); @@ -142,13 +141,6 @@ bool ShenandoahCollectorPolicy::is_explicit_gc(GCCause::Cause cause) { || cause == GCCause::_wb_young_gc; } -bool is_implicit_gc(GCCause::Cause cause) { - return cause != GCCause::_no_gc - && cause != GCCause::_shenandoah_concurrent_gc - && cause != GCCause::_allocation_failure - && !ShenandoahCollectorPolicy::is_explicit_gc(cause); -} - #ifdef ASSERT bool is_valid_request(GCCause::Cause cause) { return ShenandoahCollectorPolicy::is_explicit_gc(cause) @@ -180,12 +172,11 @@ bool ShenandoahCollectorPolicy::is_allocation_failure(GCCause::Cause cause) { || cause == GCCause::_shenandoah_humongous_allocation_failure; } -bool ShenandoahCollectorPolicy::is_requested_gc(GCCause::Cause cause) { - return is_explicit_gc(cause) || is_implicit_gc(cause); -} - bool ShenandoahCollectorPolicy::should_run_full_gc(GCCause::Cause cause) { - return is_explicit_gc(cause) ? !ExplicitGCInvokesConcurrent : !ShenandoahImplicitGCInvokesConcurrent; + if (!ShenandoahHeap::heap()->mode()->is_concurrent()) { + return true; + } + return is_explicit_gc(cause) && !ExplicitGCInvokesConcurrent; } bool ShenandoahCollectorPolicy::should_handle_requested_gc(GCCause::Cause cause) { @@ -200,9 +191,8 @@ bool ShenandoahCollectorPolicy::should_handle_requested_gc(GCCause::Cause cause) void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { out->print_cr("Under allocation pressure, concurrent cycles may cancel, and either continue cycle"); out->print_cr("under stop-the-world pause or result in stop-the-world Full GC. Increase heap size,"); - out->print_cr("tune GC heuristics, or lower allocation rate"); - out->print_cr("to avoid Degenerated and Full GC cycles. Abbreviated cycles are those which found"); - out->print_cr("enough regions with no live objects to skip evacuation."); + out->print_cr("tune GC heuristics, or lower allocation rate to avoid Degenerated and Full GC cycles."); + out->print_cr("Abbreviated cycles are those which found enough regions with no live objects to skip evacuation."); out->cr(); size_t gc_attempts = 0; @@ -211,21 +201,13 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { } size_t completed_gcs = _success_full_gcs + _success_degenerated_gcs + _success_concurrent_gcs + _success_old_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; for (int c = 0; c < GCCause::_last_gc_cause; c++) { size_t cause_count = _collection_cause_counts[c]; if (cause_count > 0) { auto cause = (GCCause::Cause) c; - if (is_explicit_gc(cause)) { - explicit_requests += cause_count; - } else if (is_implicit_gc(cause)) { - 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, gc_attempts)); } @@ -233,17 +215,11 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { out->cr(); out->print_cr("%5zu Successful Concurrent GCs (%.2f%%)", _success_concurrent_gcs, percent_of(_success_concurrent_gcs, completed_gcs)); - if (ExplicitGCInvokesConcurrent) { - out->print_cr(" %5zu invoked explicitly (%.2f%%)", explicit_requests, percent_of(explicit_requests, _success_concurrent_gcs)); - } - if (ShenandoahImplicitGCInvokesConcurrent) { - out->print_cr(" %5zu invoked implicitly (%.2f%%)", implicit_requests, percent_of(implicit_requests, _success_concurrent_gcs)); - } out->print_cr(" %5zu abbreviated (%.2f%%)", _abbreviated_concurrent_gcs, percent_of(_abbreviated_concurrent_gcs, _success_concurrent_gcs)); out->cr(); if (ShenandoahHeap::heap()->mode()->is_generational()) { - out->print_cr("%5zu Completed Old GCs (%.2f%%)", _success_old_gcs, percent_of(_success_old_gcs, completed_gcs)); + out->print_cr("%5zu Completed Old GCs (%.2f%%)", _success_old_gcs, percent_of(_success_old_gcs, completed_gcs)); out->print_cr(" %5zu mixed", _mixed_gcs); out->print_cr(" %5zu interruptions", _interrupted_old_gcs); out->cr(); @@ -263,12 +239,6 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { out->cr(); out->print_cr("%5zu Full GCs (%.2f%%)", _success_full_gcs, percent_of(_success_full_gcs, completed_gcs)); - if (!ExplicitGCInvokesConcurrent) { - out->print_cr(" %5zu invoked explicitly (%.2f%%)", explicit_requests, percent_of(explicit_requests, _success_full_gcs)); - } - if (!ShenandoahImplicitGCInvokesConcurrent) { - out->print_cr(" %5zu invoked implicitly (%.2f%%)", implicit_requests, percent_of(implicit_requests, _success_full_gcs)); - } out->print_cr(" %5zu caused by allocation failure (%.2f%%)", _alloc_failure_full, percent_of(_alloc_failure_full, _success_full_gcs)); out->print_cr(" %5zu upgraded from Degenerated GC (%.2f%%)", _alloc_failure_degenerated_upgrade_to_full, percent_of(_alloc_failure_degenerated_upgrade_to_full, _success_full_gcs)); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp index 1166333ae3a..e265da9d413 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp @@ -117,7 +117,6 @@ class ShenandoahCollectorPolicy : public CHeapObj { static bool is_allocation_failure(GCCause::Cause cause); static bool is_shenandoah_gc(GCCause::Cause cause); - static bool is_requested_gc(GCCause::Cause cause); static bool is_explicit_gc(GCCause::Cause cause); static bool should_run_full_gc(GCCause::Cause cause); static bool should_handle_requested_gc(GCCause::Cause cause); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index c36df9c209e..1eec3802775 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -118,9 +118,8 @@ void ShenandoahControlThread::run_service() { heap->set_unload_classes(heuristics->should_unload_classes()); } - // Blow all soft references on this cycle, if handling allocation failure, - // either implicit or explicit GC request, or we are requested to do so unconditionally. if (alloc_failure_pending || is_gc_requested || ShenandoahAlwaysClearSoftRefs) { + // Blow away all soft references on this cycle heap->global_generation()->ref_processor()->set_soft_reference_policy(true); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index 1c2571af208..d73ba05b44a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -239,10 +239,9 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest log_debug(gc, thread)("Starting GC (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name()); assert(gc_mode() != none, "GC mode cannot be none here"); - // Blow away all soft references on this cycle, if handling allocation failure, - // either implicit or explicit GC request, or we are requested to do so unconditionally. if (GCCause::should_clear_all_soft_refs(request.cause) || (request.generation->is_global() && (ShenandoahCollectorPolicy::is_allocation_failure(request.cause) || ShenandoahCollectorPolicy::is_explicit_gc(request.cause) || ShenandoahAlwaysClearSoftRefs))) { + // Blow away all soft references on this cycle request.generation->ref_processor()->set_soft_reference_policy(true); } @@ -349,13 +348,10 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), GCCause::to_string(_heap->cancelled_cause())); } -// Young and old concurrent cycles are initiated by the regulator. Implicit -// and explicit GC requests are handled by the controller thread and always -// run a global cycle (which is concurrent by default, but may be overridden -// by command line options). Old cycles always degenerate to a global cycle. -// Young cycles are degenerated to complete the young cycle. Young -// and old degen may upgrade to Full GC. Full GC may also be -// triggered directly by a System.gc() invocation. +// Young and old concurrent cycles are initiated by the regulator. GC requests are handled by the controller thread and +// always run a global cycle (which is concurrent by default, but may be overridden by command line options). Old cycles +// always degenerate to a global cycle. Young cycles are degenerated to complete the young cycle. Young and old degen +// may upgrade to Full GC. Full GC may also be triggered directly by a System.gc() invocation. // // // +-----+ Idle +-----+-----------+---------------------+ diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index c4639230b7a..5530af1d671 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -218,7 +218,7 @@ class ShenandoahHeap : public CollectedHeap { // WhiteBox testing support. bool supports_concurrent_gc_breakpoints() const override { - return true; + return mode()->is_concurrent(); } // ---------- Heap counters and metrics diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index e5788454836..7437586f712 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -444,12 +444,6 @@ "progress, Shenandoah will raise out of memory errors. Note " \ "that progress is determined by ShenandoahCriticalFreeThreshold") \ \ - product(bool, ShenandoahImplicitGCInvokesConcurrent, false, EXPERIMENTAL, \ - "Should internally-caused GC requests invoke concurrent cycles, " \ - "should they do the stop-the-world (Degenerated / Full GC)? " \ - "Many heuristics automatically enable this. This option is " \ - "similar to global ExplicitGCInvokesConcurrent.") \ - \ product(bool, ShenandoahHumongousMoves, true, DIAGNOSTIC, \ "Allow moving humongous regions. This makes GC more resistant " \ "to external fragmentation that may otherwise fail other " \ From 10f4d3d67975f9a18f7b490963f57066893b637d Mon Sep 17 00:00:00 2001 From: Serguei Spitsyn Date: Tue, 13 Oct 2026 03:31:15 +0000 Subject: [PATCH 053/101] 8389460: JVMTI IterateThroughHeap class filter skips flattened value objects Reviewed-by: heidinga, lmesnik --- src/hotspot/share/prims/jvmtiTagMap.cpp | 130 ++++++++--------- .../Heapwalking/FlatClassFilterTest.java | 138 ++++++++++++++++++ .../Heapwalking/libFlatClassFilterTest.cpp | 86 +++++++++++ 3 files changed, 287 insertions(+), 67 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/FlatClassFilterTest.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/libFlatClassFilterTest.cpp diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index fbf959b4c63..5f357831dfe 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -1276,78 +1276,74 @@ void IterateThroughHeapObjectClosure::do_object(oop obj) { } void IterateThroughHeapObjectClosure::visit_object(const JvmtiHeapwalkObject& obj) { - // apply class filter - if (is_filtered_by_klass_filter(obj, klass())) return; - - // prepare for callback - CallbackWrapper wrapper(tag_map(), obj); - - // check if filtered by the heap filter - if (is_filtered_by_heap_filter(wrapper.obj_tag(), wrapper.klass_tag(), heap_filter())) { - return; - } - - // for arrays we need the length, otherwise -1 bool is_array = obj.klass()->is_array_klass(); - int len = is_array ? arrayOop(obj.obj())->length() : -1; - - // invoke the object callback (if callback is provided) - if (callbacks()->heap_iteration_callback != nullptr) { - jvmtiHeapIterationCallback cb = callbacks()->heap_iteration_callback; - jint res = (*cb)(wrapper.klass_tag(), - wrapper.obj_size(), - wrapper.obj_tag_p(), - (jint)len, - (void*)user_data()); - if (check_flags_for_abort(res)) return; - } - - // for objects and classes we report primitive fields if callback provided - if (callbacks()->primitive_field_callback != nullptr && obj.klass()->is_instance_klass()) { - jint res; - jvmtiPrimitiveFieldCallback cb = callbacks()->primitive_field_callback; - if (obj.klass() == vmClasses::Class_klass()) { - assert(!obj.is_flat(), "Class object cannot be flattened"); - res = invoke_primitive_field_callback_for_static_fields(&wrapper, - obj.obj(), - cb, - (void*)user_data()); - } else { - res = invoke_primitive_field_callback_for_instance_fields(&wrapper, - obj, - cb, - (void*)user_data()); - } - if (check_flags_for_abort(res)) return; - } - // string callback - if (!is_array && - callbacks()->string_primitive_value_callback != nullptr && - obj.klass() == vmClasses::String_klass()) { - jint res = invoke_string_value_callback( - callbacks()->string_primitive_value_callback, - &wrapper, - obj, - (void*)user_data()); - if (check_flags_for_abort(res)) return; - } + if (!is_filtered_by_klass_filter(obj, klass())) { // apply class filter + // prepare for callback + CallbackWrapper wrapper(tag_map(), obj); + + // check if filtered by the heap filter + if (!is_filtered_by_heap_filter(wrapper.obj_tag(), wrapper.klass_tag(), heap_filter())) { + // for arrays we need the length, otherwise -1 + int len = is_array ? arrayOop(obj.obj())->length() : -1; + + // invoke the object callback (if callback is provided) + if (callbacks()->heap_iteration_callback != nullptr) { + jvmtiHeapIterationCallback cb = callbacks()->heap_iteration_callback; + jint res = (*cb)(wrapper.klass_tag(), + wrapper.obj_size(), + wrapper.obj_tag_p(), + (jint)len, + (void*)user_data()); + if (check_flags_for_abort(res)) return; + } - // array callback - if (is_array && - callbacks()->array_primitive_value_callback != nullptr && - obj.klass()->is_typeArray_klass()) { - jint res = invoke_array_primitive_value_callback( - callbacks()->array_primitive_value_callback, - &wrapper, - obj, - (void*)user_data()); - if (check_flags_for_abort(res)) return; - } + // for objects and classes we report primitive fields if callback provided + if (callbacks()->primitive_field_callback != nullptr && obj.klass()->is_instance_klass()) { + jint res; + jvmtiPrimitiveFieldCallback cb = callbacks()->primitive_field_callback; + if (obj.klass() == vmClasses::Class_klass()) { + assert(!obj.is_flat(), "Class object cannot be flattened"); + res = invoke_primitive_field_callback_for_static_fields(&wrapper, + obj.obj(), + cb, + (void*)user_data()); + } else { + res = invoke_primitive_field_callback_for_instance_fields(&wrapper, + obj, + cb, + (void*)user_data()); + } + if (check_flags_for_abort(res)) return; + } - // All info for the object is reported. + // string callback + if (!is_array && + callbacks()->string_primitive_value_callback != nullptr && + obj.klass() == vmClasses::String_klass()) { + jint res = invoke_string_value_callback( + callbacks()->string_primitive_value_callback, + &wrapper, + obj, + (void*)user_data()); + if (check_flags_for_abort(res)) return; + } - // If the object has flat fields, report them as heap objects. + // array callback + if (is_array && + callbacks()->array_primitive_value_callback != nullptr && + obj.klass()->is_typeArray_klass()) { + jint res = invoke_array_primitive_value_callback( + callbacks()->array_primitive_value_callback, + &wrapper, + obj, + (void*)user_data()); + if (check_flags_for_abort(res)) return; + } + } + } + // If the object has flattened fields, report them as heap objects + // even though this object was not reported. if (obj.klass()->is_instance_klass()) { if (InstanceKlass::cast(obj.klass())->has_flat_fields()) { visit_flat_fields(obj); diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/FlatClassFilterTest.java b/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/FlatClassFilterTest.java new file mode 100644 index 00000000000..7118c09226f --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/FlatClassFilterTest.java @@ -0,0 +1,138 @@ +/* + * 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 id=default + * @bug 8389460 + * @summary JVMTI IterateThroughHeap class filter should not skip flattened value objects + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @requires vm.jvmti + * @enablePreview + * @run main/othervm/native -agentlib:FlatClassFilterTest + * -XX:+UnlockDiagnosticVMOptions + * -XX:+UnlockExperimentalVMOptions + * -XX:+UseArrayFlattening + * -XX:+UseFieldFlattening + * -XX:+UseNullFreeAtomicValueFlattening + * -XX:+UseNullableAtomicValueFlattening + * FlatClassFilterTest + */ + +/* + * @test id=class_untagged + * @bug 8389460 + * @summary JVMTI IterateThroughHeap class filter should not skip flattened value objects + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @requires vm.jvmti + * @enablePreview + * @run main/othervm/native -agentlib:FlatClassFilterTest=heap_filter_class_untagged + * -XX:+UnlockDiagnosticVMOptions + * -XX:+UnlockExperimentalVMOptions + * -XX:+UseArrayFlattening + * -XX:+UseFieldFlattening + * -XX:+UseNullFreeAtomicValueFlattening + * -XX:+UseNullableAtomicValueFlattening + * FlatClassFilterTest + */ + +import java.lang.ref.Reference; +import jdk.internal.misc.Unsafe; +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; + +public class FlatClassFilterTest { + private static void log(String msg) { + System.out.println(msg); + System.out.flush(); + } + + static Value[] array; + static Value heap = new Value(999); + + static value class Value { + int x; + Value(int x) { this.x = x; } + } + + static value class ValueHolder { + @NullRestricted + public Value v1; + + public Value vn; + public Value[] v_arr; + + public ValueHolder(int seed) throws Exception { + v1 = new Value(seed); + vn = null; + v_arr = createValueArray(seed); + } + } + + static Value[] createValueArray(int seed) throws Exception { + Value[] arr = (Value[])ValueClass.newNullableAtomicArray(Value.class, 5); + for (int i = 0; i < arr.length; i++) { + arr[i] = (i == 2 ? null : new Value(seed + 10 + i)); + } + return arr; + } + + static final long CLASS_TAG = 71; + static final Unsafe U = Unsafe.getUnsafe(); + + static native int count(Class filter, Class taggedClass, long classTag); + + public static void main(String[] args) throws Exception { + System.loadLibrary("FlatClassFilterTest"); + log("FlatClassFilterTest: Started"); + + ValueHolder holder = new ValueHolder(10); + array = createValueArray(100); + + // Use Unsafe because there is no non-unsafe API to check the field flatness. + boolean isFlatV1 = U.isFlatField(ValueHolder.class.getDeclaredField("v1")); + boolean isFlatArr = ValueClass.isFlatArray(array); + int all = count(null, Value.class, CLASS_TAG); + int filtered = count(Value.class, Value.class, CLASS_TAG); + + log("v1 is flat: " + isFlatV1); + log("array is flat: " + isFlatArr); + log("all: " + all); + log("filtered: " + filtered); + + if (!isFlatV1) { + throw new AssertionError("The field holder.v1 is expected to be flat"); + } + if (!isFlatArr) { + throw new AssertionError("The array is expected to be flat"); + } + if (filtered != all) { + throw new AssertionError("V.class filter skipped flat V[] element"); + } + Reference.reachabilityFence(holder); + log("FlatClassFilterTest: PASSED"); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/libFlatClassFilterTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/libFlatClassFilterTest.cpp new file mode 100644 index 00000000000..105ccc890ca --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/valhalla/Heapwalking/libFlatClassFilterTest.cpp @@ -0,0 +1,86 @@ +/* + * 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 "jvmti_common.hpp" + +extern "C" { + +static jvmtiEnv* jvmti = nullptr; +static jlong wanted_tag = 0; +static jint object_count = 0; +static jint heap_filter = 0; + +static jint JNICALL +heap_iteration(jlong class_tag, jlong size, jlong* tag_ptr, jint length, void* user_data) { + if (class_tag == wanted_tag) { + object_count++; + } + return 0; +} + +JNIEXPORT jint JNICALL +Java_FlatClassFilterTest_count(JNIEnv* jni, jclass ignored, jclass class_filter, + jclass tagged_class, jlong class_tag) { + jvmtiError err = jvmti->SetTag(tagged_class, class_tag); + check_jvmti_status(jni, err, "SetTag"); + + jvmtiHeapCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + + wanted_tag = class_tag; + object_count = 0; + callbacks.heap_iteration_callback = heap_iteration; + + err = jvmti->IterateThroughHeap(heap_filter, class_filter, &callbacks, nullptr); + check_jvmti_status(jni, err, "IterateThroughHeap"); + + return object_count; +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { + if (vm->GetEnv(reinterpret_cast(&jvmti), JVMTI_VERSION_1_2) != JNI_OK) { + LOG("Agent_OnLoad: Failed: Could not initialize JVMTI\n"); + return JNI_ERR; + } + if (options != nullptr) { + if (strcmp(options, "heap_filter_class_untagged") == 0) { + heap_filter = JVMTI_HEAP_FILTER_CLASS_UNTAGGED; + } else { + LOG("Agent_OnLoad: Failed: unrecognized agent option"); + return JNI_ERR; + } + } + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_tag_objects = 1; + + jvmtiError err = jvmti->AddCapabilities(&caps); + check_jvmti_error(err, "AddCapabilities"); + + return JNI_OK; +} + +} // extern "C" + From ca27bf4c33135f80362285aaf06d6d0b011f4536 Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Tue, 13 Oct 2026 03:35:22 +0000 Subject: [PATCH 054/101] 8394236: RISC-V: Fix StoreLoad ordering for sequentially consistent CAS Reviewed-by: fyang, ayang --- .../cpu/riscv/c1_LIRAssembler_riscv.cpp | 12 +- .../cpu/riscv/c2_MacroAssembler_riscv.cpp | 8 +- src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad | 24 +- .../shenandoahBarrierSetAssembler_riscv.cpp | 5 +- .../riscv/gc/z/zBarrierSetAssembler_riscv.cpp | 2 +- src/hotspot/cpu/riscv/gc/z/z_riscv.ad | 8 +- .../cpu/riscv/macroAssembler_riscv.cpp | 59 ++++- .../cpu/riscv/macroAssembler_riscv.hpp | 9 +- src/hotspot/cpu/riscv/riscv.ad | 223 ++++-------------- .../gtest/riscv/test_assembler_riscv.cpp | 14 +- 10 files changed, 137 insertions(+), 227 deletions(-) diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index dfc66cfaf5c..7560d2ed3de 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -2316,24 +2316,18 @@ address LIR_Assembler::int_constant(jlong n) { } void LIR_Assembler::casw(Register addr, Register newval, Register cmpval) { - __ cmpxchg(addr, cmpval, newval, Assembler::int32, Assembler::aq /* acquire */, - Assembler::rl /* release */, t0, true /* result as bool */); + __ cmpxchg(addr, cmpval, newval, Assembler::int32, memory_order_seq_cst, t0, /* result as bool */ true); __ seqz(t0, t0); // cmpxchg not equal, set t0 to 1 - __ membar(MacroAssembler::AnyAny); } void LIR_Assembler::caswu(Register addr, Register newval, Register cmpval) { - __ cmpxchg(addr, cmpval, newval, Assembler::uint32, Assembler::aq /* acquire */, - Assembler::rl /* release */, t0, true /* result as bool */); + __ cmpxchg(addr, cmpval, newval, Assembler::uint32, memory_order_seq_cst, t0, /* result as bool */ true); __ seqz(t0, t0); // cmpxchg not equal, set t0 to 1 - __ membar(MacroAssembler::AnyAny); } void LIR_Assembler::casl(Register addr, Register newval, Register cmpval) { - __ cmpxchg(addr, cmpval, newval, Assembler::int64, Assembler::aq /* acquire */, - Assembler::rl /* release */, t0, true /* result as bool */); + __ cmpxchg(addr, cmpval, newval, Assembler::int64, memory_order_seq_cst, t0, /* result as bool */ true); __ seqz(t0, t0); // cmpxchg not equal, set t0 to 1 - __ membar(MacroAssembler::AnyAny); } void LIR_Assembler::deoptimize_trap(CodeEmitInfo *info) { diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index 66ebbbba341..6212bebab05 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -126,7 +126,7 @@ void C2_MacroAssembler::fast_lock(Register obj, Register box, ori(tmp1_mark, tmp1_mark, markWord::lock_neutral_value); xori(tmp3_t, tmp1_mark, markWord::lock_neutral_value); cmpxchg(/*addr*/ obj, /*expected*/ tmp1_mark, /*new*/ tmp3_t, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ tmp3_t); + memory_order_acquire, /*result*/ tmp3_t); bne(tmp1_mark, tmp3_t, slow_path); bind(push); @@ -206,7 +206,7 @@ void C2_MacroAssembler::fast_lock(Register obj, Register box, Register tid = tmp4; ld(tid, Address(xthread, JavaThread::monitor_owner_id_offset())); cmpxchg(/*addr*/ tmp2_owner_addr, /*expected*/ zr, /*new*/ tid, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ tmp3_owner); + memory_order_acquire, /*result*/ tmp3_owner); beqz(tmp3_owner, monitor_locked); // Normally, a contended monitor enters the runtime slow path here. @@ -229,7 +229,7 @@ void C2_MacroAssembler::fast_lock(Register obj, Register box, wrs_sto(); bind(retry); cmpxchg(/*addr*/ tmp2_owner_addr, /*expected*/ zr, /*new*/ tid, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ tmp3_owner); + memory_order_acquire, /*result*/ tmp3_owner); beqz(tmp3_owner, monitor_locked); bne(tmp3_owner, tid, slow_path); bind(recursive); @@ -321,7 +321,7 @@ void C2_MacroAssembler::fast_unlock(Register obj, Register box, assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea"); ori(tmp3_t, tmp1_mark, markWord::lock_neutral_value); cmpxchg(/*addr*/ obj, /*expected*/ tmp1_mark, /*new*/ tmp3_t, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, /*result*/ tmp3_t); + memory_order_release, /*result*/ tmp3_t); beq(tmp1_mark, tmp3_t, unlocked); bind(push_and_slow_path); diff --git a/src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad b/src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad index a7fd8ab109b..7521b0a82e6 100644 --- a/src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad +++ b/src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad @@ -392,7 +392,7 @@ instruct g1CompareAndExchangeP(iRegPNoSp res, indirect mem, iRegP oldval, iRegP RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, $newval$$Register /* new_val */, @@ -424,7 +424,7 @@ instruct g1CompareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iRe RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, $newval$$Register /* new_val */, @@ -453,7 +453,7 @@ instruct g1CompareAndExchangeN(iRegNNoSp res, indirect mem, iRegN oldval, iRegN RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -483,7 +483,7 @@ instruct g1CompareAndExchangeNAcq(iRegNNoSp res, indirect mem, iRegN oldval, iRe RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -515,8 +515,8 @@ instruct g1CompareAndSwapP(iRegINoSp res, indirect mem, iRegP newval, iRegPNoSp RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); + memory_order_release, $res$$Register, + /* result as bool */ true); write_barrier_post(masm, this, $mem$$Register /* store_addr */, $newval$$Register /* new_val */, @@ -547,8 +547,8 @@ instruct g1CompareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP newval, iRegPNo RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); + memory_order_seq_cst, $res$$Register, + /* result as bool */ true); write_barrier_post(masm, this, $mem$$Register /* store_addr */, $newval$$Register /* new_val */, @@ -579,8 +579,8 @@ instruct g1CompareAndSwapN(iRegINoSp res, indirect mem, iRegN newval, iRegPNoSp RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); + memory_order_release, $res$$Register, + /* result as bool */ true); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -612,8 +612,8 @@ instruct g1CompareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN newval, iRegPNo RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); + memory_order_seq_cst, $res$$Register, + /* result as bool */ true); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp index 53dc3b79a6b..0d1f603b9cc 100644 --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp @@ -703,14 +703,13 @@ void ShenandoahBarrierSetAssembler::store_c2(const MachNode* node, MacroAssemble void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, MacroAssembler* masm, Register res, Register addr, Register oldval, Register newval, Register tmp1, Register tmp2, Register tmp3, bool exchange, bool narrow, bool is_acquire) { - const Assembler::Aqrl acquire = is_acquire ? Assembler::aq : Assembler::relaxed; - const Assembler::Aqrl release = Assembler::rl; + const atomic_memory_order order = is_acquire ? memory_order_seq_cst : memory_order_release; const Assembler::operand_size size = narrow ? Assembler::uint32 : Assembler::int64; ShenandoahBarrierStubC2::load_store_pre(masm, node, Address(addr), tmp1, tmp2, tmp3, narrow); // CAS! - __ cmpxchg(addr, oldval, newval, size, acquire, release, /* result */ res, !exchange /* result_as_bool */); + __ cmpxchg(addr, oldval, newval, size, order, /* result */ res, /* result_as_bool */ !exchange); ShenandoahBarrierStubC2::load_store_post(masm, node, Address(addr, 0), tmp2, tmp3); } diff --git a/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp index 11f2585dbe8..0ee69fae757 100644 --- a/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp @@ -288,7 +288,7 @@ void ZBarrierSetAssembler::store_barrier_medium(MacroAssembler* masm, }, ZBarrierRelocationFormatStoreGoodBits); __ weak_cmpxchg(rtmp2, zr, rtmp1, Assembler::int64, - Assembler::relaxed /* acquire */, Assembler::relaxed /* release */, + memory_order_relaxed, rtmp3); __ beqz(rtmp3, slow_path); __ bind(slow_path_continuation); diff --git a/src/hotspot/cpu/riscv/gc/z/z_riscv.ad b/src/hotspot/cpu/riscv/gc/z/z_riscv.ad index 54ee404a6a2..3f065f0bf70 100644 --- a/src/hotspot/cpu/riscv/gc/z/z_riscv.ad +++ b/src/hotspot/cpu/riscv/gc/z/z_riscv.ad @@ -183,7 +183,7 @@ instruct zCompareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP newva Address ref_addr($mem$$Register); z_color(masm, $oldval_tmp$$Register, $oldval$$Register, $tmp1$$Register); z_store_barrier(masm, this, ref_addr, $newval$$Register, $newval_tmp$$Register, $tmp1$$Register, true /* is_atomic */); - __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::int64, Assembler::relaxed /* acquire */, Assembler::rl /* release */, $res$$Register, true /* result_as_bool */); + __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::int64, memory_order_release, $res$$Register, /* result_as_bool */ true); %} ins_pipe(pipe_slow); @@ -206,7 +206,7 @@ instruct zCompareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP ne Address ref_addr($mem$$Register); z_color(masm, $oldval_tmp$$Register, $oldval$$Register, $tmp1$$Register); z_store_barrier(masm, this, ref_addr, $newval$$Register, $newval_tmp$$Register, $tmp1$$Register, true /* is_atomic */); - __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::int64, Assembler::aq /* acquire */, Assembler::rl /* release */, $res$$Register, true /* result_as_bool */); + __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::int64, memory_order_seq_cst, $res$$Register, /* result_as_bool */ true); %} ins_pipe(pipe_slow); @@ -227,7 +227,7 @@ instruct zCompareAndExchangeP(iRegPNoSp res, indirect mem, iRegP oldval, iRegP n Address ref_addr($mem$$Register); z_color(masm, $oldval_tmp$$Register, $oldval$$Register, $tmp1$$Register); z_store_barrier(masm, this, ref_addr, $newval$$Register, $newval_tmp$$Register, $tmp1$$Register, true /* is_atomic */); - __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::int64, Assembler::relaxed /* acquire */, Assembler::rl /* release */, $res$$Register); + __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::int64, memory_order_release, $res$$Register); z_uncolor(masm, $res$$Register); %} @@ -249,7 +249,7 @@ instruct zCompareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iReg Address ref_addr($mem$$Register); z_color(masm, $oldval_tmp$$Register, $oldval$$Register, $tmp1$$Register); z_store_barrier(masm, this, ref_addr, $newval$$Register, $newval_tmp$$Register, $tmp1$$Register, true /* is_atomic */); - __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::int64, Assembler::aq /* acquire */, Assembler::rl /* release */, $res$$Register); + __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::int64, memory_order_seq_cst, $res$$Register); z_uncolor(masm, $res$$Register); %} diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index 3960e4c70cb..733464b8b26 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -685,7 +685,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // offset is no longer needed after the address is computed. add(t1, mdp, offset); weak_cmpxchg(/*addr*/ t1, /*expected*/ zr, /*new*/ recv, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::relaxed, /*result*/ t0); + memory_order_relaxed, /*result*/ t0); // 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, @@ -4602,18 +4602,52 @@ void MacroAssembler::cmpxchg_narrow_value_helper(Register addr, Register expecte andr(new_val, new_val, mask); } +// Map a C++11-style memory order onto the RISC-V aq/rl bits applied to the +// load-reserved (acquire) and store-conditional / amocas (release) halves of a CAS. +static void cmpxchg_memory_order_to_aqrl(enum atomic_memory_order order, + Assembler::Aqrl& acquire, Assembler::Aqrl& release) { + switch (order) { + case memory_order_relaxed: + acquire = Assembler::relaxed; + release = Assembler::relaxed; + break; + case memory_order_acquire: + acquire = Assembler::aq; + release = Assembler::relaxed; + break; + case memory_order_release: + acquire = Assembler::relaxed; + release = Assembler::rl; + break; + case memory_order_acq_rel: + acquire = Assembler::aq; + release = Assembler::rl; + break; + case memory_order_seq_cst: + acquire = Assembler::aq; + // Zalasr acquire loads provide RCsc ordering after SC.rl; plain loads need SC.aqrl. + release = UseZalasr ? Assembler::rl : Assembler::aqrl; + break; + default: + ShouldNotReachHere(); + } +} + // cmpxchg_narrow_value will kill t0, t1, expected, new_val and tmps. // It's designed to implement compare and swap byte/boolean/char/short by lr.w/sc.w or amocas.w, // which are forced to work with 4-byte aligned address. void MacroAssembler::cmpxchg_narrow_value(Register addr, Register expected, Register new_val, Assembler::operand_size size, - Assembler::Aqrl acquire, Assembler::Aqrl release, + enum atomic_memory_order order, Register result, bool result_as_bool, Register tmp1, Register tmp2, Register tmp3) { assert(!(UseZacas && UseZabha), "Use amocas"); assert_different_registers(addr, expected, new_val, result, tmp1, tmp2, tmp3, t0, t1); + Assembler::Aqrl acquire, release; + cmpxchg_memory_order_to_aqrl(order, acquire, release); + Register scratch0 = t0, aligned_addr = t1; Register shift = tmp1, mask = tmp2, scratch1 = tmp3; @@ -4694,12 +4728,15 @@ void MacroAssembler::cmpxchg_narrow_value(Register addr, Register expected, void MacroAssembler::weak_cmpxchg_narrow_value(Register addr, Register expected, Register new_val, Assembler::operand_size size, - Assembler::Aqrl acquire, Assembler::Aqrl release, + enum atomic_memory_order order, Register result, Register tmp1, Register tmp2, Register tmp3) { assert(!(UseZacas && UseZabha), "Use amocas"); assert_different_registers(addr, expected, new_val, result, tmp1, tmp2, tmp3, t0, t1); + Assembler::Aqrl acquire, release; + cmpxchg_memory_order_to_aqrl(order, acquire, release); + Register scratch0 = t0, aligned_addr = t1; Register shift = tmp1, mask = tmp2, scratch1 = tmp3; @@ -4763,13 +4800,16 @@ void MacroAssembler::weak_cmpxchg_narrow_value(Register addr, Register expected, void MacroAssembler::cmpxchg(Register addr, Register expected, Register new_val, Assembler::operand_size size, - Assembler::Aqrl acquire, Assembler::Aqrl release, + enum atomic_memory_order order, Register result, bool result_as_bool) { assert((UseZacas && UseZabha) || (size != int8 && size != int16), "unsupported operand size"); assert_different_registers(addr, t0); assert_different_registers(expected, t0); assert_different_registers(new_val, t0); + Assembler::Aqrl acquire, release; + cmpxchg_memory_order_to_aqrl(order, acquire, release); + // NOTE: // Register _result_ may be the same register as _new_val_ or _expected_. // Hence do NOT use _result_ until after 'cas'. @@ -4824,7 +4864,7 @@ void MacroAssembler::cmpxchg(Register addr, Register expected, void MacroAssembler::weak_cmpxchg(Register addr, Register expected, Register new_val, Assembler::operand_size size, - Assembler::Aqrl acquire, Assembler::Aqrl release, + enum atomic_memory_order order, Register result) { assert((UseZacas && UseZabha) || (size != int8 && size != int16), "unsupported operand size"); assert_different_registers(addr, t0); @@ -4832,10 +4872,13 @@ void MacroAssembler::weak_cmpxchg(Register addr, Register expected, assert_different_registers(new_val, t0); if (UseZacas) { - cmpxchg(addr, expected, new_val, size, acquire, release, result, true); + cmpxchg(addr, expected, new_val, size, order, result, true); return; } + Assembler::Aqrl acquire, release; + cmpxchg_memory_order_to_aqrl(order, acquire, release); + Label fail, done; load_reserved(t0, addr, size, acquire); bne(t0, expected, fail); @@ -7357,7 +7400,7 @@ void MacroAssembler::fast_lock(Register basic_lock, Register obj, Register tmp1, xori(t, mark, markWord::lock_neutral_value); cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ t); + memory_order_acquire, /*result*/ t); bne(mark, t, slow, /* is_far */ true); bind(push); @@ -7428,7 +7471,7 @@ void MacroAssembler::fast_unlock(Register obj, Register tmp1, Register tmp2, Reg assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea"); ori(t, mark, markWord::lock_neutral_value); cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, /*result*/ t); + memory_order_release, /*result*/ t); beq(mark, t, unlocked); bind(push_and_slow); diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index 04c42ffa291..7a65ce5a27e 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -32,6 +32,7 @@ #include "code/vmreg.hpp" #include "metaprogramming/enableIf.hpp" #include "oops/compressedOops.hpp" +#include "runtime/atomicAccess.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" #include "runtime/signature.hpp" @@ -1291,12 +1292,12 @@ INSN(sd_release, sd, sd_rl); void cmpxchg(Register addr, Register expected, Register new_val, Assembler::operand_size size, - Assembler::Aqrl acquire, Assembler::Aqrl release, + enum atomic_memory_order order, Register result, bool result_as_bool = false); void weak_cmpxchg(Register addr, Register expected, Register new_val, Assembler::operand_size size, - Assembler::Aqrl acquire, Assembler::Aqrl release, + enum atomic_memory_order order, Register result); void cmpxchg_narrow_value_helper(Register addr, Register expected, Register new_val, Assembler::operand_size size, @@ -1304,13 +1305,13 @@ INSN(sd_release, sd, sd_rl); void cmpxchg_narrow_value(Register addr, Register expected, Register new_val, Assembler::operand_size size, - Assembler::Aqrl acquire, Assembler::Aqrl release, + enum atomic_memory_order order, Register result, bool result_as_bool, Register tmp1, Register tmp2, Register tmp3); void weak_cmpxchg_narrow_value(Register addr, Register expected, Register new_val, Assembler::operand_size size, - Assembler::Aqrl acquire, Assembler::Aqrl release, + enum atomic_memory_order order, Register result, Register tmp1, Register tmp2, Register tmp3); diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 045300920a0..8330122899c 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -1068,9 +1068,6 @@ public: bool is_CAS(int opcode, bool maybe_volatile); -// predicates controlling emission of fences around volatile accesses and CAS -bool cas_leading_membar_elidable(int opcode); -bool cas_trailing_membar_elidable(int opcode); bool unnecessary_acquire(const Node *barrier); bool unnecessary_release(const Node *barrier); bool unnecessary_volatile(const Node *barrier); @@ -1189,124 +1186,6 @@ bool is_CAS(int opcode, bool maybe_volatile) } } -// predicates controlling emit of fence before and after CAS - -// Return true if the trailing MemBarAcquire around a CAS-like LoadStore node -// may be elided, that is if the node is matched to a single AMO instruction -// carrying both the aq and rl annotations: amoswap/amoadd/amocas with .aqrl. -// The leading MemBarRelease has a weaker condition of its own, see -// cas_leading_membar_elidable() below. -// -// Such an AMO is fully-ordered under RVWMO: PPO rule 5 orders it, including -// its store, before every subsequent memory access, and PPO rule 6 orders -// every preceding access before it. That subsumes both membars. -// -// An LR/SC based CAS has different ordering properties from a single AMO. -// The aq annotation on LR orders its load before later accesses (PPO rule 5), -// while the rl annotation on SC orders earlier accesses before its successful -// store (PPO rule 6). These annotations must not be treated as .aqrl on a -// single instruction: for an AMO the acquire annotation covers the store -// event too, so PPO rule 5 also orders the AMO's store before subsequent -// loads. An SC's store carries only a release annotation, so the only rule -// that could order it before a subsequent load is PPO rule 7, which requires -// both accesses to carry RCsc annotations. A subsequent volatile load never -// qualifies: without Zalasr it is a plain load followed by fence r,rw, and -// with Zalasr it is an RCpc acquire load, neither of which is RCsc. -// -// Keep the trailing MemBarAcquire for the existing LR/SC mapping rather than -// extending the single-AMO elision to LR/SC. -bool cas_trailing_membar_elidable(int opcode) -{ - assert(is_CAS(opcode, true), "expecting a compare and swap"); - - switch (opcode) { - // Always matched to the acquiring variant, hence amoswap/amoadd.aqrl. - case Op_GetAndSetI: - case Op_GetAndSetL: - case Op_GetAndSetP: - case Op_GetAndSetN: - case Op_GetAndAddI: - case Op_GetAndAddL: - return true; - // Matched to amocas.{w|d}.aqrl only when Zacas is available. - case Op_CompareAndSwapI: - case Op_CompareAndSwapL: - case Op_CompareAndSwapP: - case Op_CompareAndSwapN: - case Op_CompareAndExchangeI: - case Op_CompareAndExchangeL: - case Op_CompareAndExchangeP: - case Op_CompareAndExchangeN: - case Op_WeakCompareAndSwapI: - case Op_WeakCompareAndSwapL: - case Op_WeakCompareAndSwapP: - case Op_WeakCompareAndSwapN: - return UseZacas; - // Matched to amocas.{b|h}.aqrl only when Zacas and Zabha is available. - case Op_CompareAndSwapB: - case Op_CompareAndSwapS: - case Op_CompareAndExchangeB: - case Op_CompareAndExchangeS: - case Op_WeakCompareAndSwapB: - case Op_WeakCompareAndSwapS: - return UseZacas && UseZabha; - default: - return false; - } -} - -// Return true if the leading MemBarRelease around a CAS-like LoadStore node may -// be elided. The required release ordering places accesses preceding the CAS -// before its successful store, and the rl annotation on that store provides -// this ordering by PPO rule 6. This is the same release property relied on by -// the is_Store() branch of unnecessary_release() for a releasing store. -// An LR/SC pair therefore qualifies here, whereas cas_trailing_membar_elidable() -// above restricts elision to a single AMO carrying both aq and rl annotations. -bool cas_leading_membar_elidable(int opcode) -{ - assert(is_CAS(opcode, true), "expecting a compare and swap"); - - switch (opcode) { - // Matched to amoswap/amoadd.aqrl. - case Op_GetAndSetI: - case Op_GetAndSetL: - case Op_GetAndSetP: - case Op_GetAndSetN: - case Op_GetAndAddI: - case Op_GetAndAddL: - return true; - // Matched to amocas.{w|d}.aqrl with Zacas, to lr.{w|d}.aq + sc.{w|d}.rl - // without it. Both carry the rl annotation on their successful store. - case Op_CompareAndSwapI: - case Op_CompareAndSwapL: - case Op_CompareAndSwapP: - case Op_CompareAndSwapN: - case Op_CompareAndExchangeI: - case Op_CompareAndExchangeL: - case Op_CompareAndExchangeP: - case Op_CompareAndExchangeN: - case Op_WeakCompareAndSwapI: - case Op_WeakCompareAndSwapL: - case Op_WeakCompareAndSwapP: - case Op_WeakCompareAndSwapN: - return true; - // Sub-word forms reach amocas.{b|h}.aqrl with Zacas and Zabha. Otherwise - // they are open coded around lr.w/sc.w or amocas.w, with rl still set on - // the successful store. The Zacas expansion uses lw_acquire for its - // acquiring pre-check; early failure publishes no store, so it does not - // require a leading release fence either. - case Op_CompareAndSwapB: - case Op_CompareAndSwapS: - case Op_CompareAndExchangeB: - case Op_CompareAndExchangeS: - case Op_WeakCompareAndSwapB: - case Op_WeakCompareAndSwapS: - return true; - default: - return false; - } -} - bool unnecessary_acquire(const Node *barrier) { assert(barrier->is_MemBar(), "expecting a membar"); @@ -1320,7 +1199,7 @@ bool unnecessary_acquire(const Node *barrier) if (mb->trailing_load_store()) { Node* load_store = mb->in(MemBarNode::Precedent); assert(load_store->is_LoadStore(), "unexpected graph shape"); - return cas_trailing_membar_elidable(load_store->Opcode()); + return is_CAS(load_store->Opcode(), true); } return false; @@ -1360,7 +1239,7 @@ bool unnecessary_release(const Node *barrier) } else { assert(mem->is_LoadStore(), ""); assert(trailing_mb->Opcode() == Op_MemBarAcquire, ""); - return cas_leading_membar_elidable(mem->Opcode()); + return is_CAS(mem->Opcode(), true); } } return false; @@ -6043,8 +5922,8 @@ instruct compareAndSwapBAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval ins_encode %{ __ cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - Assembler::aq /* acquire */, Assembler::rl /* release */, $res$$Register, - true /* result as bool */, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); + memory_order_seq_cst, $res$$Register, /* result as bool */ true, + $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} ins_pipe(pipe_slow); @@ -6065,8 +5944,7 @@ instruct compareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI new ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - Assembler::aq /* acquire */, Assembler::rl /* release */, $res$$Register, - true /* result as bool */); + memory_order_seq_cst, $res$$Register, /* result as bool */ true); %} ins_pipe(pipe_slow); @@ -6090,8 +5968,8 @@ instruct compareAndSwapSAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldval ins_encode %{ __ cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - Assembler::aq /* acquire */, Assembler::rl /* release */, $res$$Register, - true /* result as bool */, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); + memory_order_seq_cst, $res$$Register, /* result as bool */ true, + $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} ins_pipe(pipe_slow); @@ -6112,8 +5990,7 @@ instruct compareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI new ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - Assembler::aq /* acquire */, Assembler::rl /* release */, $res$$Register, - true /* result as bool */); + memory_order_seq_cst, $res$$Register, /* result as bool */ true); %} ins_pipe(pipe_slow); @@ -6134,8 +6011,7 @@ instruct compareAndSwapIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI new ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); + memory_order_seq_cst, $res$$Register, /* result as bool */ true); %} ins_pipe(pipe_slow); @@ -6156,8 +6032,7 @@ instruct compareAndSwapLAcq(iRegINoSp res, indirect mem, iRegL oldval, iRegL new ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); + memory_order_seq_cst, $res$$Register, /* result as bool */ true); %} ins_pipe(pipe_slow); @@ -6178,8 +6053,7 @@ instruct compareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP new ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); + memory_order_seq_cst, $res$$Register, /* result as bool */ true); %} ins_pipe(pipe_slow); @@ -6200,8 +6074,7 @@ instruct compareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN oldval, iRegN new ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result as bool*/ true); + memory_order_seq_cst, $res$$Register, /* result as bool */ true); %} ins_pipe(pipe_slow); @@ -6230,8 +6103,8 @@ instruct compareAndExchangeB_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldva ins_encode %{ __ cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, - /*result_as_bool*/ false, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); + memory_order_release, $res$$Register, /* result as bool */ false, + $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} ins_pipe(pipe_slow); @@ -6251,7 +6124,7 @@ instruct compareAndExchangeB(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6274,8 +6147,8 @@ instruct compareAndExchangeS_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldva ins_encode %{ __ cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, - /*result_as_bool*/ false, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); + memory_order_release, $res$$Register, /* result as bool */ false, + $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} ins_pipe(pipe_slow); @@ -6295,7 +6168,7 @@ instruct compareAndExchangeS(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6315,7 +6188,7 @@ instruct compareAndExchangeI(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6335,7 +6208,7 @@ instruct compareAndExchangeL(iRegLNoSp res, indirect mem, iRegL oldval, iRegL ne ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6355,7 +6228,7 @@ instruct compareAndExchangeN(iRegNNoSp res, indirect mem, iRegN oldval, iRegN ne ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6375,7 +6248,7 @@ instruct compareAndExchangeP(iRegPNoSp res, indirect mem, iRegP oldval, iRegP ne ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6398,8 +6271,8 @@ instruct compareAndExchangeBAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 ol ins_encode %{ __ cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result_as_bool*/ false, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); + memory_order_seq_cst, $res$$Register, /* result as bool */ false, + $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} ins_pipe(pipe_slow); @@ -6419,7 +6292,7 @@ instruct compareAndExchangeBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6442,8 +6315,8 @@ instruct compareAndExchangeSAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 ol ins_encode %{ __ cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, - /*result_as_bool*/ false, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); + memory_order_seq_cst, $res$$Register, /* result as bool */ false, + $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} ins_pipe(pipe_slow); @@ -6463,7 +6336,7 @@ instruct compareAndExchangeSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6483,7 +6356,7 @@ instruct compareAndExchangeIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6503,7 +6376,7 @@ instruct compareAndExchangeLAcq(iRegLNoSp res, indirect mem, iRegL oldval, iRegL ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6523,7 +6396,7 @@ instruct compareAndExchangeNAcq(iRegNNoSp res, indirect mem, iRegN oldval, iRegN ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6543,7 +6416,7 @@ instruct compareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iRegP ins_encode %{ __ cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6567,7 +6440,7 @@ instruct weakCompareAndSwapB_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldva ins_encode %{ __ weak_cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, + memory_order_release, $res$$Register, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} @@ -6589,7 +6462,7 @@ instruct weakCompareAndSwapB(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6613,7 +6486,7 @@ instruct weakCompareAndSwapS_narrow(iRegINoSp res, indirect mem, iRegI_R12 oldva ins_encode %{ __ weak_cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register, + memory_order_release, $res$$Register, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} @@ -6635,7 +6508,7 @@ instruct weakCompareAndSwapS(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6656,7 +6529,7 @@ instruct weakCompareAndSwapI(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6677,7 +6550,7 @@ instruct weakCompareAndSwapL(iRegINoSp res, indirect mem, iRegL oldval, iRegL ne ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6698,7 +6571,7 @@ instruct weakCompareAndSwapN(iRegINoSp res, indirect mem, iRegN oldval, iRegN ne ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6719,7 +6592,7 @@ instruct weakCompareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP ne ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::relaxed, /*release*/ Assembler::rl, $res$$Register); + memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); @@ -6743,7 +6616,7 @@ instruct weakCompareAndSwapBAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 ol ins_encode %{ __ weak_cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, + memory_order_seq_cst, $res$$Register, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} @@ -6765,7 +6638,7 @@ instruct weakCompareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int8, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6789,7 +6662,7 @@ instruct weakCompareAndSwapSAcq_narrow(iRegINoSp res, indirect mem, iRegI_R12 ol ins_encode %{ __ weak_cmpxchg_narrow_value(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register, + memory_order_seq_cst, $res$$Register, $tmp1$$Register, $tmp2$$Register, $tmp3$$Register); %} @@ -6811,7 +6684,7 @@ instruct weakCompareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int16, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6832,7 +6705,7 @@ instruct weakCompareAndSwapIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6853,7 +6726,7 @@ instruct weakCompareAndSwapLAcq(iRegINoSp res, indirect mem, iRegL oldval, iRegL ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6874,7 +6747,7 @@ instruct weakCompareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN oldval, iRegN ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::uint32, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); @@ -6895,7 +6768,7 @@ instruct weakCompareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP ins_encode %{ __ weak_cmpxchg(as_Register($mem$$base), $oldval$$Register, $newval$$Register, Assembler::int64, - /*acquire*/ Assembler::aq, /*release*/ Assembler::rl, $res$$Register); + memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); diff --git a/test/hotspot/gtest/riscv/test_assembler_riscv.cpp b/test/hotspot/gtest/riscv/test_assembler_riscv.cpp index 6281144fed6..40fb2488cfc 100644 --- a/test/hotspot/gtest/riscv/test_assembler_riscv.cpp +++ b/test/hotspot/gtest/riscv/test_assembler_riscv.cpp @@ -136,7 +136,7 @@ class CmpxchgTester { if (using_narrow()) { address entry = _masm.pc(); _masm.cmpxchg_narrow_value(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2, - ASMSIZE, Assembler::relaxed, Assembler::relaxed, + ASMSIZE, memory_order_relaxed, /*result*/ c_rarg3, boolean_result, c_rarg4, c_rarg5, c_rarg6); /* Uses also t0-t1, caller saved */ _masm.mv(c_rarg0, c_rarg3); _masm.ret(); @@ -145,28 +145,28 @@ class CmpxchgTester { switch(variant) { default: _masm.cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2, - ASMSIZE, Assembler::aq, Assembler::rl, + ASMSIZE, memory_order_seq_cst, /*result*/ c_rarg3, boolean_result); _masm.mv(c_rarg0, c_rarg3); break; case 1: // expected == result _masm.cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2, - ASMSIZE, Assembler::aq, Assembler::rl, + ASMSIZE, memory_order_seq_cst, /*result*/ c_rarg1, boolean_result); _masm.mv(c_rarg0, c_rarg1); break; case 2: // new_value == result _masm.cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/c_rarg2, - ASMSIZE, Assembler::aq, Assembler::rl, + ASMSIZE, memory_order_seq_cst, /*result*/ c_rarg2, boolean_result); _masm.mv(c_rarg0, c_rarg2); break; case 3: // expected == new_value _masm.cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/ c_rarg1, - ASMSIZE, Assembler::aq, Assembler::rl, + ASMSIZE, memory_order_seq_cst, /*result*/ c_rarg2, boolean_result); _masm.mv(c_rarg0, c_rarg2); break; @@ -581,7 +581,7 @@ class WeakCmpxchgTester { if (using_narrow()) { address entry = _masm.pc(); _masm.weak_cmpxchg_narrow_value(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/ c_rarg2, - ASMSIZE, Assembler::relaxed, Assembler::relaxed, + ASMSIZE, memory_order_relaxed, /*result*/ c_rarg3, c_rarg4, c_rarg5, c_rarg6); /* Uses also t0-t1, caller saved */ _masm.mv(c_rarg0, c_rarg3); _masm.ret(); @@ -589,7 +589,7 @@ class WeakCmpxchgTester { } else { address entry = _masm.pc(); _masm.weak_cmpxchg(/*addr*/ c_rarg0, /*expected*/ c_rarg1, /*new_value*/ c_rarg2, - ASMSIZE, Assembler::relaxed, Assembler::relaxed, /*result*/ c_rarg3); + ASMSIZE, memory_order_relaxed, /*result*/ c_rarg3); _masm.mv(c_rarg0, c_rarg3); _masm.ret(); _weak = ((weak_cmpxchg_func)entry); From dac5598774a02c6787bffae5236bf14974a270c2 Mon Sep 17 00:00:00 2001 From: Yunbo Zhang Date: Tue, 13 Oct 2026 04:15:39 +0000 Subject: [PATCH 055/101] 8384612: Test serviceability/dcmd/gc/HeapDumpParallelTest.java failed on a single core machine Reviewed-by: lmesnik, sspitsyn --- .../dcmd/gc/HeapDumpParallelTest.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java index 7eecf527850..71507fa6e70 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java @@ -25,14 +25,12 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.test.lib.Asserts; import jdk.test.lib.JDKToolLauncher; -import jdk.test.lib.Utils; import jdk.test.lib.apps.LingeredApp; import jdk.test.lib.dcmd.PidJcmdExecutor; import jdk.test.lib.process.OutputAnalyzer; @@ -42,7 +40,7 @@ /** * @test - * @bug 8306441 8319053 + * @bug 8306441 8319053 8384612 * @summary Verify the integrity of generated heap dump and capability of parallel dump * @library /test/lib * @run main HeapDumpParallelTest @@ -57,12 +55,13 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File dcmdOut.shouldContain("Heap dump file created"); OutputAnalyzer appOut = new OutputAnalyzer(app.getProcessStdout()); appOut.shouldMatch("\\[heapdump *\\]"); - String opts = Arrays.asList(Utils.getTestJavaOpts()).toString(); - if (opts.contains("-XX:+UseSerialGC") || opts.contains("-XX:+UseEpsilonGC")) { - System.out.println("UseSerialGC detected."); + // Check the target VM's selected GC, including ergonomic selection. + appOut.shouldMatch("\\[gc *\\] Using "); + if (appOut.getStdout().contains("Using Serial") || appOut.getStdout().contains("Using Epsilon")) { + System.out.println("Serial or Epsilon GC detected."); expectSerial = true; } - if (!expectSerial && Runtime.getRuntime().availableProcessors() > 1) { + if (!expectSerial) { appOut.shouldContain("Dump heap objects in parallel"); appOut.shouldContain("Merge heap files complete"); } else { @@ -86,7 +85,7 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File private static LingeredApp launchApp() throws IOException { LingeredApp theApp = new LingeredApp(); - LingeredApp.startApp(theApp, "-Xlog:heapdump", "-Xmx512m", + LingeredApp.startApp(theApp, "-Xlog:gc,heapdump", "-Xmx512m", "-XX:-UseDynamicNumberOfGCThreads", "-XX:ParallelGCThreads=2"); return theApp; @@ -118,7 +117,7 @@ public static void main(String[] args) throws Exception { test(heapDumpFile, "-parallel=" + Integer.MAX_VALUE, false); // Expect parallel dump - test(heapDumpFile, "-gz=9 -overwrite -parallel=" + Runtime.getRuntime().availableProcessors(), false); + test(heapDumpFile, "-gz=9 -overwrite -parallel=" + Math.max(Runtime.getRuntime().availableProcessors(), 2), false); } finally { theApp.stopApp(); } From 6386689966fea6daaf5cb1e3face5d19c64943cc Mon Sep 17 00:00:00 2001 From: Vladimir Petko Date: Tue, 13 Oct 2026 04:38:18 +0000 Subject: [PATCH 056/101] 8387577: [s390x] TestSharedCloseJFR.java failure due to null pc Reviewed-by: rrich, amitkumar --- src/hotspot/cpu/s390/frame_s390.cpp | 10 +++++- src/hotspot/cpu/s390/frame_s390.hpp | 5 ++- src/hotspot/cpu/s390/interp_masm_s390.cpp | 3 +- src/hotspot/cpu/s390/sharedRuntime_s390.cpp | 36 +++++++++++++------ .../linux_s390/javaThread_linux_s390.cpp | 17 ++++++--- .../sharedclosejfr/TestSharedCloseJFR.java | 4 +-- 6 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/hotspot/cpu/s390/frame_s390.cpp b/src/hotspot/cpu/s390/frame_s390.cpp index dab31481c92..35d325ceb84 100644 --- a/src/hotspot/cpu/s390/frame_s390.cpp +++ b/src/hotspot/cpu/s390/frame_s390.cpp @@ -231,7 +231,15 @@ frame frame::sender_for_entry_frame(RegisterMap *map) const { frame fr(jfa->last_Java_sp(), jfa->last_Java_pc()); return fr; } - // Last_java_pc is not set if we come here from compiled code. + +#ifdef ASSERT + // Last_java_pc is not set if we come here from C2 runtime stubs. + address return_pc = (address)((frame::z_common_abi*)jfa->last_Java_sp())->return_pc; + CodeBlob* cb = CodeCache::find_blob(return_pc); + assert(cb != nullptr, "The return address must be a CodeBlob"); + assert(cb->is_runtime_stub(), "The return address must be a runtime stub"); +#endif + frame fr(jfa->last_Java_sp()); return fr; } diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp index afbc5923466..2946ed60d3e 100644 --- a/src/hotspot/cpu/s390/frame_s390.hpp +++ b/src/hotspot/cpu/s390/frame_s390.hpp @@ -76,7 +76,10 @@ log_2_of_alignment_in_bits = 6 } frame_constants; - // Common ABI. On top of all frames, C and Java + // Common ABI. On top of all frames, C and Java. + // Note: return_pc is only valid for Java calls. + // Calls following the native convention would use + // z_abi_160_base::gpr14 to save their return pc. struct z_common_abi { uint64_t callers_sp; uint64_t return_pc; diff --git a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp index 4e5d3c8f148..c3775f8038b 100644 --- a/src/hotspot/cpu/s390/interp_masm_s390.cpp +++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp @@ -1093,7 +1093,8 @@ void InterpreterMacroAssembler::remove_activation(TosState state, branch_optimized(Assembler::bcondAlways, fast_path); bind (slow_path); push(state); - set_last_Java_frame(Z_SP, noreg); + get_PC(Z_R1_scratch); + set_last_Java_frame(Z_SP, Z_R1_scratch); call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::at_unwind), Z_thread); reset_last_Java_frame(); pop(state); diff --git a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp index fe7325aaa99..afb5ef7162b 100644 --- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp +++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp @@ -1126,9 +1126,11 @@ static void gen_c2i_adapter(MacroAssembler *masm, // objects. Allocate the buffers here with a runtime call. OopMap* map = RegisterSaver::save_live_registers(masm, RegisterSaver::all_registers); + Label frame_pc; + __ z_larl(Z_R1_scratch, frame_pc); frame_complete = __ offset(); - __ set_last_Java_frame(/*sp=*/Z_SP, /*pc=*/noreg); + __ set_last_Java_frame(/*sp=*/Z_SP, /*pc=*/Z_R1_scratch); __ z_lgr(Z_ARG1, Z_thread); __ z_lgr(Z_ARG2, Z_method); @@ -1138,6 +1140,7 @@ static void gen_c2i_adapter(MacroAssembler *masm, // TODO: use blob-relative offset, matching the pattern in the rest of // sharedRuntime_s390.cpp ("offset() - start_off") oop_maps->add_gc_map((int)(__ offset() - (start - masm->code()->insts_begin())), map); + __ bind(frame_pc); __ reset_last_Java_frame(); RegisterSaver::restore_live_registers(masm, RegisterSaver::all_registers); @@ -3433,15 +3436,16 @@ void SharedRuntime::generate_deopt_blob() { // call `last_Java_frame()'. however we can't block and no gc will // occur so we don't need an oopmap. the value of the pc in the // frame is not particularly important. it just needs to identify the blob. + Label frame_pc; + __ z_larl(Z_R1_scratch, frame_pc); + __ set_last_Java_frame(/*sp*/Z_SP, Z_R1_scratch); - // Don't set last_Java_pc anymore here (is implicitly null then). - // the correct PC is retrieved in pd_last_frame() in that case. - __ set_last_Java_frame(/*sp*/Z_SP, noreg); // With EscapeAnalysis turned on, this call may safepoint // despite it's marked as "leaf call"! __ call_VM_leaf(CAST_FROM_FN_PTR(address, Deoptimization::fetch_unroll_info), Z_thread, exec_mode_reg); - // Set an oopmap for the call site this describes all our saved volatile registers int oop_map_offs = __ offset(); + __ bind(frame_pc); + // Set an oopmap for the call site this describes all our saved volatile registers oop_maps->add_gc_map(oop_map_offs, map); __ reset_last_Java_frame(); @@ -3710,10 +3714,13 @@ SafepointBlob* SharedRuntime::generate_handler_blob(StubId id, address call_ptr) __ z_lgr(Z_R6, Z_R14); } + Label frame_pc; + __ z_larl(Z_R1_scratch, frame_pc); + // The following is basically a call_VM. However, we need the precise // address of the call in order to generate an oopmap. Hence, we do all the // work ourselves. - __ set_last_Java_frame(Z_SP, noreg); + __ set_last_Java_frame(Z_SP, Z_R1_scratch); // call into the runtime to handle the safepoint poll __ call_VM_leaf(call_ptr, Z_thread); @@ -3725,6 +3732,7 @@ SafepointBlob* SharedRuntime::generate_handler_blob(StubId id, address call_ptr) // debug-info recordings, as well as let GC find all oops. oop_maps->add_gc_map((int)(__ offset()-start_off), map); + __ bind(frame_pc); Label noException; @@ -4206,11 +4214,15 @@ RuntimeStub* SharedRuntime::generate_jfr_write_checkpoint() { address start = __ pc(); __ save_return_pc(); // save return_pc (Z_R14) __ push_frame_abi160(0); + Label frame_pc; + __ z_larl(Z_R1_scratch, frame_pc); int frame_complete = __ pc() - start; - __ set_last_Java_frame(Z_SP, noreg); + __ set_last_Java_frame(Z_SP, Z_R1_scratch); __ call_VM_leaf(CAST_FROM_FN_PTR(address, JfrIntrinsicSupport::write_checkpoint), Z_thread); - address calls_return_pc = __ last_calls_return_pc(); + address calls_return_pc = __ pc(); + assert(calls_return_pc == __ last_calls_return_pc(), "frame_pc should be equal to the call's return pc"); + __ bind(frame_pc); __ reset_last_Java_frame(); // The handle is dereferenced through a load barrier. @@ -4241,11 +4253,15 @@ RuntimeStub* SharedRuntime::generate_jfr_return_lease() { address start = __ pc(); __ save_return_pc(); // save return_pc (Z_R14) __ push_frame_abi160(0); + Label frame_pc; + __ z_larl(Z_R1_scratch, frame_pc); int frame_complete = __ pc() - start; - __ set_last_Java_frame(Z_SP, noreg); + __ set_last_Java_frame(Z_SP, Z_R1_scratch); __ call_VM_leaf(CAST_FROM_FN_PTR(address, JfrIntrinsicSupport::return_lease), Z_thread); - address calls_return_pc = __ last_calls_return_pc(); + address calls_return_pc = __ pc(); + assert(calls_return_pc == __ last_calls_return_pc(), "frame_pc should be equal to the call's return pc"); + __ bind(frame_pc); __ reset_last_Java_frame(); diff --git a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp index 5da39b13a14..6643187170a 100644 --- a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp +++ b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2022 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -33,12 +33,19 @@ frame JavaThread::pd_last_frame() { intptr_t* sp = last_Java_sp(); address pc = _anchor.last_Java_pc(); - // Last_Java_pc is not set if we come here from compiled code. - // Assume spill slot for Z_R14 (return register) contains a suitable pc. - // Should have been filled by method entry code. +#ifdef ASSERT + // pc == nullptr is a valid case for C2 runtime stubs + // as they do not set last_Java_pc. + // C2 stubs set their caller's SP as last_Java_sp and store their return pc + // in the caller's frame::z_common_abi::return_pc. + // where frame::setup() finds and reads it. if (pc == nullptr) { - pc = (address) *(sp + 14); + address return_pc = (address)((frame::z_common_abi*)sp)->return_pc; + CodeBlob* cb = CodeCache::find_blob(return_pc); + assert(cb != nullptr, "The return address must be a CodeBlob"); + assert(cb->is_runtime_stub(), "The return address must be a runtime stub"); } +#endif return frame(sp, pc); } diff --git a/test/jdk/java/foreign/sharedclosejfr/TestSharedCloseJFR.java b/test/jdk/java/foreign/sharedclosejfr/TestSharedCloseJFR.java index 913be05ac50..01a52102246 100644 --- a/test/jdk/java/foreign/sharedclosejfr/TestSharedCloseJFR.java +++ b/test/jdk/java/foreign/sharedclosejfr/TestSharedCloseJFR.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 8370344 + * @bug 8370344 8387577 * @requires os.family != "windows" * @requires vm.flavor != "zero" * @requires vm.hasJFR From 2495eaecc1eea890292f7cf8ba2128364097ea81 Mon Sep 17 00:00:00 2001 From: Tobias Hartmann Date: Tue, 13 Oct 2026 06:16:24 +0000 Subject: [PATCH 057/101] 8393995: Some tests fail due to unexpected flags after JDK-8393182 Reviewed-by: shade, kvn --- test/jdk/java/util/Arrays/ArrayObjectMethods.java | 1 + test/jdk/java/util/Arrays/SortingNearlySortedPrimitive.java | 3 +-- test/jdk/valhalla/valuetypes/NullRestrictedTest.java | 1 + test/jdk/valhalla/valuetypes/Reflection.java | 1 + test/jdk/valhalla/valuetypes/ValueClassPreviewTest.java | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/util/Arrays/ArrayObjectMethods.java b/test/jdk/java/util/Arrays/ArrayObjectMethods.java index a33b399bfdf..edcbb69e2c8 100644 --- a/test/jdk/java/util/Arrays/ArrayObjectMethods.java +++ b/test/jdk/java/util/Arrays/ArrayObjectMethods.java @@ -28,6 +28,7 @@ * @author Josh Bloch, Martin Buchholz * @key randomness * @library /test/lib + * @run main/othervm -Xmx64m ${test.main.class} */ import java.util.*; diff --git a/test/jdk/java/util/Arrays/SortingNearlySortedPrimitive.java b/test/jdk/java/util/Arrays/SortingNearlySortedPrimitive.java index b9ad458688e..51440ca5eeb 100644 --- a/test/jdk/java/util/Arrays/SortingNearlySortedPrimitive.java +++ b/test/jdk/java/util/Arrays/SortingNearlySortedPrimitive.java @@ -29,8 +29,7 @@ * code that detects patterns in the array to determine if it is nearly * sorted and if so employs and optimizes merge sort rather than a * Dual-Pivot QuickSort. - * - * @run junit SortingNearlySortedPrimitive + * @run junit/othervm -Xmx64m SortingNearlySortedPrimitive */ diff --git a/test/jdk/valhalla/valuetypes/NullRestrictedTest.java b/test/jdk/valhalla/valuetypes/NullRestrictedTest.java index 1bea720ca06..43ad325d19d 100644 --- a/test/jdk/valhalla/valuetypes/NullRestrictedTest.java +++ b/test/jdk/valhalla/valuetypes/NullRestrictedTest.java @@ -27,6 +27,7 @@ * @summary Test reflection and method handle on accessing a field of a null-restricted value class * that may be flattened or non-flattened * @enablePreview + * @requires (vm.opt.PreloadClasses == null | vm.opt.PreloadClasses == "true") * @run junit/othervm NullRestrictedTest * @run junit/othervm -XX:+UnlockDiagnosticVMOptions -XX:-UseFieldFlattening NullRestrictedTest */ diff --git a/test/jdk/valhalla/valuetypes/Reflection.java b/test/jdk/valhalla/valuetypes/Reflection.java index bcf68372ed8..f0fb5c20da3 100644 --- a/test/jdk/valhalla/valuetypes/Reflection.java +++ b/test/jdk/valhalla/valuetypes/Reflection.java @@ -26,6 +26,7 @@ * @test * @summary test core reflection on value classes * @enablePreview + * @requires (vm.opt.PreloadClasses == null | vm.opt.PreloadClasses == "true") * @run junit/othervm Reflection */ diff --git a/test/jdk/valhalla/valuetypes/ValueClassPreviewTest.java b/test/jdk/valhalla/valuetypes/ValueClassPreviewTest.java index 377d61071bd..ed259be03d9 100644 --- a/test/jdk/valhalla/valuetypes/ValueClassPreviewTest.java +++ b/test/jdk/valhalla/valuetypes/ValueClassPreviewTest.java @@ -27,6 +27,7 @@ * @modules java.base/jdk.internal.value * @library /test/lib * @enablePreview + * @requires (vm.opt.UseFieldFlattening == null | vm.opt.UseFieldFlattening == "true") * @compile ${test.file} * @run driver jdk.test.lib.helpers.StrictProcessor * ValueClassPreviewTest$StrictStuff From aacc17aa99de36d8f926d05274ae80d2b1979cba Mon Sep 17 00:00:00 2001 From: Richard Reingruber Date: Tue, 13 Oct 2026 06:32:50 +0000 Subject: [PATCH 058/101] 8394255: PPC64: Assembler::is_within_range_of_b() and ::is_within_range_of_bcxx are too strict Reviewed-by: mdoerr --- src/hotspot/cpu/ppc/assembler_ppc.inline.hpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp index ec45c96415e..df2ef98de5c 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp @@ -499,11 +499,10 @@ inline bool Assembler::is_within_range_of_b(address a, address pc) { // Guard against illegal branch targets, e.g. -1 (see CompiledDirectCall and ad-file). if ((((uint64_t)a) & 0x3) != 0) return false; - const int range = 1 << (29-6); // li field is from bit 6 to bit 29. - int value = disp(intptr_t(a), intptr_t(pc)); - bool result = -range <= value && value < range-1; + int disp_instr_words = disp(intptr_t(a), intptr_t(pc)); + bool result = is_simm(disp_instr_words, 24); // li field is 24 bits. #ifdef ASSERT - if (result) li(value); // Assert that value is in correct range. + if (result) li(disp_instr_words); // Assert that value is in correct range. #endif return result; } @@ -513,11 +512,10 @@ inline bool Assembler::is_within_range_of_bcxx(address a, address pc) { // Guard against illegal branch targets, e.g. -1 (see CompiledDirectCall and ad-file). if ((((uint64_t)a) & 0x3) != 0) return false; - const int range = 1 << (29-16); // bd field is from bit 16 to bit 29. - int value = disp(intptr_t(a), intptr_t(pc)); - bool result = -range <= value && value < range-1; + int disp_instr_words = disp(intptr_t(a), intptr_t(pc)); + bool result = is_simm(disp_instr_words, 14); // bd field is 14 bits. #ifdef ASSERT - if (result) bd(value); // Assert that value is in correct range. + if (result) bd(disp_instr_words); // Assert that value is in correct range. #endif return result; } From 97f3240e291728f9fdc1d79d6fe54aea18e88d08 Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Tue, 13 Oct 2026 06:40:44 +0000 Subject: [PATCH 059/101] 8392087: Test compiler/vectorapi/VectorMaskCompareNotTest.java fails IR verification Reviewed-by: erfang, thartmann --- test/hotspot/jtreg/ProblemList.txt | 4 - .../vectorapi/VectorCompareWithZeroTest.java | 33 +- .../vectorapi/VectorMaskCompareNotTest.java | 492 ++++++------------ 3 files changed, 175 insertions(+), 354 deletions(-) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 922567005a8..3c5950c771a 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -69,10 +69,6 @@ compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64 -compiler/vectorapi/AllBitsSetVectorMatchRuleTest.java 8391806 generic-aarch64 - -compiler/vectorapi/VectorMaskCompareNotTest.java 8392087 windows-x64 - compiler/vectorapi/VectorExpressionFuzzer.java 8337281 linux-aarch64,macosx-aarch64 compiler/igvn/TestIncorrectDeadPathTypeNode.java 8392354 generic-all diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorCompareWithZeroTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorCompareWithZeroTest.java index 3d939ccdd22..26e159fb768 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorCompareWithZeroTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorCompareWithZeroTest.java @@ -159,8 +159,7 @@ private static void assertArrayEquals(double[] a, boolean[] r, DoubleOp f) { } @Test - @IR(counts = { IRNode.VMASK_CMP_ZERO_I_NEON, ">= 1" }, - applyIf = { "TieredCompilation", "true" }) + @IR(counts = { IRNode.VMASK_CMP_ZERO_I_NEON, ">= 1" }) public static void testByteVectorEqualToZero() { ByteVector av = ByteVector.fromArray(B_SPECIES, ba, 0); av.compare(VectorOperators.EQ, 0).intoArray(br, 0); @@ -173,8 +172,7 @@ public static void testByteVectorEqualToZero_runner() { } @Test - @IR(counts = { IRNode.VMASK_CMP_ZERO_I_NEON, ">= 1" }, - applyIf = { "TieredCompilation", "true" }) + @IR(counts = { IRNode.VMASK_CMP_ZERO_I_NEON, ">= 1" }) public static void testShortVectorNotEqualToZero() { ShortVector av = ShortVector.fromArray(S_SPECIES, sa, 0); av.compare(VectorOperators.NE, 0).intoArray(sr, 0); @@ -187,8 +185,7 @@ public static void testShortVectorNotEqualToZero_runner() { } @Test - @IR(counts = { IRNode.VMASK_CMP_ZERO_I_NEON, ">= 1" }, - applyIf = { "TieredCompilation", "true" }) + @IR(counts = { IRNode.VMASK_CMP_ZERO_I_NEON, ">= 1" }) public static void testIntVectorGreaterEqualToZero() { IntVector av = IntVector.fromArray(I_SPECIES, ia, 0); av.compare(VectorOperators.GE, 0).intoArray(ir, 0); @@ -201,8 +198,7 @@ public static void testIntVectorGreaterEqualToZero_runner() { } @Test - @IR(counts = { IRNode.VMASK_CMP_ZERO_L_NEON, ">= 1" }, - applyIf = { "TieredCompilation", "true" }) + @IR(counts = { IRNode.VMASK_CMP_ZERO_L_NEON, ">= 1" }) public static void testLongVectorGreaterThanZero() { LongVector av = LongVector.fromArray(L_SPECIES, la, 0); av.compare(VectorOperators.GT, 0).intoArray(lr, 0); @@ -215,8 +211,7 @@ public static void testLongVectorGreaterThanZero_runner() { } @Test - @IR(counts = { IRNode.VMASK_CMP_ZERO_F_NEON, ">= 1" }, - applyIf = { "TieredCompilation", "true" }) + @IR(counts = { IRNode.VMASK_CMP_ZERO_F_NEON, ">= 1" }) public static void testFloatVectorLessEqualToZero() { FloatVector av = FloatVector.fromArray(F_SPECIES, fa, 0); av.compare(VectorOperators.LE, 0).intoArray(fr, 0); @@ -229,8 +224,7 @@ public static void testFloatVectorLessEqualToZero_runner() { } @Test - @IR(counts = { IRNode.VMASK_CMP_ZERO_D_NEON, ">= 1" }, - applyIf = { "TieredCompilation", "true" }) + @IR(counts = { IRNode.VMASK_CMP_ZERO_D_NEON, ">= 1" }) public static void testDoubleVectorLessThanZero() { DoubleVector av = DoubleVector.fromArray(D_SPECIES, da, 0); av.compare(VectorOperators.LT, 0).intoArray(dr, 0); @@ -243,31 +237,20 @@ public static void testDoubleVectorLessThanZero_runner() { } @Test - @IR(failOn = { IRNode.VMASK_CMP_ZERO_I_NEON }, - applyIf = { "TieredCompilation", "true" }) + @IR(failOn = { IRNode.VMASK_CMP_ZERO_I_NEON }) public static void testIntVectorUnsignedCondition() { IntVector av = IntVector.fromArray(I_SPECIES, ia, 0); av.compare(VectorOperators.UGT, 0).intoArray(ir, 0); } @Test - @IR(failOn = { IRNode.VMASK_CMP_ZERO_L_NEON }, - applyIf = { "TieredCompilation", "true" }) + @IR(failOn = { IRNode.VMASK_CMP_ZERO_L_NEON }) public static void testLongVectorUnsignedCondition() { LongVector av = LongVector.fromArray(L_SPECIES, la, 0); av.compare(VectorOperators.UGE, 0).intoArray(lr, 0); } public static void main(String[] args) { - // The @IR rules in this test verify that the optimized NEON compare-with-zero - // instructions (vmaskcmp_zero*_neon) are generated. IncrementalInlineVector is - // enabled by default; when a vector intrinsic fails to intrinsify, inlining its - // fallback implementation enlarges the compilation unit and, under unstable profiling, - // may prevent AbstractMask::intoArray() from being inlined. When intoArray() is not - // inlined, the mask is boxed before the call, which breaks the compare-with-zero - // match so the expected vmaskcmp_zero*_neon nodes are not produced. This only happens - // under unstable profiling (which makes the intoArray inlining decision - // non-deterministic); under the default profiling the match is stable. TestFramework testFramework = new TestFramework(); testFramework.setDefaultWarmup(10000) .addFlags("--add-modules=jdk.incubator.vector") diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCompareNotTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCompareNotTest.java index f895283160a..a5da6a87cd7 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCompareNotTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCompareNotTest.java @@ -276,8 +276,7 @@ public static void testCompareMaskNotDouble(VectorSpecies vs, VectorOper @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -289,8 +288,7 @@ public static void testCompareEQMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -302,8 +300,7 @@ public static void testCompareNEMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -315,8 +312,7 @@ public static void testCompareLTMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -328,8 +324,7 @@ public static void testCompareGTMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -341,8 +336,7 @@ public static void testCompareLEMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -354,8 +348,7 @@ public static void testCompareGEMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -367,8 +360,7 @@ public static void testCompareULTMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -380,8 +372,7 @@ public static void testCompareUGTMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -393,8 +384,7 @@ public static void testCompareULEMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -407,8 +397,7 @@ public static void testCompareUGEMaskNotByte() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -420,8 +409,7 @@ public static void testCompareEQMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -433,8 +421,7 @@ public static void testCompareNEMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -446,8 +433,7 @@ public static void testCompareLTMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -459,8 +445,7 @@ public static void testCompareGTMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -472,8 +457,7 @@ public static void testCompareLEMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -485,8 +469,7 @@ public static void testCompareGEMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -498,8 +481,7 @@ public static void testCompareULTMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -511,8 +493,7 @@ public static void testCompareUGTMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -524,8 +505,7 @@ public static void testCompareULEMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -538,8 +518,7 @@ public static void testCompareUGEMaskNotShort() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -551,8 +530,7 @@ public static void testCompareEQMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -564,8 +542,7 @@ public static void testCompareNEMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -577,8 +554,7 @@ public static void testCompareLTMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -590,8 +566,7 @@ public static void testCompareGTMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -603,8 +578,7 @@ public static void testCompareLEMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -616,8 +590,7 @@ public static void testCompareGEMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -629,8 +602,7 @@ public static void testCompareULTMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -642,8 +614,7 @@ public static void testCompareUGTMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -655,8 +626,7 @@ public static void testCompareULEMaskNotInt() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -670,8 +640,7 @@ public static void testCompareUGEMaskNotInt() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareEQMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.EQ, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.EQ); @@ -687,8 +656,7 @@ public static void testCompareEQMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareNEMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.NE, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.NE); @@ -704,8 +672,7 @@ public static void testCompareNEMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareLTMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.LT, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.LT); @@ -721,8 +688,7 @@ public static void testCompareLTMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareGTMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.GT, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.GT); @@ -738,8 +704,7 @@ public static void testCompareGTMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareLEMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.LE, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.LE); @@ -755,8 +720,7 @@ public static void testCompareLEMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareGEMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.GE, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.GE); @@ -772,8 +736,7 @@ public static void testCompareGEMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareULTMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.ULT, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.ULT); @@ -789,8 +752,7 @@ public static void testCompareULTMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareUGTMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.UGT, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.UGT); @@ -806,8 +768,7 @@ public static void testCompareUGTMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareULEMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.ULE, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.ULE); @@ -823,8 +784,7 @@ public static void testCompareULEMaskNotLong() { IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CAST, "= 1", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareUGEMaskNotLong() { testCompareMaskNotLong(L_SPECIES, VectorOperators.UGE, (m) -> { return m.not(); }); verifyResultsLong(L_SPECIES, VectorOperators.UGE); @@ -839,8 +799,7 @@ public static void testCompareUGEMaskNotLong() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareEQMaskNotFloat() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.EQ, fa, fb, (m) -> { return m.not(); }); verifyResultsFloat(F_SPECIES, VectorOperators.EQ, fa, fb); @@ -852,8 +811,7 @@ public static void testCompareEQMaskNotFloat() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareNEMaskNotFloat() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.NE, fa, fb, (m) -> { return m.not(); }); verifyResultsFloat(F_SPECIES, VectorOperators.NE, fa, fb); @@ -865,8 +823,7 @@ public static void testCompareNEMaskNotFloat() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareEQMaskNotFloatNaN() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.EQ, fa, fnan, (m) -> { return m.not(); }); verifyResultsFloat(F_SPECIES, VectorOperators.EQ, fa, fnan); @@ -878,8 +835,7 @@ public static void testCompareEQMaskNotFloatNaN() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareNEMaskNotFloatNaN() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.NE, fa, fnan, (m) -> { return m.not(); }); verifyResultsFloat(F_SPECIES, VectorOperators.NE, fa, fnan); @@ -891,8 +847,7 @@ public static void testCompareNEMaskNotFloatNaN() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareEQMaskNotFloatPositiveInfinity() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.EQ, fa, fpinf, (m) -> { return m.not(); }); verifyResultsFloat(F_SPECIES, VectorOperators.EQ, fa, fpinf); @@ -904,8 +859,7 @@ public static void testCompareEQMaskNotFloatPositiveInfinity() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareNEMaskNotFloatPositiveInfinity() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.NE, fa, fpinf, (m) -> { return m.not(); }); verifyResultsFloat(F_SPECIES, VectorOperators.NE, fa, fpinf); @@ -917,8 +871,7 @@ public static void testCompareNEMaskNotFloatPositiveInfinity() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareEQMaskNotFloatNegativeInfinity() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.EQ, fa, fninf, (m) -> { return m.not(); }); verifyResultsFloat(F_SPECIES, VectorOperators.EQ, fa, fninf); @@ -930,8 +883,7 @@ public static void testCompareEQMaskNotFloatNegativeInfinity() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx", "true", "rvv", "true" }) public static void testCompareNEMaskNotFloatNegativeInfinity() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.NE, fa, fninf, (m) -> { return m.not(); }); verifyResultsFloat(F_SPECIES, VectorOperators.NE, fa, fninf); @@ -943,8 +895,7 @@ public static void testCompareNEMaskNotFloatNegativeInfinity() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareEQMaskNotDouble() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.EQ, da, db, (m) -> { return m.not(); }); verifyResultsDouble(D_SPECIES, VectorOperators.EQ, da, db); @@ -956,8 +907,7 @@ public static void testCompareEQMaskNotDouble() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareNEMaskNotDouble() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.NE, da, db, (m) -> { return m.not(); }); verifyResultsDouble(D_SPECIES, VectorOperators.NE, da, db); @@ -969,8 +919,7 @@ public static void testCompareNEMaskNotDouble() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareEQMaskNotDoubleNaN() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.EQ, da, dnan, (m) -> { return m.not(); }); verifyResultsDouble(D_SPECIES, VectorOperators.EQ, da, dnan); @@ -982,8 +931,7 @@ public static void testCompareEQMaskNotDoubleNaN() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareNEMaskNotDoubleNaN() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.NE, da, dnan, (m) -> { return m.not(); }); verifyResultsDouble(D_SPECIES, VectorOperators.NE, da, dnan); @@ -995,8 +943,7 @@ public static void testCompareNEMaskNotDoubleNaN() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareEQMaskNotDoublePositiveInfinity() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.EQ, da, dpinf, (m) -> { return m.not(); }); verifyResultsDouble(D_SPECIES, VectorOperators.EQ, da, dpinf); @@ -1008,8 +955,7 @@ public static void testCompareEQMaskNotDoublePositiveInfinity() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareNEMaskNotDoublePositiveInfinity() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.NE, da, dpinf, (m) -> { return m.not(); }); verifyResultsDouble(D_SPECIES, VectorOperators.NE, da, dpinf); @@ -1021,8 +967,7 @@ public static void testCompareNEMaskNotDoublePositiveInfinity() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareEQMaskNotDoubleNegativeInfinity() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.EQ, da, dninf, (m) -> { return m.not(); }); verifyResultsDouble(D_SPECIES, VectorOperators.EQ, da, dninf); @@ -1034,8 +979,7 @@ public static void testCompareEQMaskNotDoubleNegativeInfinity() { @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" }) public static void testCompareNEMaskNotDoubleNegativeInfinity() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.NE, da, dninf, (m) -> { return m.not(); }); verifyResultsDouble(D_SPECIES, VectorOperators.NE, da, dninf); @@ -1048,16 +992,13 @@ public static void testCompareNEMaskNotDoubleNegativeInfinity() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }) @IR(counts = { IRNode.XOR_V, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) @IR(counts = { IRNode.XOR_V, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureAnd = { "avx2", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "avx2", "true" }) public static void testCompareMaskNotByteNegative() { testCompareMaskNotByte(B_SPECIES, VectorOperators.EQ, (m) -> { // The vector mask is used multiple times. @@ -1074,16 +1015,13 @@ public static void testCompareMaskNotByteNegative() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }) @IR(counts = { IRNode.XOR_V, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) @IR(counts = { IRNode.XOR_V, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureAnd = { "avx2", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "avx2", "true" }) public static void testCompareMaskNotShortNegative() { testCompareMaskNotShort(S_SPECIES, VectorOperators.EQ, (m) -> { // The vector mask is used multiple times. @@ -1100,16 +1038,13 @@ public static void testCompareMaskNotShortNegative() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }) @IR(counts = { IRNode.XOR_V, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) @IR(counts = { IRNode.XOR_V, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureAnd = { "avx2", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "avx2", "true" }) public static void testCompareMaskNotIntNegative() { testCompareMaskNotInt(I_SPECIES, VectorOperators.EQ, (m) -> { // The vector mask is used multiple times. @@ -1126,16 +1061,13 @@ public static void testCompareMaskNotIntNegative() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }) @IR(counts = { IRNode.XOR_V, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) @IR(counts = { IRNode.XOR_V, "= 2", IRNode.VECTOR_MASK_CMP, "= 2" }, - applyIfCPUFeatureAnd = { "avx2", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "avx2", "true" }) public static void testCompareMaskNotLongNegative() { testCompareMaskNotLong(L_SPECIES, VectorOperators.EQ, (m) -> { // The vector mask is used multiple times. @@ -1152,16 +1084,13 @@ public static void testCompareMaskNotLongNegative() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 3", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }) @IR(counts = { IRNode.XOR_V, "= 3", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) @IR(counts = { IRNode.XOR_V, "= 3", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureAnd = { "avx2", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "avx2", "true" }) public static void testCompareMaskNotFloatNegative() { testCompareMaskNotFloat(F_SPECIES, VectorOperators.EQ, fa, fb, (m) -> { // The vector mask is used multiple times. @@ -1182,16 +1111,13 @@ public static void testCompareMaskNotFloatNegative() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 3", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }) @IR(counts = { IRNode.XOR_V, "= 3", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) @IR(counts = { IRNode.XOR_V, "= 3", IRNode.VECTOR_MASK_CMP, "= 3" }, - applyIfCPUFeatureAnd = { "avx2", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "avx2", "true" }) public static void testCompareMaskNotDoubleNegative() { testCompareMaskNotDouble(D_SPECIES, VectorOperators.EQ, da, db, (m) -> { // The vector mask is used multiple times. @@ -1230,18 +1156,15 @@ public static void testCompareMaskNotDoubleNegative() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1250,18 +1173,15 @@ public static void testCompareEQMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1270,18 +1190,15 @@ public static void testCompareNEMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1290,18 +1207,15 @@ public static void testCompareLTMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1310,18 +1224,15 @@ public static void testCompareGTMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1330,18 +1241,15 @@ public static void testCompareLEMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1350,18 +1258,15 @@ public static void testCompareGEMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1370,18 +1275,15 @@ public static void testCompareULTMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1390,18 +1292,15 @@ public static void testCompareUGTMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1410,18 +1309,15 @@ public static void testCompareULEMaskNotByteCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "sve", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "sve", "true" }) @IR(counts = { IRNode.XOR_V_MASK, "= 0", IRNode.XOR_V, "= 0", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1431,17 +1327,15 @@ public static void testCompareUGEMaskNotByteCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1453,17 +1347,15 @@ public static void testCompareEQMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1475,17 +1367,15 @@ public static void testCompareNEMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1497,17 +1387,15 @@ public static void testCompareLTMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1519,17 +1407,15 @@ public static void testCompareGTMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1541,17 +1427,15 @@ public static void testCompareLEMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1563,17 +1447,15 @@ public static void testCompareGEMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1585,17 +1467,15 @@ public static void testCompareULTMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1607,17 +1487,15 @@ public static void testCompareUGTMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1629,17 +1507,15 @@ public static void testCompareULEMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1651,17 +1527,15 @@ public static void testCompareUGEMaskNotShortCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1673,17 +1547,15 @@ public static void testCompareEQMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1695,17 +1567,15 @@ public static void testCompareNEMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1717,17 +1587,15 @@ public static void testCompareLTMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1739,17 +1607,15 @@ public static void testCompareGTMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1761,17 +1627,15 @@ public static void testCompareLEMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1783,17 +1647,15 @@ public static void testCompareGEMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1805,17 +1667,15 @@ public static void testCompareULTMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1827,17 +1687,15 @@ public static void testCompareUGTMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1849,17 +1707,15 @@ public static void testCompareULEMaskNotIntCast() { @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 2" }, applyIfCPUFeatureAnd = { "sve", "true" }, - applyIfAnd = { "MaxVectorSize", "= 16", "TieredCompilation", "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" }, - applyIf = { "TieredCompilation", "true" }) + 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" }, - applyIf = { "TieredCompilation", "true" }) + 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); @@ -1870,16 +1726,13 @@ public static void testCompareUGEMaskNotIntCast() { @Test @IR(counts = { IRNode.XOR_V_MASK, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureOr = { "sve", "true", "avx512", "true", "rvv", "true" }) @IR(counts = { IRNode.XOR_V, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "asimd", "true", "sve", "false" }) @IR(counts = { IRNode.XOR_V, "= 1", IRNode.VECTOR_MASK_CMP, "= 1" }, - applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }, - applyIf = { "TieredCompilation", "true" }) + applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" }) public static void testMaskedCompareMaskNotNegative() { int expected = F_SPECIES.length(); var ones = FloatVector.broadcast(F_SPECIES, 1f); @@ -1892,17 +1745,6 @@ public static void testMaskedCompareMaskNotNegative() { } public static void main(String[] args) { - // The @IR rules in this test verify that XorVMask(VectorMaskCmp, maskAll(true)) - // is folded away, i.e. that no XorV/XorVMask node survives. IncrementalInlineVector - // is enabled by default; when a vector intrinsic fails to intrinsify, inlining its - // fallback implementation enlarges the compilation unit and, under unstable profiling, - // may prevent AbstractMask::intoArray() from being inlined. When intoArray() is not - // inlined, the mask must be boxed before the call, and during VectorBoxNode - // scalarization a VectorStoreMask user is added to the VectorMaskCmp node. The extra - // out-edge raises VectorMaskCmp's out-count, which inhibits the fold and leaves the - // XorVMask/XorV nodes the rules expect to be absent. This only happens under - // unstable profiling (which makes the intoArray inlining decision non-deterministic); - // under the default profiling the fold is stable. TestFramework testFramework = new TestFramework(); testFramework.setDefaultWarmup(10000) .addFlags("--add-modules=jdk.incubator.vector") From dcf8c880b7caeafd7bbe023898e98d58874b29ee Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 13 Oct 2026 07:27:51 +0000 Subject: [PATCH 060/101] 8393469: Avoid switching off the unused-variable warning in BUILD_LIBAWT Reviewed-by: clanger, prr, serb --- .../modules/java.desktop/lib/AwtLibraries.gmk | 4 +-- .../native/common/awt/debug/debug_assert.c | 4 --- .../share/native/common/awt/debug/debug_mem.c | 4 --- .../native/common/awt/debug/debug_trace.h | 8 +++-- .../libawt/awt/medialib/awt_ImagingLib.c | 4 --- .../share/native/libawt/java2d/Disposer.c | 4 +-- .../libawt/java2d/loops/AnyByteBinary.h | 31 +++++++------------ .../native/libawt/java2d/loops/ByteIndexed.c | 4 +-- .../native/libawt/java2d/loops/Index12Gray.c | 4 +-- .../native/libawt/java2d/loops/Index8Gray.c | 4 +-- .../libawt/java2d/loops/UshortIndexed.c | 4 +-- .../libawt/java2d/pipe/ShapeSpanIterator.c | 3 +- .../unix/native/libawt/awt/awt_LoadLibrary.c | 6 ---- .../unix/native/libawt/awt/awt_Mlib.c | 7 +---- 14 files changed, 27 insertions(+), 64 deletions(-) diff --git a/make/modules/java.desktop/lib/AwtLibraries.gmk b/make/modules/java.desktop/lib/AwtLibraries.gmk index ca98434d383..dbc45bb92bd 100644 --- a/make/modules/java.desktop/lib/AwtLibraries.gmk +++ b/make/modules/java.desktop/lib/AwtLibraries.gmk @@ -122,14 +122,14 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBAWT, \ CXXFLAGS := $(LIBAWT_CFLAGS) $(X_CFLAGS), \ CFLAGS_gcc := -fgcse-after-reload, \ EXTRA_HEADER_DIRS := $(LIBAWT_EXTRA_HEADER_DIRS), \ - DISABLED_WARNINGS_gcc := unused-but-set-variable unused-variable, \ + DISABLED_WARNINGS_gcc := unused-but-set-variable, \ DISABLED_WARNINGS_gcc_awt_LoadLibrary.c := unused-result, \ DISABLED_WARNINGS_gcc_debug_mem.c := format-nonliteral, \ DISABLED_WARNINGS_gcc_ProcessPath.c := maybe-uninitialized, \ DISABLED_WARNINGS_gcc_Region.c := maybe-uninitialized, \ DISABLED_WARNINGS_gcc_SurfaceData.c := unused-value, \ DISABLED_WARNINGS_gcc_TransformHelper.c := sign-compare, \ - DISABLED_WARNINGS_clang := unused-but-set-variable unused-variable, \ + DISABLED_WARNINGS_clang := unused-but-set-variable, \ DISABLED_WARNINGS_clang_awt_ImagingLib.c := deprecated-non-prototype, \ DISABLED_WARNINGS_clang_debug_trace.c := format-nonliteral, \ DISABLED_WARNINGS_clang_Trace.c := format-nonliteral, \ diff --git a/src/java.desktop/share/native/common/awt/debug/debug_assert.c b/src/java.desktop/share/native/common/awt/debug/debug_assert.c index eb3e380c8e9..12f32f2d595 100644 --- a/src/java.desktop/share/native/common/awt/debug/debug_assert.c +++ b/src/java.desktop/share/native/common/awt/debug/debug_assert.c @@ -54,7 +54,3 @@ void DAssert_SetCallback(DASSERT_CALLBACK pfn) { #endif /* defined(DEBUG) */ -/* The following line is only here to prevent compiler warnings - * on release (non-debug) builds - */ -static int dummyVariable = 0; diff --git a/src/java.desktop/share/native/common/awt/debug/debug_mem.c b/src/java.desktop/share/native/common/awt/debug/debug_mem.c index 311476695dd..afb97c90a66 100644 --- a/src/java.desktop/share/native/common/awt/debug/debug_mem.c +++ b/src/java.desktop/share/native/common/awt/debug/debug_mem.c @@ -336,7 +336,3 @@ void DMem_DisableMutex() { #endif /* defined(DEBUG) */ -/* The following line is only here to prevent compiler warnings - * on release (non-debug) builds - */ -static int dummyVariable = 0; diff --git a/src/java.desktop/share/native/common/awt/debug/debug_trace.h b/src/java.desktop/share/native/common/awt/debug/debug_trace.h index 1f7064ef003..2d054d8be60 100644 --- a/src/java.desktop/share/native/common/awt/debug/debug_trace.h +++ b/src/java.desktop/share/native/common/awt/debug/debug_trace.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2018, 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 @@ -62,7 +62,11 @@ JNIEXPORT void JNICALL DTrace_VPrint(const char * file, int line, int argc, cons JNIEXPORT void JNICALL DTrace_VPrintln(const char * file, int line, int argc, const char * fmt, va_list arglist); /* each file includes this flag indicating module trace status */ -static dtrace_id _Dt_FileTraceId = UNDEFINED_TRACE_ID; +#if defined(__GNUC__) +static dtrace_id _Dt_FileTraceId __attribute__((unused)) = UNDEFINED_TRACE_ID; +#else +static dtrace_id _Dt_FileTraceId = UNDEFINED_TRACE_ID; +#endif /* not meant to be called from client code-- * it's just a template for the other macros diff --git a/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c b/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c index 64a0579b90e..dd282324df3 100644 --- a/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c +++ b/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c @@ -1211,7 +1211,6 @@ static int lookupShortData(mlib_image* src, mlib_image* dst, LookupArrayInfo* lookup) { int x, y; - unsigned int mask = NLUT-1; unsigned short* srcLine = (unsigned short*)src->data; unsigned char* dstLine = (unsigned char*)dst->data; @@ -2115,7 +2114,6 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, unsigned char *cDataP; RasterS_t *rasterP = &imageP->raster; ColorModelS_t *cmP = &imageP->cmodel; - int dataType = BYTE_DATA_TYPE; int width; int height; HintS_t *hintP = &imageP->hints; @@ -2134,7 +2132,6 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, /* around pointers and incrementing the width/height */ if (cvtToDefault) { - int status = 0; *mlibImagePP = (*sMlibSysFns.createFP)(MLIB_BYTE, 4, width, height); if (*mlibImagePP == NULL) { return -1; @@ -2262,7 +2259,6 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP, mlib_image **mlibImagePP, void **dataPP, int isSrc) { void *dataP; unsigned char *cDataP; - int dataType = BYTE_DATA_TYPE; int width; int height; int dataSize; diff --git a/src/java.desktop/share/native/libawt/java2d/Disposer.c b/src/java.desktop/share/native/libawt/java2d/Disposer.c index d27157e1a89..aff1904b060 100644 --- a/src/java.desktop/share/native/libawt/java2d/Disposer.c +++ b/src/java.desktop/share/native/libawt/java2d/Disposer.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 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 @@ -48,7 +48,7 @@ Disposer_AddRecord(JNIEnv *env, jobject obj, GeneralDisposeFunc disposer, jlong if (dispClass == NULL) { /* Needed to initialize the Disposer class as it may be not yet referenced */ - jclass clazz = (*env)->FindClass(env, "sun/java2d/Disposer"); + (*env)->FindClass(env, "sun/java2d/Disposer"); if ((*env)->ExceptionCheck(env)) { // If there's exception pending, we'll just return. return; diff --git a/src/java.desktop/share/native/libawt/java2d/loops/AnyByteBinary.h b/src/java.desktop/share/native/libawt/java2d/loops/AnyByteBinary.h index bf50ff1a373..46b87abf5f1 100644 --- a/src/java.desktop/share/native/libawt/java2d/loops/AnyByteBinary.h +++ b/src/java.desktop/share/native/libawt/java2d/loops/AnyByteBinary.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -190,16 +190,14 @@ DSTTYPE ## DataType *DSTPTR = (DSTTYPE ## DataType *) (DSTBASE); \ jint srcScan = (SRCINFO)->scanStride; \ jint dstScan = (DSTINFO)->scanStride; \ - jint srcx1 = (SRCINFO)->bounds.x1; \ - jint dstx1 = (DSTINFO)->bounds.x1; \ Init ## DSTTYPE ## StoreVarsY(DSTPREFIX, DSTINFO); \ srcScan -= (WIDTH) * SRCTYPE ## PixelStride; \ dstScan -= (WIDTH) * DSTTYPE ## PixelStride; \ do { \ Declare ## SRCTYPE ## InitialLoadVars(SRCINFO, SRCPTR, SRCPREFIX, \ - srcx1) \ + (SRCINFO)->bounds.x1) \ Declare ## DSTTYPE ## InitialLoadVars(DSTINFO, DSTPTR, DSTPREFIX, \ - dstx1) \ + (DSTINFO)->bounds.x1) \ juint w = WIDTH; \ Init ## DSTTYPE ## StoreVarsX(DSTPREFIX, DSTINFO); \ do { \ @@ -265,7 +263,6 @@ void NAME_XOR_BLIT(SRC, DST)(void *srcBase, void *dstBase, \ CompositeInfo *pCompInfo) \ { \ jint xorpixel = pCompInfo->details.xorPixel; \ - juint alphamask = pCompInfo->alphaMask; \ Declare ## SRC ## LoadVars(SrcRead) \ Declare ## DST ## StoreVars(DstWrite) \ \ @@ -276,7 +273,7 @@ void NAME_XOR_BLIT(SRC, DST)(void *srcBase, void *dstBase, \ BBXorVia1IntArgb(pSrc, SRC, SrcRead, \ pDst, DST, DstWrite, \ 0, xorpixel, \ - alphamask, pDstInfo)); \ + pCompInfo->alphaMask, pDstInfo)); \ } #define DEFINE_BYTE_BINARY_SOLID_FILLRECT(DST) \ @@ -387,7 +384,6 @@ void NAME_XOR_FILLRECT(DST)(SurfaceDataRasInfo *pRasInfo, \ CompositeInfo *pCompInfo) \ { \ jint xorpixel = pCompInfo->details.xorPixel; \ - juint alphamask = pCompInfo->alphaMask; \ DST ## DataType *pPix; \ jint scan = pRasInfo->scanStride; \ juint height = hiy - loy; \ @@ -400,7 +396,7 @@ void NAME_XOR_FILLRECT(DST)(SurfaceDataRasInfo *pRasInfo, \ do { \ InitialLoad ## DST(pPix, DstPix); \ Xor ## DST ## PixelData(pPix, 0, DstPix, \ - pixel, xorpixel, alphamask); \ + pixel, xorpixel, pCompInfo->alphaMask); \ ShiftBits ## DST(DstPix); \ } while (--w > 0); \ FinalStore ## DST(pPix, DstPix); \ @@ -417,7 +413,6 @@ void NAME_XOR_FILLSPANS(DST)(SurfaceDataRasInfo *pRasInfo, \ { \ void *pBase = pRasInfo->rasBase; \ jint xorpixel = pCompInfo->details.xorPixel; \ - juint alphamask = pCompInfo->alphaMask; \ jint scan = pRasInfo->scanStride; \ jint bbox[4]; \ \ @@ -435,7 +430,7 @@ void NAME_XOR_FILLSPANS(DST)(SurfaceDataRasInfo *pRasInfo, \ do { \ InitialLoad ## DST(pPix, DstPix); \ Xor ## DST ## PixelData(pPix, 0, DstPix, \ - pixel, xorpixel, alphamask); \ + pixel, xorpixel, pCompInfo->alphaMask); \ ShiftBits ## DST(DstPix); \ } while (--relx > 0); \ FinalStore ## DST(pPix, DstPix); \ @@ -454,7 +449,6 @@ void NAME_XOR_DRAWLINE(DST)(SurfaceDataRasInfo *pRasInfo, \ CompositeInfo *pCompInfo) \ { \ jint xorpixel = pCompInfo->details.xorPixel; \ - juint alphamask = pCompInfo->alphaMask; \ jint scan = pRasInfo->scanStride; \ DST ## DataType *pPix = PtrCoord(pRasInfo->rasBase, \ x1, DST ## PixelStride, \ @@ -468,7 +462,7 @@ void NAME_XOR_DRAWLINE(DST)(SurfaceDataRasInfo *pRasInfo, \ do { \ Declare ## DST ## InitialLoadVars(pRasInfo, pPix, DstPix, x1) \ Xor ## DST ## PixelData(pPix, 0, DstPix, \ - pixel, xorpixel, alphamask); \ + pixel, xorpixel, pCompInfo->alphaMask); \ FinalStore ## DST(pPix, DstPix); \ x1 += bumpmajor; \ } while (--steps > 0); \ @@ -476,7 +470,7 @@ void NAME_XOR_DRAWLINE(DST)(SurfaceDataRasInfo *pRasInfo, \ do { \ Declare ## DST ## InitialLoadVars(pRasInfo, pPix, DstPix, x1) \ Xor ## DST ## PixelData(pPix, 0, DstPix, \ - pixel, xorpixel, alphamask); \ + pixel, xorpixel, pCompInfo->alphaMask); \ FinalStore ## DST(pPix, DstPix); \ if (error < 0) { \ x1 += bumpmajor; \ @@ -599,7 +593,6 @@ void NAME_XOR_DRAWGLYPHLIST(DST)(SurfaceDataRasInfo *pRasInfo, \ jint glyphCounter; \ jint scan = pRasInfo->scanStride; \ jint xorpixel = pCompInfo->details.xorPixel; \ - juint alphamask = pCompInfo->alphaMask; \ DST ## DataType *pPix; \ \ for (glyphCounter = 0; glyphCounter < totalGlyphs; glyphCounter++) { \ @@ -618,7 +611,7 @@ void NAME_XOR_DRAWGLYPHLIST(DST)(SurfaceDataRasInfo *pRasInfo, \ InitialLoad ## DST(pPix, DstPix); \ if (pixels[x]) { \ Xor ## DST ## PixelData(pPix, 0, DstPix, \ - fgpixel, xorpixel, alphamask); \ + fgpixel, xorpixel, pCompInfo->alphaMask); \ } \ ShiftBits ## DST(DstPix); \ } while (++x < width); \ @@ -646,8 +639,6 @@ void NAME_ALPHA_MASKBLIT(SRC, DST) \ jint srcScan = pSrcInfo->scanStride; \ jint dstScan = pDstInfo->scanStride; \ jboolean loadsrc, loaddst; \ - jint srcx1 = pSrcInfo->bounds.x1; \ - jint dstx1 = pDstInfo->bounds.x1; \ SRC ## DataType *pSrc = (SRC ## DataType *) (srcBase); \ DST ## DataType *pDst = (DST ## DataType *) (dstBase); \ Declare ## SRC ## AlphaLoadData(SrcRead) \ @@ -674,8 +665,8 @@ void NAME_ALPHA_MASKBLIT(SRC, DST) \ \ Init ## DST ## StoreVarsY(DstWrite, pDstInfo); \ do { \ - Declare ## SRC ## InitialLoadVars(pSrcInfo, pSrc, SrcRead, srcx1) \ - Declare ## DST ## InitialLoadVars(pDstInfo, pDst, DstWrite, dstx1) \ + Declare ## SRC ## InitialLoadVars(pSrcInfo, pSrc, SrcRead, pSrcInfo->bounds.x1) \ + Declare ## DST ## InitialLoadVars(pDstInfo, pDst, DstWrite, pDstInfo->bounds.x1) \ jint w = width; \ Init ## DST ## StoreVarsX(DstWrite, pDstInfo); \ do { \ diff --git a/src/java.desktop/share/native/libawt/java2d/loops/ByteIndexed.c b/src/java.desktop/share/native/libawt/java2d/loops/ByteIndexed.c index 88eea948202..8b465d89041 100644 --- a/src/java.desktop/share/native/libawt/java2d/loops/ByteIndexed.c +++ b/src/java.desktop/share/native/libawt/java2d/loops/ByteIndexed.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -205,8 +205,6 @@ void NAME_SCALE_BLIT(ByteIndexed, ByteIndexed) { DeclareByteIndexedLoadVars(SrcRead) DeclareByteIndexedLoadVars(DstRead) - jint srcScan = pSrcInfo->scanStride; - jint dstScan = pDstInfo->scanStride; DeclareByteIndexedStoreVars(DstWrite) InitByteIndexedLoadVars(SrcRead, pSrcInfo); diff --git a/src/java.desktop/share/native/libawt/java2d/loops/Index12Gray.c b/src/java.desktop/share/native/libawt/java2d/loops/Index12Gray.c index 5b95697af3c..ebcef1350d6 100644 --- a/src/java.desktop/share/native/libawt/java2d/loops/Index12Gray.c +++ b/src/java.desktop/share/native/libawt/java2d/loops/Index12Gray.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2004, 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 @@ -193,8 +193,6 @@ void NAME_SCALE_BLIT(Index12Gray, Index12Gray) { DeclareIndex8GrayLoadVars(SrcRead) DeclareIndex8GrayLoadVars(DstRead) - jint srcScan = pSrcInfo->scanStride; - jint dstScan = pDstInfo->scanStride; DeclareIndex8GrayStoreVars(DstWrite) InitIndex8GrayLoadVars(SrcRead, pSrcInfo); diff --git a/src/java.desktop/share/native/libawt/java2d/loops/Index8Gray.c b/src/java.desktop/share/native/libawt/java2d/loops/Index8Gray.c index f72406e904b..3873fe46cb2 100644 --- a/src/java.desktop/share/native/libawt/java2d/loops/Index8Gray.c +++ b/src/java.desktop/share/native/libawt/java2d/loops/Index8Gray.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2004, 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 @@ -190,8 +190,6 @@ void NAME_SCALE_BLIT(Index8Gray, Index8Gray) { DeclareIndex8GrayLoadVars(SrcRead) DeclareIndex8GrayLoadVars(DstRead) - jint srcScan = pSrcInfo->scanStride; - jint dstScan = pDstInfo->scanStride; DeclareIndex8GrayStoreVars(DstWrite) InitIndex8GrayLoadVars(SrcRead, pSrcInfo); diff --git a/src/java.desktop/share/native/libawt/java2d/loops/UshortIndexed.c b/src/java.desktop/share/native/libawt/java2d/loops/UshortIndexed.c index 071459d2969..4832df50982 100644 --- a/src/java.desktop/share/native/libawt/java2d/loops/UshortIndexed.c +++ b/src/java.desktop/share/native/libawt/java2d/loops/UshortIndexed.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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 @@ -196,8 +196,6 @@ void NAME_SCALE_BLIT(UshortIndexed, UshortIndexed) { DeclareUshortIndexedLoadVars(SrcRead) DeclareUshortIndexedLoadVars(DstRead) - jint srcScan = pSrcInfo->scanStride; - jint dstScan = pDstInfo->scanStride; DeclareUshortIndexedStoreVars(DstWrite) InitUshortIndexedLoadVars(SrcRead, pSrcInfo); diff --git a/src/java.desktop/share/native/libawt/java2d/pipe/ShapeSpanIterator.c b/src/java.desktop/share/native/libawt/java2d/pipe/ShapeSpanIterator.c index 5b34ac9fbaa..eb1bc8f8335 100644 --- a/src/java.desktop/share/native/libawt/java2d/pipe/ShapeSpanIterator.c +++ b/src/java.desktop/share/native/libawt/java2d/pipe/ShapeSpanIterator.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 @@ -418,7 +418,6 @@ Java_sun_java2d_pipe_ShapeSpanIterator_addSegment jfloat x1, y1, x2, y2, x3, y3; jboolean oom = JNI_FALSE; pathData *pd; - int numpts = 0; pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE); if (pd == NULL) { diff --git a/src/java.desktop/unix/native/libawt/awt/awt_LoadLibrary.c b/src/java.desktop/unix/native/libawt/awt/awt_LoadLibrary.c index af50fdbb5c0..a5f98588018 100644 --- a/src/java.desktop/unix/native/libawt/awt/awt_LoadLibrary.c +++ b/src/java.desktop/unix/native/libawt/awt/awt_LoadLibrary.c @@ -33,7 +33,6 @@ #include "gdefs.h" #include -#include #ifdef AIX #define dladdr JVM_dladdr @@ -51,8 +50,6 @@ static void *awtHandle = NULL; -typedef jint JNICALL JNI_OnLoad_type(JavaVM *vm, void *reserved); - /* Initialize the Java VM instance variable when the library is first loaded */ JNIEXPORT JavaVM *jvm; @@ -102,10 +99,7 @@ AWT_OnLoad(JavaVM *vm, void *reserved) char buf[MAXPATHLEN]; int32_t len; char *p, *tk; - JNI_OnLoad_type *JNI_OnLoad_ptr; - struct utsname name; JNIEnv *env = (JNIEnv *)JNU_GetEnv(vm, JNI_VERSION_1_2); - void *v; if (awtHandle != NULL) { /* Avoid several loading attempts */ diff --git a/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c b/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c index c3ecec3380e..5862ab1f47b 100644 --- a/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c +++ b/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2020, 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 @@ -85,11 +85,7 @@ mlib_status awt_getImagingLib(JNIEnv *env, mlibFnS_t *sMlibFns, */ mlib_status awt_getImagingLib(JNIEnv *env, mlibFnS_t *sMlibFns, mlibSysFnS_t *sMlibSysFns) { - int status; - jstring jstr = NULL; mlibFnS_t *mptr; - void *(*vPtr)(); - int (*intPtr)(); mlib_status (*fPtr)(); int i; void *handle = NULL; @@ -97,7 +93,6 @@ mlib_status awt_getImagingLib(JNIEnv *env, mlibFnS_t *sMlibFns, static int s_timeIt = 0; static int s_verbose = 1; mlib_status ret = MLIB_SUCCESS; - struct utsname name; handle = dlopen(JNI_LIB_NAME("mlib_image"), RTLD_LAZY); From de43cedb1d67a56e60dfecfb10026241ae6c16a8 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 13 Oct 2026 07:33:23 +0000 Subject: [PATCH 061/101] 8394181: Call free in getMacAddress on original malloced nddp pointer and not on some other pointer Reviewed-by: vyazici --- src/java.base/unix/native/libnet/NetworkInterface.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/java.base/unix/native/libnet/NetworkInterface.c b/src/java.base/unix/native/libnet/NetworkInterface.c index 5ed99f256d8..9cf50845bfd 100644 --- a/src/java.base/unix/native/libnet/NetworkInterface.c +++ b/src/java.base/unix/native/libnet/NetworkInterface.c @@ -1593,7 +1593,7 @@ static int getMacAddress unsigned char *buf) { int size; - struct kinfo_ndd *nddp; + struct kinfo_ndd *nddp, *orig_nddp; void *end; size = getkerninfo(KINFO_NDD, 0, 0, 0); @@ -1621,18 +1621,20 @@ static int getMacAddress } end = (void *)nddp + size; + orig_nddp = nddp; + while ((void *)nddp < end) { if (!strcmp(nddp->ndd_alias, ifname) || !strcmp(nddp->ndd_name, ifname)) { bcopy(nddp->ndd_addr, buf, 6); - free(nddp); + free(orig_nddp); return 6; } else { nddp++; } } - free(nddp); + free(orig_nddp); return -1; } From 76950fad51e9cf3142b68f357f443417c88bdde0 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 13 Oct 2026 07:39:54 +0000 Subject: [PATCH 062/101] 8393077: Shenandoah: Worker SATB buffers should be processed in concurrent phase Reviewed-by: wkemper, kdnilsen, xpeng --- .../share/gc/shenandoah/shenandoahConcurrentMark.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp index 2196083e124..0297c4c2ff0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentMark.cpp @@ -59,10 +59,6 @@ class ShenandoahConcurrentMarkingTask : public WorkerTask { ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_mark, ShenandoahPhaseTimings::Work, worker_id, true); SuspendibleThreadSetJoiner stsj; _cm->mark_loop(worker_id, _terminator, GENERATION, true /*cancellable*/); - // Concurrent marking loop flushes Java thread buffers, coordinating with a handshake. - // Here, a GC worker has completed marking work, so it is a good time to flush its SATB buffers too. - SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set(); - satb_mq_set.flush_queue(ShenandoahThreadLocalData::satb_mark_queue(Thread::current())); } }; @@ -216,15 +212,14 @@ void ShenandoahConcurrentMark::concurrent_mark() { break; } - size_t before = qset.completed_buffers_num(); { ShenandoahTimingsTracker t(ShenandoahPhaseTimings::conc_mark_satb_flush, true); Handshake::execute(&flush_satb); + workers->threads_do(&flush_satb); } - size_t after = qset.completed_buffers_num(); - if (before == after) { - // No more retries needed, break out. + if (qset.completed_buffers_num() == 0) { + // No complete SATB buffers after the flush, break out. break; } } From bcaba363e114ba457f21df5953caedab65342570 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 13 Oct 2026 07:45:52 +0000 Subject: [PATCH 063/101] 8394235: PPC64: Remove unreachable non-acquiring CAS match rules Reviewed-by: mdoerr --- src/hotspot/cpu/ppc/gc/g1/g1_ppc.ad | 73 +---------------------------- 1 file changed, 2 insertions(+), 71 deletions(-) diff --git a/src/hotspot/cpu/ppc/gc/g1/g1_ppc.ad b/src/hotspot/cpu/ppc/gc/g1/g1_ppc.ad index edf36edec97..6368842dced 100644 --- a/src/hotspot/cpu/ppc/gc/g1/g1_ppc.ad +++ b/src/hotspot/cpu/ppc/gc/g1/g1_ppc.ad @@ -419,43 +419,9 @@ instruct g1CompareAndExchangeN_acq(iRegNdst res, indirect mem, iRegNsrc oldval, ins_pipe(pipe_class_default); %} -instruct g1CompareAndSwapP(iRegIdst res, indirect mem, iRegPsrc oldval, iRegPsrc newval, iRegPdst tmp, flagsRegCR0 cr0) -%{ - predicate(UseG1GC && n->as_LoadStore()->barrier_data() != 0 && - (((CompareAndSwapNode*)n)->order() != MemNode::acquire && ((CompareAndSwapNode*)n)->order() != MemNode::seqcst)); - match(Set res (CompareAndSwapP mem (Binary oldval newval))); - effect(TEMP_DEF res, TEMP tmp, KILL cr0); - format %{ "CMPXCHGD $res, $mem, $oldval, $newval; as bool; ptr" %} - ins_encode %{ - Label no_update; - __ li($res$$Register, 0); - __ cmpxchgd(CR0, R0, $oldval$$Register, $newval$$Register, $mem$$Register, - MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, &no_update, true); - // Pass oldval to SATB which is the only value which can get overwritten. - // Can be done after cmpxchg because there's no safepoint here. - pre_write_barrier(masm, this, - noreg, - $oldval$$Register /* pre_val */, - $tmp$$Register, - $res$$Register /* temp */, - RegSet::of($mem$$Register, $newval$$Register) /* preserve */, - RegSet::of($res$$Register) /* no_preserve */); - post_write_barrier(masm, this, - $mem$$Register, - $newval$$Register, - $tmp$$Register, - $res$$Register /* temp */); - __ li($res$$Register, 1); - __ bind(no_update); - %} - ins_pipe(pipe_class_default); -%} - instruct g1CompareAndSwapP_acq(iRegIdst res, indirect mem, iRegPsrc oldval, iRegPsrc newval, iRegPdst tmp, flagsRegCR0 cr0) %{ - predicate(UseG1GC && n->as_LoadStore()->barrier_data() != 0 && - (((CompareAndSwapNode*)n)->order() == MemNode::acquire || ((CompareAndSwapNode*)n)->order() == MemNode::seqcst)); + predicate(UseG1GC && n->as_LoadStore()->barrier_data() != 0); match(Set res (CompareAndSwapP mem (Binary oldval newval))); effect(TEMP_DEF res, TEMP tmp, KILL cr0); format %{ "CMPXCHGD acq $res, $mem, $oldval, $newval; as bool; ptr" %} @@ -491,44 +457,9 @@ instruct g1CompareAndSwapP_acq(iRegIdst res, indirect mem, iRegPsrc oldval, iReg ins_pipe(pipe_class_default); %} -instruct g1CompareAndSwapN(iRegIdst res, indirect mem, iRegNsrc oldval, iRegNsrc newval, iRegPdst tmp, flagsRegCR0 cr0) -%{ - predicate(UseG1GC && n->as_LoadStore()->barrier_data() != 0 && - (((CompareAndSwapNode*)n)->order() != MemNode::acquire && ((CompareAndSwapNode*)n)->order() != MemNode::seqcst)); - match(Set res (CompareAndSwapN mem (Binary oldval newval))); - effect(TEMP_DEF res, TEMP tmp, KILL cr0); - format %{ "CMPXCHGW $res, $mem, $oldval, $newval; as bool; ptr" %} - ins_encode %{ - Label no_update; - __ li($res$$Register, 0); - __ cmpxchgw(CR0, R0, $oldval$$Register, $newval$$Register, $mem$$Register, - MacroAssembler::MemBarNone, MacroAssembler::cmpxchgx_hint_atomic_update(), - noreg, &no_update, true); - // Pass oldval to SATB which is the only value which can get overwritten. - // Can be done after cmpxchg because there's no safepoint here. - pre_write_barrier(masm, this, - noreg, - $oldval$$Register /* pre_val */, - $tmp$$Register, - $res$$Register /* temp */, - RegSet::of($mem$$Register, $newval$$Register) /* preserve */, - RegSet::of($res$$Register) /* no_preserve */); - post_write_barrier(masm, this, - $mem$$Register, - $newval$$Register, - $tmp$$Register, - $res$$Register /* temp */, - true /* decode_new_val */); - __ li($res$$Register, 1); - __ bind(no_update); - %} - ins_pipe(pipe_class_default); -%} - instruct g1CompareAndSwapN_acq(iRegIdst res, indirect mem, iRegNsrc oldval, iRegNsrc newval, iRegPdst tmp, flagsRegCR0 cr0) %{ - predicate(UseG1GC && n->as_LoadStore()->barrier_data() != 0 && - (((CompareAndSwapNode*)n)->order() == MemNode::acquire || ((CompareAndSwapNode*)n)->order() == MemNode::seqcst)); + predicate(UseG1GC && n->as_LoadStore()->barrier_data() != 0); match(Set res (CompareAndSwapN mem (Binary oldval newval))); effect(TEMP_DEF res, TEMP tmp, KILL cr0); format %{ "CMPXCHGW acq $res, $mem, $oldval, $newval; as bool; ptr" %} From 059de78365982782d3c5ce55c3d5fbf5bbf93817 Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Tue, 13 Oct 2026 08:09:07 +0000 Subject: [PATCH 064/101] 8391895: RISC-V: Add unsigned vector min/max reductions Co-authored-by: Dingli Zhang Reviewed-by: ayang, fyang --- .../cpu/riscv/c2_MacroAssembler_riscv.cpp | 6 ++++ src/hotspot/cpu/riscv/riscv_v.ad | 8 +++++ .../vectorapi/VectorUMinMaxReductionTest.java | 34 +++++++++---------- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index 6212bebab05..977554e3b01 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -3132,9 +3132,15 @@ void C2_MacroAssembler::reduce_integral_v(Register dst, Register src1, case Op_MaxReductionV: vredmax_vs(tmp, src2, tmp, vm); break; + case Op_UMaxReductionV: + vredmaxu_vs(tmp, src2, tmp, vm); + break; case Op_MinReductionV: vredmin_vs(tmp, src2, tmp, vm); break; + case Op_UMinReductionV: + vredminu_vs(tmp, src2, tmp, vm); + break; default: ShouldNotReachHere(); } diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index d6381b6dd57..f22c79259fb 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -2705,6 +2705,7 @@ instruct vreduce_max(iRegINoSp dst, iRegIorL2I src1, vReg src2, vReg tmp) %{ Matcher::vector_element_basic_type(n->in(2)) == T_SHORT || Matcher::vector_element_basic_type(n->in(2)) == T_INT); match(Set dst (MaxReductionV src1 src2)); + match(Set dst (UMaxReductionV src1 src2)); effect(TEMP tmp); format %{ "vreduce_max $dst, $src1, $src2\t# KILL $tmp" %} ins_encode %{ @@ -2719,6 +2720,7 @@ instruct vreduce_max(iRegINoSp dst, iRegIorL2I src1, vReg src2, vReg tmp) %{ instruct vreduce_maxL(iRegLNoSp dst, iRegL src1, vReg src2, vReg tmp) %{ predicate(Matcher::vector_element_basic_type(n->in(2)) == T_LONG); match(Set dst (MaxReductionV src1 src2)); + match(Set dst (UMaxReductionV src1 src2)); effect(TEMP tmp); format %{ "vreduce_maxL $dst, $src1, $src2\t# KILL $tmp" %} ins_encode %{ @@ -2737,6 +2739,7 @@ instruct vreduce_max_masked(iRegINoSp dst, iRegIorL2I src1, vReg src2, vRegMask_ Matcher::vector_element_basic_type(n->in(2)) == T_SHORT || Matcher::vector_element_basic_type(n->in(2)) == T_INT); match(Set dst (MaxReductionV (Binary src1 src2) v0)); + match(Set dst (UMaxReductionV (Binary src1 src2) v0)); effect(TEMP tmp); format %{ "vreduce_max_masked $dst, $src1, $src2, $v0\t# KILL $tmp" %} ins_encode %{ @@ -2752,6 +2755,7 @@ instruct vreduce_max_masked(iRegINoSp dst, iRegIorL2I src1, vReg src2, vRegMask_ instruct vreduce_maxL_masked(iRegLNoSp dst, iRegL src1, vReg src2, vRegMask_V0 v0, vReg tmp) %{ predicate(Matcher::vector_element_basic_type(n->in(2)) == T_LONG); match(Set dst (MaxReductionV (Binary src1 src2) v0)); + match(Set dst (UMaxReductionV (Binary src1 src2) v0)); effect(TEMP tmp); format %{ "vreduce_maxL_masked $dst, $src1, $src2, $v0\t# KILL $tmp" %} ins_encode %{ @@ -2771,6 +2775,7 @@ instruct vreduce_min(iRegINoSp dst, iRegIorL2I src1, vReg src2, vReg tmp) %{ Matcher::vector_element_basic_type(n->in(2)) == T_SHORT || Matcher::vector_element_basic_type(n->in(2)) == T_INT); match(Set dst (MinReductionV src1 src2)); + match(Set dst (UMinReductionV src1 src2)); effect(TEMP tmp); format %{ "vreduce_min $dst, $src1, $src2\t# KILL $tmp" %} ins_encode %{ @@ -2785,6 +2790,7 @@ instruct vreduce_min(iRegINoSp dst, iRegIorL2I src1, vReg src2, vReg tmp) %{ instruct vreduce_minL(iRegLNoSp dst, iRegL src1, vReg src2, vReg tmp) %{ predicate(Matcher::vector_element_basic_type(n->in(2)) == T_LONG); match(Set dst (MinReductionV src1 src2)); + match(Set dst (UMinReductionV src1 src2)); effect(TEMP tmp); format %{ "vreduce_minL $dst, $src1, $src2\t# KILL $tmp" %} ins_encode %{ @@ -2803,6 +2809,7 @@ instruct vreduce_min_masked(iRegINoSp dst, iRegIorL2I src1, vReg src2, vRegMask_ Matcher::vector_element_basic_type(n->in(2)) == T_SHORT || Matcher::vector_element_basic_type(n->in(2)) == T_INT); match(Set dst (MinReductionV (Binary src1 src2) v0)); + match(Set dst (UMinReductionV (Binary src1 src2) v0)); effect(TEMP tmp); format %{ "vreduce_min_masked $dst, $src1, $src2, $v0\t# KILL $tmp" %} ins_encode %{ @@ -2818,6 +2825,7 @@ instruct vreduce_min_masked(iRegINoSp dst, iRegIorL2I src1, vReg src2, vRegMask_ instruct vreduce_minL_masked(iRegLNoSp dst, iRegL src1, vReg src2, vRegMask_V0 v0, vReg tmp) %{ predicate(Matcher::vector_element_basic_type(n->in(2)) == T_LONG); match(Set dst (MinReductionV (Binary src1 src2) v0)); + match(Set dst (UMinReductionV (Binary src1 src2) v0)); effect(TEMP tmp); format %{ "vreduce_minL_masked $dst, $src1, $src2, $v0\t# KILL $tmp" %} ins_encode %{ diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorUMinMaxReductionTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorUMinMaxReductionTest.java index 4c753d91afd..a113663027b 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorUMinMaxReductionTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorUMinMaxReductionTest.java @@ -34,7 +34,7 @@ /** * @test - * @bug 8372980 + * @bug 8372980 8391895 * @key randomness * @summary Add intrinsic support for unsigned min/max reduction operations * @library /test/lib / @@ -141,7 +141,7 @@ private static void verifyLong(VectorSpecies species, long got, long init, @Test @IR(counts = {IRNode.UMIN_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testByteUMin() { byte got = ByteVector.fromArray(B_SPECIES, ba, 0).reduceLanes(VectorOperators.UMIN); verifyByte(B_SPECIES, got, BYTE_UMIN_IDENTITY, VectorMath::minUnsigned, false); @@ -149,7 +149,7 @@ public static void testByteUMin() { @Test @IR(counts = {IRNode.UMAX_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testByteUMax() { byte got = ByteVector.fromArray(B_SPECIES, ba, 0).reduceLanes(VectorOperators.UMAX); verifyByte(B_SPECIES, got, BYTE_UMAX_IDENTITY, VectorMath::maxUnsigned, false); @@ -157,7 +157,7 @@ public static void testByteUMax() { @Test @IR(counts = {IRNode.UMIN_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testByteUMinMasked() { byte got = ByteVector.fromArray(B_SPECIES, ba, 0) .reduceLanes(VectorOperators.UMIN, @@ -167,7 +167,7 @@ public static void testByteUMinMasked() { @Test @IR(counts = {IRNode.UMAX_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testByteUMaxMasked() { byte got = ByteVector.fromArray(B_SPECIES, ba, 0) .reduceLanes(VectorOperators.UMAX, @@ -179,7 +179,7 @@ public static void testByteUMaxMasked() { @Test @IR(counts = {IRNode.UMIN_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testShortUMin() { short got = ShortVector.fromArray(S_SPECIES, sa, 0).reduceLanes(VectorOperators.UMIN); verifyShort(S_SPECIES, got, SHORT_UMIN_IDENTITY, VectorMath::minUnsigned, false); @@ -187,7 +187,7 @@ public static void testShortUMin() { @Test @IR(counts = {IRNode.UMAX_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testShortUMax() { short got = ShortVector.fromArray(S_SPECIES, sa, 0).reduceLanes(VectorOperators.UMAX); verifyShort(S_SPECIES, got, SHORT_UMAX_IDENTITY, VectorMath::maxUnsigned, false); @@ -195,7 +195,7 @@ public static void testShortUMax() { @Test @IR(counts = {IRNode.UMIN_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testShortUMinMasked() { short got = ShortVector.fromArray(S_SPECIES, sa, 0) .reduceLanes(VectorOperators.UMIN, @@ -205,7 +205,7 @@ public static void testShortUMinMasked() { @Test @IR(counts = {IRNode.UMAX_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testShortUMaxMasked() { short got = ShortVector.fromArray(S_SPECIES, sa, 0) .reduceLanes(VectorOperators.UMAX, @@ -217,7 +217,7 @@ public static void testShortUMaxMasked() { @Test @IR(counts = {IRNode.UMIN_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testIntUMin() { int got = IntVector.fromArray(I_SPECIES, ia, 0).reduceLanes(VectorOperators.UMIN); verifyInt(I_SPECIES, got, INT_UMIN_IDENTITY, VectorMath::minUnsigned, false); @@ -225,7 +225,7 @@ public static void testIntUMin() { @Test @IR(counts = {IRNode.UMAX_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testIntUMax() { int got = IntVector.fromArray(I_SPECIES, ia, 0).reduceLanes(VectorOperators.UMAX); verifyInt(I_SPECIES, got, INT_UMAX_IDENTITY, VectorMath::maxUnsigned, false); @@ -233,7 +233,7 @@ public static void testIntUMax() { @Test @IR(counts = {IRNode.UMIN_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testIntUMinMasked() { int got = IntVector.fromArray(I_SPECIES, ia, 0) .reduceLanes(VectorOperators.UMIN, @@ -243,7 +243,7 @@ public static void testIntUMinMasked() { @Test @IR(counts = {IRNode.UMAX_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx", "true", "rvv", "true"}) public static void testIntUMaxMasked() { int got = IntVector.fromArray(I_SPECIES, ia, 0) .reduceLanes(VectorOperators.UMAX, @@ -255,7 +255,7 @@ public static void testIntUMaxMasked() { @Test @IR(counts = {IRNode.UMIN_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx512vl", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx512vl", "true", "rvv", "true"}) public static void testLongUMin() { long got = LongVector.fromArray(L_SPECIES, la, 0).reduceLanes(VectorOperators.UMIN); verifyLong(L_SPECIES, got, LONG_UMIN_IDENTITY, VectorMath::minUnsigned, false); @@ -263,7 +263,7 @@ public static void testLongUMin() { @Test @IR(counts = {IRNode.UMAX_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx512vl", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx512vl", "true", "rvv", "true"}) public static void testLongUMax() { long got = LongVector.fromArray(L_SPECIES, la, 0).reduceLanes(VectorOperators.UMAX); verifyLong(L_SPECIES, got, LONG_UMAX_IDENTITY, VectorMath::maxUnsigned, false); @@ -271,7 +271,7 @@ public static void testLongUMax() { @Test @IR(counts = {IRNode.UMIN_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx512vl", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx512vl", "true", "rvv", "true"}) public static void testLongUMinMasked() { long got = LongVector.fromArray(L_SPECIES, la, 0) .reduceLanes(VectorOperators.UMIN, @@ -281,7 +281,7 @@ public static void testLongUMinMasked() { @Test @IR(counts = {IRNode.UMAX_REDUCTION_V, "= 1"}, - applyIfCPUFeatureOr = {"asimd", "true", "avx512vl", "true"}) + applyIfCPUFeatureOr = {"asimd", "true", "avx512vl", "true", "rvv", "true"}) public static void testLongUMaxMasked() { long got = LongVector.fromArray(L_SPECIES, la, 0) .reduceLanes(VectorOperators.UMAX, From dfebf8c5872f662352b9348ded6daa4c657b2bd8 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Tue, 13 Oct 2026 09:38:50 +0000 Subject: [PATCH 065/101] 8394217: Allow poller group be configured to have no write pollers Reviewed-by: djelinski --- .../share/classes/sun/nio/ch/Poller.java | 34 ++++++++++++++----- .../sun/nio/ch/DefaultPollerProvider.java | 9 ++++- .../java/net/vthread/BlockingSocketOps.java | 21 ++++++++---- .../channels/vthread/BlockingChannelOps.java | 21 ++++++++---- 4 files changed, 62 insertions(+), 23 deletions(-) diff --git a/src/java.base/share/classes/sun/nio/ch/Poller.java b/src/java.base/share/classes/sun/nio/ch/Poller.java index 9360bcc8327..52363060071 100644 --- a/src/java.base/share/classes/sun/nio/ch/Poller.java +++ b/src/java.base/share/classes/sun/nio/ch/Poller.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -36,6 +36,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.LockSupport; import java.util.function.BooleanSupplier; import jdk.internal.access.JavaLangAccess; @@ -413,7 +414,11 @@ static void closeAll(Poller... pollers) { } /** - * SYSTEM_THREADS poller group. The read and write pollers are system-wide platform threads. + * SYSTEM_THREADS poller group. The read and write pollers are system-wide platform + * threads. To avoid a file descriptor being registered with both a read and write + * poller at the same time, the poller group can be configured to run without a + * write poller, in which case polling for POLLOUT does timed-parked to force the + * caller to retry write ops until they succeed (or fail). */ private static class SystemThreadsPollerGroup extends PollerGroup { // system-wide read and write pollers @@ -459,16 +464,29 @@ private Poller readPoller(int fdVal) { } private Poller writePoller(int fdVal) { - int index = provider().fdValToIndex(fdVal, writePollers.length); - return writePollers[index]; + if (writePollers.length > 0) { + int index = provider().fdValToIndex(fdVal, writePollers.length); + return writePollers[index]; + } else { + return null; + } } @Override void poll(int fdVal, int event, long nanos, BooleanSupplier isOpen) throws IOException { - Poller poller = (event == Net.POLLIN) - ? readPoller(fdVal) - : writePoller(fdVal); - poller.poll(fdVal, nanos, isOpen); + if (event == Net.POLLIN) { + readPoller(fdVal).poll(fdVal, nanos, isOpen); + } else { + Poller writePoller = writePoller(fdVal); + if (writePoller != null) { + writePoller.poll(fdVal, nanos, isOpen); + } else { + // random delay in [50ms, 120ms) + int max = ThreadLocalRandom.current().nextInt(50_000_000, 120_000_000); + long delay = (nanos > 0) ? Math.min(nanos, max) : max; + LockSupport.parkNanos(delay); + } + } } @Override diff --git a/src/java.base/windows/classes/sun/nio/ch/DefaultPollerProvider.java b/src/java.base/windows/classes/sun/nio/ch/DefaultPollerProvider.java index d1af62fbd73..8f3339227f0 100644 --- a/src/java.base/windows/classes/sun/nio/ch/DefaultPollerProvider.java +++ b/src/java.base/windows/classes/sun/nio/ch/DefaultPollerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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,6 +47,13 @@ int defaultReadPollers() { return Math.max(Integer.highestOneBit(ncpus / 8), 1); } + @Override + int defaultWritePollers() { + // No write pollers to ensure at most one AFD_POLL outstanding per socket + // (https://github.com/piscisaureus/wepoll/issues/35) + return 0; + } + @Override int fdValToIndex(int fdVal, int toIndex) { return (fdVal >> 2) & (toIndex - 1); diff --git a/test/jdk/java/net/vthread/BlockingSocketOps.java b/test/jdk/java/net/vthread/BlockingSocketOps.java index d3db734f36a..629406967af 100644 --- a/test/jdk/java/net/vthread/BlockingSocketOps.java +++ b/test/jdk/java/net/vthread/BlockingSocketOps.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 @@ -23,26 +23,33 @@ /* * @test id=default - * @bug 8284161 8372958 + * @bug 8284161 8372958 8394217 * @summary Test virtual threads doing blocking I/O on java.net Sockets * @library /test/lib - * @run junit BlockingSocketOps + * @run junit ${test.main.class} */ /* * @test id=poller-modes * @requires (os.family == "linux") | (os.family == "mac") * @library /test/lib - * @run junit/othervm -Djdk.pollerMode=1 BlockingSocketOps - * @run junit/othervm -Djdk.pollerMode=2 BlockingSocketOps - * @run junit/othervm -Djdk.pollerMode=3 BlockingSocketOps + * @run junit/othervm -Djdk.pollerMode=1 ${test.main.class} + * @run junit/othervm -Djdk.pollerMode=2 ${test.main.class} + * @run junit/othervm -Djdk.pollerMode=3 ${test.main.class} + */ + +/* + * @test id=no-write-poller-thread + * @requires (os.family == "linux") | (os.family == "mac") + * @library /test/lib + * @run junit/othervm -Djdk.pollerMode=1 -Djdk.writePollers=0 ${test.main.class} */ /* * @test id=no-vmcontinuations * @requires vm.continuations * @library /test/lib - * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations BlockingSocketOps + * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations ${test.main.class} */ import java.io.Closeable; diff --git a/test/jdk/java/nio/channels/vthread/BlockingChannelOps.java b/test/jdk/java/nio/channels/vthread/BlockingChannelOps.java index 7e934301892..2514dd582e8 100644 --- a/test/jdk/java/nio/channels/vthread/BlockingChannelOps.java +++ b/test/jdk/java/nio/channels/vthread/BlockingChannelOps.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 @@ -23,26 +23,33 @@ /* * @test id=default - * @bug 8284161 + * @bug 8284161 8394217 * @summary Test virtual threads doing blocking I/O on NIO channels * @library /test/lib - * @run junit/timeout=480 BlockingChannelOps + * @run junit/timeout=480 ${test.main.class} */ /* * @test id=poller-modes * @requires (os.family == "linux") | (os.family == "mac") * @library /test/lib - * @run junit/othervm/timeout=480 -Djdk.pollerMode=1 BlockingChannelOps - * @run junit/othervm/timeout=480 -Djdk.pollerMode=2 BlockingChannelOps - * @run junit/othervm/timeout=480 -Djdk.pollerMode=3 BlockingChannelOps + * @run junit/othervm/timeout=480 -Djdk.pollerMode=1 ${test.main.class} + * @run junit/othervm/timeout=480 -Djdk.pollerMode=2 ${test.main.class} + * @run junit/othervm/timeout=480 -Djdk.pollerMode=3 ${test.main.class} + */ + +/* + * @test id=no-write-poller-thread + * @requires (os.family == "linux") | (os.family == "mac") + * @library /test/lib + * @run junit/othervm/timeout=480 -Djdk.pollerMode=1 -Djdk.writePollers=0 ${test.main.class} */ /* * @test id=no-vmcontinuations * @requires vm.continuations * @library /test/lib - * @run junit/othervm/timeout=480 -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations BlockingChannelOps + * @run junit/othervm/timeout=480 -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations ${test.main.class} */ import java.io.Closeable; From 7da6b6eb2c2cb19d2da11d56c1bea319b824b1fb Mon Sep 17 00:00:00 2001 From: Aggelos Biboudis Date: Tue, 13 Oct 2026 10:43:25 +0000 Subject: [PATCH 066/101] 8394375: Non-literal boolean constant expressions in switch crash the compiler Reviewed-by: vromero, mcimadamore, jlahoda --- .../javac/comp/ExhaustivenessComputer.java | 2 +- .../patterns/PrimitivePatternsSwitch.java | 81 ++++++++++++++++++- .../PrimitivePatternsSwitchErrors.java | 32 +++++++- .../PrimitivePatternsSwitchErrors.out | 6 +- 4 files changed, 116 insertions(+), 5 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java index 0697a80a81a..6d754fb567f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ExhaustivenessComputer.java @@ -135,7 +135,7 @@ private ExhaustivenessResult doExhausts(JCExpression selector, List case } } else if (l instanceof JCConstantCaseLabel constantLabel) { if (types.unboxedTypeOrType(selector.type).hasTag(TypeTag.BOOLEAN)) { - Object value = ((JCLiteral) constantLabel.expr).value; + Object value = constantLabel.expr.type.constValue(); booleanLiterals.remove(value); } else { Symbol s = TreeInfo.symbol(constantLabel.expr); diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitch.java b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitch.java index ef95399c8b4..1435b1270ee 100644 --- a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitch.java +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitch.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 @@ -22,7 +22,7 @@ */ /* * @test - * @bug 8304487 + * @bug 8304487 8394375 * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) * @enablePreview * @compile PrimitivePatternsSwitch.java @@ -82,6 +82,18 @@ public static void main(String[] args) { assertEquals(2, switchOverPrimitiveBooleanWithDefault(false)); assertEquals(1, switchOverPrimitiveBoolean(true)); assertEquals(2, switchOverPrimitiveBoolean(false)); + assertEquals(1, switchOverPrimitiveBooleanConstants(true)); + assertEquals(2, switchOverPrimitiveBooleanConstants(false)); + assertEquals(1, switchOverBoxedBooleanConstants(true)); + assertEquals(2, switchOverBoxedBooleanConstants(false)); + assertEquals(-1, switchOverBoxedBooleanConstants(null)); + assertEquals(1, switchOverPrimitiveBooleanLocalConstants(true)); + assertEquals(2, switchOverPrimitiveBooleanLocalConstants(false)); + assertEquals(1, switchOverBoxedBooleanLocalConstants(true)); + assertEquals(2, switchOverBoxedBooleanLocalConstants(false)); + assertEquals(-1, switchOverBoxedBooleanLocalConstants(null)); + testBooleanConstantExpressions(true); + testBooleanConstantExpressions(false); assertEquals(1, switchOverPrimitiveFloat(0.0f/0.0f)); assertEquals(2, switchOverPrimitiveFloat((float) Math.pow(0.0f/0.0f, 0))); assertEquals(3, switchOverPrimitiveFloat(0.0f)); @@ -414,6 +426,71 @@ public static int switchOverPrimitiveBoolean(boolean b) { }; } + private static final boolean TRUE_CONSTANT = 1 < 2; + private static final boolean FALSE_CONSTANT = 1 > 2; + + public static int switchOverPrimitiveBooleanConstants(boolean b) { + return switch (b) { + case TRUE_CONSTANT -> 1; + case FALSE_CONSTANT -> 2; + }; + } + + public static int switchOverBoxedBooleanConstants(Boolean b) { + return switch (b) { + case PrimitivePatternsSwitch.TRUE_CONSTANT -> 1; + case PrimitivePatternsSwitch.FALSE_CONSTANT -> 2; + case null -> -1; + }; + } + + public static int switchOverPrimitiveBooleanLocalConstants(boolean b) { + final boolean yes = !false; + final boolean no = !true; + switch (b) { + case yes: return 1; + case no: return 2; + } + } + + public static int switchOverBoxedBooleanLocalConstants(Boolean b) { + final boolean yes = !false; + final boolean no = !true; + switch (b) { + case yes: return 1; + case no: return 2; + case null: return -1; + } + } + + public static void testBooleanConstantExpressions(boolean b) { + int expected = b ? 1 : 2; + assertEquals(expected, switch (b) { + case (true) -> 1; + case (false) -> 2; + }); + assertEquals(expected, switch (b) { + case (boolean) true -> 1; + case (boolean) false -> 2; + }); + assertEquals(expected, switch (b) { + case !false -> 1; + case !true -> 2; + }); + assertEquals(expected, switch (b) { + case 1 < 2 -> 1; + case 1 > 2 -> 2; + }); + assertEquals(expected, switch (b) { + case true && true -> 1; + case true && false -> 2; + }); + assertEquals(expected, switch (b) { + case true ? true : false -> 1; + case true ? false : true -> 2; + }); + } + public static int switchOverPrimitiveChar(char c) { return switch (c) { case 'a' -> 1; diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java index 3eab2fc83d0..d7451047767 100644 --- a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.java @@ -1,6 +1,6 @@ /* * @test /nodynamiccopyright/ - * @bug 8304487 8325653 8332463 + * @bug 8304487 8325653 8332463 8394375 * @summary Compiler Implementation for Primitive types in patterns, instanceof, and switch (Preview) * @enablePreview * @compile/fail/ref=PrimitivePatternsSwitchErrors.out -XDrawDiagnostics -XDshould-stop.at=FLOW -XDexhaustivityMaxBaseChecks=0 PrimitivePatternsSwitchErrors.java @@ -330,4 +330,34 @@ public static void unconditionalFollowedByDefault() { default -> {} // Error - unconditional and default } } + + public static int nonExhaustiveBooleanConstantExpression(boolean b) { + final boolean yes = true; + return switch (b) { // error: not exhaustive + case yes -> 1; + }; + } + + public static void nonExhaustiveBooleanConstantStatement(boolean b) { + final boolean no = false; + switch (b) { // error: not exhaustive + case no -> {} + } + } + + public static int nonExhaustiveBoxedBooleanConstantExpression(Boolean b) { + final boolean yes = true; + return switch (b) { // error: not exhaustive + case yes -> 1; + case null -> 2; + }; + } + + public static void nonExhaustiveBoxedBooleanConstantStatement(Boolean b) { + final boolean no = false; + switch (b) { // error: not exhaustive + case no -> {} + case null -> {} + } + } } diff --git a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out index 5a0822a9aef..27169f46598 100644 --- a/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out +++ b/test/langtools/tools/javac/patterns/PrimitivePatternsSwitchErrors.out @@ -50,6 +50,10 @@ PrimitivePatternsSwitchErrors.java:239:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:247:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:254:16: compiler.err.not.exhaustive PrimitivePatternsSwitchErrors.java:260:16: compiler.err.not.exhaustive +PrimitivePatternsSwitchErrors.java:336:16: compiler.err.not.exhaustive +PrimitivePatternsSwitchErrors.java:343:9: compiler.err.not.exhaustive.statement +PrimitivePatternsSwitchErrors.java:350:16: compiler.err.not.exhaustive +PrimitivePatternsSwitchErrors.java:358:9: compiler.err.not.exhaustive.statement - compiler.note.preview.filename: PrimitivePatternsSwitchErrors.java, DEFAULT - compiler.note.preview.recompile -52 errors +56 errors From 262d92d30cfb5b4c7b694e0076aac4d0d10fd555 Mon Sep 17 00:00:00 2001 From: Stefan Karlsson Date: Tue, 13 Oct 2026 11:34:32 +0000 Subject: [PATCH 067/101] 8394182: Move oopCast.inline.hpp functions to oop.inline.hpp Reviewed-by: shade, sspitsyn --- .../cpu/aarch64/foreignGlobals_aarch64.cpp | 2 +- src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp | 2 +- .../cpu/riscv/foreignGlobals_riscv.cpp | 2 +- src/hotspot/cpu/s390/foreignGlobals_s390.cpp | 2 +- src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp | 2 +- src/hotspot/share/c1/c1_Runtime1.cpp | 1 - src/hotspot/share/cds/aotMappedHeapLoader.cpp | 1 - .../share/cds/aotReferenceObjSupport.cpp | 1 - .../share/cds/aotStreamedHeapLoader.cpp | 1 - src/hotspot/share/cds/cdsHeapVerifier.cpp | 1 - src/hotspot/share/cds/cdsProtectionDomain.cpp | 2 +- src/hotspot/share/cds/heapShared.cpp | 1 - src/hotspot/share/cds/lambdaFormInvokers.cpp | 1 - src/hotspot/share/ci/ciArray.cpp | 1 - src/hotspot/share/ci/ciEnv.cpp | 1 - src/hotspot/share/ci/ciObjArray.cpp | 2 +- src/hotspot/share/ci/ciReplay.cpp | 1 - src/hotspot/share/classfile/javaClasses.cpp | 1 - src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 1 - .../share/interpreter/bootstrapInfo.cpp | 2 +- src/hotspot/share/jfr/dcmd/jfrDcmds.cpp | 1 - src/hotspot/share/jfr/jni/jfrJavaSupport.cpp | 1 - .../share/jfr/utilities/jfrJavaLog.cpp | 2 +- src/hotspot/share/memory/oopFactory.cpp | 1 - src/hotspot/share/memory/universe.cpp | 1 - src/hotspot/share/oops/constantPool.cpp | 1 - src/hotspot/share/oops/cpCache.inline.hpp | 2 +- src/hotspot/share/oops/flatArrayKlass.cpp | 1 - src/hotspot/share/oops/objArrayKlass.cpp | 1 - src/hotspot/share/oops/oop.cpp | 5 +- src/hotspot/share/oops/oop.hpp | 38 ++++++++----- src/hotspot/share/oops/oop.inline.hpp | 24 ++++++++- src/hotspot/share/oops/oopCast.inline.hpp | 54 ------------------- src/hotspot/share/oops/refArrayKlass.cpp | 1 - .../share/prims/foreignGlobals.inline.hpp | 2 +- src/hotspot/share/prims/jni.cpp | 1 - src/hotspot/share/prims/jvm.cpp | 1 - src/hotspot/share/prims/jvmtiTagMap.cpp | 1 - src/hotspot/share/prims/nativeEntryPoint.cpp | 2 +- src/hotspot/share/prims/unsafe.cpp | 1 - .../share/services/diagnosticCommand.cpp | 1 - src/hotspot/share/services/heapDumper.cpp | 1 - src/hotspot/share/services/management.cpp | 1 - 43 files changed, 61 insertions(+), 111 deletions(-) delete mode 100644 src/hotspot/share/oops/oopCast.inline.hpp diff --git a/src/hotspot/cpu/aarch64/foreignGlobals_aarch64.cpp b/src/hotspot/cpu/aarch64/foreignGlobals_aarch64.cpp index b9fcb447d65..eb528efbf19 100644 --- a/src/hotspot/cpu/aarch64/foreignGlobals_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/foreignGlobals_aarch64.cpp @@ -26,7 +26,7 @@ #include "runtime/jniHandles.hpp" #include "runtime/jniHandles.inline.hpp" #include "oops/typeArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "prims/foreignGlobals.hpp" #include "prims/foreignGlobals.inline.hpp" #include "prims/vmstorage.hpp" diff --git a/src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp b/src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp index 5069030ce51..49830ba8f96 100644 --- a/src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp +++ b/src/hotspot/cpu/ppc/foreignGlobals_ppc.cpp @@ -27,7 +27,7 @@ #include "runtime/jniHandles.hpp" #include "runtime/jniHandles.inline.hpp" #include "oops/typeArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "prims/foreignGlobals.hpp" #include "prims/foreignGlobals.inline.hpp" #include "prims/vmstorage.hpp" diff --git a/src/hotspot/cpu/riscv/foreignGlobals_riscv.cpp b/src/hotspot/cpu/riscv/foreignGlobals_riscv.cpp index a56d16f6680..4e16dfce1e1 100644 --- a/src/hotspot/cpu/riscv/foreignGlobals_riscv.cpp +++ b/src/hotspot/cpu/riscv/foreignGlobals_riscv.cpp @@ -27,7 +27,7 @@ #include "runtime/jniHandles.hpp" #include "runtime/jniHandles.inline.hpp" #include "oops/typeArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "prims/foreignGlobals.hpp" #include "prims/foreignGlobals.inline.hpp" #include "prims/vmstorage.hpp" diff --git a/src/hotspot/cpu/s390/foreignGlobals_s390.cpp b/src/hotspot/cpu/s390/foreignGlobals_s390.cpp index 81331d1e179..ced6a341f50 100644 --- a/src/hotspot/cpu/s390/foreignGlobals_s390.cpp +++ b/src/hotspot/cpu/s390/foreignGlobals_s390.cpp @@ -27,7 +27,7 @@ #include "runtime/jniHandles.hpp" #include "runtime/jniHandles.inline.hpp" #include "oops/typeArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "prims/foreignGlobals.hpp" #include "prims/foreignGlobals.inline.hpp" #include "prims/vmstorage.hpp" diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp index 075c16e6811..55952bba063 100644 --- a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp +++ b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp @@ -24,7 +24,7 @@ #include "classfile/javaClasses.hpp" #include "runtime/jniHandles.inline.hpp" #include "oops/typeArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "prims/foreignGlobals.inline.hpp" #include "runtime/sharedRuntime.hpp" #include "utilities/formatBuffer.hpp" diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index 40c3f6a99b9..7268997bd41 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -58,7 +58,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/atomicAccess.hpp" #include "runtime/fieldDescriptor.inline.hpp" diff --git a/src/hotspot/share/cds/aotMappedHeapLoader.cpp b/src/hotspot/share/cds/aotMappedHeapLoader.cpp index 74dfcb0b77f..4d08781ebab 100644 --- a/src/hotspot/share/cds/aotMappedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotMappedHeapLoader.cpp @@ -41,7 +41,6 @@ #include "memory/iterator.inline.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" -#include "oops/oopCast.inline.hpp" #include "sanitizers/ub.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/copy.hpp" diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp index 8f003b836ee..9af1e836391 100644 --- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp +++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp @@ -32,7 +32,6 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/javaCalls.hpp" diff --git a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp index 8fb48edb434..a396c4d3af9 100644 --- a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp @@ -41,7 +41,6 @@ #include "oops/access.inline.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "runtime/globals.hpp" #include "runtime/globals_extension.hpp" #include "runtime/handles.inline.hpp" diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index 5608395db13..b0aaede89c1 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -39,7 +39,6 @@ #include "oops/fieldStreams.inline.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "runtime/fieldDescriptor.inline.hpp" diff --git a/src/hotspot/share/cds/cdsProtectionDomain.cpp b/src/hotspot/share/cds/cdsProtectionDomain.cpp index b34cc68b542..a4b449a0b03 100644 --- a/src/hotspot/share/cds/cdsProtectionDomain.cpp +++ b/src/hotspot/share/cds/cdsProtectionDomain.cpp @@ -36,7 +36,7 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/instanceKlass.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "oops/refArrayOop.hpp" #include "oops/symbol.hpp" #include "runtime/javaCalls.hpp" diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 9289bad0c67..b486b2f17cb 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -66,7 +66,6 @@ #include "oops/instanceKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "prims/jvmtiExport.hpp" diff --git a/src/hotspot/share/cds/lambdaFormInvokers.cpp b/src/hotspot/share/cds/lambdaFormInvokers.cpp index 9b87438ff11..9d52dff147c 100644 --- a/src/hotspot/share/cds/lambdaFormInvokers.cpp +++ b/src/hotspot/share/cds/lambdaFormInvokers.cpp @@ -47,7 +47,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "runtime/handles.inline.hpp" diff --git a/src/hotspot/share/ci/ciArray.cpp b/src/hotspot/share/ci/ciArray.cpp index 6e8ba210131..d06c5f9796d 100644 --- a/src/hotspot/share/ci/ciArray.cpp +++ b/src/hotspot/share/ci/ciArray.cpp @@ -31,7 +31,6 @@ #include "oops/layoutKind.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "utilities/powerOfTwo.hpp" diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index 505cbd65fba..bd0e723c0c2 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -67,7 +67,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/symbolHandle.hpp" #include "oops/trainingData.hpp" diff --git a/src/hotspot/share/ci/ciObjArray.cpp b/src/hotspot/share/ci/ciObjArray.cpp index 5bbf87b3f59..138cb79e961 100644 --- a/src/hotspot/share/ci/ciObjArray.cpp +++ b/src/hotspot/share/ci/ciObjArray.cpp @@ -26,7 +26,7 @@ #include "ci/ciObjArray.hpp" #include "ci/ciUtilities.inline.hpp" #include "oops/objArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" // ciObjArray // diff --git a/src/hotspot/share/ci/ciReplay.cpp b/src/hotspot/share/ci/ciReplay.cpp index 741e095bb6c..eac3cf73ce6 100644 --- a/src/hotspot/share/ci/ciReplay.cpp +++ b/src/hotspot/share/ci/ciReplay.cpp @@ -46,7 +46,6 @@ #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/valueKlass.inline.hpp" #include "prims/jvmtiExport.hpp" diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 8d5b7ab78a4..c399d9e0a9b 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -54,7 +54,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/recordComponent.hpp" #include "oops/refArrayOop.inline.hpp" #include "oops/symbol.hpp" diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index d6cbc82a6f4..4527226569d 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -70,7 +70,6 @@ #include "nmt/memTracker.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "runtime/globals_extension.hpp" #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" diff --git a/src/hotspot/share/interpreter/bootstrapInfo.cpp b/src/hotspot/share/interpreter/bootstrapInfo.cpp index c56af63c651..8bbb504f41c 100644 --- a/src/hotspot/share/interpreter/bootstrapInfo.cpp +++ b/src/hotspot/share/interpreter/bootstrapInfo.cpp @@ -36,7 +36,7 @@ #include "oops/constantPool.inline.hpp" #include "oops/cpCache.inline.hpp" #include "oops/objArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "oops/resolvedIndyEntry.hpp" #include "oops/typeArrayOop.inline.hpp" #include "runtime/handles.inline.hpp" diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp index 5df0e257c09..5eee5aeda96 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp @@ -39,7 +39,6 @@ #include "memory/resourceArea.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/symbol.hpp" #include "runtime/handles.inline.hpp" #include "runtime/jniHandles.hpp" diff --git a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp index 5810651fcaa..d821b2a5ae6 100644 --- a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp +++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp @@ -42,7 +42,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" diff --git a/src/hotspot/share/jfr/utilities/jfrJavaLog.cpp b/src/hotspot/share/jfr/utilities/jfrJavaLog.cpp index 0a102144148..c89e9b1a784 100644 --- a/src/hotspot/share/jfr/utilities/jfrJavaLog.cpp +++ b/src/hotspot/share/jfr/utilities/jfrJavaLog.cpp @@ -30,7 +30,7 @@ #include "logging/logMessage.hpp" #include "memory/resourceArea.hpp" #include "oops/objArrayOop.inline.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "runtime/javaThread.hpp" #define JFR_LOG_TAGS_CONCATED(T0, T1, T2, T3, T4, T5, ...) \ diff --git a/src/hotspot/share/memory/oopFactory.cpp b/src/hotspot/share/memory/oopFactory.cpp index 90d03dd8c5f..6e5f0baee77 100644 --- a/src/hotspot/share/memory/oopFactory.cpp +++ b/src/hotspot/share/memory/oopFactory.cpp @@ -37,7 +37,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/oopsHierarchy.hpp" #include "oops/refArrayKlass.hpp" #include "oops/typeArrayKlass.hpp" diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index 39bb2a8fcbf..a4c46a4f0f4 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -66,7 +66,6 @@ #include "oops/objArrayOop.inline.hpp" #include "oops/objLayout.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "oops/refArrayKlass.hpp" #include "oops/typeArrayKlass.hpp" diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index e96b79014a5..95c4673edd2 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -59,7 +59,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/refArrayOop.hpp" #include "oops/typeArrayOop.inline.hpp" #include "prims/jvmtiExport.hpp" diff --git a/src/hotspot/share/oops/cpCache.inline.hpp b/src/hotspot/share/oops/cpCache.inline.hpp index 9d8fbc5be1c..3c7cd2c5ce2 100644 --- a/src/hotspot/share/oops/cpCache.inline.hpp +++ b/src/hotspot/share/oops/cpCache.inline.hpp @@ -27,7 +27,7 @@ #include "oops/cpCache.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "oops/resolvedFieldEntry.hpp" #include "oops/resolvedIndyEntry.hpp" diff --git a/src/hotspot/share/oops/flatArrayKlass.cpp b/src/hotspot/share/oops/flatArrayKlass.cpp index 73c3b233182..2f32c81cec3 100644 --- a/src/hotspot/share/oops/flatArrayKlass.cpp +++ b/src/hotspot/share/oops/flatArrayKlass.cpp @@ -46,7 +46,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/valueKlass.hpp" #include "oops/valuePayload.inline.hpp" #include "oops/verifyOopClosure.hpp" diff --git a/src/hotspot/share/oops/objArrayKlass.cpp b/src/hotspot/share/oops/objArrayKlass.cpp index 1fa79b65f7b..d5f328c0b2a 100644 --- a/src/hotspot/share/oops/objArrayKlass.cpp +++ b/src/hotspot/share/oops/objArrayKlass.cpp @@ -45,7 +45,6 @@ #include "oops/objArrayKlass.inline.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/refArrayKlass.hpp" #include "oops/symbol.hpp" #include "oops/valueKlass.hpp" diff --git a/src/hotspot/share/oops/oop.cpp b/src/hotspot/share/oops/oop.cpp index 5ae20b4041b..a3fa4d79af0 100644 --- a/src/hotspot/share/oops/oop.cpp +++ b/src/hotspot/share/oops/oop.cpp @@ -161,14 +161,15 @@ template void VerifyOopClosure::do_oop_work(T* p) { void VerifyOopClosure::do_oop(oop* p) { VerifyOopClosure::do_oop_work(p); } void VerifyOopClosure::do_oop(narrowOop* p) { VerifyOopClosure::do_oop_work(p); } -// type test operations that doesn't require inclusion of oop.inline.hpp. +// Type test operations that doesn't require inclusion of oop.inline.hpp. bool oopDesc::is_instance_noinline() const { return is_instance(); } +bool oopDesc::is_value_noinline() const { return is_value(); } bool oopDesc::is_instanceRef_noinline() const { return is_instanceRef(); } bool oopDesc::is_stackChunk_noinline() const { return is_stackChunk(); } bool oopDesc::is_array_noinline() const { return is_array(); } +bool oopDesc::is_typeArray_noinline() const { return is_typeArray(); } bool oopDesc::is_objArray_noinline() const { return is_objArray(); } bool oopDesc::is_refArray_noinline() const { return is_refArray(); } -bool oopDesc::is_typeArray_noinline() const { return is_typeArray(); } bool oopDesc::is_flatArray_noinline() const { return is_flatArray(); } #if INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/oops/oop.hpp b/src/hotspot/share/oops/oop.hpp index 876026291fa..6b49a437ea2 100644 --- a/src/hotspot/share/oops/oop.hpp +++ b/src/hotspot/share/oops/oop.hpp @@ -115,30 +115,40 @@ class oopDesc { // to be able to figure out the size of an object knowing its klass. inline size_t size_given_klass(Klass* klass); - // type test operations (inlined in oop.inline.hpp) + // Type test operations (inlined in oop.inline.hpp) inline bool is_instance() const; inline bool is_value() const; inline bool is_instanceRef() const; inline bool is_stackChunk() const; inline bool is_array() const; - inline bool is_objArray() const; inline bool is_typeArray() const; - inline bool is_flatArray() const; + inline bool is_objArray() const; inline bool is_refArray() const; + inline bool is_flatArray() const; + + // Templatized type test operation + template + inline bool is() const; + + // Type test operations that don't require inclusion of oop.inline.hpp. + bool is_instance_noinline() const; + bool is_value_noinline() const; + bool is_instanceRef_noinline() const; + bool is_stackChunk_noinline() const; + bool is_array_noinline() const; + bool is_typeArray_noinline() const; + bool is_objArray_noinline() const; + bool is_refArray_noinline() const; + bool is_flatArray_noinline() const; + + // Type test for either flatArray or refArray inline bool is_refined_objArray() const; + + // Type test for arrays containing oops in the elements inline bool is_array_with_oops() const; - inline bool is_value_type() const; - - // type test operations that don't require inclusion of oop.inline.hpp. - bool is_instance_noinline() const; - bool is_instanceRef_noinline() const; - bool is_stackChunk_noinline() const; - bool is_array_noinline() const; - bool is_objArray_noinline() const; - bool is_refArray_noinline() const; - bool is_typeArray_noinline() const; - bool is_flatArray_noinline() const; + // Type test for value class instances via the mark word + inline bool is_value_type() const; protected: inline oop as_oop() const { return const_cast(this); } diff --git a/src/hotspot/share/oops/oop.inline.hpp b/src/hotspot/share/oops/oop.inline.hpp index fbd526b3c96..b1b3899096c 100644 --- a/src/hotspot/share/oops/oop.inline.hpp +++ b/src/hotspot/share/oops/oop.inline.hpp @@ -214,12 +214,32 @@ bool oopDesc::is_value() const { return klass()->is_value_klass(); bool oopDesc::is_instanceRef() const { return klass()->is_reference_instance_klass(); } bool oopDesc::is_stackChunk() const { return klass()->is_stack_chunk_instance_klass(); } bool oopDesc::is_array() const { return klass()->is_array_klass(); } +bool oopDesc::is_typeArray() const { return klass()->is_typeArray_klass(); } bool oopDesc::is_objArray() const { return klass()->is_objArray_klass(); } bool oopDesc::is_refArray() const { return klass()->is_refArray_klass(); } -bool oopDesc::is_typeArray() const { return klass()->is_typeArray_klass(); } -bool oopDesc::is_refined_objArray() const { return klass()->is_refined_objArray_klass(); } bool oopDesc::is_flatArray() const { return klass()->is_flatArray_klass(); } +template +bool oopDesc::is() const { + static_assert(sizeof(OopType) == 0, "No is specialization found for this type"); + return false; +} + +template<> inline bool oopDesc::is() const { return is_instance(); } +template<> inline bool oopDesc::is() const { return is_array(); } +template<> inline bool oopDesc::is() const { return is_objArray(); } +template<> inline bool oopDesc::is() const { return is_refArray(); } +template<> inline bool oopDesc::is() const { return is_typeArray(); } +template<> inline bool oopDesc::is() const { return is_flatArray(); } + +template +OopType oop_cast(oop obj) { + assert(obj->is(), "Invalid cast"); + return (OopType) obj; +} + +bool oopDesc::is_refined_objArray() const { return klass()->is_refined_objArray_klass(); } + bool oopDesc::is_array_with_oops() const { if (!is_objArray()) { return false; diff --git a/src/hotspot/share/oops/oopCast.inline.hpp b/src/hotspot/share/oops/oopCast.inline.hpp deleted file mode 100644 index 5a6b1ae4bb6..00000000000 --- a/src/hotspot/share/oops/oopCast.inline.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022, 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. - */ - -#ifndef SHARE_OOPS_OOPCAST_INLINE_HPP -#define SHARE_OOPS_OOPCAST_INLINE_HPP - -#include "oops/oop.inline.hpp" -#include "oops/oopsHierarchy.hpp" - -template -static bool is_oop_type(oop theOop) { - static_assert(sizeof(T) == 0, "No is_oop_type specialization found for this type"); - return false; -} -template<> -inline bool is_oop_type(oop theOop) { return theOop->is_instance(); } -template<> -inline bool is_oop_type(oop theOop) { return theOop->is_array(); } -template<> -inline bool is_oop_type(oop theOop) { return theOop->is_objArray(); } -template<> -inline bool is_oop_type(oop theOop) { return theOop->is_refArray(); } -template<> -inline bool is_oop_type(oop theOop) { return theOop->is_flatArray(); } -template<> -inline bool is_oop_type(oop theOop) { return theOop->is_typeArray(); } - -template -R oop_cast(oop theOop) { - assert(is_oop_type(theOop), "Invalid cast"); - return (R) theOop; -} - -#endif // SHARE_OOPS_OOPCAST_INLINE_HPP diff --git a/src/hotspot/share/oops/refArrayKlass.cpp b/src/hotspot/share/oops/refArrayKlass.cpp index 7e5a181fd66..f5b15e97786 100644 --- a/src/hotspot/share/oops/refArrayKlass.cpp +++ b/src/hotspot/share/oops/refArrayKlass.cpp @@ -40,7 +40,6 @@ #include "oops/markWord.hpp" #include "oops/objArrayKlass.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/refArrayKlass.inline.hpp" #include "oops/refArrayOop.inline.hpp" #include "oops/symbol.hpp" diff --git a/src/hotspot/share/prims/foreignGlobals.inline.hpp b/src/hotspot/share/prims/foreignGlobals.inline.hpp index da28b153f1d..bd02dff37f6 100644 --- a/src/hotspot/share/prims/foreignGlobals.inline.hpp +++ b/src/hotspot/share/prims/foreignGlobals.inline.hpp @@ -28,7 +28,7 @@ #include "classfile/javaClasses.hpp" #include "oops/objArrayOop.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "oops/oopsHierarchy.hpp" template diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index fdbb405cb0b..6d271e17cca 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -60,7 +60,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/symbol.hpp" #include "oops/typeArrayKlass.hpp" #include "oops/typeArrayOop.inline.hpp" diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index e4e37636a87..b83fb90bb73 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -66,7 +66,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/recordComponent.hpp" #include "oops/refArrayOop.inline.hpp" #include "oops/valueKlass.inline.hpp" diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index 5f357831dfe..03d6c15cfbd 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -44,7 +44,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "oops/valueKlass.inline.hpp" #include "oops/valuePayload.inline.hpp" diff --git a/src/hotspot/share/prims/nativeEntryPoint.cpp b/src/hotspot/share/prims/nativeEntryPoint.cpp index 96f63d75887..2160d4859c1 100644 --- a/src/hotspot/share/prims/nativeEntryPoint.cpp +++ b/src/hotspot/share/prims/nativeEntryPoint.cpp @@ -27,7 +27,7 @@ #include "code/vmreg.hpp" #include "logging/logStream.hpp" #include "memory/resourceArea.hpp" -#include "oops/oopCast.inline.hpp" +#include "oops/oop.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "prims/downcallLinker.hpp" #include "prims/foreignGlobals.inline.hpp" diff --git a/src/hotspot/share/prims/unsafe.cpp b/src/hotspot/share/prims/unsafe.cpp index 6d44b1ab9c3..d1a06beafee 100644 --- a/src/hotspot/share/prims/unsafe.cpp +++ b/src/hotspot/share/prims/unsafe.cpp @@ -44,7 +44,6 @@ #include "oops/klass.inline.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "oops/valueKlass.inline.hpp" #include "oops/valuePayload.hpp" diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index ad6bc89c0ec..3b4939a9d95 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -49,7 +49,6 @@ #include "oops/instanceKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "prims/jvmtiAgentList.hpp" #include "runtime/fieldDescriptor.inline.hpp" diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 480b4323ac2..906a24746c1 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -45,7 +45,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/typeArrayOop.inline.hpp" #include "runtime/arguments.hpp" #include "runtime/atomicAccess.hpp" diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index e3fd236c29f..ecadbe845d7 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -38,7 +38,6 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "oops/oopCast.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "oops/refArrayOop.inline.hpp" #include "oops/typeArrayOop.inline.hpp" From b235d1432a1fd47f6856ece7b4bb4aaab963f5a9 Mon Sep 17 00:00:00 2001 From: Yunbo Zhang Date: Tue, 13 Oct 2026 12:06:58 +0000 Subject: [PATCH 068/101] 8387719: Building the JDK: running `bash configure` does not detect musl Reviewed-by: shade --- make/autoconf/platform.m4 | 4 ++-- test/make/autoconf/test.m4 | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/make/autoconf/platform.m4 b/make/autoconf/platform.m4 index cdeae97c8ac..4b0dc721c1a 100644 --- a/make/autoconf/platform.m4 +++ b/make/autoconf/platform.m4 @@ -226,10 +226,10 @@ AC_DEFUN([PLATFORM_EXTRACT_VARS_FROM_OS], AC_DEFUN([PLATFORM_EXTRACT_VARS_FROM_LIBC], [ case "$1" in - *linux*-musl) + *linux*-musl*) VAR_LIBC=musl ;; - *linux*-gnu) + *linux*-gnu*) VAR_LIBC=gnu ;; *) diff --git a/test/make/autoconf/test.m4 b/test/make/autoconf/test.m4 index c4b8b0ebbac..ed8ae45c3cc 100644 --- a/test/make/autoconf/test.m4 +++ b/test/make/autoconf/test.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2020, 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 @@ -89,6 +89,19 @@ AC_DEFUN([TEST_ARG_ENABLE], UTIL_ASSERT_NOT_TRUE($disabled) ]) +AC_DEFUN([TEST_PLATFORM_LIBC], +[ + for TEST_OS in linux-musl linux-musleabi linux-musleabihf; do + PLATFORM_EXTRACT_VARS_FROM_LIBC($TEST_OS) + UTIL_ASSERT_STRING_EQUALS($VAR_LIBC, musl, [Incorrect libc for $TEST_OS]) + done + + for TEST_OS in linux-gnu linux-gnueabi linux-gnueabihf linux-gnuabi64; do + PLATFORM_EXTRACT_VARS_FROM_LIBC($TEST_OS) + UTIL_ASSERT_STRING_EQUALS($VAR_LIBC, gnu, [Incorrect libc for $TEST_OS]) + done +]) + # Use the CUSTOM_EARLY_HOOK to inject our test after basic init is done. AC_DEFUN_ONCE([CUSTOM_EARLY_HOOK], [ @@ -98,6 +111,7 @@ AC_DEFUN_ONCE([CUSTOM_EARLY_HOOK], TEST_STRING_OPS TEST_ARG_ENABLE + TEST_PLATFORM_LIBC # If no assertions failed, report success $ECHO "==============================" From 64f829f1ed9768176d5de8cf48b076ff4d1fedd7 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 13 Oct 2026 13:32:49 +0000 Subject: [PATCH 069/101] 8393664: G1: Rename inactive region state to pending uncommit Reviewed-by: ayang --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 28 +++---- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 19 ++--- .../share/gc/g1/g1CommittedRegionMap.cpp | 80 +++++++++---------- .../share/gc/g1/g1CommittedRegionMap.hpp | 50 ++++++------ .../gc/g1/g1CommittedRegionMap.inline.hpp | 4 +- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 10 +-- src/hotspot/share/gc/g1/g1FullCollector.cpp | 2 +- .../share/gc/g1/g1HeapRegionManager.cpp | 77 +++++++++--------- .../share/gc/g1/g1HeapRegionManager.hpp | 36 ++++----- .../share/gc/g1/g1HeapRegionPrinter.hpp | 4 +- src/hotspot/share/gc/g1/g1Policy.cpp | 2 +- .../share/gc/g1/g1UncommitRegionTask.cpp | 2 +- src/hotspot/share/prims/whitebox.cpp | 2 +- test/hotspot/gtest/gc/g1/test_g1RegionMap.cpp | 10 +-- 14 files changed, 165 insertions(+), 161 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index ed7dfa4e3e4..984e206c5e2 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -388,14 +388,14 @@ HeapWord* G1CollectedHeap::humongous_obj_allocate(size_t word_size) { G1HeapRegion* humongous_start = _hrm.allocate_humongous(obj_regions); if (humongous_start == nullptr) { // Policy: We could not find enough regions for the humongous object in the - // free list. Look through the heap to find a mix of free and uncommitted regions. + // free list. Look through the heap to find a mix of free, pending uncommit or uncommitted regions. // If so, expand the heap and allocate the humongous object. humongous_start = _hrm.expand_and_allocate_humongous(obj_regions); if (humongous_start != nullptr) { // We managed to find a region by expanding the heap. log_debug(gc, ergo, heap)("Heap expansion (humongous allocation request). Allocation request: %zuB", word_size * HeapWordSize); - policy()->record_new_heap_size(num_committed_regions()); + policy()->record_new_heap_size(num_active_regions()); } else { // Policy: Potentially trigger a defragmentation GC. } @@ -1151,7 +1151,7 @@ bool G1CollectedHeap::expand(size_t expand_bytes, WorkerThreads* pretouch_worker log_debug(gc, ergo, heap)("Heap resize. Requested expansion amount: %zuB aligned expansion amount: %zuB (%u regions)", expand_bytes, aligned_expand_bytes, num_regions_to_expand); - if (num_inactive_regions() == 0) { + if (num_non_active_regions() == 0) { log_debug(gc, ergo, heap)("Heap resize. Did not expand the heap (heap already fully expanded)"); return false; } @@ -1160,7 +1160,7 @@ bool G1CollectedHeap::expand(size_t expand_bytes, WorkerThreads* pretouch_worker size_t actual_expand_bytes = expanded_by * G1HeapRegion::GrainBytes; assert(actual_expand_bytes <= aligned_expand_bytes, "post-condition"); - policy()->record_new_heap_size(num_committed_regions()); + policy()->record_new_heap_size(num_active_regions()); return true; } @@ -1169,12 +1169,12 @@ bool G1CollectedHeap::expand_single_region(uint node_index) { uint expanded_by = _hrm.expand_on_preferred_node(node_index); if (expanded_by == 0) { - assert(num_inactive_regions() == 0, "Should be no regions left, available: %u", num_inactive_regions()); + assert(num_non_active_regions() == 0, "Should be no regions left, available: %u", num_non_active_regions()); log_debug(gc, ergo, heap)("Did not expand the heap (heap already fully expanded)"); return false; } - policy()->record_new_heap_size(num_committed_regions()); + policy()->record_new_heap_size(num_active_regions()); return true; } @@ -1192,7 +1192,7 @@ void G1CollectedHeap::shrink_helper(size_t shrink_bytes) { log_debug(gc, ergo, heap)("Heap resize. Requested shrinking amount: %zuB actual shrinking amount: %zuB (%u regions)", shrink_bytes, shrunk_bytes, num_regions_removed); if (num_regions_removed > 0) { - policy()->record_new_heap_size(num_committed_regions()); + policy()->record_new_heap_size(num_active_regions()); } else { log_debug(gc, ergo, heap)("Heap resize. Did not shrink the heap (heap shrinking operation failed)"); } @@ -1745,7 +1745,7 @@ void G1CollectedHeap::ref_processing_init() { } size_t G1CollectedHeap::capacity() const { - return _hrm.num_committed_regions() * G1HeapRegion::GrainBytes; + return _hrm.num_active_regions() * G1HeapRegion::GrainBytes; } size_t G1CollectedHeap::unused_committed_regions_in_bytes() const { @@ -2461,7 +2461,7 @@ G1HeapSummary G1CollectedHeap::create_g1_heap_summary() { VirtualSpaceSummary heap_summary = create_heap_space_summary(); return G1HeapSummary(heap_summary, heap_used, eden_used_bytes, eden_capacity_bytes, - survivor_used_bytes, old_gen_used_bytes, num_committed_regions()); + survivor_used_bytes, old_gen_used_bytes, num_active_regions()); } G1EvacSummary G1CollectedHeap::create_g1_evac_summary(G1EvacStats* stats) { @@ -2513,15 +2513,15 @@ void G1CollectedHeap::gc_epilogue(bool full) { } uint G1CollectedHeap::uncommit_regions(uint max_num_regions_to_uncommit) { - return _hrm.uncommit_inactive_regions(max_num_regions_to_uncommit); + return _hrm.uncommit_regions(max_num_regions_to_uncommit); } -bool G1CollectedHeap::has_uncommittable_regions() { - return _hrm.has_inactive_regions(); +bool G1CollectedHeap::has_pending_uncommit_regions() { + return _hrm.has_pending_uncommit_regions(); } void G1CollectedHeap::uncommit_regions_if_necessary() { - if (has_uncommittable_regions()) { + if (has_pending_uncommit_regions()) { G1UncommitRegionTask::enqueue(); } } @@ -2864,7 +2864,7 @@ void G1CollectedHeap::record_obj_copy_mem_stats() { log_debug(gc)("Allocated %u survivor %u old percent total %1.2f%% (%u%%)", _survivor_evac_stats.num_filled_regions(), _old_evac_stats.num_filled_regions(), - percent_of(num_allocated_regions, num_committed_regions() - num_allocated_regions), + percent_of(num_allocated_regions, num_active_regions() - num_allocated_regions), G1ReservePercent); policy()->old_gen_alloc_tracker()-> diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index ce57d371506..448a40c1d2e 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -583,9 +583,10 @@ class G1CollectedHeap : public CollectedHeap { // Check if there is memory to uncommit and if so schedule a task to do it. void uncommit_regions_if_necessary(); - // Immediately uncommit uncommittable regions. + // Immediately uncommit regions, at most the given number of regions. Returns + // the number of regions actually uncommitted. uint uncommit_regions(uint max_num_regions_to_uncommit); - bool has_uncommittable_regions(); + bool has_pending_uncommit_regions(); G1NUMA* numa() const { return _numa; } @@ -988,11 +989,11 @@ class G1CollectedHeap : public CollectedHeap { return num_available_regions() == 0; } - // The number of inactive regions. - uint num_inactive_regions() const { return _hrm.num_inactive_regions(); } + // The number of not-active regions. + uint num_non_active_regions() const { return _hrm.num_non_active_regions(); } - // The current number of regions in the heap. - uint num_committed_regions() const { return _hrm.num_committed_regions(); } + // The current number of active regions in the heap. + uint num_active_regions() const { return _hrm.num_active_regions(); } // The max number of regions reserved for the heap. uint max_num_regions() const { return _hrm.max_num_regions(); } @@ -1004,7 +1005,7 @@ class G1CollectedHeap : public CollectedHeap { uint num_used_regions() const { return _hrm.num_used_regions(); } // The number of regions that can be allocated into. - uint num_available_regions() const { return num_free_regions() + num_inactive_regions(); } + uint num_available_regions() const { return num_free_regions() + num_non_active_regions(); } MemoryUsage get_auxiliary_data_memory_usage() const { return _hrm.get_auxiliary_data_memory_usage(); @@ -1161,8 +1162,8 @@ class G1CollectedHeap : public CollectedHeap { // Returns the G1HeapRegion that contains addr. addr must not be null. inline G1HeapRegion* heap_region_containing(const void* addr) const; - // Returns the G1HeapRegion that contains addr, or null if that is an uncommitted - // region. addr must not be null. + // Returns the G1HeapRegion that contains addr, or null if the region is not active. + // addr must not be null. inline G1HeapRegion* heap_region_containing_or_null(const void* addr) const; // A CollectedHeap is divided into a dense sequence of "blocks"; that is, diff --git a/src/hotspot/share/gc/g1/g1CommittedRegionMap.cpp b/src/hotspot/share/gc/g1/g1CommittedRegionMap.cpp index c0304ec9339..85540f34ef3 100644 --- a/src/hotspot/share/gc/g1/g1CommittedRegionMap.cpp +++ b/src/hotspot/share/gc/g1/g1CommittedRegionMap.cpp @@ -37,21 +37,21 @@ G1HeapRegionRange::G1HeapRegionRange(uint start, uint end) : G1CommittedRegionMap::G1CommittedRegionMap() : _active(mtGC), - _inactive(mtGC), + _pending_uncommit(mtGC), _num_active_regions(0), - _num_inactive_regions(0) { } + _num_pending_uncommit_regions(0) { } void G1CommittedRegionMap::initialize(uint num_regions) { _active.initialize(num_regions); - _inactive.initialize(num_regions); + _pending_uncommit.initialize(num_regions); } uint G1CommittedRegionMap::num_active_regions() const { return _num_active_regions; } -uint G1CommittedRegionMap::num_inactive_regions() const { - return _num_inactive_regions; +uint G1CommittedRegionMap::num_pending_uncommit_regions() const { + return _num_pending_uncommit_regions; } uint G1CommittedRegionMap::max_num_regions() const { @@ -60,7 +60,7 @@ uint G1CommittedRegionMap::max_num_regions() const { void G1CommittedRegionMap::activate(uint start, uint end) { verify_num_active_regions(start, end, 0); - verify_num_inactive_regions(start, end, 0); + verify_num_pending_uncommit_regions(start, end, 0); log_debug(gc, heap, region)("Activate regions [%u, %u)", start, end); @@ -69,31 +69,31 @@ void G1CommittedRegionMap::activate(uint start, uint end) { void G1CommittedRegionMap::reactivate(uint start, uint end) { verify_num_active_regions(start, end, 0); - verify_num_inactive_regions(start, end, (end - start)); + verify_num_pending_uncommit_regions(start, end, (end - start)); log_debug(gc, heap, region)("Reactivate regions [%u, %u)", start, end); active_set_range(start, end); - inactive_clear_range(start, end); + clear_pending_uncommit_range(start, end); } void G1CommittedRegionMap::deactivate(uint start, uint end) { verify_num_active_regions(start, end, (end - start)); - verify_num_inactive_regions(start, end, 0); + verify_num_pending_uncommit_regions(start, end, 0); log_debug(gc, heap, region)("Deactivate regions [%u, %u)", start, end); active_clear_range(start, end); - inactive_set_range(start, end); + set_range_to_pending_uncommit(start, end); } void G1CommittedRegionMap::uncommit(uint start, uint end) { verify_num_active_regions(start, end, 0); - verify_num_inactive_regions(start, end, (end-start)); + verify_num_pending_uncommit_regions(start, end, (end-start)); log_debug(gc, heap, region)("Uncommit regions [%u, %u)", start, end); - inactive_clear_range(start, end); + clear_pending_uncommit_range(start, end); } G1HeapRegionRange G1CommittedRegionMap::next_active_range(uint offset) const { @@ -111,8 +111,8 @@ G1HeapRegionRange G1CommittedRegionMap::next_active_range(uint offset) const { } G1HeapRegionRange G1CommittedRegionMap::next_committable_range(uint offset) const { - // We should only call this function when there are no inactive regions. - verify_no_inactive_regons(); + // We should only call this function when there are no pending uncommit regions. + verify_no_pending_uncommit_regions(); // Find first free region from offset. uint start = (uint) _active.find_first_clear_bit(offset); @@ -127,17 +127,17 @@ G1HeapRegionRange G1CommittedRegionMap::next_committable_range(uint offset) cons return G1HeapRegionRange(start, end); } -G1HeapRegionRange G1CommittedRegionMap::next_inactive_range(uint offset) const { - // Find first inactive region from offset. - uint start = (uint) _inactive.find_first_set_bit(offset); +G1HeapRegionRange G1CommittedRegionMap::next_pending_uncommit_range(uint offset) const { + // Find first pending uncommit region from offset. + uint start = (uint) _pending_uncommit.find_first_set_bit(offset); if (start == max_num_regions()) { - // Early when no inactive regions are found. + // Early when no pending uncommit regions are found. return G1HeapRegionRange(max_num_regions(), max_num_regions()); } - uint end = (uint) _inactive.find_first_clear_bit(start); - verify_inactive_range(start, end); + uint end = (uint) _pending_uncommit.find_first_clear_bit(start); + verify_pending_uncommit_range(start, end); return G1HeapRegionRange(start, end); } @@ -156,18 +156,18 @@ void G1CommittedRegionMap::active_clear_range(uint start, uint end) { _num_active_regions -= (end - start); } -void G1CommittedRegionMap::inactive_set_range(uint start, uint end) { - guarantee_mt_safety_inactive(); +void G1CommittedRegionMap::set_range_to_pending_uncommit(uint start, uint end) { + guarantee_mt_safety_pending_uncommit(); - _inactive.par_set_range(start, end, BitMap::unknown_range); - _num_inactive_regions += (end - start); + _pending_uncommit.par_set_range(start, end, BitMap::unknown_range); + _num_pending_uncommit_regions += (end - start); } -void G1CommittedRegionMap::inactive_clear_range(uint start, uint end) { - guarantee_mt_safety_inactive(); +void G1CommittedRegionMap::clear_pending_uncommit_range(uint start, uint end) { + guarantee_mt_safety_pending_uncommit(); - _inactive.par_clear_range(start, end, BitMap::unknown_range); - _num_inactive_regions -= (end - start); + _pending_uncommit.par_clear_range(start, end, BitMap::unknown_range); + _num_pending_uncommit_regions -= (end - start); } void G1CommittedRegionMap::guarantee_mt_safety_active() const { @@ -191,8 +191,8 @@ void G1CommittedRegionMap::guarantee_mt_safety_active() const { } } -void G1CommittedRegionMap::guarantee_mt_safety_inactive() const { - // G1CommittedRegionMap _inactive-map MT safety protocol: +void G1CommittedRegionMap::guarantee_mt_safety_pending_uncommit() const { + // G1CommittedRegionMap _pending_uncommit-map MT safety protocol: // (a) If we're at a safepoint, the caller must either be the VM thread or // hold the FreeList_lock. // (b) If we're not at a safepoint, the caller must hold the Uncommit_lock. @@ -219,10 +219,10 @@ void G1CommittedRegionMap::verify_active_range(uint start, uint end) const { assert(end == _active.size() || !is_active(end), "Region (%u) is active but not included in range", end); } -void G1CommittedRegionMap::verify_inactive_range(uint start, uint end) const { - assert(is_inactive(start), "First region (%u) is not inactive", start); - assert(is_inactive(end - 1), "Last region (%u) in range is not inactive", end - 1); - assert(end == _inactive.size() || !is_inactive(end), "Region (%u) is inactive but not included in range", end); +void G1CommittedRegionMap::verify_pending_uncommit_range(uint start, uint end) const { + assert(is_pending_uncommit(start), "First region (%u) is not pending uncommit", start); + assert(is_pending_uncommit(end - 1), "Last region (%u) in range is not pending uncommit", end - 1); + assert(end == _pending_uncommit.size() || !is_pending_uncommit(end), "Region (%u) is pending uncommit but not included in range", end); } void G1CommittedRegionMap::verify_free_range(uint start, uint end) const { @@ -230,9 +230,9 @@ void G1CommittedRegionMap::verify_free_range(uint start, uint end) const { assert(!is_active(end - 1), "Last region (%u) in range is active", end - 1); } -void G1CommittedRegionMap::verify_no_inactive_regons() const { - BitMap::idx_t first_inactive = _inactive.find_first_set_bit(0); - assert(first_inactive == _inactive.size(), "Should be no inactive regions, but was at index: %zu", first_inactive); +void G1CommittedRegionMap::verify_no_pending_uncommit_regions() const { + BitMap::idx_t first_pending_uncommit = _pending_uncommit.find_first_set_bit(0); + assert(first_pending_uncommit == _pending_uncommit.size(), "Should be no pending uncommit regions, but was at index: %zu", first_pending_uncommit); } void G1CommittedRegionMap::verify_num_active_regions(uint start, uint end, uint expected) const { @@ -240,9 +240,9 @@ void G1CommittedRegionMap::verify_num_active_regions(uint start, uint end, uint assert(found == expected, "Unexpected number of active regions, found: %u, expected: %u", found, expected); } -void G1CommittedRegionMap::verify_num_inactive_regions(uint start, uint end, uint expected) const { - uint found = (uint) _inactive.count_one_bits(start, end); - assert(found == expected, "Unexpected number of inactive regions, found: %u, expected: %u", found, expected); +void G1CommittedRegionMap::verify_num_pending_uncommit_regions(uint start, uint end, uint expected) const { + uint found = (uint) _pending_uncommit.count_one_bits(start, end); + assert(found == expected, "Unexpected number of pending uncommit regions, found: %u, expected: %u", found, expected); } #endif //ASSERT diff --git a/src/hotspot/share/gc/g1/g1CommittedRegionMap.hpp b/src/hotspot/share/gc/g1/g1CommittedRegionMap.hpp index 5129f35d903..60ac349355f 100644 --- a/src/hotspot/share/gc/g1/g1CommittedRegionMap.hpp +++ b/src/hotspot/share/gc/g1/g1CommittedRegionMap.hpp @@ -43,16 +43,16 @@ class G1HeapRegionRange : public StackObj { uint num_regions() const { return _end - _start; } }; -// The G1CommittedRegionMap keeps track of the state of regions. -// It tracks both regions ready for use (active) and if there are any regions ready for -// uncommit (inactive). We basically have three states. Uncommitted, Active, Inactive. All -// regions that are either Active or Inactive are committed. +// The G1CommittedRegionMap keeps track of the state of regions, one of +// Active, PendingUncommit and Uncommitted. +// Committed regions can be either ready for use (active) or ready for +// uncommit (PendingUncommit). // // State transitions: -// Uncommitted -> Active (activate()) -// Active -> Inactive (deactivate()) -// Inactive -> Active (reactivate()) -// Inactive -> Uncommitted (uncommit()) +// Uncommitted -> Active (activate()) +// Active -> PendingUncommit (deactivate()) +// PendingUncommit -> Active (reactivate()) +// PendingUncommit -> Uncommitted (uncommit()) // class G1CommittedRegionMap : public CHeapObj { // Each bit in this bitmap indicates that the corresponding region is active @@ -60,14 +60,14 @@ class G1CommittedRegionMap : public CHeapObj { CHeapBitMap _active; // Each bit in this bitmap indicates that the corresponding region is no longer // active and it can be uncommitted. - CHeapBitMap _inactive; + CHeapBitMap _pending_uncommit; // The union of these two bitmaps are the regions that are currently committed. // The number of regions active and available for use. uint _num_active_regions; // The number of regions ready to be uncommitted. - uint _num_inactive_regions; + uint _num_pending_uncommit_regions; uint max_num_regions() const; @@ -76,49 +76,49 @@ class G1CommittedRegionMap : public CHeapObj { // details. void active_set_range(uint start, uint end); void active_clear_range(uint start, uint end); - void inactive_set_range(uint start, uint end); - void inactive_clear_range(uint start, uint end); + void set_range_to_pending_uncommit(uint start, uint end); + void clear_pending_uncommit_range(uint start, uint end); public: G1CommittedRegionMap(); void initialize(uint num_regions); uint num_active_regions() const; - uint num_inactive_regions() const; + uint num_pending_uncommit_regions() const; // Check if a region is marked active. inline bool is_active(uint index) const; - // Check if a region is marked inactive. - inline bool is_inactive(uint index) const; + // Check if a region is marked pending uncommit. + inline bool is_pending_uncommit(uint index) const; // Mark a range of regions as active. void activate(uint start, uint end); - // Mark a range of regions as inactive and ready to be uncommitted. + // Mark a range of regions as pending uncommit. void deactivate(uint start, uint end); - // Mark a range of regions active again and no longer ready for uncommit. + // Mark a range of regions active again and no longer pending for uncommit. void reactivate(uint start, uint end); - // Uncommit a range of inactive regions. + // Uncommit a range of PendingUncommit regions. void uncommit(uint start, uint end); // Finds the next range of active regions starting at offset. G1HeapRegionRange next_active_range(uint offset) const; - // Finds the next range of inactive regions starting at offset. - G1HeapRegionRange next_inactive_range(uint offset) const; + // Finds the next range of PendingUncommit regions starting at offset. + G1HeapRegionRange next_pending_uncommit_range(uint offset) const; // Finds the next range of committable regions starting at offset. - // This function must only be called when no inactive regions are + // This function must only be called when no PendingUncommit regions are // present and can be used to activate more regions. G1HeapRegionRange next_committable_range(uint offset) const; protected: virtual void guarantee_mt_safety_active() const; - virtual void guarantee_mt_safety_inactive() const; + virtual void guarantee_mt_safety_pending_uncommit() const; void verify_active_range(uint start, uint end) const NOT_DEBUG_RETURN; void verify_free_range(uint start, uint end) const NOT_DEBUG_RETURN; - void verify_inactive_range(uint start, uint end) const NOT_DEBUG_RETURN; - void verify_no_inactive_regons() const NOT_DEBUG_RETURN; + void verify_pending_uncommit_range(uint start, uint end) const NOT_DEBUG_RETURN; + void verify_no_pending_uncommit_regions() const NOT_DEBUG_RETURN; void verify_num_active_regions(uint start, uint end, uint expected) const NOT_DEBUG_RETURN; - void verify_num_inactive_regions(uint start, uint end, uint expected) const NOT_DEBUG_RETURN; + void verify_num_pending_uncommit_regions(uint start, uint end, uint expected) const NOT_DEBUG_RETURN; }; #endif // SHARE_GC_G1_G1COMMITTEDREGIONMAP_HPP diff --git a/src/hotspot/share/gc/g1/g1CommittedRegionMap.inline.hpp b/src/hotspot/share/gc/g1/g1CommittedRegionMap.inline.hpp index 6f3611443c0..d35aeb6d195 100644 --- a/src/hotspot/share/gc/g1/g1CommittedRegionMap.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CommittedRegionMap.inline.hpp @@ -33,8 +33,8 @@ inline bool G1CommittedRegionMap::is_active(uint index) const { return _active.par_at(index); } -inline bool G1CommittedRegionMap::is_inactive(uint index) const { - return _inactive.at(index); +inline bool G1CommittedRegionMap::is_pending_uncommit(uint index) const { + return _pending_uncommit.at(index); } #endif // SHARE_GC_G1_G1COMMITTEDREGIONMAP_INLINE_HPP diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 4527226569d..9961c49802a 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -822,7 +822,7 @@ class G1ClearBitMapTask : public WorkerTask { if (_cm->has_aborted()) { return true; } - // Re-read end. The region might have been uncommitted. + // Re-read end. The region might have been deactivated. end = region_clear_limit(r); } } @@ -853,7 +853,7 @@ class G1ClearBitMapTask : public WorkerTask { void G1ConcurrentMark::clear_bitmap(WorkerThreads* workers, bool may_yield) { assert(may_yield || SafepointSynchronize::is_at_safepoint(), "Non-yielding bitmap clear only allowed at safepoint."); - size_t const num_bytes_to_clear = (G1HeapRegion::GrainBytes * _g1h->num_committed_regions()) / G1CMBitMap::heap_map_factor(); + size_t const num_bytes_to_clear = (G1HeapRegion::GrainBytes * _g1h->num_active_regions()) / G1CMBitMap::heap_map_factor(); size_t const num_chunks = align_up(num_bytes_to_clear, G1ClearBitMapTask::chunk_size()) / G1ClearBitMapTask::chunk_size(); uint const num_workers = (uint)MIN2(num_chunks, (size_t)workers->active_workers()); @@ -1405,13 +1405,13 @@ void G1ConcurrentMark::remark() { GCTraceTime(Debug, gc, phases) debug("Select For Rebuild and Reclaim Empty Regions", _gc_timer_cm); G1UpdateRegionLivenessAndSelectForRebuildTask cl(_g1h, this, _g1h->workers()->active_workers()); - uint const num_workers = MIN2(G1UpdateRegionLivenessAndSelectForRebuildTask::desired_num_workers(_g1h->num_committed_regions()), + uint const num_workers = MIN2(G1UpdateRegionLivenessAndSelectForRebuildTask::desired_num_workers(_g1h->num_active_regions()), _g1h->workers()->active_workers()); - log_debug(gc,ergo)("Running %s using %u workers for %u regions in heap", cl.name(), num_workers, _g1h->num_committed_regions()); + log_debug(gc,ergo)("Running %s using %u workers for %u regions in heap", cl.name(), num_workers, _g1h->num_active_regions()); _g1h->workers()->run_task(&cl, num_workers); log_debug(gc, remset, tracking)("Remembered Set Tracking update regions total %u, selected %u", - _g1h->num_committed_regions(), cl.total_selected_for_rebuild()); + _g1h->num_active_regions(), cl.total_selected_for_rebuild()); _needs_remembered_set_rebuild = (cl.total_selected_for_rebuild() > 0); diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index 21461e52fd6..2838d3a2585 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -85,7 +85,7 @@ uint G1FullCollector::calc_active_workers() { // Consider G1HeapWastePercent to decide max number of workers. Each worker // will in average cause half a region waste. - uint max_wasted_regions_allowed = ((heap->num_committed_regions() * G1HeapWastePercent) / 100); + uint max_wasted_regions_allowed = ((heap->num_active_regions() * G1HeapWastePercent) / 100); uint waste_worker_count = MAX2((max_wasted_regions_allowed * 2) , 1u); uint heap_waste_worker_limit = MIN2(waste_worker_count, max_worker_count); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp index ff86b003e75..83f62fd7bab 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp @@ -179,7 +179,7 @@ void G1HeapRegionManager::expand(uint start, uint num_regions, WorkerThreads* pr void G1HeapRegionManager::commit_regions(uint index, size_t num_regions, WorkerThreads* pretouch_workers) { guarantee(num_regions > 0, "Must commit more than zero regions"); - guarantee(num_regions <= num_inactive_regions(), + guarantee(num_regions <= num_non_active_regions(), "Cannot commit more than the maximum amount of regions"); _heap_mapper->commit_regions(index, num_regions, pretouch_workers); @@ -246,14 +246,14 @@ void G1HeapRegionManager::reactivate_regions(uint start, uint num_regions) { void G1HeapRegionManager::deactivate_regions(uint start, uint num_regions) { assert(num_regions > 0, "Need to specify at least one region to deactivate, tried to deactivate zero regions at %u", start); - assert(num_committed_regions() >= num_regions, "pre-condition"); + assert(num_active_regions() >= num_regions, "pre-condition"); // Reset NUMA index to and print state change. uint end = start + num_regions; for (uint i = start; i < end; i++) { G1HeapRegion* hr = at(i); hr->set_node_index(G1NUMA::UnknownNodeIndex); - G1HeapRegionPrinter::inactive(hr); + G1HeapRegionPrinter::pending_uncommit(hr); } _committed_map.deactivate(start, end); @@ -286,20 +286,21 @@ MemoryUsage G1HeapRegionManager::get_auxiliary_data_memory_usage() const { return MemoryUsage(0, used_sz, committed_sz, committed_sz); } -bool G1HeapRegionManager::has_inactive_regions() const { - return _committed_map.num_inactive_regions() > 0; +bool G1HeapRegionManager::has_pending_uncommit_regions() const { + return _committed_map.num_pending_uncommit_regions() > 0; } -uint G1HeapRegionManager::uncommit_inactive_regions(uint max_num_regions_to_uncommit) { +uint G1HeapRegionManager::uncommit_regions(uint max_num_regions_to_uncommit) { assert(max_num_regions_to_uncommit > 0, "Need to specify at least one region to uncommit"); uint num_uncommitted_regions = 0; uint offset = 0; do { MutexLocker uc(G1Uncommit_lock, Mutex::_no_safepoint_check_flag); - G1HeapRegionRange range = _committed_map.next_inactive_range(offset); + G1HeapRegionRange range = _committed_map.next_pending_uncommit_range(offset); // No more regions available for uncommit. Return the number of regions - // already uncommitted or 0 if there were no longer any inactive regions. + // already uncommitted or 0 if there were no longer any regions that were pending + // uncommit. if (range.num_regions() == 0) { return num_uncommitted_regions; } @@ -314,14 +315,14 @@ uint G1HeapRegionManager::uncommit_inactive_regions(uint max_num_regions_to_unco return num_uncommitted_regions; } -uint G1HeapRegionManager::expand_inactive(uint num_regions) { +uint G1HeapRegionManager::expand_pending_uncommit(uint num_regions) { uint offset = 0; uint num_expanded_regions = 0; do { - G1HeapRegionRange regions = _committed_map.next_inactive_range(offset); + G1HeapRegionRange regions = _committed_map.next_pending_uncommit_range(offset); if (regions.num_regions() == 0) { - // No more unavailable regions. + // No more pending uncommit regions. break; } @@ -361,7 +362,7 @@ uint G1HeapRegionManager::expand_by(uint num_regions, WorkerThreads* pretouch_wo // First "undo" any requests to uncommit memory concurrently by // reverting such regions to being available. - uint num_expanded_regions = expand_inactive(num_regions); + uint num_expanded_regions = expand_pending_uncommit(num_regions); // Commit more regions if needed. if (num_expanded_regions < num_regions) { @@ -377,18 +378,18 @@ void G1HeapRegionManager::expand_exact(uint start, uint num_regions, WorkerThrea uint end = start + num_regions; for (uint i = start; i < end; i++) { - // First check inactive. If the regions is inactive, try to reactivate it - // before it get uncommitted by the G1SeriveThread. - if (_committed_map.is_inactive(i)) { + // First check pending uncommit. If the regions are pending uncommit, try to reactivate them + // before they get uncommitted by the G1ServiceThread. + if (_committed_map.is_pending_uncommit(i)) { // Need to grab the lock since this can be called by a java thread // doing humongous allocations. MutexLocker uc(G1Uncommit_lock, Mutex::_no_safepoint_check_flag); // State might change while getting the lock. - if (_committed_map.is_inactive(i)) { + if (_committed_map.is_pending_uncommit(i)) { reactivate_regions(i, 1); } } - // Not else-if to catch the case where the inactive region was uncommitted + // Not else-if to catch the case where the pending uncommit region was uncommitted // while waiting to get the lock. if (!_committed_map.is_active(i)) { expand(i, 1, pretouch_workers); @@ -403,7 +404,7 @@ void G1HeapRegionManager::expand_exact(uint start, uint num_regions, WorkerThrea uint G1HeapRegionManager::expand_on_preferred_node(uint preferred_index) { uint expand_candidate = UINT_MAX; - if (num_inactive_regions() >= 1) { + if (num_non_active_regions() >= 1) { for (uint i = 0; i < max_num_regions(); i++) { if (is_available(i)) { // Already in use continue @@ -532,7 +533,7 @@ bool G1HeapRegionManager::allocate_containing_regions(MemRegion range, size_t* n uint last_index = (uint)_regions.get_index_by_address(range.last()); // Ensure that each G1 region in the range is free, returning false if not. - // Activates inactive regions and commits others (making them Active) if necessary. + // Activates pending uncommit regions and commits others (making them Active) if necessary. for (uint curr_index = start_index; curr_index <= last_index; curr_index++) { if (!is_available(curr_index)) { num_activated_regions++; @@ -551,7 +552,7 @@ bool G1HeapRegionManager::allocate_containing_regions(MemRegion range, size_t* n void G1HeapRegionManager::par_iterate(G1HeapRegionClosure* blk, G1HeapRegionClaimer* hrclaimer, const uint start_index) const { // Every worker will actually look at all regions, skipping over regions that - // are currently not committed. + // are currently not active. // This also (potentially) iterates over regions newly allocated during GC. This // is no problem except for some extra work. const uint num_regions = hrclaimer->num_regions(); @@ -579,10 +580,10 @@ void G1HeapRegionManager::par_iterate(G1HeapRegionClosure* blk, G1HeapRegionClai } uint G1HeapRegionManager::shrink_by(uint num_regions_to_remove) { - assert(num_committed_regions() > 0, "the region sequence should not be empty"); - assert(num_committed_regions() <= _next_highest_used_hrm_index, "invariant"); + assert(num_active_regions() > 0, "the region sequence should not be empty"); + assert(num_active_regions() <= _next_highest_used_hrm_index, "invariant"); assert(_next_highest_used_hrm_index > 0, "we should have at least one region committed"); - assert(num_regions_to_remove < num_committed_regions(), "We should never remove all regions"); + assert(num_regions_to_remove < num_active_regions(), "We should never remove all regions"); if (num_regions_to_remove == 0) { return 0; @@ -616,7 +617,7 @@ void G1HeapRegionManager::shrink_at(uint index, size_t num_regions) { assert(at(i)->is_free(), "Expected free region at index %u", i); } #endif - // Mark regions as inactive making them ready for uncommit. + // Mark regions as pending uncommit. deactivate_regions(index, (uint) num_regions); } @@ -655,28 +656,28 @@ uint G1HeapRegionManager::find_empty_from_idx_reverse(uint start_idx, uint* res_ } void G1HeapRegionManager::verify() { - guarantee(num_committed_regions() <= _next_highest_used_hrm_index, - "invariant: committed regions: %u _next_highest_used_hrm_index: %u", - num_committed_regions(), _next_highest_used_hrm_index); + guarantee(num_active_regions() <= _next_highest_used_hrm_index, + "invariant: active regions: %u _next_highest_used_hrm_index: %u", + num_active_regions(), _next_highest_used_hrm_index); guarantee(_next_highest_used_hrm_index <= max_num_regions(), "invariant: _next_highest_used_hrm_index: %u max_num_regions: %u", _next_highest_used_hrm_index, max_num_regions()); - guarantee(num_committed_regions() <= max_num_regions(), - "invariant: committed regions: %u max_num_regions: %u", - num_committed_regions(), max_num_regions()); + guarantee(num_active_regions() <= max_num_regions(), + "invariant: active regions: %u max_num_regions: %u", + num_active_regions(), max_num_regions()); - bool prev_committed = true; - uint num_committed = 0; + bool prev_region_was_active = true; + uint num_found_active_regions = 0; HeapWord* prev_end = heap_bottom(); for (uint i = 0; i < _next_highest_used_hrm_index; i++) { if (!is_available(i)) { - prev_committed = false; + prev_region_was_active = false; continue; } - num_committed++; + num_found_active_regions++; G1HeapRegion* hr = _regions.get_by_index(i); guarantee(hr != nullptr, "invariant: i: %u", i); - guarantee(!prev_committed || hr->bottom() == prev_end, + guarantee(!prev_region_was_active || hr->bottom() == prev_end, "invariant i: %u " HR_FORMAT " prev_end: " PTR_FORMAT, i, HR_FORMAT_PARAMS(hr), p2i(prev_end)); guarantee(hr->hrm_index() == i, @@ -686,14 +687,16 @@ void G1HeapRegionManager::verify() { // We cannot check whether the region is part of a particular set: at the time // this method may be called, we have only completed allocation of the regions, // but not put into a region set. - prev_committed = true; + prev_region_was_active = true; prev_end = hr->end(); } for (uint i = _next_highest_used_hrm_index; i < max_num_regions(); i++) { guarantee(_regions.get_by_index(i) == nullptr, "invariant i: %u", i); } - guarantee(num_committed == num_committed_regions(), "Found %u committed regions, but should be %u", num_committed, num_committed_regions()); + guarantee(num_found_active_regions == num_active_regions(), + "Found %u active regions, but should be %u", + num_found_active_regions, num_active_regions()); _free_list.verify(); } diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp index 69c708717b4..8edfc99e3ef 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp @@ -63,7 +63,7 @@ class G1HeapRegionTable : public G1BiasedMappedArray { // // We keep track of three region counts: // -// * num_committed_regions() is the number of currently committed regions using +// * num_active_regions() is the number of currently active regions using // the committed map. These may not be contiguous. // * _next_highest_used_hrm_index (not exposed outside this class) is the // highest heap region index +1 for which we have G1HeapRegions. @@ -79,7 +79,7 @@ class G1HeapRegionManager: public CHeapObj { G1RegionToSpaceMapper* _refinement_table_mapper; // Keeps track of the currently committed regions in the heap. The committed regions - // can either be active (ready for use) or inactive (ready for uncommit). + // can either be active (ready for use) or pending uncommit (ready for uncommit). G1CommittedRegionMap _committed_map; // Internal only. The highest heap region index +1 we allocated a G1HeapRegion instance for. @@ -94,7 +94,7 @@ class G1HeapRegionManager: public CHeapObj { // Initialize the G1HeapRegions in the range and put them on the free list. void initialize_regions(uint start, uint num_regions); - // Find a contiguous set of empty or uncommitted regions of length num_regions and return + // Find a contiguous set of free active, pending uncommit or uncommitted regions of length num_regions and return // the index of the first region or G1_NO_HRM_INDEX if the search was unsuccessful. // Start and end defines the range to seek in, policy is first-fit. uint find_contiguous_in_range(uint start, uint end, uint num_regions); @@ -147,8 +147,8 @@ class G1HeapRegionManager: public CHeapObj { // Expand helper for cases when the regions to expand are well defined. void expand_exact(uint start, uint num_regions, WorkerThreads* pretouch_workers); - // Expand helper activating inactive regions rather than committing new ones. - uint expand_inactive(uint num_regions); + // Expand helper activating pending uncommit regions rather than committing new ones. + uint expand_pending_uncommit(uint num_regions); // Expand helper finding new regions to commit. uint expand_any(uint num_regions, WorkerThreads* pretouch_workers); @@ -187,8 +187,8 @@ class G1HeapRegionManager: public CHeapObj { // humongous object that hr is part of. inline G1HeapRegion* next_region_in_humongous(G1HeapRegion* hr) const; - // If addr is within the committed space return its corresponding - // G1HeapRegion, otherwise return null. + // Return the G1HeapRegion the given addr is in. May return null for regions + // that were never committed. inline G1HeapRegion* addr_to_region(HeapWord* addr) const; // Insert the given region into the free region list. @@ -223,7 +223,7 @@ class G1HeapRegionManager: public CHeapObj { return _free_list.num_regions(); } - uint num_used_regions() const { return num_committed_regions() - num_free_regions(); } + uint num_used_regions() const { return num_active_regions() - num_free_regions(); } uint num_free_regions(uint node_index) const { return _free_list.num_regions_on_node(node_index); @@ -233,11 +233,11 @@ class G1HeapRegionManager: public CHeapObj { return num_free_regions() * G1HeapRegion::GrainBytes; } - // Return the number of regions uncommitted or ready to be uncommitted. - uint num_inactive_regions() const { return max_num_regions() - num_committed_regions(); } + // Return the number of regions uncommitted or pending uncommit. + uint num_non_active_regions() const { return max_num_regions() - num_active_regions(); } // Return the number of regions currently active and available for use. - uint num_committed_regions() const { return _committed_map.num_active_regions(); } + uint num_active_regions() const { return _committed_map.num_active_regions(); } // The number of regions reserved for the heap. uint max_num_regions() const { return (uint)_regions.length(); } @@ -262,7 +262,7 @@ class G1HeapRegionManager: public CHeapObj { // and not free, and return the number of regions newly activated in num_newly_activated_regions. bool allocate_containing_regions(MemRegion range, size_t* num_newly_activated_regions, WorkerThreads* pretouch_workers); - // Apply blk->do_heap_region() on all committed regions in address order, + // Apply blk->do_heap_region() on all active regions in address order, // terminating the iteration early if do_heap_region() returns true. void iterate(G1HeapRegionClosure* blk) const; void iterate(G1HeapRegionIndexClosure* blk) const; @@ -274,15 +274,15 @@ class G1HeapRegionManager: public CHeapObj { uint shrink_by(uint num_regions_to_remove); // Remove a number of regions starting at the specified index, which must be available, - // empty, and free. The regions are marked inactive and can later be uncommitted. + // empty, and free. The regions are marked pending uncommit and can later be uncommitted. void shrink_at(uint index, size_t num_regions); - // Check if there are any inactive regions that can be uncommitted. - bool has_inactive_regions() const; + // Check if there are any pending uncommit regions that can be uncommitted. + bool has_pending_uncommit_regions() const; - // Uncommit inactive regions. Limit the number of regions to uncommit and return - // actual number uncommitted. - uint uncommit_inactive_regions(uint max_num_regions_to_uncommit); + // Uncommit pending uncommit regions. Limit the number of regions to uncommit by the + // given value and return actual number uncommitted. + uint uncommit_regions(uint max_num_regions_to_uncommit); void verify(); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionPrinter.hpp b/src/hotspot/share/gc/g1/g1HeapRegionPrinter.hpp index 577a8552091..4826471f552 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionPrinter.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionPrinter.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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 @@ -69,7 +69,7 @@ class G1HeapRegionPrinter : public AllStatic { static void active(G1HeapRegion* hr) { print("ACTIVE", hr); } - static void inactive(G1HeapRegion* hr) { print("INACTIVE", hr); } + static void pending_uncommit(G1HeapRegion* hr) { print("PENDING-UNCOMMIT", hr); } static void uncommit(G1HeapRegion* hr) { print("UNCOMMIT", hr); } }; diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 34918b7ce3f..5fdfdec67e0 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -1518,7 +1518,7 @@ uint G1Policy::calc_max_num_old_cset_regions() const { // as a percentage of the heap size. I.e., it should bound the // number of old regions added to the CSet irrespective of how many // of them are available. - double result = (double)_g1h->num_committed_regions() * G1OldCSetRegionThresholdPercent / 100; + double result = (double)_g1h->num_active_regions() * G1OldCSetRegionThresholdPercent / 100; // Round up to be conservative. return (uint)ceil(result); } diff --git a/src/hotspot/share/gc/g1/g1UncommitRegionTask.cpp b/src/hotspot/share/gc/g1/g1UncommitRegionTask.cpp index 989a1292c08..67c99ae2e81 100644 --- a/src/hotspot/share/gc/g1/g1UncommitRegionTask.cpp +++ b/src/hotspot/share/gc/g1/g1UncommitRegionTask.cpp @@ -122,7 +122,7 @@ void G1UncommitRegionTask::execute() { // Reschedule if there are more regions to uncommit, otherwise // change state to inactive. - if (g1h->has_uncommittable_regions()) { + if (g1h->has_pending_uncommit_regions()) { // Delay to avoid starving application. schedule(UncommitTaskDelayMs); } else { diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 8a2a751fb70..18b6d00a089 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -596,7 +596,7 @@ WB_END WB_ENTRY(jboolean, WB_G1HasRegionsToUncommit(JNIEnv* env, jobject o)) if (UseG1GC) { - return G1CollectedHeap::heap()->has_uncommittable_regions(); + return G1CollectedHeap::heap()->has_pending_uncommit_regions(); } THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_G1HasRegionsToUncommit: G1 GC is not enabled"); WB_END diff --git a/test/hotspot/gtest/gc/g1/test_g1RegionMap.cpp b/test/hotspot/gtest/gc/g1/test_g1RegionMap.cpp index 388c78074a0..917267c5081 100644 --- a/test/hotspot/gtest/gc/g1/test_g1RegionMap.cpp +++ b/test/hotspot/gtest/gc/g1/test_g1RegionMap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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,12 +32,12 @@ class G1CommittedRegionMapSerial : public G1CommittedRegionMap { void verify_counts() { verify_num_active_regions(0, TestRegions, num_active_regions()); - verify_num_inactive_regions(0, TestRegions, num_inactive_regions()); + verify_num_pending_uncommit_regions(0, TestRegions, num_pending_uncommit_regions()); } protected: void guarantee_mt_safety_active() const { } - void guarantee_mt_safety_inactive() const { } + void guarantee_mt_safety_pending_uncommit() const { } }; static bool mutate() { @@ -78,7 +78,7 @@ static void random_deactivate(G1CommittedRegionMap* map) { static void random_uncommit_or_reactive(G1CommittedRegionMap* map) { uint current_offset = 0; do { - G1HeapRegionRange current = map->next_inactive_range(current_offset); + G1HeapRegionRange current = map->next_pending_uncommit_range(current_offset); // Randomly either reactivate or uncommit if (mutate()) { map->reactivate(current.start(), current.end()); @@ -124,6 +124,6 @@ TEST(G1CommittedRegionMapTest, serial) { serial_map.verify_counts(); random_activate_free(&serial_map); serial_map.verify_counts(); - ASSERT_EQ(serial_map.num_inactive_regions(), 0u); + ASSERT_EQ(serial_map.num_pending_uncommit_regions(), 0u); } } From 46d348681ff6459cd2e9a24ff75937a5eef6a616 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Tue, 13 Oct 2026 14:05:44 +0000 Subject: [PATCH 070/101] 8394316: (ch) Channels.newInputStream skip returns negative value if position > size Reviewed-by: vyazici --- .../sun/nio/ch/ChannelInputStream.java | 15 +- .../java/nio/channels/Channels/Available.java | 76 ++-- test/jdk/java/nio/channels/Channels/Skip.java | 330 ++++++++++++++++++ 3 files changed, 381 insertions(+), 40 deletions(-) create mode 100644 test/jdk/java/nio/channels/Channels/Skip.java diff --git a/src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java b/src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java index 09f6d7b1d75..54a3052b70f 100644 --- a/src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java +++ b/src/java.base/share/classes/sun/nio/ch/ChannelInputStream.java @@ -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 @@ -223,18 +223,15 @@ public int available() throws IOException { @Override public synchronized long skip(long n) throws IOException { // special case where the channel is to a file - if (ch instanceof SeekableByteChannel sbc && - !(ch instanceof FileChannelImpl fci && isOther())) { + if (ch instanceof SeekableByteChannel sbc + && !(ch instanceof FileChannelImpl && isOther())) { long pos = sbc.position(); long newPos; if (n > 0) { - newPos = pos + n; - long size = sbc.size(); - if (newPos < 0 || newPos > size) { - newPos = size; - } + long remaining = Long.max(sbc.size() - pos, 0L); + newPos = pos + Long.min(n, remaining); } else { - newPos = Long.max(pos + n, 0); + newPos = Long.max(pos + n, 0L); } sbc.position(newPos); return newPos - pos; diff --git a/test/jdk/java/nio/channels/Channels/Available.java b/test/jdk/java/nio/channels/Channels/Available.java index 9e37598bbb6..4c02aa062aa 100644 --- a/test/jdk/java/nio/channels/Channels/Available.java +++ b/test/jdk/java/nio/channels/Channels/Available.java @@ -44,55 +44,67 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.Arrays; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.*; class Available { + /** + * Supplies (file) sizes and positions to test. + */ + static Stream testCases() { + return IntStream.of(0, 1, 100) // sizes + .boxed() + .flatMap(size -> { + long[] positions = { + 0, + 1, + 2, + size - 1, + size, + size + 1, + Integer.MAX_VALUE - size - 1, + Integer.MAX_VALUE - size, + Integer.MAX_VALUE - size + 1, + Integer.MAX_VALUE - 1, + Integer.MAX_VALUE, + Integer.MAX_VALUE + 1L + }; + return Arrays.stream(positions) + .filter(pos -> pos >= 0) + .distinct() + .mapToObj(pos -> Arguments.of(size, pos)); + }); + } + /** * Test InputStream.available when input stream reads from a SeekableByteChannel. */ - private static void testSeekableByteChannel(SeekableByteChannel ch) throws Exception { + private static void testAvailable(SeekableByteChannel ch) throws Exception { InputStream in = Channels.newInputStream(ch); - long size = ch.size(); - long[] positions = new long[] { - 0L, - size - 1L, - size, - size + 1L, - Integer.MAX_VALUE - size - 1L, - Integer.MAX_VALUE - size, - Integer.MAX_VALUE - size + 1L, - Integer.MAX_VALUE - 1L, - Integer.MAX_VALUE, - Integer.MAX_VALUE + 1L - }; - for (long pos : positions) { - if (pos >= 0) { - int expectedAvailable = Math.clamp(size - pos, 0, Integer.MAX_VALUE); - System.err.format( - " size = %d, position = %d, expected available = %d%n", - size, pos, expectedAvailable); - ch.position(pos); - assertEquals(expectedAvailable, in.available()); - } - } + int expectedAvailable = Math.clamp(ch.size() - ch.position(), 0, Integer.MAX_VALUE); + assertEquals(expectedAvailable, in.available()); } /** * Test InputStream.available when input stream reads from a FileChannel. */ @ParameterizedTest - @ValueSource(ints = { 0, 100 }) - void testFileChannel(int size) throws Exception { + @MethodSource("testCases") + void testFileChannel(int size, long pos) throws Exception { Path file = Files.createTempFile(Path.of("."), "blah", "dat"); try { Files.write(file, new byte[size]); try (FileChannel fc = FileChannel.open(file)) { - testSeekableByteChannel(fc); + fc.position(pos); + testAvailable(fc); } } finally { Files.delete(file); @@ -103,8 +115,8 @@ void testFileChannel(int size) throws Exception { * Test InputStream.available when input stream reads from a SeekableByteChannel. */ @ParameterizedTest - @ValueSource(ints = { 0, 100 }) - void testSeekableByteChannel(int size) throws Exception { + @MethodSource("testCases") + void testSeekableByteChannel(int size, long pos) throws Exception { var ch = new SeekableByteChannel() { long position; @Override @@ -143,7 +155,9 @@ public void close() throws IOException { throw new IOException(); } }; - testSeekableByteChannel(ch); + + ch.position(pos); + testAvailable(ch); } /** diff --git a/test/jdk/java/nio/channels/Channels/Skip.java b/test/jdk/java/nio/channels/Channels/Skip.java new file mode 100644 index 00000000000..86586a3c740 --- /dev/null +++ b/test/jdk/java/nio/channels/Channels/Skip.java @@ -0,0 +1,330 @@ +/* + * 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 8394316 + * @summary Test Channels.newInputStream skip method + * @run junit ${test.main.class} + */ + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.NonWritableChannelException; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +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.*; + +class Skip { + + /** + * Supplies (file) sizes, positions and skip values to test. + */ + static Stream testCases() { + return IntStream.of(0, 1, 100) // sizes + .boxed() + .flatMap(size -> { + long[] positions = { + 0, + 1, + 2, + size - 1L, + size, + size + 1L, + Integer.MAX_VALUE - 1, + Integer.MAX_VALUE + }; + long[] skips = { + Long.MIN_VALUE, + Long.MIN_VALUE + 1, + -size - 1, + -size, + -size + 1, + -2, + -1, + 0, + 1, + 2, + size - 1, + size, + size + 1, + Long.MAX_VALUE - 1, + Long.MAX_VALUE + }; + return Arrays.stream(positions) + .filter(pos -> pos >= 0) + .distinct() + .boxed() + .flatMap(position -> Arrays.stream(skips) + .distinct() + .mapToObj(skip -> Arguments.of(size, position, skip))); + }); + } + + + /** + * Test InputStream.skip when input stream reads from a SeekableByteChannel. + */ + private static void testSkip(SeekableByteChannel ch, long skip) throws Exception { + InputStream in = Channels.newInputStream(ch); + long pos = ch.position(); + long expectSkip = (skip >= 0) + ? Long.min(skip, Long.max(ch.size() - pos, 0L)) + : Long.max(-pos, skip); + assertEquals(expectSkip, in.skip(skip), "skip"); + assertEquals(pos + expectSkip, ch.position(), "position"); + } + + /** + * Test InputStream.skip when input stream reads from a FileChannel. + */ + @ParameterizedTest + @MethodSource("testCases") + void testFileChannel(int size, long pos, long skip) throws Exception { + Path file = Files.createTempFile(Path.of("."), "blah", "dat"); + try { + Files.write(file, new byte[size]); + try (FileChannel fc = FileChannel.open(file)) { + fc.position(pos); + testSkip(fc, skip); + } + } finally { + Files.delete(file); + } + } + + /** + * Test InputStream.skip when input stream reads from a SeekableByteChannel. + */ + @ParameterizedTest + @MethodSource("testCases") + void testSeekableByteChannel(int size, long pos, long skip) throws Exception { + byte[] bytes = "X".repeat(size).getBytes("UTF-8"); + + var ch = new SeekableByteChannel() { + int offset; + @Override + public int read(ByteBuffer dst) { + if (!dst.hasRemaining()) + return 0; + if (offset >= size) + return -1; + int n = (int) Long.min(dst.remaining(), size - offset); + dst.put(bytes, offset, n); + offset += n; + return n; + } + @Override + public int write(ByteBuffer src) { + throw new NonWritableChannelException(); + } + @Override + public long position() { + return offset; + } + @Override + public SeekableByteChannel position(long newPosition) { + if (newPosition < 0 || newPosition > Integer.MAX_VALUE) + throw new IllegalArgumentException(); + offset = (int) newPosition; + return this; + } + @Override + public long size() { + return size; + } + @Override + public SeekableByteChannel truncate(long size) throws IOException { + throw new IOException(); + } + @Override + public boolean isOpen() { + return true; + } + @Override + public void close() throws IOException { + throw new IOException(); + } + }; + + ch.position(pos); + testSkip(ch, skip); + } + + /** + * Test InputStream.skip when input stream reads from a ReadableByteChannel. + */ + @Test + void testReadableByteChannel() throws Exception { + final int size = 10; + byte[] bytes = "X".repeat(size).getBytes("UTF-8"); + + var ch = new ReadableByteChannel() { + private final ByteBuffer src = ByteBuffer.wrap(bytes); + @Override + public int read(ByteBuffer dst) { + if (!dst.hasRemaining()) + return 0; + if (!src.hasRemaining()) + return -1; + int n = Math.min(src.remaining(), dst.remaining()); + dst.put(src.array(), src.position(), n); + src.position(src.position() + n); + return n; + } + @Override + public boolean isOpen() { + return true; + } + @Override + public void close() throws IOException { + throw new IOException(); + } + }; + + InputStream in = Channels.newInputStream(ch); + assertEquals(0L, in.skip(-1)); + assertEquals(0L, in.skip(0)); + + int remaining = size; + for (int n = 1; n <= 3; n++) { + assertEquals(n, in.skip(n)); + remaining -= n; + } + assertTrue(remaining >= 0); + assertEquals(remaining, in.skip(100)); + assertEquals(0, in.skip(100)); + } + + /** + * Test InputStream.skip when input stream reads from a socket. + * @param in input stream that reads from a socket + * @param peer output stream of connected peer + */ + void testSocketInputStreamSkip(InputStream in, OutputStream peer) throws Exception { + assertEquals(0L, in.skip(-1)); + assertEquals(0L, in.skip(0)); + + final int count = 10; + peer.write("X".repeat(count).getBytes("UTF-8")); + + assertEquals(0L, in.skip(-1)); + assertEquals(0L, in.skip(0)); + + // consume bytes + int remaining = count; + for (int n = 1; n <= 3; n++) { + assertEquals(n, in.skip(n)); + remaining -= n; + } + assertTrue(remaining >= 0); + + assertEquals(0L, in.skip(0)); + + peer.close(); + + assertEquals(remaining, in.skip(100)); + assertEquals(0, in.skip(100)); + } + + /** + * Test InputStream.skip for input stream that reads from a socket channel. + */ + @Test + void testSocketChannel() throws Exception { + try (var connection = new Connection()) { + InputStream in = Channels.newInputStream(connection.channel1()); + OutputStream out = Channels.newOutputStream(connection.channel2()); + testSocketInputStreamSkip(in, out); + } + } + + /** + * Test InputStream.skip for input stream that reads from a socket associated + * with a socket channel. + */ + @Test + void testSocketChannelAdaptor() throws Exception { + try (var connection = new Connection()) { + Socket s1 = connection.channel1().socket(); + Socket s2 = connection.channel2().socket(); + testSocketInputStreamSkip(s1.getInputStream(), s2.getOutputStream()); + } + } + + /** + * A loopback connection. + */ + private static class Connection implements Closeable { + private final SocketChannel sc1; + private final SocketChannel sc2; + Connection() throws IOException { + var lh = InetAddress.getLoopbackAddress(); + try (var listener = ServerSocketChannel.open()) { + listener.bind(new InetSocketAddress(lh, 0)); + SocketChannel sc1 = SocketChannel.open(); + SocketChannel sc2 = null; + try { + sc1.socket().connect(listener.getLocalAddress()); + sc2 = listener.accept(); + } catch (IOException ioe) { + sc1.close(); + throw ioe; + } + this.sc1 = sc1; + this.sc2 = sc2; + } + } + SocketChannel channel1() { + return sc1; + } + SocketChannel channel2() { + return sc2; + } + @Override + public void close() throws IOException { + sc1.close(); + sc2.close(); + } + } +} From 0bfa055c9a7121964bf89db9d1e2f715024a3944 Mon Sep 17 00:00:00 2001 From: Mikael Vidstedt Date: Tue, 13 Oct 2026 15:31:48 +0000 Subject: [PATCH 071/101] 8394031: Include MetalToolchain in the macOS devkit Reviewed-by: erikj --- make/devkit/createMacosxDevkit.sh | 42 ++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/make/devkit/createMacosxDevkit.sh b/make/devkit/createMacosxDevkit.sh index 414773194d9..ce14719837a 100644 --- a/make/devkit/createMacosxDevkit.sh +++ b/make/devkit/createMacosxDevkit.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 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 @@ -30,7 +30,9 @@ # The easiest way to accomplish this is to right click the file in Finder # and choose "Open With -> Archive Utility", or possible typing # "open Xcode_9.2.xip" in a terminal. -# erik.joelsson@oracle.com + +set -e +set -o pipefail USAGE="$0 " @@ -111,6 +113,38 @@ rsync -rlH $INCLUDE_ARGS $EXCLUDE_ARGS "$XCODE_APP/." $DEVKIT_ROOT/Xcode ################################################################################ +echo "Adding Metal toolchain..." +METAL_EXPORT_PATH="$(mktemp -d "${BUILD_DIR}/metal-for-Xcode${XCODE_VERSION}.export.XXXXXX")" + +DEVELOPER_DIR="${XCODE_APP}" xcodebuild -downloadComponent MetalToolchain -exportPath "${METAL_EXPORT_PATH}" + +METAL_EXPORT_BUNDLES=("${METAL_EXPORT_PATH}"/MetalToolchain-*.exportedBundle) +if [[ ${#METAL_EXPORT_BUNDLES[@]} != 1 || ! -d "${METAL_EXPORT_BUNDLES[0]}" ]]; then + echo "Expected exactly one exportBundle directory in ${METAL_EXPORT_PATH}" + exit 1 +fi + +METAL_DMG_DIR="${METAL_EXPORT_BUNDLES[0]}/Restore" +METAL_DMGS=("${METAL_DMG_DIR}"/*.dmg) +if [[ ${#METAL_DMGS[@]} != 1 || ! -f "${METAL_DMGS[0]}" ]]; then + echo "Expected exactly one dmg file in ${METAL_DMG_DIR}" + exit 1 +fi +METAL_DMG="${METAL_DMGS[0]}" + +echo "Mounting Metal dmg (${METAL_DMG})..." +( + METAL_MOUNTPOINT="$(mktemp -d "$BUILD_DIR/metal-for-Xcode${XCODE_VERSION}.mountpoint.XXXXXX")" + + trap 'hdiutil detach "${METAL_MOUNTPOINT}" >/dev/null 2>&1 || true' EXIT + hdiutil attach -readonly -noautoopen -mountpoint "${METAL_MOUNTPOINT}" "${METAL_DMG}" + + echo "Copying Metal.xctoolchain..." + rsync -rlH "${METAL_MOUNTPOINT}/Metal.xctoolchain/." "${DEVKIT_ROOT}/Metal.xctoolchain" +) + +################################################################################ + echo-info() { echo "$1" >> $DEVKIT_ROOT/devkit.info } @@ -119,7 +153,7 @@ echo "Generating devkit.info..." rm -f $DEVKIT_ROOT/devkit.info echo-info "# This file describes to configure how to interpret the contents of this devkit" echo-info "DEVKIT_NAME=\"Xcode $XCODE_VERSION (devkit)\"" -echo-info "DEVKIT_TOOLCHAIN_PATH=\"\$DEVKIT_ROOT/Xcode/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:\$DEVKIT_ROOT/Xcode/Contents/Developer/usr/bin\"" +echo-info "DEVKIT_TOOLCHAIN_PATH=\"\$DEVKIT_ROOT/Metal.xctoolchain/usr/bin:\$DEVKIT_ROOT/Xcode/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:\$DEVKIT_ROOT/Xcode/Contents/Developer/usr/bin\"" echo-info "DEVKIT_SYSROOT=\"\$DEVKIT_ROOT/Xcode/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/$SDK_VERSION.sdk\"" echo-info "DEVKIT_EXTRA_PATH=\"\$DEVKIT_TOOLCHAIN_PATH\"" @@ -133,7 +167,7 @@ cp $0 $DEVKIT_ROOT/ # Create bundle echo "Creating bundle..." -GZIP=$(command -v pigz) +GZIP=$(command -v pigz || true) if [ -z "$GZIP" ]; then GZIP="gzip" fi From d75a38d1d88dab025addf0183b000734f38cfb86 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Tue, 13 Oct 2026 16:27:21 +0000 Subject: [PATCH 072/101] 8392991: SimpleDateFormat CLDR zone name format and parse can change the instant Reviewed-by: naoto --- .../classes/java/text/SimpleDateFormat.java | 122 +++++++++++++----- ...neTest.java => ExplicitDstOffsetTest.java} | 101 ++++++++++++++- 2 files changed, 190 insertions(+), 33 deletions(-) rename test/jdk/java/text/Format/DateFormat/{CustomZoneTest.java => ExplicitDstOffsetTest.java} (54%) diff --git a/src/java.base/share/classes/java/text/SimpleDateFormat.java b/src/java.base/share/classes/java/text/SimpleDateFormat.java index 66bbd55192a..1c45b4a249a 100644 --- a/src/java.base/share/classes/java/text/SimpleDateFormat.java +++ b/src/java.base/share/classes/java/text/SimpleDateFormat.java @@ -41,6 +41,8 @@ import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; +import java.time.Instant; +import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Calendar; import java.util.Date; @@ -1303,20 +1305,7 @@ private void subFormat(int patternCharIndex, int count, int zoneOffset = calendar.get(Calendar.ZONE_OFFSET); int dstOffset = calendar.get(Calendar.DST_OFFSET) + zoneOffset; - ZoneOffset explicitDstOffset = null; - // Check if an explicit metazone DST offset exists. - // Only check against instances of ZoneInfo, since the standard JDK timezones - // are guaranteed to extend this internal type. - if (tz instanceof ZoneInfo zi) { - explicitDstOffset = TimeZoneNameUtility.explicitDstOffset(tzid); - if (explicitDstOffset != null) { - // The time zone ID has an explicit dst offset. Ensure that - // our current TimeZone is canonical. - if (!ZoneInfo.hasCanonicalRule(zi)) { - explicitDstOffset = null; - } - } - } + ZoneOffset explicitDstOffset = getExplicitDstOffset(tz); boolean daylight = explicitDstOffset != null ? dstOffset == explicitDstOffset.getTotalSeconds() * 1_000 : dstOffset != zoneOffset; @@ -1528,6 +1517,7 @@ public Date parse(String text, ParsePosition pos) int textLength = text.length(); boolean[] ambiguousYear = {false}; + int[] matchedName = {0}; CalendarBuilder calb = new CalendarBuilder(); @@ -1599,7 +1589,7 @@ public Date parse(String text, ParsePosition pos) } } start = subParse(text, start, tag, count, obeyCount, - ambiguousYear, pos, + ambiguousYear, matchedName, pos, useFollowingMinusSignAsDelimiter, calb); if (start < 0) { pos.index = oldStart; @@ -1616,12 +1606,15 @@ public Date parse(String text, ParsePosition pos) Date parsedDate; try { - parsedDate = calb.establish(calendar).getTime(); + parsedDate = resolveExplicitDstOffset( + calb, matchedName, calb.establish(calendar).getTime()); + // If the year value is ambiguous, // then the two-digit year == the default start year if (ambiguousYear[0]) { if (parsedDate.before(defaultCenturyStart)) { - parsedDate = calb.addYear(100).establish(calendar).getTime(); + parsedDate = resolveExplicitDstOffset(calb, + matchedName, calb.addYear(100).establish(calendar).getTime()); } } } @@ -1788,7 +1781,7 @@ private boolean matchDSTString(String text, int start, int zoneIndex, int standa * find time zone 'text' matched zoneStrings and set to internal * calendar. */ - private int subParseZoneString(String text, int start, CalendarBuilder calb) { + private int subParseZoneString(String text, int start, CalendarBuilder calb, int[] matchedName) { boolean useSameName = false; // true if standard and daylight time use the same abbreviation. TimeZone currentTimeZone = getTimeZone(); @@ -1840,15 +1833,30 @@ private int subParseZoneString(String text, int start, CalendarBuilder calb) { if (!tz.equals(currentTimeZone)) { setTimeZone(tz); } - // If the time zone matched uses the same name - // (abbreviation) for both standard and daylight time, - // let the time zone in the Calendar decide which one. - // - // Also if tz.getDSTSaving() returns 0 for DST, use tz to - // determine the local time. (6645292) - int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0; - if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) { - calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount); + matchedName[0] = 0; + ZoneOffset explicitDstOffset = getExplicitDstOffset(tz); + // If we have an explicit dstOffset, let the zone rules determine + // the eventual offset instead of the parsed zone name from the text. + // Note that we retain the matched name to handle the transition period. + if (explicitDstOffset == null) { + // If the time zone matched uses the same name + // (abbreviation) for both standard and daylight time, + // let the time zone in the Calendar decide which one. + // + // Also if tz.getDSTSaving() returns 0 for DST, use tz to + // determine the local time. (6645292) + int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0; + if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) { + calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount); + } + } else { + // If the names are identical, they cannot be used to help resolve + // the correct offset during the transition period. (Practically, this + // will likely never happen for an explicit dstOffset CLDR zone). + if (!useSameName) { + matchedName[0] = nameIndex < 3 ? 1 : 2; + calb.clear(Calendar.ZONE_OFFSET).clear(Calendar.DST_OFFSET); + } } return (start + zoneNames[nameIndex].length()); } @@ -1941,6 +1949,9 @@ private boolean isDigit(char c) { * and we should use the count to know when to stop parsing. * @param ambiguousYear return parameter; upon return, if ambiguousYear[0] * is true, then a two-digit year was parsed and may need to be readjusted. + * @param matchedName return parameter; upon return, if matchedName[0] + * is 0, then either the names were identical or no name was matched. If + * 1, then a standard name was matched, and if 2, then a daylight name was matched. * @param origPos origPos.errorIndex is used to return an error index * at which a parse error occurred, if matching failure occurs. * @return the new start position if matching succeeded; -1 indicating @@ -1948,7 +1959,7 @@ private boolean isDigit(char c) { * an error index is set to origPos.errorIndex. */ private int subParse(String text, int start, int patternCharIndex, int count, - boolean obeyCount, boolean[] ambiguousYear, + boolean obeyCount, boolean[] ambiguousYear, int[] matchedName, ParsePosition origPos, boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) { Number number; @@ -2233,7 +2244,7 @@ private int subParse(String text, int start, int patternCharIndex, int count, } else { // Try parsing the text as a time zone // name or abbreviation. - int i = subParseZoneString(text, pos.index, calb); + int i = subParseZoneString(text, pos.index, calb, matchedName); if (i > 0) { return i; } @@ -2551,6 +2562,59 @@ private Map getDisplayContextNamesMap(int field, Locale locale) return map; } + /** + * Obtains the dstOffset for a time zone if it exists, otherwise null. + */ + private ZoneOffset getExplicitDstOffset(TimeZone tz) { + ZoneOffset explicitDstOffset = null; + // Check if an explicit metazone DST offset exists. + // Only check against instances of ZoneInfo, since the standard JDK timezones + // are guaranteed to extend this internal type. + if (tz instanceof ZoneInfo zi) { + explicitDstOffset = TimeZoneNameUtility.explicitDstOffset(tz.getID()); + if (explicitDstOffset != null) { + // The time zone ID has an explicit dst offset. Ensure that + // our current TimeZone is canonical. + if (!ZoneInfo.hasCanonicalRule(zi)) { + explicitDstOffset = null; + } + } + } + return explicitDstOffset; + } + + /** + * Apply the explicit dst offset if the zone is applicable and is needed to resolve + * an ambiguous date during a transition period. This method does nothing to the passed date + * if none of the preconditions are relevant, which occurs for the majority of cases. + */ + private Date resolveExplicitDstOffset(CalendarBuilder calb, int[] matchedName, Date parsedDate) { + // For explicit dstOffset cases, we need to use the name in the text + // to determine which offset to use during transition periods. + var tz = calendar.getTimeZone(); + ZoneOffset explicitDstOffset; + if (!calb.isSet(Calendar.ZONE_OFFSET) && matchedName[0] != 0 + && (explicitDstOffset = getExplicitDstOffset(tz)) != null) { + var ldt = LocalDateTime.ofInstant(parsedDate.toInstant(), tz.toZoneId()); + var offs = tz.toZoneId().getRules().getValidOffsets(ldt); + // If size is 2, then we have multiple offsets at the date, implying + // a transition period. Pick the offset that aligns with the matched name. + if (offs.size() == 2 && offs.contains(explicitDstOffset)) { + if (matchedName[0] == 1) { + // standard + var stdOffset = explicitDstOffset.equals(offs.get(0)) ? + offs.get(1) : offs.get(0); + parsedDate = Date.from(Instant.from(ldt.atOffset(stdOffset))); + } else if (matchedName[0] == 2) { + // daylight + parsedDate = Date.from(Instant.from(ldt.atOffset(explicitDstOffset))); + } + calendar.setTime(parsedDate); + } + } + return parsedDate; + } + /** * After reading an object from the input stream, the format * pattern in the object is verified. diff --git a/test/jdk/java/text/Format/DateFormat/CustomZoneTest.java b/test/jdk/java/text/Format/DateFormat/ExplicitDstOffsetTest.java similarity index 54% rename from test/jdk/java/text/Format/DateFormat/CustomZoneTest.java rename to test/jdk/java/text/Format/DateFormat/ExplicitDstOffsetTest.java index 87ec0577abb..27c7fdced60 100644 --- a/test/jdk/java/text/Format/DateFormat/CustomZoneTest.java +++ b/test/jdk/java/text/Format/DateFormat/ExplicitDstOffsetTest.java @@ -23,21 +23,27 @@ /* * @test - * @bug 8392995 + * @bug 8392995 8392991 * @summary Ensure that a custom time zone does not look up CLDR metazone data via * an explicit offset for daylight names during SimpleDateFormat formatting. Only - * a canonical JDK TimeZone should defer to the metazone data in such cases. - * @run junit CustomZoneTest + * a canonical JDK TimeZone should defer to the metazone data in such cases. Parsing + * should consult the explicit dstOffset of a zone when it exists. + * @run junit ExplicitDstOffsetTest */ import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.FieldSource; import java.text.DateFormatSymbols; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Date; +import java.util.List; import java.util.Locale; import java.util.SimpleTimeZone; import java.util.TimeZone; @@ -45,11 +51,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; // Any fixed standard strings correspond to CLDR data as of v48.2 -public class CustomZoneTest { +public class ExplicitDstOffsetTest { private static final String AMERICA_VANCOUVER = "America/Vancouver"; + private static final String EUROPE_DUBLIN = "Europe/Dublin"; private static final TimeZone JDK_AMERICA_VANCOUVER = TimeZone.getTimeZone(AMERICA_VANCOUVER); + private static final TimeZone JDK_EUROPE_DUBLIN = + TimeZone.getTimeZone(EUROPE_DUBLIN); // Fixed raw offset (that mimics daylight savings for America/Vancouver) // but in actuality has no daylight savings rules and is named "America/Vancouver". @@ -137,4 +146,88 @@ void modifiedStandardZoneTest() { assertEquals("Pacific Daylight Time", format.format( Date.from(Instant.parse("2020-07-01T12:00:00Z")))); } + + // These instants are formatted/parsed under Europe/Dublin + private static final List EXPLICIT_DST_OFFSET_DATES = List.of( + // Historical daylight offset: +00:34:39 + Date.from(Instant.parse("1916-07-15T00:00:00Z")), + // Formats to -> 1916-07-15 00:34:39 Greenwich Mean Time + // ------------- + // Historical standard offset: −00:25:21 + Date.from(Instant.parse("1916-01-15T00:00:00Z")), + // Formats to -> 1916-01-14 23:34:39 Greenwich Mean Time + // ------------- + // Modern daylight offset: +01:00 + Date.from(Instant.parse("2026-04-04T23:00:00Z")), + // Formats to -> 2026-04-05 00:00:00 Irish Standard Time + // ------------- + // Modern standard offset: +00:00 + Date.from(Instant.parse("2026-12-05T00:00:00Z")), + // Formats to -> 2026-12-05 00:00:00 Greenwich Mean Time + //-------------- + // Test dates at the dst/std transition period + // Dublin transitions on 10/25 at 2:00 AM + // During daylight + Date.from(Instant.parse("2026-10-25T00:30:00Z")), + // Formats to 2026-10-25 01:30:00 Irish Standard Time + // During standard + Date.from(Instant.parse("2026-10-25T01:30:00Z")) + ); + + // Europe/Dublin uses explicit dstOffset. For such cases, the zone should be used to derive the offset + // at the given date. When the date is during a transition period, then the matched name can be used + // as a tiebreaker. This contrasts from normal parsing, which prefers using the matched name to determine + // the offset. + @ParameterizedTest + @FieldSource("EXPLICIT_DST_OFFSET_DATES") + void dstOffsetRoundTripParsingTest(Date before) throws ParseException { + var format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzzz", Locale.US); + format.setTimeZone(JDK_EUROPE_DUBLIN); + String text = format.format(before); + Date after = format.parse(text); + assertEquals(before, after, "explicit dstOffset zone did not round trip correctly"); + } + + // These instants are formatted/parsed under Europe/Dublin + private static final List EXPLICIT_DST_OFFSET_CONFLICTING_PATTERN_DATES = List.of( + // Even though the text is in a transition period, which indicates the name should be + // used to dictate std time, the explicit numeric offset should take precedence and produce the + // resultant date in dst. + Arguments.of("yyyy-MM-dd HH:mm:ss zzzz XXX", + "2026-10-25 01:30:00 Greenwich Mean Time +01:00", + Date.from(Instant.parse("2026-10-25T00:30:00Z"))), + // Same as above, but in the other direction + Arguments.of("yyyy-MM-dd HH:mm:ss XXX zzzz", + "2026-10-25 01:30:00 +01:00 Greenwich Mean Time", + Date.from(Instant.parse("2026-10-25T01:30:00Z"))) + ); + + // Ensure that the new explicit dstOffset behavior during ambiguous transition period does not conflict + // with a subsequent time zone pattern and vice versa. Latest should always win. + @ParameterizedTest + @FieldSource("EXPLICIT_DST_OFFSET_CONFLICTING_PATTERN_DATES") + void dstOffsetConflictingZonesTest(String pattern, String text, Date date) throws ParseException { + var format = new SimpleDateFormat(pattern, Locale.US); + format.setTimeZone(JDK_EUROPE_DUBLIN); + var parsed = format.parse(text); + assertEquals(date, parsed, "Pattern precedence incorrect for explicit dstOffset"); + } + + // Ensure that explicit dstOffset is taken into account even on century changes. + @Test + void dstOffsetCenturyTest() throws ParseException { + var format = new SimpleDateFormat("yy-MM-dd HH:mm:ss zzzz", Locale.US); + format.setTimeZone(JDK_EUROPE_DUBLIN); + format.set2DigitYearStart(Date.from(Instant.parse("2026-10-28T00:00:00Z"))); + var before = Date.from(Instant.parse("2126-10-27T00:30:00Z")); + var formatted = format.format(before); + // Formats as -> 26-10-27 01:30:00 Irish Standard Time + // When we parse it, 2026/10/27 is before the century start, so 100 years is incremented. + // This moves the new date to 2126-10-27 01:30:00 Irish Standard Time. This lands + // on a transition date, and we need to use the Irish Standard Time naming to determine + // the appropriate offset. Otherwise, the calendar would default to the standard time, + // and the date produced would be "2126-10-27T01:30:00Z" + assertEquals(before, format.parse(formatted), + "explicit dstOffset not taken into account for next century"); + } } From a5afd6e6bf1be7c2c6b166b70e5e704e692b9854 Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Tue, 13 Oct 2026 16:59:38 +0000 Subject: [PATCH 073/101] 8393752: jdi/stress/serial/heapwalking002/TestDescription.java fails with "Unexpected size of referenceType.instances(java.lang.ThreadGroup)" Reviewed-by: lmesnik, sspitsyn --- .../referringObjects003.java | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ObjectReference/referringObjects/referringObjects003/referringObjects003.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ObjectReference/referringObjects/referringObjects003/referringObjects003.java index a1a191d0071..529649c516b 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ObjectReference/referringObjects/referringObjects003/referringObjects003.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ObjectReference/referringObjects/referringObjects003/referringObjects003.java @@ -133,6 +133,17 @@ public void checkThreadReferrersCount(List objectReferences, in } } + private void logLeftover(ObjectReference objectReference) { + try { + log.complain(objectReference.toString()); + for (ObjectReference referrer : objectReference.referringObjects(0)) { + log.complain(" referred from " + referrer); + } + } catch (ObjectCollectedException e) { + log.complain("object " + objectReference.uniqueID() + " was collected before it could be described"); + } + } + public void doTest() { int threadCount = 15; @@ -178,35 +189,40 @@ public void doTest() { if (!isDebuggeeReady()) return; - // Force the test ThreadGroup to be collected. The only reference to it is a weak - // reference from the parent ThreadGroup. - forceGC(); + // The joined threads still hold the group in holder.group and the VM + // releases its handles to them a little later on another thread. The + // retry gives that release time to happen. Each forceGC has to stay a + // real full collection, a cheaper one here would not clear the group + // and the loop would just run out of retries. + for (int i = 0; i < 5; i++) { + forceGC(); + threadGroups = HeapwalkingDebugger.filterObjectReferrence(threadGroupsToFilter, HeapwalkingDebugger + .getObjectReferences("java.lang.ThreadGroup", vm)); + threads = HeapwalkingDebugger.filterObjectReferrence(threadsToFilter, HeapwalkingDebugger + .getObjectReferences("java.lang.Thread", vm)); + if (threadGroups.isEmpty() && threads.isEmpty()) { + break; + } + } checkDebugeeAnswer_instances("java.lang.ThreadGroup", threadGroupsToFilter.size()); checkDebugeeAnswer_instances("java.lang.Thread", threadsToFilter.size()); - threadGroups = HeapwalkingDebugger.filterObjectReferrence(threadGroupsToFilter, HeapwalkingDebugger - .getObjectReferences("java.lang.ThreadGroup", vm)); - if (threadGroups.size() != 0) { setSuccess(false); log.complain("All test threads groups should be removed"); log.complain("Unexpected threads groups:"); for (ObjectReference objectReference : threadGroups) { - log.complain(objectReference.toString()); + logLeftover(objectReference); } } - threads = HeapwalkingDebugger.filterObjectReferrence(threadsToFilter, HeapwalkingDebugger.getObjectReferences( - "java.lang.Thread", - vm)); - if (threads.size() != 0) { setSuccess(false); log.complain("All test threads should be removed"); log.complain("Unexpected threads:"); for (ObjectReference objectReference : threads) { - log.complain(objectReference.toString()); + logLeftover(objectReference); } } } From 9a95fa6d93955484e62d8aebae2f3ee6a0f7f219 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Tue, 13 Oct 2026 19:25:48 +0000 Subject: [PATCH 074/101] 8394032: Remove un-used math tables from medialib native code Reviewed-by: psadhukhan, serb --- .../native/libmlib_image/mlib_ImageAffine.h | 1 - .../libmlib_image/mlib_ImageAffineEdge.c | 2 +- .../libmlib_image/mlib_ImageDivTables.c | 446 ------------------ .../libmlib_image/mlib_ImageDivTables.h | 61 --- .../libmlib_image/mlib_c_ImageAffine_BL.c | 2 +- 5 files changed, 2 insertions(+), 510 deletions(-) delete mode 100644 src/java.desktop/share/native/libmlib_image/mlib_ImageDivTables.c delete mode 100644 src/java.desktop/share/native/libmlib_image/mlib_ImageDivTables.h diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageAffine.h b/src/java.desktop/share/native/libmlib_image/mlib_ImageAffine.h index 567d8f3491e..1687b452878 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageAffine.h +++ b/src/java.desktop/share/native/libmlib_image/mlib_ImageAffine.h @@ -28,7 +28,6 @@ #define __MLIB_IMAGEAFFINE_H #include "mlib_image.h" -#include "mlib_ImageDivTables.h" #include "mlib_ImageFilters.h" #ifdef __cplusplus diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageAffineEdge.c b/src/java.desktop/share/native/libmlib_image/mlib_ImageAffineEdge.c index 2cb39d0b55a..91d5f50e566 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageAffineEdge.c +++ b/src/java.desktop/share/native/libmlib_image/mlib_ImageAffineEdge.c @@ -84,7 +84,7 @@ #define MLIB_SIGN_SHIFT 31 /***************************************************************/ -#define D64mlib_u8(X) mlib_U82D64[X] +#define D64mlib_u8(X) ((mlib_d64)(X)) #define D64mlib_s16(X) ((mlib_d64)(X)) #define D64mlib_u16(X) ((mlib_d64)(X)) #define D64mlib_s32(X) ((mlib_d64)(X)) diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageDivTables.c b/src/java.desktop/share/native/libmlib_image/mlib_ImageDivTables.c deleted file mode 100644 index 5a12ec39af8..00000000000 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageDivTables.c +++ /dev/null @@ -1,446 +0,0 @@ -/* - * Copyright (c) 1998, 2003, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - - -/* - * DESCRIPTION: - * - * mlib_u16 mlib_div6_tab[x] = 0xff00/6/x; - * mlib_u16 mlib_div1_tab[x] = 0xff00/x; - * mlib_d64 mlib_U82D64[x] = (mlib_d64)x; - * mlib_f32 mlib_U82F32[x] = (mlib_f32)x; - * mlib_f32 mlib_HSL2RGB_L2[x] = x/255.0 for x<128, and (255-x)/255.0 for x >= 128; - * mlib_f32 mlib_HSL2RGB_F[x] = 1.0 - fabs(fraction(x*3/256.) * 4.0f - 2.0) - * - * REFERENCING: - * mlib_div* tables are used to exclude dividing in functions - * mlib_ImageColorRGB2HSV - * mlib_ImageColorRGB2HSL - * mlib_ImageDivAlpha - * - * table mlib_U82D64 is used to exclude conversion unsigned - * byte to double in functions - * mlib_ImageColorConvert... - * mlib_ImageDataTypeConvert - * mlib_ImageAffine - * mlib_ImageAffineTable - * mlib_ImageZoomTranslate - * mlib_ImageZoomTranslateTable - * mlib_ImageGridWarp - * mlib_ImagePolynomialWarp - * mlib_ImageCrossCorrel - * mlib_ImageMoment2 - * mlib_ImageStdDev - * mlib_ImageFourierTransform - * - * table mlib_U82F32 is used to exclude conversion unsigned - * byte to float in functions - * mlib_ImageBlend - * mlib_ImageDivShift - * mlib_ImageMulAlpha - * mlib_ImageMulAlpha_Inp - * mlib_ImageMulShift - * mlib_ImageBlend_BDST_BSRC - * mlib_ImageColorHSL2RGB - * mlib_ImageColorHSV2RGB - * mlib_ImageDataTypeConvert - * mlib_ImageAffine - * mlib_ImageAffineTable - * mlib_ImageZoomTranslate - * mlib_ImageZoomTranslateTable - * mlib_ImageConvMxN - * mlib_ImageSobel - * mlib_ImageGradient3x3 - * mlib_ImageGradientMxN - * - * tables mlib_HSL2RGB_* are used to exclude conversion unsigned - * byte to float in functions with some computations - * mlib_ImageColorHSL2RGB - */ - -#include "mlib_image.h" -#define __DIV_TABLE_DEFINED -#include "mlib_ImageDivTables.h" - -/***************************************************************/ -/* *INDENT-OFF* */ - -const mlib_u16 mlib_div6_tab[] = { - 32767, 10880, 5440, 3627, 2720, 2176, 1813, 1554, - 1360, 1209, 1088, 989, 907, 837, 777, 725, - 680, 640, 604, 573, 544, 518, 495, 473, - 453, 435, 418, 403, 389, 375, 363, 351, - 340, 330, 320, 311, 302, 294, 286, 279, - 272, 265, 259, 253, 247, 242, 237, 231, - 227, 222, 218, 213, 209, 205, 201, 198, - 194, 191, 188, 184, 181, 178, 175, 173, - 170, 167, 165, 162, 160, 158, 155, 153, - 151, 149, 147, 145, 143, 141, 139, 138, - 136, 134, 133, 131, 130, 128, 127, 125, - 124, 122, 121, 120, 118, 117, 116, 115, - 113, 112, 111, 110, 109, 108, 107, 106, - 105, 104, 103, 102, 101, 100, 99, 98, - 97, 96, 95, 95, 94, 93, 92, 91, - 91, 90, 89, 88, 88, 87, 86, 86, - 85, 84, 84, 83, 82, 82, 81, 81, - 80, 79, 79, 78, 78, 77, 77, 76, - 76, 75, 75, 74, 74, 73, 73, 72, - 72, 71, 71, 70, 70, 69, 69, 68, - 68, 68, 67, 67, 66, 66, 66, 65, - 65, 64, 64, 64, 63, 63, 63, 62, - 62, 61, 61, 61, 60, 60, 60, 59, - 59, 59, 58, 58, 58, 58, 57, 57, - 57, 56, 56, 56, 56, 55, 55, 55, - 54, 54, 54, 54, 53, 53, 53, 53, - 52, 52, 52, 52, 51, 51, 51, 51, - 50, 50, 50, 50, 49, 49, 49, 49, - 49, 48, 48, 48, 48, 48, 47, 47, - 47, 47, 46, 46, 46, 46, 46, 46, - 45, 45, 45, 45, 45, 44, 44, 44, - 44, 44, 44, 43, 43, 43, 43, 43 -}; - -/***************************************************************/ - -const mlib_u16 mlib_div1_tab[] = { - 65280, 65280, 32640, 21760, 16320, 13056, 10880, 9326, - 8160, 7253, 6528, 5935, 5440, 5022, 4663, 4352, - 4080, 3840, 3627, 3436, 3264, 3109, 2967, 2838, - 2720, 2611, 2511, 2418, 2331, 2251, 2176, 2106, - 2040, 1978, 1920, 1865, 1813, 1764, 1718, 1674, - 1632, 1592, 1554, 1518, 1484, 1451, 1419, 1389, - 1360, 1332, 1306, 1280, 1255, 1232, 1209, 1187, - 1166, 1145, 1126, 1106, 1088, 1070, 1053, 1036, - 1020, 1004, 989, 974, 960, 946, 933, 919, - 907, 894, 882, 870, 859, 848, 837, 826, - 816, 806, 796, 787, 777, 768, 759, 750, - 742, 733, 725, 717, 710, 702, 694, 687, - 680, 673, 666, 659, 653, 646, 640, 634, - 628, 622, 616, 610, 604, 599, 593, 588, - 583, 578, 573, 568, 563, 558, 553, 549, - 544, 540, 535, 531, 526, 522, 518, 514, - 510, 506, 502, 498, 495, 491, 487, 484, - 480, 476, 473, 470, 466, 463, 460, 457, - 453, 450, 447, 444, 441, 438, 435, 432, - 429, 427, 424, 421, 418, 416, 413, 411, - 408, 405, 403, 400, 398, 396, 393, 391, - 389, 386, 384, 382, 380, 377, 375, 373, - 371, 369, 367, 365, 363, 361, 359, 357, - 355, 353, 351, 349, 347, 345, 344, 342, - 340, 338, 336, 335, 333, 331, 330, 328, - 326, 325, 323, 322, 320, 318, 317, 315, - 314, 312, 311, 309, 308, 306, 305, 304, - 302, 301, 299, 298, 297, 295, 294, 293, - 291, 290, 289, 288, 286, 285, 284, 283, - 281, 280, 279, 278, 277, 275, 274, 273, - 272, 271, 270, 269, 268, 266, 265, 264, - 263, 262, 261, 260, 259, 258, 257, 256 -}; - -/***************************************************************/ - -const mlib_f32 mlib_HSL2RGB_L2[] = { - 0.000000f, 0.003922f, 0.007843f, 0.011765f, 0.015686f, 0.019608f, 0.023529f, 0.027451f, - 0.031373f, 0.035294f, 0.039216f, 0.043137f, 0.047059f, 0.050980f, 0.054902f, 0.058824f, - 0.062745f, 0.066667f, 0.070588f, 0.074510f, 0.078431f, 0.082353f, 0.086275f, 0.090196f, - 0.094118f, 0.098039f, 0.101961f, 0.105882f, 0.109804f, 0.113725f, 0.117647f, 0.121569f, - 0.125490f, 0.129412f, 0.133333f, 0.137255f, 0.141176f, 0.145098f, 0.149020f, 0.152941f, - 0.156863f, 0.160784f, 0.164706f, 0.168627f, 0.172549f, 0.176471f, 0.180392f, 0.184314f, - 0.188235f, 0.192157f, 0.196078f, 0.200000f, 0.203922f, 0.207843f, 0.211765f, 0.215686f, - 0.219608f, 0.223529f, 0.227451f, 0.231373f, 0.235294f, 0.239216f, 0.243137f, 0.247059f, - 0.250980f, 0.254902f, 0.258824f, 0.262745f, 0.266667f, 0.270588f, 0.274510f, 0.278431f, - 0.282353f, 0.286275f, 0.290196f, 0.294118f, 0.298039f, 0.301961f, 0.305882f, 0.309804f, - 0.313725f, 0.317647f, 0.321569f, 0.325490f, 0.329412f, 0.333333f, 0.337255f, 0.341176f, - 0.345098f, 0.349020f, 0.352941f, 0.356863f, 0.360784f, 0.364706f, 0.368627f, 0.372549f, - 0.376471f, 0.380392f, 0.384314f, 0.388235f, 0.392157f, 0.396078f, 0.400000f, 0.403922f, - 0.407843f, 0.411765f, 0.415686f, 0.419608f, 0.423529f, 0.427451f, 0.431373f, 0.435294f, - 0.439216f, 0.443137f, 0.447059f, 0.450980f, 0.454902f, 0.458824f, 0.462745f, 0.466667f, - 0.470588f, 0.474510f, 0.478431f, 0.482353f, 0.486275f, 0.490196f, 0.494118f, 0.498039f, - 0.498039f, 0.494118f, 0.490196f, 0.486275f, 0.482353f, 0.478431f, 0.474510f, 0.470588f, - 0.466667f, 0.462745f, 0.458824f, 0.454902f, 0.450980f, 0.447059f, 0.443137f, 0.439216f, - 0.435294f, 0.431373f, 0.427451f, 0.423529f, 0.419608f, 0.415686f, 0.411765f, 0.407843f, - 0.403922f, 0.400000f, 0.396078f, 0.392157f, 0.388235f, 0.384314f, 0.380392f, 0.376471f, - 0.372549f, 0.368627f, 0.364706f, 0.360784f, 0.356863f, 0.352941f, 0.349020f, 0.345098f, - 0.341176f, 0.337255f, 0.333333f, 0.329412f, 0.325490f, 0.321569f, 0.317647f, 0.313725f, - 0.309804f, 0.305882f, 0.301961f, 0.298039f, 0.294118f, 0.290196f, 0.286275f, 0.282353f, - 0.278431f, 0.274510f, 0.270588f, 0.266667f, 0.262745f, 0.258824f, 0.254902f, 0.250980f, - 0.247059f, 0.243137f, 0.239216f, 0.235294f, 0.231373f, 0.227451f, 0.223529f, 0.219608f, - 0.215686f, 0.211765f, 0.207843f, 0.203922f, 0.200000f, 0.196078f, 0.192157f, 0.188235f, - 0.184314f, 0.180392f, 0.176471f, 0.172549f, 0.168627f, 0.164706f, 0.160784f, 0.156863f, - 0.152941f, 0.149020f, 0.145098f, 0.141176f, 0.137255f, 0.133333f, 0.129412f, 0.125490f, - 0.121569f, 0.117647f, 0.113725f, 0.109804f, 0.105882f, 0.101961f, 0.098039f, 0.094118f, - 0.090196f, 0.086275f, 0.082353f, 0.078431f, 0.074510f, 0.070588f, 0.066667f, 0.062745f, - 0.058824f, 0.054902f, 0.050980f, 0.047059f, 0.043137f, 0.039216f, 0.035294f, 0.031373f, - 0.027451f, 0.023529f, 0.019608f, 0.015686f, 0.011765f, 0.007843f, 0.003922f, 0.000000f -}; - -/***************************************************************/ - -const mlib_f32 mlib_HSL2RGB_F[] = { - -1.000000f, -0.953125f, -0.906250f, -0.859375f, -0.812500f, -0.765625f, -0.718750f, -0.671875f, - -0.625000f, -0.578125f, -0.531250f, -0.484375f, -0.437500f, -0.390625f, -0.343750f, -0.296875f, - -0.250000f, -0.203125f, -0.156250f, -0.109375f, -0.062500f, -0.015625f, 0.031250f, 0.078125f, - 0.125000f, 0.171875f, 0.218750f, 0.265625f, 0.312500f, 0.359375f, 0.406250f, 0.453125f, - 0.500000f, 0.546875f, 0.593750f, 0.640625f, 0.687500f, 0.734375f, 0.781250f, 0.828125f, - 0.875000f, 0.921875f, 0.968750f, 0.984375f, 0.937500f, 0.890625f, 0.843750f, 0.796875f, - 0.750000f, 0.703125f, 0.656250f, 0.609375f, 0.562500f, 0.515625f, 0.468750f, 0.421875f, - 0.375000f, 0.328125f, 0.281250f, 0.234375f, 0.187500f, 0.140625f, 0.093750f, 0.046875f, - 0.000000f, -0.046875f, -0.093750f, -0.140625f, -0.187500f, -0.234375f, -0.281250f, -0.328125f, - -0.375000f, -0.421875f, -0.468750f, -0.515625f, -0.562500f, -0.609375f, -0.656250f, -0.703125f, - -0.750000f, -0.796875f, -0.843750f, -0.890625f, -0.937500f, -0.984375f, -0.968750f, -0.921875f, - -0.875000f, -0.828125f, -0.781250f, -0.734375f, -0.687500f, -0.640625f, -0.593750f, -0.546875f, - -0.500000f, -0.453125f, -0.406250f, -0.359375f, -0.312500f, -0.265625f, -0.218750f, -0.171875f, - -0.125000f, -0.078125f, -0.031250f, 0.015625f, 0.062500f, 0.109375f, 0.156250f, 0.203125f, - 0.250000f, 0.296875f, 0.343750f, 0.390625f, 0.437500f, 0.484375f, 0.531250f, 0.578125f, - 0.625000f, 0.671875f, 0.718750f, 0.765625f, 0.812500f, 0.859375f, 0.906250f, 0.953125f, - 1.000000f, 0.953125f, 0.906250f, 0.859375f, 0.812500f, 0.765625f, 0.718750f, 0.671875f, - 0.625000f, 0.578125f, 0.531250f, 0.484375f, 0.437500f, 0.390625f, 0.343750f, 0.296875f, - 0.250000f, 0.203125f, 0.156250f, 0.109375f, 0.062500f, 0.015625f, -0.031250f, -0.078125f, - -0.125000f, -0.171875f, -0.218750f, -0.265625f, -0.312500f, -0.359375f, -0.406250f, -0.453125f, - -0.500000f, -0.546875f, -0.593750f, -0.640625f, -0.687500f, -0.734375f, -0.781250f, -0.828125f, - -0.875000f, -0.921875f, -0.968750f, -0.984375f, -0.937500f, -0.890625f, -0.843750f, -0.796875f, - -0.750000f, -0.703125f, -0.656250f, -0.609375f, -0.562500f, -0.515625f, -0.468750f, -0.421875f, - -0.375000f, -0.328125f, -0.281250f, -0.234375f, -0.187500f, -0.140625f, -0.093750f, -0.046875f, - 0.000000f, 0.046875f, 0.093750f, 0.140625f, 0.187500f, 0.234375f, 0.281250f, 0.328125f, - 0.375000f, 0.421875f, 0.468750f, 0.515625f, 0.562500f, 0.609375f, 0.656250f, 0.703125f, - 0.750000f, 0.796875f, 0.843750f, 0.890625f, 0.937500f, 0.984375f, 0.968750f, 0.921875f, - 0.875000f, 0.828125f, 0.781250f, 0.734375f, 0.687500f, 0.640625f, 0.593750f, 0.546875f, - 0.500000f, 0.453125f, 0.406250f, 0.359375f, 0.312500f, 0.265625f, 0.218750f, 0.171875f, - 0.125000f, 0.078125f, 0.031250f, -0.015625f, -0.062500f, -0.109375f, -0.156250f, -0.203125f, - -0.250000f, -0.296875f, -0.343750f, -0.390625f, -0.437500f, -0.484375f, -0.531250f, -0.578125f, - -0.625000f, -0.671875f, -0.718750f, -0.765625f, -0.812500f, -0.859375f, -0.906250f, -0.953125f -}; - -/***************************************************************/ - -const mlib_f32 mlib_U82F32[] = { - 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, - 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, - 16.0f, 17.0f, 18.0f, 19.0f, 20.0f, 21.0f, 22.0f, 23.0f, - 24.0f, 25.0f, 26.0f, 27.0f, 28.0f, 29.0f, 30.0f, 31.0f, - 32.0f, 33.0f, 34.0f, 35.0f, 36.0f, 37.0f, 38.0f, 39.0f, - 40.0f, 41.0f, 42.0f, 43.0f, 44.0f, 45.0f, 46.0f, 47.0f, - 48.0f, 49.0f, 50.0f, 51.0f, 52.0f, 53.0f, 54.0f, 55.0f, - 56.0f, 57.0f, 58.0f, 59.0f, 60.0f, 61.0f, 62.0f, 63.0f, - 64.0f, 65.0f, 66.0f, 67.0f, 68.0f, 69.0f, 70.0f, 71.0f, - 72.0f, 73.0f, 74.0f, 75.0f, 76.0f, 77.0f, 78.0f, 79.0f, - 80.0f, 81.0f, 82.0f, 83.0f, 84.0f, 85.0f, 86.0f, 87.0f, - 88.0f, 89.0f, 90.0f, 91.0f, 92.0f, 93.0f, 94.0f, 95.0f, - 96.0f, 97.0f, 98.0f, 99.0f, 100.0f, 101.0f, 102.0f, 103.0f, - 104.0f, 105.0f, 106.0f, 107.0f, 108.0f, 109.0f, 110.0f, 111.0f, - 112.0f, 113.0f, 114.0f, 115.0f, 116.0f, 117.0f, 118.0f, 119.0f, - 120.0f, 121.0f, 122.0f, 123.0f, 124.0f, 125.0f, 126.0f, 127.0f, - 128.0f, 129.0f, 130.0f, 131.0f, 132.0f, 133.0f, 134.0f, 135.0f, - 136.0f, 137.0f, 138.0f, 139.0f, 140.0f, 141.0f, 142.0f, 143.0f, - 144.0f, 145.0f, 146.0f, 147.0f, 148.0f, 149.0f, 150.0f, 151.0f, - 152.0f, 153.0f, 154.0f, 155.0f, 156.0f, 157.0f, 158.0f, 159.0f, - 160.0f, 161.0f, 162.0f, 163.0f, 164.0f, 165.0f, 166.0f, 167.0f, - 168.0f, 169.0f, 170.0f, 171.0f, 172.0f, 173.0f, 174.0f, 175.0f, - 176.0f, 177.0f, 178.0f, 179.0f, 180.0f, 181.0f, 182.0f, 183.0f, - 184.0f, 185.0f, 186.0f, 187.0f, 188.0f, 189.0f, 190.0f, 191.0f, - 192.0f, 193.0f, 194.0f, 195.0f, 196.0f, 197.0f, 198.0f, 199.0f, - 200.0f, 201.0f, 202.0f, 203.0f, 204.0f, 205.0f, 206.0f, 207.0f, - 208.0f, 209.0f, 210.0f, 211.0f, 212.0f, 213.0f, 214.0f, 215.0f, - 216.0f, 217.0f, 218.0f, 219.0f, 220.0f, 221.0f, 222.0f, 223.0f, - 224.0f, 225.0f, 226.0f, 227.0f, 228.0f, 229.0f, 230.0f, 231.0f, - 232.0f, 233.0f, 234.0f, 235.0f, 236.0f, 237.0f, 238.0f, 239.0f, - 240.0f, 241.0f, 242.0f, 243.0f, 244.0f, 245.0f, 246.0f, 247.0f, - 248.0f, 249.0f, 250.0f, 251.0f, 252.0f, 253.0f, 254.0f, 255.0f, - 256.0f -}; - -/***************************************************************/ - -const mlib_d64 mlib_U82D64[] = { - 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, - 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, - 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, - 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, - 32.0, 33.0, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, - 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, - 48.0, 49.0, 50.0, 51.0, 52.0, 53.0, 54.0, 55.0, - 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, - 64.0, 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, - 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 79.0, - 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, - 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, - 96.0, 97.0, 98.0, 99.0, 100.0, 101.0, 102.0, 103.0, - 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0, 111.0, - 112.0, 113.0, 114.0, 115.0, 116.0, 117.0, 118.0, 119.0, - 120.0, 121.0, 122.0, 123.0, 124.0, 125.0, 126.0, 127.0, - 128.0, 129.0, 130.0, 131.0, 132.0, 133.0, 134.0, 135.0, - 136.0, 137.0, 138.0, 139.0, 140.0, 141.0, 142.0, 143.0, - 144.0, 145.0, 146.0, 147.0, 148.0, 149.0, 150.0, 151.0, - 152.0, 153.0, 154.0, 155.0, 156.0, 157.0, 158.0, 159.0, - 160.0, 161.0, 162.0, 163.0, 164.0, 165.0, 166.0, 167.0, - 168.0, 169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0, - 176.0, 177.0, 178.0, 179.0, 180.0, 181.0, 182.0, 183.0, - 184.0, 185.0, 186.0, 187.0, 188.0, 189.0, 190.0, 191.0, - 192.0, 193.0, 194.0, 195.0, 196.0, 197.0, 198.0, 199.0, - 200.0, 201.0, 202.0, 203.0, 204.0, 205.0, 206.0, 207.0, - 208.0, 209.0, 210.0, 211.0, 212.0, 213.0, 214.0, 215.0, - 216.0, 217.0, 218.0, 219.0, 220.0, 221.0, 222.0, 223.0, - 224.0, 225.0, 226.0, 227.0, 228.0, 229.0, 230.0, 231.0, - 232.0, 233.0, 234.0, 235.0, 236.0, 237.0, 238.0, 239.0, - 240.0, 241.0, 242.0, 243.0, 244.0, 245.0, 246.0, 247.0, - 248.0, 249.0, 250.0, 251.0, 252.0, 253.0, 254.0, 255.0, - 256.0 -}; - -/***************************************************************/ - -const mlib_u32 mlib_FlipAndFixRotateTable[] = { - 0x00000000, 0x00000000, 0x00000000, 0x00000080, - 0x00000000, 0x00008000, 0x00000000, 0x00008080, - 0x00000000, 0x00800000, 0x00000000, 0x00800080, - 0x00000000, 0x00808000, 0x00000000, 0x00808080, - 0x00000000, 0x80000000, 0x00000000, 0x80000080, - 0x00000000, 0x80008000, 0x00000000, 0x80008080, - 0x00000000, 0x80800000, 0x00000000, 0x80800080, - 0x00000000, 0x80808000, 0x00000000, 0x80808080, - 0x00000080, 0x00000000, 0x00000080, 0x00000080, - 0x00000080, 0x00008000, 0x00000080, 0x00008080, - 0x00000080, 0x00800000, 0x00000080, 0x00800080, - 0x00000080, 0x00808000, 0x00000080, 0x00808080, - 0x00000080, 0x80000000, 0x00000080, 0x80000080, - 0x00000080, 0x80008000, 0x00000080, 0x80008080, - 0x00000080, 0x80800000, 0x00000080, 0x80800080, - 0x00000080, 0x80808000, 0x00000080, 0x80808080, - 0x00008000, 0x00000000, 0x00008000, 0x00000080, - 0x00008000, 0x00008000, 0x00008000, 0x00008080, - 0x00008000, 0x00800000, 0x00008000, 0x00800080, - 0x00008000, 0x00808000, 0x00008000, 0x00808080, - 0x00008000, 0x80000000, 0x00008000, 0x80000080, - 0x00008000, 0x80008000, 0x00008000, 0x80008080, - 0x00008000, 0x80800000, 0x00008000, 0x80800080, - 0x00008000, 0x80808000, 0x00008000, 0x80808080, - 0x00008080, 0x00000000, 0x00008080, 0x00000080, - 0x00008080, 0x00008000, 0x00008080, 0x00008080, - 0x00008080, 0x00800000, 0x00008080, 0x00800080, - 0x00008080, 0x00808000, 0x00008080, 0x00808080, - 0x00008080, 0x80000000, 0x00008080, 0x80000080, - 0x00008080, 0x80008000, 0x00008080, 0x80008080, - 0x00008080, 0x80800000, 0x00008080, 0x80800080, - 0x00008080, 0x80808000, 0x00008080, 0x80808080, - 0x00800000, 0x00000000, 0x00800000, 0x00000080, - 0x00800000, 0x00008000, 0x00800000, 0x00008080, - 0x00800000, 0x00800000, 0x00800000, 0x00800080, - 0x00800000, 0x00808000, 0x00800000, 0x00808080, - 0x00800000, 0x80000000, 0x00800000, 0x80000080, - 0x00800000, 0x80008000, 0x00800000, 0x80008080, - 0x00800000, 0x80800000, 0x00800000, 0x80800080, - 0x00800000, 0x80808000, 0x00800000, 0x80808080, - 0x00800080, 0x00000000, 0x00800080, 0x00000080, - 0x00800080, 0x00008000, 0x00800080, 0x00008080, - 0x00800080, 0x00800000, 0x00800080, 0x00800080, - 0x00800080, 0x00808000, 0x00800080, 0x00808080, - 0x00800080, 0x80000000, 0x00800080, 0x80000080, - 0x00800080, 0x80008000, 0x00800080, 0x80008080, - 0x00800080, 0x80800000, 0x00800080, 0x80800080, - 0x00800080, 0x80808000, 0x00800080, 0x80808080, - 0x00808000, 0x00000000, 0x00808000, 0x00000080, - 0x00808000, 0x00008000, 0x00808000, 0x00008080, - 0x00808000, 0x00800000, 0x00808000, 0x00800080, - 0x00808000, 0x00808000, 0x00808000, 0x00808080, - 0x00808000, 0x80000000, 0x00808000, 0x80000080, - 0x00808000, 0x80008000, 0x00808000, 0x80008080, - 0x00808000, 0x80800000, 0x00808000, 0x80800080, - 0x00808000, 0x80808000, 0x00808000, 0x80808080, - 0x00808080, 0x00000000, 0x00808080, 0x00000080, - 0x00808080, 0x00008000, 0x00808080, 0x00008080, - 0x00808080, 0x00800000, 0x00808080, 0x00800080, - 0x00808080, 0x00808000, 0x00808080, 0x00808080, - 0x00808080, 0x80000000, 0x00808080, 0x80000080, - 0x00808080, 0x80008000, 0x00808080, 0x80008080, - 0x00808080, 0x80800000, 0x00808080, 0x80800080, - 0x00808080, 0x80808000, 0x00808080, 0x80808080, - 0x80000000, 0x00000000, 0x80000000, 0x00000080, - 0x80000000, 0x00008000, 0x80000000, 0x00008080, - 0x80000000, 0x00800000, 0x80000000, 0x00800080, - 0x80000000, 0x00808000, 0x80000000, 0x00808080, - 0x80000000, 0x80000000, 0x80000000, 0x80000080, - 0x80000000, 0x80008000, 0x80000000, 0x80008080, - 0x80000000, 0x80800000, 0x80000000, 0x80800080, - 0x80000000, 0x80808000, 0x80000000, 0x80808080, - 0x80000080, 0x00000000, 0x80000080, 0x00000080, - 0x80000080, 0x00008000, 0x80000080, 0x00008080, - 0x80000080, 0x00800000, 0x80000080, 0x00800080, - 0x80000080, 0x00808000, 0x80000080, 0x00808080, - 0x80000080, 0x80000000, 0x80000080, 0x80000080, - 0x80000080, 0x80008000, 0x80000080, 0x80008080, - 0x80000080, 0x80800000, 0x80000080, 0x80800080, - 0x80000080, 0x80808000, 0x80000080, 0x80808080, - 0x80008000, 0x00000000, 0x80008000, 0x00000080, - 0x80008000, 0x00008000, 0x80008000, 0x00008080, - 0x80008000, 0x00800000, 0x80008000, 0x00800080, - 0x80008000, 0x00808000, 0x80008000, 0x00808080, - 0x80008000, 0x80000000, 0x80008000, 0x80000080, - 0x80008000, 0x80008000, 0x80008000, 0x80008080, - 0x80008000, 0x80800000, 0x80008000, 0x80800080, - 0x80008000, 0x80808000, 0x80008000, 0x80808080, - 0x80008080, 0x00000000, 0x80008080, 0x00000080, - 0x80008080, 0x00008000, 0x80008080, 0x00008080, - 0x80008080, 0x00800000, 0x80008080, 0x00800080, - 0x80008080, 0x00808000, 0x80008080, 0x00808080, - 0x80008080, 0x80000000, 0x80008080, 0x80000080, - 0x80008080, 0x80008000, 0x80008080, 0x80008080, - 0x80008080, 0x80800000, 0x80008080, 0x80800080, - 0x80008080, 0x80808000, 0x80008080, 0x80808080, - 0x80800000, 0x00000000, 0x80800000, 0x00000080, - 0x80800000, 0x00008000, 0x80800000, 0x00008080, - 0x80800000, 0x00800000, 0x80800000, 0x00800080, - 0x80800000, 0x00808000, 0x80800000, 0x00808080, - 0x80800000, 0x80000000, 0x80800000, 0x80000080, - 0x80800000, 0x80008000, 0x80800000, 0x80008080, - 0x80800000, 0x80800000, 0x80800000, 0x80800080, - 0x80800000, 0x80808000, 0x80800000, 0x80808080, - 0x80800080, 0x00000000, 0x80800080, 0x00000080, - 0x80800080, 0x00008000, 0x80800080, 0x00008080, - 0x80800080, 0x00800000, 0x80800080, 0x00800080, - 0x80800080, 0x00808000, 0x80800080, 0x00808080, - 0x80800080, 0x80000000, 0x80800080, 0x80000080, - 0x80800080, 0x80008000, 0x80800080, 0x80008080, - 0x80800080, 0x80800000, 0x80800080, 0x80800080, - 0x80800080, 0x80808000, 0x80800080, 0x80808080, - 0x80808000, 0x00000000, 0x80808000, 0x00000080, - 0x80808000, 0x00008000, 0x80808000, 0x00008080, - 0x80808000, 0x00800000, 0x80808000, 0x00800080, - 0x80808000, 0x00808000, 0x80808000, 0x00808080, - 0x80808000, 0x80000000, 0x80808000, 0x80000080, - 0x80808000, 0x80008000, 0x80808000, 0x80008080, - 0x80808000, 0x80800000, 0x80808000, 0x80800080, - 0x80808000, 0x80808000, 0x80808000, 0x80808080, - 0x80808080, 0x00000000, 0x80808080, 0x00000080, - 0x80808080, 0x00008000, 0x80808080, 0x00008080, - 0x80808080, 0x00800000, 0x80808080, 0x00800080, - 0x80808080, 0x00808000, 0x80808080, 0x00808080, - 0x80808080, 0x80000000, 0x80808080, 0x80000080, - 0x80808080, 0x80008000, 0x80808080, 0x80008080, - 0x80808080, 0x80800000, 0x80808080, 0x80800080, - 0x80808080, 0x80808000, 0x80808080, 0x80808080, -}; - -/* *INDENT-ON* */ -/***************************************************************/ diff --git a/src/java.desktop/share/native/libmlib_image/mlib_ImageDivTables.h b/src/java.desktop/share/native/libmlib_image/mlib_ImageDivTables.h deleted file mode 100644 index 831d9cc2053..00000000000 --- a/src/java.desktop/share/native/libmlib_image/mlib_ImageDivTables.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2003, 2020, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ - - -#ifndef __MLIB_IMAGEDIVTABLES_H -#define __MLIB_IMAGEDIVTABLES_H - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#ifdef __DIV_TABLE_DEFINED - -const mlib_u16 mlib_div6_tab[]; -const mlib_u16 mlib_div1_tab[]; -const mlib_f32 mlib_HSL2RGB_L2[]; -const mlib_f32 mlib_HSL2RGB_F[]; -const mlib_f32 mlib_U82F32[]; -const mlib_d64 mlib_U82D64[]; -const mlib_u32 mlib_FlipAndFixRotateTable[]; - -#else - -extern const mlib_u16 mlib_div6_tab[]; -extern const mlib_u16 mlib_div1_tab[]; -extern const mlib_f32 mlib_HSL2RGB_L2[]; -extern const mlib_f32 mlib_HSL2RGB_F[]; -extern const mlib_f32 mlib_U82F32[]; -extern const mlib_d64 mlib_U82D64[]; -extern const mlib_u32 mlib_FlipAndFixRotateTable[]; - -#endif /* __DIV_TABLE_DEFINED */ - -#ifdef __cplusplus -} -#endif /* __cplusplus */ -#endif /* __MLIB_IMAGEDIVTABLES_H */ - -/***************************************************************/ diff --git a/src/java.desktop/share/native/libmlib_image/mlib_c_ImageAffine_BL.c b/src/java.desktop/share/native/libmlib_image/mlib_c_ImageAffine_BL.c index ccb0a564674..631956ce44a 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_c_ImageAffine_BL.c +++ b/src/java.desktop/share/native/libmlib_image/mlib_c_ImageAffine_BL.c @@ -71,7 +71,7 @@ /***************************************************************/ #define TTYPE mlib_f32 -#define I2F(x) mlib_U82F32[x] +#define I2F(x) ((mlib_f32)(x)) #define ROUND(x) ((x) + 0.5f) #define FUN_NAME(CHAN) mlib_ImageAffine_u8_##CHAN##_bl From eba245f2ff30e4b9c7bd0fecb58980d478350551 Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Tue, 13 Oct 2026 19:26:34 +0000 Subject: [PATCH 075/101] 8394229: Add missing @Override annotations in "sun.print" package part 2 Reviewed-by: azvegint, prr --- .../classes/sun/print/PSPathGraphics.java | 16 +++- .../share/classes/sun/print/PSPrinterJob.java | 21 +++++- .../classes/sun/print/PSStreamPrintJob.java | 10 ++- .../sun/print/PSStreamPrintService.java | 22 +++++- .../sun/print/PSStreamPrinterFactory.java | 5 +- .../classes/sun/print/ProxyGraphics2D.java | 74 ++++++++++++++++++- .../classes/sun/print/ProxyPrintGraphics.java | 7 +- .../classes/sun/print/RasterPrinterJob.java | 19 +++++ .../classes/sun/print/ServiceDialog.java | 31 +++++++- .../classes/sun/print/ServiceNotifier.java | 3 +- .../classes/sun/print/SunAlternateMedia.java | 6 +- .../classes/sun/print/SunMinMaxPage.java | 4 +- .../classes/sun/print/SunPageSelection.java | 5 +- 13 files changed, 211 insertions(+), 12 deletions(-) diff --git a/src/java.desktop/share/classes/sun/print/PSPathGraphics.java b/src/java.desktop/share/classes/sun/print/PSPathGraphics.java index 293cdd7f10d..d06cb0cc427 100644 --- a/src/java.desktop/share/classes/sun/print/PSPathGraphics.java +++ b/src/java.desktop/share/classes/sun/print/PSPathGraphics.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 @@ -78,6 +78,7 @@ class PSPathGraphics extends PathGraphics { * this graphics context. * @since 1.0 */ + @Override public Graphics create() { return new PSPathGraphics((Graphics2D) getDelegate().create(), @@ -94,6 +95,7 @@ public Graphics create() { * so that we can generate PostScript in user space * rather than device space. */ + @Override public void fill(Shape s, Color color) { deviceFill(s.getPathIterator(new AffineTransform()), color); } @@ -110,6 +112,7 @@ public void fill(Shape s, Color color) { * @see java.awt.Graphics#drawChars * @since 1.0 */ + @Override public void drawString(String str, int x, int y) { drawString(str, (float) x, (float) y); } @@ -136,20 +139,24 @@ public void drawString(String str, int x, int y) { * @see #setComposite * @see #setClip */ + @Override public void drawString(String str, float x, float y) { drawString(str, x, y, getFont(), getFontRenderContext(), 0f); } + @Override protected boolean canDrawStringToWidth() { return true; } + @Override protected int platformFontCount(Font font, String str) { PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob(); return psPrinterJob.platformFontCount(font, str); } + @Override protected void drawString(String str, float x, float y, Font font, FontRenderContext frc, float w) { if (str.length() == 0) { @@ -276,6 +283,7 @@ protected void drawString(String str, float x, float y, * @param handlingTransparency if being recursively called to * print opaque region of transparent image */ + @Override protected boolean drawImageToPlatform(Image image, AffineTransform xform, Color bgcolor, int srcX, int srcY, @@ -636,6 +644,7 @@ && canDoRedraws()) { * */ + @Override public void redrawRegion(Rectangle2D region, double scaleX, double scaleY, Shape savedClip, AffineTransform savedTransform) @@ -736,6 +745,7 @@ public void redrawRegion(Rectangle2D region, double scaleX, double scaleY, * with the specified color. * The path is provided in current user space. */ + @Override protected void deviceFill(PathIterator pathIter, Color color) { PSPrinterJob psPrinterJob = (PSPrinterJob) getPrinterJob(); @@ -746,6 +756,7 @@ protected void deviceFill(PathIterator pathIter, Color color) { * Draw the bounding rectangle using path by calling draw() * function and passing a rectangle shape. */ + @Override protected void deviceFrameRect(int x, int y, int width, int height, Color color) { @@ -756,6 +767,7 @@ protected void deviceFrameRect(int x, int y, int width, int height, * Draw a line using path by calling draw() function and passing * a line shape. */ + @Override protected void deviceDrawLine(int xBegin, int yBegin, int xEnd, int yEnd, Color color) { @@ -765,6 +777,7 @@ protected void deviceDrawLine(int xBegin, int yBegin, /* * Fill the rectangle with the specified color by calling fill(). */ + @Override protected void deviceFillRect(int x, int y, int width, int height, Color color) { fill(new Rectangle2D.Float(x, y, width, height)); @@ -776,6 +789,7 @@ protected void deviceFillRect(int x, int y, int width, int height, * FIX: Rework PathGraphics so that this method is * not an abstract method there. */ + @Override protected void deviceClip(PathIterator pathIter) { } diff --git a/src/java.desktop/share/classes/sun/print/PSPrinterJob.java b/src/java.desktop/share/classes/sun/print/PSPrinterJob.java index 03fdb532b71..576b4ffda68 100644 --- a/src/java.desktop/share/classes/sun/print/PSPrinterJob.java +++ b/src/java.desktop/share/classes/sun/print/PSPrinterJob.java @@ -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 @@ -387,6 +387,7 @@ public PSPrinterJob() * returns true. * @see java.awt.GraphicsEnvironment#isHeadless */ + @Override public boolean printDialog() throws HeadlessException { if (GraphicsEnvironment.isHeadless()) { @@ -477,6 +478,7 @@ protected void setAttributes(PrintRequestAttributeSet attributes) * this method is called to mark the start of a * document. */ + @Override protected void startDoc() throws PrinterException { // A security check has been performed in the @@ -720,6 +722,7 @@ mNoJobSheet, getJobNameInt(), /** * Invoked if the application cancelled the printjob. */ + @Override protected void abortDoc() { if (mPSStream != null && mDestType != RasterPrinterJob.STREAM) { mPSStream.close(); @@ -734,6 +737,7 @@ protected void abortDoc() { * this method is called after that last page * has been imaged. */ + @Override protected void endDoc() throws PrinterException { if (mPSStream != null) { mPSStream.println(EOF_COMMENT); @@ -778,6 +782,7 @@ private String getCoordPrep() { * The RasterPrintJob super class calls this method * at the start of each page. */ + @Override protected void startPage(PageFormat pageFormat, Printable painter, int index, boolean paperChanged) throws PrinterException @@ -828,6 +833,7 @@ protected void startPage(PageFormat pageFormat, Printable painter, * The RasterPrintJob super class calls this method * at the end of each page. */ + @Override protected void endPage(PageFormat format, Printable painter, int index) throws PrinterException @@ -934,6 +940,7 @@ protected void drawImageBGR(byte[] bgrData, * specified by the caller. Currently the data * is 24 bits per pixel in BGR format. */ + @Override protected void printBand(byte[] bgrData, int x, int y, int width, int height) throws PrinterException @@ -999,6 +1006,7 @@ protected void printBand(byte[] bgrData, int x, int y, * causes the print job to be rasterized. */ + @Override protected Graphics2D createPathGraphics(PeekGraphics peekGraphics, PrinterJob printerJob, Printable painter, @@ -1433,6 +1441,7 @@ protected float getPenY() { * Return the x resolution of the coordinates * to be rendered. */ + @Override protected double getXRes() { return xres; } @@ -1440,6 +1449,7 @@ protected double getXRes() { * Return the y resolution of the coordinates * to be rendered. */ + @Override protected double getYRes() { return yres; } @@ -1447,6 +1457,7 @@ protected double getYRes() { /** * Set the resolution at which to print. */ + @Override protected void setXYRes(double x, double y) { xres = x; yres = y; @@ -1456,6 +1467,7 @@ protected void setXYRes(double x, double y) { * For PostScript the origin is in the upper-left of the * paper not at the imageable area corner. */ + @Override protected double getPhysicalPrintableX(Paper p) { return 0; @@ -1465,22 +1477,27 @@ protected double getPhysicalPrintableX(Paper p) { * For PostScript the origin is in the upper-left of the * paper not at the imageable area corner. */ + @Override protected double getPhysicalPrintableY(Paper p) { return 0; } + @Override protected double getPhysicalPrintableWidth(Paper p) { return p.getImageableWidth(); } + @Override protected double getPhysicalPrintableHeight(Paper p) { return p.getImageableHeight(); } + @Override protected double getPhysicalPageWidth(Paper p) { return p.getWidth(); } + @Override protected double getPhysicalPageHeight(Paper p) { return p.getHeight(); } @@ -1491,10 +1508,12 @@ protected double getPhysicalPageHeight(Paper p) { * If the printer makes copies itself then this * method should return 1. */ + @Override protected int getNoncollatedCopies() { return 1; } + @Override protected int getCollatedCopies() { return 1; } diff --git a/src/java.desktop/share/classes/sun/print/PSStreamPrintJob.java b/src/java.desktop/share/classes/sun/print/PSStreamPrintJob.java index 0f420186f60..8ace6e58312 100644 --- a/src/java.desktop/share/classes/sun/print/PSStreamPrintJob.java +++ b/src/java.desktop/share/classes/sun/print/PSStreamPrintJob.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -93,10 +93,12 @@ public class PSStreamPrintJob implements CancelablePrintJob { this.service = service; } + @Override public PrintService getPrintService() { return service; } + @Override public PrintJobAttributeSet getAttributes() { synchronized (this) { if (jobAttrSet == null) { @@ -109,6 +111,7 @@ public PrintJobAttributeSet getAttributes() { } } + @Override public void addPrintJobListener(PrintJobListener listener) { synchronized (this) { if (listener == null) { @@ -121,6 +124,7 @@ public void addPrintJobListener(PrintJobListener listener) { } } + @Override public void removePrintJobListener(PrintJobListener listener) { synchronized (this) { if (listener == null || jobListeners == null ) { @@ -220,6 +224,7 @@ private void notifyEvent(int reason) { } } + @Override public void addPrintJobAttributeListener( PrintJobAttributeListener listener, PrintJobAttributeSet attributes) { @@ -239,6 +244,7 @@ public void addPrintJobAttributeListener( } } + @Override public void removePrintJobAttributeListener( PrintJobAttributeListener listener) { synchronized (this) { @@ -259,6 +265,7 @@ public void removePrintJobAttributeListener( } } + @Override public void print(Doc doc, PrintRequestAttributeSet attributes) throws PrintException { @@ -519,6 +526,7 @@ private void getAttributeValues(DocFlavor flavor) throws PrintException { } /* Cancel PrinterJob jobs that haven't yet completed. */ + @Override public void cancel() throws PrintException { synchronized (this) { if (!printing) { diff --git a/src/java.desktop/share/classes/sun/print/PSStreamPrintService.java b/src/java.desktop/share/classes/sun/print/PSStreamPrintService.java index e1594e051f5..a65c967a794 100644 --- a/src/java.desktop/share/classes/sun/print/PSStreamPrintService.java +++ b/src/java.desktop/share/classes/sun/print/PSStreamPrintService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -95,38 +95,46 @@ public PSStreamPrintService(OutputStream out) { super(out); } + @Override public String getOutputFormat() { return PSStreamPrinterFactory.psMimeType; } + @Override public DocFlavor[] getSupportedDocFlavors() { return PSStreamPrinterFactory.getFlavors(); } + @Override public DocPrintJob createPrintJob() { return new PSStreamPrintJob(this); } + @Override public boolean usesClass(Class c) { return (c == sun.print.PSPrinterJob.class); } + @Override public String getName() { return "Postscript output"; } + @Override public void addPrintServiceAttributeListener( PrintServiceAttributeListener listener) { return; } + @Override public void removePrintServiceAttributeListener( PrintServiceAttributeListener listener) { return; } + @Override public T getAttribute(Class category) { @@ -144,6 +152,7 @@ T getAttribute(Class category) return null; } } + @Override public PrintServiceAttributeSet getAttributes() { PrintServiceAttributeSet attrs = new HashPrintServiceAttributeSet(); attrs.add(ColorSupported.SUPPORTED); @@ -151,6 +160,7 @@ public PrintServiceAttributeSet getAttributes() { return AttributeSetUtilities.unmodifiableView(attrs); } + @Override public boolean isDocFlavorSupported(DocFlavor flavor) { DocFlavor [] flavors = getSupportedDocFlavors(); for (int f=0; f[] getSupportedAttributeCategories() { Class[] cats = new Class[suppAttrCats.length]; System.arraycopy(suppAttrCats, 0, cats, 0, cats.length); return cats; } + @Override public boolean isAttributeCategorySupported(Class category) { @@ -188,6 +200,7 @@ public Class[] getSupportedAttributeCategories() { } + @Override public Object getDefaultAttributeValue(Class category) { @@ -249,6 +262,7 @@ public Class[] getSupportedAttributeCategories() { } + @Override public Object getSupportedAttributeValues(Class category, DocFlavor flavor, @@ -401,6 +415,7 @@ private boolean isSupportedMedia(MediaSizeName msn) { return false; } + @Override public boolean isAttributeValueSupported(Attribute attr, DocFlavor flavor, AttributeSet attributes) { @@ -462,6 +477,7 @@ else if (attr.getCategory() == Copies.class) { return true; } + @Override public AttributeSet getUnsupportedAttributes(DocFlavor flavor, AttributeSet attributes) { @@ -496,10 +512,12 @@ public AttributeSet getUnsupportedAttributes(DocFlavor flavor, } } + @Override public ServiceUIFactory getServiceUIFactory() { return null; } + @Override public String toString() { return "PSStreamPrintService: " + getName(); } @@ -507,12 +525,14 @@ public String toString() { /* Stream services have an output stream which cannot be shared, * so two services are equal only if they are the same object. */ + @Override public boolean equals(Object obj) { return (obj == this || (obj instanceof PSStreamPrintService && ((PSStreamPrintService)obj).getName().equals(getName()))); } + @Override public int hashCode() { return this.getClass().hashCode()+getName().hashCode(); } diff --git a/src/java.desktop/share/classes/sun/print/PSStreamPrinterFactory.java b/src/java.desktop/share/classes/sun/print/PSStreamPrinterFactory.java index 3da8b03039a..7b6b6201cf2 100644 --- a/src/java.desktop/share/classes/sun/print/PSStreamPrinterFactory.java +++ b/src/java.desktop/share/classes/sun/print/PSStreamPrinterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2001, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -49,10 +49,12 @@ public class PSStreamPrinterFactory extends StreamPrintServiceFactory { DocFlavor.URL.PNG, }; + @Override public String getOutputFormat() { return psMimeType; } + @Override public DocFlavor[] getSupportedDocFlavors() { return getFlavors(); } @@ -63,6 +65,7 @@ static DocFlavor[] getFlavors() { return flavors; } + @Override public StreamPrintService getPrintService(OutputStream out) { return new PSStreamPrintService(out); } diff --git a/src/java.desktop/share/classes/sun/print/ProxyGraphics2D.java b/src/java.desktop/share/classes/sun/print/ProxyGraphics2D.java index 598bedb5613..9de9675f4d4 100644 --- a/src/java.desktop/share/classes/sun/print/ProxyGraphics2D.java +++ b/src/java.desktop/share/classes/sun/print/ProxyGraphics2D.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022, 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 @@ -95,6 +95,7 @@ public void setDelegate(Graphics2D graphics) { mGraphics = graphics; } + @Override public PrinterJob getPrinterJob() { return mPrinterJob; } @@ -102,6 +103,7 @@ public PrinterJob getPrinterJob() { /** * Returns the device configuration associated with this Graphics2D. */ + @Override public GraphicsConfiguration getDeviceConfiguration() { return ((RasterPrinterJob)mPrinterJob).getPrinterGraphicsConfig(); } @@ -115,6 +117,7 @@ public GraphicsConfiguration getDeviceConfiguration() { * this graphics context. * @since 1.0 */ + @Override public Graphics create() { return new ProxyGraphics2D((Graphics2D) mGraphics.create(), mPrinterJob); @@ -132,6 +135,7 @@ public Graphics create() { * @param y the y coordinate. * @since 1.0 */ + @Override public void translate(int x, int y) { mGraphics.translate(x, y); } @@ -147,6 +151,7 @@ public void translate(int x, int y) { * [ 0 0 1 ] * */ + @Override public void translate(double tx, double ty) { mGraphics.translate(tx, ty); } @@ -165,6 +170,7 @@ public void translate(double tx, double ty) { * x axis toward the positive y axis. * @param theta The angle of rotation in radians. */ + @Override public void rotate(double theta) { mGraphics.rotate(theta); } @@ -184,6 +190,7 @@ public void rotate(double theta) { * @param x The x coordinate of the origin of the rotation * @param y The x coordinate of the origin of the rotation */ + @Override public void rotate(double theta, double x, double y) { mGraphics.rotate(theta, x, y); } @@ -199,6 +206,7 @@ public void rotate(double theta, double x, double y) { * [ 0 0 1 ] * */ + @Override public void scale(double sx, double sy) { mGraphics.scale(sx, sy); } @@ -218,6 +226,7 @@ public void scale(double sx, double sy) { * @param shy The factor by which coordinates are shifted towards the * positive Y axis direction according to their X coordinate */ + @Override public void shear(double shx, double shy) { mGraphics.shear(shx, shy); } @@ -229,6 +238,7 @@ public void shear(double shx, double shy) { * @see java.awt.Graphics#setColor * @since 1.0 */ + @Override public Color getColor() { return mGraphics.getColor(); } @@ -242,6 +252,7 @@ public Color getColor() { * @see java.awt.Graphics#getColor * @since 1.0 */ + @Override public void setColor(Color c) { mGraphics.setColor(c); } @@ -254,6 +265,7 @@ public void setColor(Color c) { * overwrite the destination with the current color. * @since 1.0 */ + @Override public void setPaintMode() { mGraphics.setPaintMode(); } @@ -274,6 +286,7 @@ public void setPaintMode() { * @param c1 the XOR alternation color * @since 1.0 */ + @Override public void setXORMode(Color c1) { mGraphics.setXORMode(c1); } @@ -285,6 +298,7 @@ public void setXORMode(Color c1) { * @see java.awt.Graphics#setFont * @since 1.0 */ + @Override public Font getFont() { return mGraphics.getFont(); } @@ -300,6 +314,7 @@ public Font getFont() { * @see java.awt.Graphics#drawBytes(byte[], int, int, int, int) * @since 1.0 */ + @Override public void setFont(Font font) { mGraphics.setFont(font); } @@ -313,6 +328,7 @@ public void setFont(Font font) { * @see java.awt.Graphics#getFontMetrics() * @since 1.0 */ + @Override public FontMetrics getFontMetrics(Font f) { return mGraphics.getFontMetrics(f); } @@ -321,6 +337,7 @@ public FontMetrics getFontMetrics(Font f) { * Get the rendering context of the font * within this Graphics2D context. */ + @Override public FontRenderContext getFontRenderContext() { return mGraphics.getFontRenderContext(); } @@ -336,6 +353,7 @@ public FontRenderContext getFontRenderContext() { * @see java.awt.Graphics#setClip(Shape) * @since 1.1 */ + @Override public Rectangle getClipBounds() { return mGraphics.getClipBounds(); } @@ -355,6 +373,7 @@ public Rectangle getClipBounds() { * @see #setClip(int, int, int, int) * @see #setClip(Shape) */ + @Override public void clipRect(int x, int y, int width, int height) { mGraphics.clipRect(x, y, width, height); } @@ -372,6 +391,7 @@ public void clipRect(int x, int y, int width, int height) { * @see java.awt.Graphics#setClip(Shape) * @since 1.1 */ + @Override public void setClip(int x, int y, int width, int height) { mGraphics.setClip(x, y, width, height); } @@ -386,6 +406,7 @@ public void setClip(int x, int y, int width, int height) { * @see java.awt.Graphics#setClip(Shape) * @since 1.1 */ + @Override public Shape getClip() { return mGraphics.getClip(); } @@ -404,6 +425,7 @@ public Shape getClip() { * @see java.awt.Graphics#setClip(int, int, int, int) * @since 1.1 */ + @Override public void setClip(Shape clip) { mGraphics.setClip(clip); } @@ -429,6 +451,7 @@ public void setClip(Shape clip) { * @param dy the vertical distance to copy the pixels. * @since 1.0 */ + @Override public void copyArea(int x, int y, int width, int height, int dx, int dy) { mGraphics.copyArea(x, y, width, height, dx, dy); @@ -444,6 +467,7 @@ public void copyArea(int x, int y, int width, int height, * @param y2 the second point's y coordinate. * @since 1.0 */ + @Override public void drawLine(int x1, int y1, int x2, int y2) { mGraphics.drawLine(x1, y1, x2, y2); } @@ -469,6 +493,7 @@ public void drawLine(int x1, int y1, int x2, int y2) { * @see java.awt.Graphics#clearRect * @since 1.0 */ + @Override public void fillRect(int x, int y, int width, int height) { mGraphics.fillRect(x, y, width, height); } @@ -493,6 +518,7 @@ public void fillRect(int x, int y, int width, int height) { * @see java.awt.Graphics#setXORMode(java.awt.Color) * @since 1.0 */ + @Override public void clearRect(int x, int y, int width, int height) { mGraphics.clearRect(x, y, width, height); } @@ -514,6 +540,7 @@ public void clearRect(int x, int y, int width, int height) { * @see java.awt.Graphics#fillRoundRect * @since 1.0 */ + @Override public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { mGraphics.drawRoundRect(x, y, width, height, arcWidth, arcHeight); @@ -536,6 +563,7 @@ public void drawRoundRect(int x, int y, int width, int height, * @see java.awt.Graphics#drawRoundRect * @since 1.0 */ + @Override public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) { mGraphics.fillRoundRect(x, y, width, height, arcWidth, arcHeight); @@ -559,6 +587,7 @@ public void fillRoundRect(int x, int y, int width, int height, * @see java.awt.Graphics#fillOval * @since 1.0 */ + @Override public void drawOval(int x, int y, int width, int height) { mGraphics.drawOval(x, y, width, height); } @@ -575,6 +604,7 @@ public void drawOval(int x, int y, int width, int height) { * @see java.awt.Graphics#drawOval * @since 1.0 */ + @Override public void fillOval(int x, int y, int width, int height) { mGraphics.fillOval(x, y, width, height); } @@ -609,6 +639,7 @@ public void fillOval(int x, int y, int width, int height) { * @see java.awt.Graphics#fillArc * @since 1.0 */ + @Override public void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) { mGraphics.drawArc(x, y, width, height, startAngle, arcAngle); @@ -643,6 +674,7 @@ public void drawArc(int x, int y, int width, int height, * @see java.awt.Graphics#drawArc * @since 1.0 */ + @Override public void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle) { mGraphics.fillArc(x, y, width, height, startAngle, arcAngle); @@ -660,6 +692,7 @@ public void fillArc(int x, int y, int width, int height, * @see java.awt.Graphics#drawPolygon(int[], int[], int) * @since 1.1 */ + @Override public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) { mGraphics.drawPolyline(xPoints, yPoints, nPoints); @@ -685,6 +718,7 @@ public void drawPolyline(int[] xPoints, int[] yPoints, * @see java.awt.Graphics#drawPolyline * @since 1.0 */ + @Override public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) { mGraphics.drawPolygon(xPoints, yPoints, nPoints); @@ -711,6 +745,7 @@ public void drawPolygon(int[] xPoints, int[] yPoints, * @see java.awt.Graphics#drawPolygon(int[], int[], int) * @since 1.0 */ + @Override public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) { mGraphics.fillPolygon(xPoints, yPoints, nPoints); @@ -728,6 +763,7 @@ public void fillPolygon(int[] xPoints, int[] yPoints, * @see java.awt.Graphics#drawChars * @since 1.0 */ + @Override public void drawString(String str, int x, int y) { mGraphics.drawString(str, x, y); } @@ -752,6 +788,7 @@ public void drawString(String str, int x, int y) { * @see #setComposite * @see #setClip */ + @Override public void drawString(AttributedCharacterIterator iterator, int x, int y) { mGraphics.drawString(iterator, x, y); @@ -777,6 +814,7 @@ public void drawString(AttributedCharacterIterator iterator, * @see #setComposite * @see #setClip */ + @Override public void drawString(AttributedCharacterIterator iterator, float x, float y) { mGraphics.drawString(iterator, x, y); @@ -807,6 +845,7 @@ public void drawString(AttributedCharacterIterator iterator, * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) * @since 1.0 */ + @Override public boolean drawImage(Image img, int x, int y, ImageObserver observer) { @@ -847,6 +886,7 @@ public boolean drawImage(Image img, int x, int y, * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) * @since 1.0 */ + @Override public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { @@ -885,6 +925,7 @@ public boolean drawImage(Image img, int x, int y, * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) * @since 1.0 */ + @Override public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver observer) { @@ -944,6 +985,7 @@ public boolean drawImage(Image img, int x, int y, * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) * @since 1.0 */ + @Override public boolean drawImage(Image img, int x, int y, int width, int height, Color bgcolor, @@ -1013,6 +1055,7 @@ public boolean drawImage(Image img, int x, int y, * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) * @since 1.1 */ + @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, @@ -1075,6 +1118,7 @@ public boolean drawImage(Image img, * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) * @since 1.1 */ + @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, @@ -1195,6 +1239,7 @@ private BufferedImage getBufferedImageCopy(Image img, Color bgcolor) { * @see #clip * @see #setClip */ + @Override public void drawRenderedImage(RenderedImage img, AffineTransform xform) { mGraphics.drawRenderedImage(img, xform); @@ -1202,6 +1247,7 @@ public void drawRenderedImage(RenderedImage img, + @Override public void drawRenderableImage(RenderableImage img, AffineTransform xform) { @@ -1255,6 +1301,7 @@ public void drawRenderableImage(RenderableImage img, * @see java.awt.Graphics#create * @since 1.0 */ + @Override public void dispose() { mGraphics.dispose(); } @@ -1262,6 +1309,7 @@ public void dispose() { /** * Empty finalizer as no clean up needed here. */ + @Override @SuppressWarnings("removal") public void finalize() { } @@ -1283,6 +1331,7 @@ public void finalize() { * @see #setClip * @see #setComposite */ + @Override public void draw(Shape s) { mGraphics.draw(s); } @@ -1307,6 +1356,7 @@ public void draw(Shape s) { * @see #clip * @see #setClip */ + @Override public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { @@ -1331,6 +1381,7 @@ public boolean drawImage(Image img, * @see #clip * @see #setClip */ + @Override public void drawImage(BufferedImage img, BufferedImageOp op, int x, @@ -1355,6 +1406,7 @@ public void drawImage(BufferedImage img, * @see #clip * @see #setClip */ + @Override public void drawString(String str, float x, float y) { @@ -1376,6 +1428,7 @@ public void drawString(String str, * @see #clip * @see #setClip */ + @Override public void drawGlyphVector(GlyphVector g, float x, float y) { @@ -1394,6 +1447,7 @@ public void drawGlyphVector(GlyphVector g, * @see #clip * @see #setClip */ + @Override public void fill(Shape s) { mGraphics.fill(s); } @@ -1416,6 +1470,7 @@ public void fill(Shape s) { * @see #clip * @see #setClip */ + @Override public boolean hit(Rectangle rect, Shape s, boolean onStroke) { @@ -1433,6 +1488,7 @@ public boolean hit(Rectangle rect, * @see java.awt.Graphics#setPaintMode * @see java.awt.AlphaComposite */ + @Override public void setComposite(Composite comp) { mGraphics.setComposite(comp); } @@ -1446,6 +1502,7 @@ public void setComposite(Composite comp) { * @see java.awt.GradientPaint * @see java.awt.TexturePaint */ + @Override public void setPaint(Paint paint) { mGraphics.setPaint(paint); } @@ -1456,6 +1513,7 @@ public void setPaint(Paint paint) { * the rendering process. * @see java.awt.BasicStroke */ + @Override public void setStroke(Stroke s) { mGraphics.setStroke(s); } @@ -1469,6 +1527,7 @@ public void setStroke(Stroke s) { * hint category. * @see RenderingHints */ + @Override public void setRenderingHint(Key hintCategory, Object hintValue) { mGraphics.setRenderingHint(hintCategory, hintValue); } @@ -1479,6 +1538,7 @@ public void setRenderingHint(Key hintCategory, Object hintValue) { * @return The preferences for rendering algorithms. * @see RenderingHints */ + @Override public Object getRenderingHint(Key hintCategory) { return mGraphics.getRenderingHint(hintCategory); } @@ -1490,6 +1550,7 @@ public Object getRenderingHint(Key hintCategory) { * @param hints The rendering hints to be set * @see RenderingHints */ + @Override public void setRenderingHints(Map hints) { mGraphics.setRenderingHints(hints); } @@ -1501,6 +1562,7 @@ public void setRenderingHints(Map hints) { * @param hints The rendering hints to be set * @see RenderingHints */ + @Override public void addRenderingHints(Map hints) { mGraphics.addRenderingHints(hints); } @@ -1511,6 +1573,7 @@ public void addRenderingHints(Map hints) { * overall time/quality trade-off in the rendering process. * @see RenderingHints */ + @Override public RenderingHints getRenderingHints() { return mGraphics.getRenderingHints(); } @@ -1532,6 +1595,7 @@ public RenderingHints getRenderingHints() { * @see #setTransform * @see AffineTransform */ + @Override public void transform(AffineTransform Tx) { mGraphics.transform(Tx); } @@ -1542,6 +1606,7 @@ public void transform(AffineTransform Tx) { * @see #transform * @see AffineTransform */ + @Override public void setTransform(AffineTransform Tx) { mGraphics.setTransform(Tx); } @@ -1551,6 +1616,7 @@ public void setTransform(AffineTransform Tx) { * @see #transform * @see #setTransform */ + @Override public AffineTransform getTransform() { return mGraphics.getTransform(); } @@ -1560,6 +1626,7 @@ public AffineTransform getTransform() { * @see #setPaint * @see java.awt.Graphics#setColor */ + @Override public Paint getPaint() { return mGraphics.getPaint(); } @@ -1568,6 +1635,7 @@ public Paint getPaint() { * Returns the current Composite in the Graphics2D state. * @see #setComposite */ + @Override public Composite getComposite() { return mGraphics.getComposite(); } @@ -1584,6 +1652,7 @@ public Composite getComposite() { * @see #getBackground * @see Graphics#clearRect */ + @Override public void setBackground(Color color) { mGraphics.setBackground(color); } @@ -1592,6 +1661,7 @@ public void setBackground(Color color) { * Returns the background color used for clearing a region. * @see #setBackground */ + @Override public Color getBackground() { return mGraphics.getBackground(); } @@ -1600,6 +1670,7 @@ public Color getBackground() { * Returns the current Stroke in the Graphics2D state. * @see #setStroke */ + @Override public Stroke getStroke() { return mGraphics.getStroke(); } @@ -1613,6 +1684,7 @@ public Stroke getStroke() { * To make the clip larger, use any setClip method. * @param s The Shape to be intersected with the current clip. */ + @Override public void clip(Shape s) { mGraphics.clip(s); } diff --git a/src/java.desktop/share/classes/sun/print/ProxyPrintGraphics.java b/src/java.desktop/share/classes/sun/print/ProxyPrintGraphics.java index 7bf125f185f..6bccd59e237 100644 --- a/src/java.desktop/share/classes/sun/print/ProxyPrintGraphics.java +++ b/src/java.desktop/share/classes/sun/print/ProxyPrintGraphics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -49,6 +49,7 @@ public ProxyPrintGraphics(Graphics graphics, PrintJob thePrintJob) { * Returns the PrintJob object from which this PrintGraphics * object originated. */ + @Override public PrintJob getPrintJob() { return printJob; } @@ -59,6 +60,7 @@ public PrintJob getPrintJob() { * @return a new graphics context that is a copy of * this graphics context. */ + @Override public Graphics create() { return new ProxyPrintGraphics(getGraphics().create(), printJob); } @@ -80,11 +82,13 @@ public Graphics create() { * @see java.awt.Graphics#translate * @see java.awt.Graphics#clipRect */ + @Override public Graphics create(int x, int y, int width, int height) { Graphics g = getGraphics().create(x, y, width, height); return new ProxyPrintGraphics(g, printJob); } + @Override public Graphics getGraphics() { return super.getGraphics(); } @@ -96,6 +100,7 @@ public Graphics getGraphics() { * We will preserve that behaviour and there is consequently no need * to take any action in this dispose method. */ + @Override public void dispose() { super.dispose(); } diff --git a/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java b/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java index b28723f94a6..db0276ae100 100644 --- a/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java +++ b/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java @@ -451,6 +451,7 @@ protected static PrintService lookupDefaultPrintService() { * @return the service for this printer job. * */ + @Override public PrintService getPrintService() { if (myService == null) { PrintService svc = PrintServiceLookup.lookupDefaultPrintService(); @@ -489,6 +490,7 @@ public PrintService getPrintService() { * @throws PrinterException if the specified service does not support * 2D printing or no longer available. */ + @Override public void setPrintService(PrintService service) throws PrinterException { if (service == null) { @@ -713,6 +715,7 @@ protected void updateAttributesWithPageFormat(PrintService service, * @see java.awt.GraphicsEnvironment#isHeadless * @since 1.2 */ + @Override public PageFormat pageDialog(PageFormat page) throws HeadlessException { if (GraphicsEnvironment.isHeadless()) { @@ -755,6 +758,7 @@ public PageFormat pageDialog(PageFormat page) * return a PageFormat corresponding to the updated attributes, * or null if the user cancelled the dialog. */ + @Override @SuppressWarnings("deprecation") public PageFormat pageDialog(final PrintRequestAttributeSet attributes) throws HeadlessException { @@ -920,6 +924,7 @@ protected PageFormat getPageFormatFromAttributes() { * returns true. * @see java.awt.GraphicsEnvironment#isHeadless */ + @Override public boolean printDialog(final PrintRequestAttributeSet attributes) throws HeadlessException { if (GraphicsEnvironment.isHeadless()) { @@ -1049,6 +1054,7 @@ public boolean printDialog(final PrintRequestAttributeSet attributes) * returns true. * @see java.awt.GraphicsEnvironment#isHeadless */ + @Override public boolean printDialog() throws HeadlessException { if (GraphicsEnvironment.isHeadless()) { @@ -1105,6 +1111,7 @@ public boolean printDialog() throws HeadlessException { * for each page is the default page format. * @param painter Called to render each page of the document. */ + @Override public void setPrintable(Printable painter) { setPageable(new OpenBook(defaultPage(new PageFormat()), painter)); } @@ -1117,6 +1124,7 @@ public void setPrintable(Printable painter) { * @param format The size and orientation of each page to * be printed. */ + @Override public void setPrintable(Printable painter, PageFormat format) { setPageable(new OpenBook(format, painter)); updatePageAttributes(getPrintService(), format); @@ -1132,6 +1140,7 @@ public void setPrintable(Printable painter, PageFormat format) { * @see PageFormat * @see Printable */ + @Override public void setPageable(Pageable document) throws NullPointerException { if (document != null) { mDocument = document; @@ -1430,6 +1439,7 @@ protected void spoolToService(PrintService psvc, * @see java.awt.print.Pageable * @see java.awt.print.Printable */ + @Override public void print() throws PrinterException { print(attributes); } @@ -1441,6 +1451,7 @@ protected void debug_println(String str) { } } + @Override public void print(PrintRequestAttributeSet attributes) throws PrinterException { @@ -1717,6 +1728,7 @@ protected void validatePaper(Paper origPaper, Paper newPaper) { * Platform subclasses which can access the actual default paper size * for a printer may override this method. */ + @Override public PageFormat defaultPage(PageFormat page) { PageFormat newPage = (PageFormat)page.clone(); newPage.setOrientation(PageFormat.PORTRAIT); @@ -1772,6 +1784,7 @@ public PageFormat defaultPage(PageFormat page) { * The passed in PageFormat is cloned and altered to be usable on * the PrinterJob's current printer. */ + @Override public PageFormat validatePage(PageFormat page) { PageFormat newPage = (PageFormat)page.clone(); Paper newPaper = new Paper(); @@ -1784,6 +1797,7 @@ public PageFormat validatePage(PageFormat page) { /** * Set the number of copies to be printed. */ + @Override public void setCopies(int copies) { mNumCopies = copies; } @@ -1791,6 +1805,7 @@ public void setCopies(int copies) { /** * Get the number of copies to be printed. */ + @Override public int getCopies() { return mNumCopies; } @@ -1805,6 +1820,7 @@ protected int getCopiesInt() { /** * Get the name of the printing user. */ + @Override public String getUserName() { return System.getProperty("user.name"); } @@ -1824,6 +1840,7 @@ protected String getUserNameInt() { * Set the name of the document to be printed. * The document name can not be null. */ + @Override public void setJobName(String jobName) { if (jobName != null) { mDocName = jobName; @@ -1835,6 +1852,7 @@ public void setJobName(String jobName) { /** * Get the name of the document to be printed. */ + @Override public String getJobName() { return mDocName; } @@ -2396,6 +2414,7 @@ protected int printPage(Pageable document, int pageIndex) * chance. If there is no print job in progress then * this call does nothing. */ + @Override public void cancel() { synchronized (this) { if (performingPrinting) { diff --git a/src/java.desktop/share/classes/sun/print/ServiceDialog.java b/src/java.desktop/share/classes/sun/print/ServiceDialog.java index d6d3b92ee8d..39dd3d8d997 100644 --- a/src/java.desktop/share/classes/sun/print/ServiceDialog.java +++ b/src/java.desktop/share/classes/sun/print/ServiceDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -210,6 +210,7 @@ void initPrintDialog(int x, int y, c.add(pnlSouth, BorderLayout.SOUTH); addWindowListener(new WindowAdapter() { + @Override public void windowClosing(WindowEvent event) { dispose(CANCEL); } @@ -274,6 +275,7 @@ void initPageDialog(int x, int y, c.add(pnlSouth, BorderLayout.SOUTH); addWindowListener(new WindowAdapter() { + @Override public void windowClosing(WindowEvent event) { dispose(CANCEL); } @@ -290,6 +292,7 @@ public void windowClosing(WindowEvent event) { */ private void handleEscKey(JButton btnCancel) { Action cancelKeyAction = new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { dispose(CANCEL); } @@ -351,6 +354,7 @@ public void dispose(int status) { super.dispose(); } + @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); boolean approved = false; @@ -754,6 +758,7 @@ private JLabel addLabel(String text, return label; } + @Override @SuppressWarnings("deprecation") public void actionPerformed(ActionEvent e) { Object source = e.getSource(); @@ -798,6 +803,7 @@ public void actionPerformed(ActionEvent e) { } } + @Override public void itemStateChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { int index = cbName.getSelectedIndex(); @@ -844,10 +850,12 @@ public void itemStateChanged(ItemEvent e) { } } + @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { changedService = false; } + @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { if (changedService) { changedService = false; @@ -855,6 +863,7 @@ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { } } + @Override public void popupMenuCanceled(PopupMenuEvent e) { } @@ -1006,6 +1015,7 @@ public PrintRangePanel() { addToGB(pnlBottom, this, gridbag, c); } + @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); SunPageSelection select = SunPageSelection.ALL; @@ -1028,6 +1038,7 @@ public void actionPerformed(ActionEvent e) { } } + @Override public void focusLost(FocusEvent e) { Object source = e.getSource(); @@ -1036,6 +1047,7 @@ public void focusLost(FocusEvent e) { } } + @Override public void focusGained(FocusEvent e) {} private void setupRangeWidgets() { @@ -1175,6 +1187,7 @@ public CopiesPanel() { addToGB(cbCollate, this, gridbag, c); } + @Override public void actionPerformed(ActionEvent e) { if (cbCollate.isSelected()) { asCurrent.add(SheetCollate.COLLATED); @@ -1183,6 +1196,7 @@ public void actionPerformed(ActionEvent e) { } } + @Override public void stateChanged(ChangeEvent e) { updateCollateCB(); @@ -1440,16 +1454,19 @@ public MarginsPanel() { } + @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); updateMargins(source); } + @Override public void focusLost(FocusEvent e) { Object source = e.getSource(); updateMargins(source); } + @Override public void focusGained(FocusEvent e) {} /* Get the numbers, use to create a MPA. @@ -1905,6 +1922,7 @@ private String getMediaName(String key) { } } + @Override public void itemStateChanged(ItemEvent e) { Object source = e.getSource(); @@ -2128,6 +2146,7 @@ public OrientationPanel() { addToGB(rbRevLandscape, this, gridbag, c); } + @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); @@ -2327,6 +2346,7 @@ public ChromaticityPanel() { addToGB(rbColor, this, gridbag, c); } + @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); @@ -2421,6 +2441,7 @@ public QualityPanel() { addToGB(rbHigh, this, gridbag, c); } + @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); @@ -2531,6 +2552,7 @@ public SidesPanel() { addToGB(rbDuplex, this, gridbag, c); } + @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); @@ -2674,6 +2696,7 @@ public JobAttributesPanel() { addToGB(tfUserName, this, gridbag, c); } + @Override public void actionPerformed(ActionEvent e) { if (cbJobSheets.isSelected()) { asCurrent.add(JobSheets.STANDARD); @@ -2682,10 +2705,12 @@ public void actionPerformed(ActionEvent e) { } } + @Override public void stateChanged(ChangeEvent e) { asCurrent.add(new JobPriority(snModel.getNumber().intValue())); } + @Override public void focusLost(FocusEvent e) { Object source = e.getSource(); @@ -2698,6 +2723,7 @@ public void focusLost(FocusEvent e) { } } + @Override public void focusGained(FocusEvent e) {} public void updateInfo() { @@ -2808,6 +2834,7 @@ public OutputPanel() { addToGB(cbOutput, this, gridbag, c); } + @Override public void itemStateChanged(ItemEvent e) { Object source = e.getSource(); @@ -2908,6 +2935,7 @@ public boolean isSameAs(Object source) { return (rb == source); } + @Override public void setEnabled(boolean enabled) { rb.setEnabled(enabled); lbl.setEnabled(enabled); @@ -2929,6 +2957,7 @@ public void setSelected(boolean selected) { */ @SuppressWarnings("serial") // JDK implementation class private static class ValidatingFileChooser extends JFileChooser { + @Override public void approveSelection() { File selected = getSelectedFile(); boolean exists = selected.exists(); diff --git a/src/java.desktop/share/classes/sun/print/ServiceNotifier.java b/src/java.desktop/share/classes/sun/print/ServiceNotifier.java index f42489debc7..702b3596b73 100644 --- a/src/java.desktop/share/classes/sun/print/ServiceNotifier.java +++ b/src/java.desktop/share/classes/sun/print/ServiceNotifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -98,6 +98,7 @@ void wake() { * a minimum of 15 seconds. Ensures this won't take more than 10% * of available time. */ + @Override public void run() { long minSleepTime = 15000; diff --git a/src/java.desktop/share/classes/sun/print/SunAlternateMedia.java b/src/java.desktop/share/classes/sun/print/SunAlternateMedia.java index cf88c671cca..24ac8b5df92 100644 --- a/src/java.desktop/share/classes/sun/print/SunAlternateMedia.java +++ b/src/java.desktop/share/classes/sun/print/SunAlternateMedia.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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,14 +54,17 @@ public Media getMedia() { return media; } + @Override public final Class getCategory() { return SunAlternateMedia.class; } + @Override public final String getName() { return "sun-alternate-media"; } + @Override public String toString() { return "alternate-media: " + media.toString(); } @@ -70,6 +73,7 @@ public String toString() { * Returns a hash code value for this enumeration value. The hash code is * just this enumeration value's integer value. */ + @Override public int hashCode() { return media.hashCode(); } diff --git a/src/java.desktop/share/classes/sun/print/SunMinMaxPage.java b/src/java.desktop/share/classes/sun/print/SunMinMaxPage.java index b4a5054ee58..fa4630bfcd8 100644 --- a/src/java.desktop/share/classes/sun/print/SunMinMaxPage.java +++ b/src/java.desktop/share/classes/sun/print/SunMinMaxPage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2014, 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 @@ -40,6 +40,7 @@ public SunMinMaxPage(int min, int max) { page_max = max; } + @Override public Class getCategory() { return SunMinMaxPage.class; } @@ -52,6 +53,7 @@ public int getMax() { return page_max; } + @Override public String getName() { return "sun-page-minmax"; } diff --git a/src/java.desktop/share/classes/sun/print/SunPageSelection.java b/src/java.desktop/share/classes/sun/print/SunPageSelection.java index 6d7af26aadd..ec66c648517 100644 --- a/src/java.desktop/share/classes/sun/print/SunPageSelection.java +++ b/src/java.desktop/share/classes/sun/print/SunPageSelection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2014, 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 @@ -44,14 +44,17 @@ public SunPageSelection(int value) { pages = value; } + @Override public Class getCategory() { return SunPageSelection.class; } + @Override public String getName() { return "sun-page-selection"; } + @Override public String toString() { return "page-selection: " + pages; } From f9533916e031933f8c76cc4fed9e39e8952b9347 Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Tue, 13 Oct 2026 21:06:06 +0000 Subject: [PATCH 076/101] 8388986: javac is crashing with SOE in Types.glbFlattened Reviewed-by: mcimadamore --- .../com/sun/tools/javac/code/Types.java | 26 +++++++++++++++---- .../javac/generics/InfiniteLoopGLBTest1.java | 24 +++++++++++++++++ .../javac/generics/InfiniteLoopGLBTest1.out | 4 +++ .../javac/generics/InfiniteLoopGLBTest2.java | 25 ++++++++++++++++++ .../javac/generics/InfiniteLoopGLBTest2.out | 5 ++++ 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 test/langtools/tools/javac/generics/InfiniteLoopGLBTest1.java create mode 100644 test/langtools/tools/javac/generics/InfiniteLoopGLBTest1.out create mode 100644 test/langtools/tools/javac/generics/InfiniteLoopGLBTest2.java create mode 100644 test/langtools/tools/javac/generics/InfiniteLoopGLBTest2.out diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java index c87948fa66d..fabf3d15018 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java @@ -4184,16 +4184,24 @@ private Type arraySuperType() { // public Type glb(List ts) { + return glb(ts, new HashSet<>()); + } + //where + private Type glb(List ts, Set seenCvars) { Type t1 = ts.head; for (Type t2 : ts.tail) { if (t1.isErroneous()) return t1; - t1 = glb(t1, t2); + t1 = glb(t1, t2, seenCvars); } return t1; } //where public Type glb(Type t, Type s) { + return glb(t, s, new HashSet<>()); + } + //where + private Type glb(Type t, Type s, Set seenCvars) { if (s == null) return t; else if (t.isPrimitive() || s.isPrimitive()) @@ -4204,7 +4212,7 @@ else if (isSubtypeNoCapture(s, t)) return s; List closure = union(closure(t), closure(s)); - return glbFlattened(closure, t); + return glbFlattened(closure, t, seenCvars); } //where /** @@ -4214,8 +4222,10 @@ else if (isSubtypeNoCapture(s, t)) * * @param flatBounds List of type to glb * @param errT Original type to use if the result is an error type + * @param seenCvars capture variables already replaced by their + * lower bound earlier in this glb computation */ - private Type glbFlattened(List flatBounds, Type errT) { + private Type glbFlattened(List flatBounds, Type errT, Set seenCvars) { List bounds = closureMin(flatBounds); if (bounds.isEmpty()) { // length == 0 @@ -4237,12 +4247,18 @@ private Type glbFlattened(List flatBounds, Type errT) { } } if (classCount > 1) { - if (lowers.isEmpty()) { + boolean progress = true; + for (Type cvar : cvars) { + if (!seenCvars.add(cvar)) { + progress = false; + } + } + if (lowers.isEmpty() || !progress) { return createErrorType(errT); } else { // try again with lower bounds included instead of capture variables List newBounds = bounds.diff(cvars).appendList(lowers); - return glb(newBounds); + return glb(newBounds, seenCvars); } } } diff --git a/test/langtools/tools/javac/generics/InfiniteLoopGLBTest1.java b/test/langtools/tools/javac/generics/InfiniteLoopGLBTest1.java new file mode 100644 index 00000000000..1892595ae44 --- /dev/null +++ b/test/langtools/tools/javac/generics/InfiniteLoopGLBTest1.java @@ -0,0 +1,24 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8388986 + * @summary infinite loop in Types.glbFlattened + * @compile/fail/ref=InfiniteLoopGLBTest1.out -XDrawDiagnostics InfiniteLoopGLBTest1.java + */ + +import java.util.List; + +class InfiniteLoopGLBTest1 { + class Min { + void m(Object o) { + ((C, ?>) o).f().hashCode(); + } + } + + class B {} + + class C, R extends B> { + R f() { + return null; + } + } +} diff --git a/test/langtools/tools/javac/generics/InfiniteLoopGLBTest1.out b/test/langtools/tools/javac/generics/InfiniteLoopGLBTest1.out new file mode 100644 index 00000000000..d416b24d918 --- /dev/null +++ b/test/langtools/tools/javac/generics/InfiniteLoopGLBTest1.out @@ -0,0 +1,4 @@ +InfiniteLoopGLBTest1.java:13:17: compiler.err.not.within.bounds: ? super java.util.List, N +- compiler.note.unchecked.filename: InfiniteLoopGLBTest1.java +- compiler.note.unchecked.recompile +1 error diff --git a/test/langtools/tools/javac/generics/InfiniteLoopGLBTest2.java b/test/langtools/tools/javac/generics/InfiniteLoopGLBTest2.java new file mode 100644 index 00000000000..886157f8a7e --- /dev/null +++ b/test/langtools/tools/javac/generics/InfiniteLoopGLBTest2.java @@ -0,0 +1,25 @@ +/* + * @test /nodynamiccopyright/ + * @bug 8388986 + * @summary infinite loop in Types.glbFlattened + * @compile/fail/ref=InfiniteLoopGLBTest2.out -XDrawDiagnostics InfiniteLoopGLBTest2.java + */ + +class InfiniteLoopGLBTest2 { + class Main { + static public final C, ?, ?> test() { + final C, ?, ?> x = null; + x.f().f(null); + return x; + } + } + + class A {} + + abstract class B {} + + abstract class C, R extends B, E extends Object & Runnable & Comparable> { + abstract public R f(); + public E f(){return null;}; + } +} diff --git a/test/langtools/tools/javac/generics/InfiniteLoopGLBTest2.out b/test/langtools/tools/javac/generics/InfiniteLoopGLBTest2.out new file mode 100644 index 00000000000..56214955179 --- /dev/null +++ b/test/langtools/tools/javac/generics/InfiniteLoopGLBTest2.out @@ -0,0 +1,5 @@ +InfiniteLoopGLBTest2.java:23:18: compiler.err.already.defined: kindname.method, f(), kindname.class, InfiniteLoopGLBTest2.C +InfiniteLoopGLBTest2.java:10:31: compiler.err.not.within.bounds: ? super InfiniteLoopGLBTest2.A, N +InfiniteLoopGLBTest2.java:11:21: compiler.err.not.within.bounds: ? super InfiniteLoopGLBTest2.A, N +InfiniteLoopGLBTest2.java:12:18: compiler.err.cant.resolve.location.args: kindname.method, f, , compiler.misc.type.null, (compiler.misc.location: kindname.class, InfiniteLoopGLBTest2.B,compiler.misc.type.captureof: 3, ? extends compiler.misc.type.captureof: 2, ? super InfiniteLoopGLBTest2.A>, null) +4 errors From 493cb30889d461d8b78c04ad23ab3b7ed302660b Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 14 Oct 2026 03:23:55 +0000 Subject: [PATCH 077/101] 8394234: RISC-V: Remove unreachable non-acquiring CAS match rules Reviewed-by: gcao, fyang --- src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad | 55 ------------ .../riscv/gc/shenandoah/shenandoah_riscv.ad | 84 ------------------- src/hotspot/cpu/riscv/gc/z/z_riscv.ad | 19 ----- 3 files changed, 158 deletions(-) diff --git a/src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad b/src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad index 7521b0a82e6..784e9629e36 100644 --- a/src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad +++ b/src/hotspot/cpu/riscv/gc/g1/g1_riscv.ad @@ -497,7 +497,6 @@ instruct g1CompareAndExchangeNAcq(iRegNNoSp res, indirect mem, iRegN oldval, iRe instruct g1CompareAndSwapP(iRegINoSp res, indirect mem, iRegP newval, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegP oldval, rFlagsReg cr) %{ predicate(UseG1GC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - match(Set res (CompareAndSwapP mem (Binary oldval newval))); match(Set res (WeakCompareAndSwapP mem (Binary oldval newval))); effect(TEMP res, TEMP tmp1, TEMP tmp2, KILL cr); ins_cost(2 * VOLATILE_REF_COST); @@ -561,7 +560,6 @@ instruct g1CompareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP newval, iRegPNo instruct g1CompareAndSwapN(iRegINoSp res, indirect mem, iRegN newval, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3, iRegN oldval, rFlagsReg cr) %{ predicate(UseG1GC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - match(Set res (CompareAndSwapN mem (Binary oldval newval))); match(Set res (WeakCompareAndSwapN mem (Binary oldval newval))); effect(TEMP res, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr); ins_cost(2 * VOLATILE_REF_COST); @@ -624,32 +622,6 @@ instruct g1CompareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN newval, iRegPNo ins_pipe(pipe_slow); %} -instruct g1GetAndSetP(indirect mem, iRegP newval, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp preval, rFlagsReg cr) -%{ - predicate(UseG1GC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - match(Set preval (GetAndSetP mem newval)); - effect(TEMP preval, TEMP tmp1, TEMP tmp2, KILL cr); - ins_cost(2 * VOLATILE_REF_COST); - format %{ "atomic_xchg $preval, $newval, [$mem]" %} - ins_encode %{ - guarantee($mem$$disp == 0, "impossible encoding"); - assert_different_registers($mem$$Register, $newval$$Register); - write_barrier_pre(masm, this, - $mem$$Register /* obj */, - $preval$$Register /* pre_val (as a temporary register) */, - $tmp1$$Register /* tmp1 */, - $tmp2$$Register /* tmp2 */, - RegSet::of($mem$$Register, $preval$$Register, $newval$$Register) /* preserve */); - __ atomic_xchg($preval$$Register, $newval$$Register, $mem$$Register); - write_barrier_post(masm, this, - $mem$$Register /* store_addr */, - $newval$$Register /* new_val */, - $tmp1$$Register /* tmp1 */, - $tmp2$$Register /* tmp2 */); - %} - ins_pipe(pipe_serial); -%} - instruct g1GetAndSetPAcq(indirect mem, iRegP newval, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp preval, rFlagsReg cr) %{ predicate(UseG1GC && needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); @@ -676,33 +648,6 @@ instruct g1GetAndSetPAcq(indirect mem, iRegP newval, iRegPNoSp tmp1, iRegPNoSp t ins_pipe(pipe_serial); %} -instruct g1GetAndSetN(indirect mem, iRegN newval, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3, iRegNNoSp preval, rFlagsReg cr) -%{ - predicate(UseG1GC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - match(Set preval (GetAndSetN mem newval)); - effect(TEMP preval, TEMP tmp1, TEMP tmp2, TEMP tmp3, KILL cr); - ins_cost(2 * VOLATILE_REF_COST); - format %{ "atomic_xchgwu $preval, $newval, [$mem]" %} - ins_encode %{ - guarantee($mem$$disp == 0, "impossible encoding"); - assert_different_registers($mem$$Register, $newval$$Register); - write_barrier_pre(masm, this, - $mem$$Register /* obj */, - $tmp1$$Register /* pre_val */, - $tmp2$$Register /* tmp1 */, - $tmp3$$Register /* tmp2 */, - RegSet::of($mem$$Register, $preval$$Register, $newval$$Register) /* preserve */); - __ atomic_xchgwu($preval$$Register, $newval$$Register, $mem$$Register); - __ decode_heap_oop($tmp1$$Register, $newval$$Register); - write_barrier_post(masm, this, - $mem$$Register /* store_addr */, - $tmp1$$Register /* new_val */, - $tmp2$$Register /* tmp1 */, - $tmp3$$Register /* tmp2 */); - %} - ins_pipe(pipe_serial); -%} - instruct g1GetAndSetNAcq(indirect mem, iRegN newval, iRegPNoSp tmp1, iRegPNoSp tmp2, iRegPNoSp tmp3, iRegNNoSp preval, rFlagsReg cr) %{ predicate(UseG1GC && needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoah_riscv.ad b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoah_riscv.ad index 9fef57c1f09..78adb993a98 100644 --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoah_riscv.ad +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoah_riscv.ad @@ -238,28 +238,6 @@ instruct compareAndSwap_P_A_shenandoah(iRegINoSp res, indirect mem, iRegPNoSp ol ins_pipe(pipe_slow); %} -instruct compareAndSwap_P_shenandoah(iRegINoSp res, indirect mem, iRegPNoSp oldval, iRegP newval, iRegPNoSp tmp, rFlagsReg cr) %{ - match(Set res (CompareAndSwapP mem (Binary oldval newval))); - predicate(UseShenandoahGC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - effect(TEMP_DEF res, TEMP tmp, KILL cr); - format %{ "cmpxchg_P_N_shenandoah $mem, $oldval, $newval\t# (ptr) if $mem == $oldval then $mem <-- $newval, #@compareAndSwap_P_shenandoah" %} - ins_encode %{ - ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, - as_Register($res$$reg), - as_Register($mem$$base), - as_Register($oldval$$reg), - as_Register($newval$$reg), - as_Register($tmp$$reg), - t0, - t1, - /* exchange */ false, - /* is_narrow */ false, - /* acquire */ false); - %} - ins_cost(10 * DEFAULT_COST); - ins_pipe(pipe_slow); -%} - instruct compareAndSwap_N_A_shenandoah(iRegINoSp res, indirect mem, iRegNNoSp oldval, iRegN newval, iRegPNoSp tmp, rFlagsReg cr) %{ match(Set res (CompareAndSwapN mem (Binary oldval newval))); predicate(UseShenandoahGC && needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); @@ -282,28 +260,6 @@ instruct compareAndSwap_N_A_shenandoah(iRegINoSp res, indirect mem, iRegNNoSp ol ins_pipe(pipe_slow); %} -instruct compareAndSwap_N_shenandoah(iRegINoSp res, indirect mem, iRegNNoSp oldval, iRegN newval, iRegPNoSp tmp, rFlagsReg cr) %{ - match(Set res (CompareAndSwapN mem (Binary oldval newval))); - predicate(UseShenandoahGC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - effect(TEMP_DEF res, TEMP tmp, KILL cr); - format %{ "cmpxchg_N_shenandoah $mem, $oldval, $newval\t# (narrow ptr) if $mem == $oldval then $mem <-- $newval, #@compareAndSwap_N_shenandoah" %} - ins_encode %{ - ShenandoahBarrierSet::assembler()->compare_and_set_c2(this, masm, - as_Register($res$$reg), - as_Register($mem$$base), - as_Register($oldval$$reg), - as_Register($newval$$reg), - as_Register($tmp$$reg), - t0, - t1, - /* exchange */ false, - /* is_narrow */ true, - /* acquire */ false); - %} - ins_cost(10 * DEFAULT_COST); - ins_pipe(pipe_slow); -%} - instruct compareAndExchange_N_A_shenandoah(iRegNNoSp res, indirect mem, iRegN oldval, iRegN newval, iRegPNoSp tmp, rFlagsReg cr) %{ match(Set res (CompareAndExchangeN mem (Binary oldval newval))); predicate(UseShenandoahGC && needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); @@ -480,26 +436,6 @@ instruct weakCompareAndSwap_P_shenandoah(iRegINoSp res, indirect mem, iRegPNoSp ins_pipe(pipe_slow); %} -instruct getAndSet_P_shenandoah(indirect mem, iRegP newval, iRegPNoSp preval, iRegPNoSp tmp, rFlagsReg cr) -%{ - match(Set preval (GetAndSetP mem newval)); - predicate(UseShenandoahGC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - effect(TEMP_DEF preval, TEMP tmp, KILL cr); - format %{ "get_and_set_P $preval, $newval, [$mem], #@getAndSet_P_shenandoah" %} - ins_encode %{ - ShenandoahBarrierSet::assembler()->get_and_set_c2(this, masm, - as_Register($preval$$reg), - as_Register($newval$$reg), - as_Register($mem$$base), - as_Register($tmp$$reg), - t0, - t1, - /* acquire */ false); - %} - ins_cost(10 * DEFAULT_COST); - ins_pipe(pipe_slow); -%} - instruct getAndSet_P_A_shenandoah(indirect mem, iRegP newval, iRegPNoSp preval, iRegPNoSp tmp, rFlagsReg cr) %{ match(Set preval (GetAndSetP mem newval)); @@ -539,23 +475,3 @@ instruct getAndSet_N_A_shenandoah(indirect mem, iRegN newval, iRegNNoSp preval, ins_cost(10 * DEFAULT_COST); ins_pipe(pipe_slow); %} - -instruct getAndSet_N_shenandoah(indirect mem, iRegN newval, iRegNNoSp preval, iRegPNoSp tmp, rFlagsReg cr) -%{ - match(Set preval (GetAndSetN mem newval)); - predicate(UseShenandoahGC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - effect(TEMP_DEF preval, TEMP tmp, KILL cr); - format %{ "get_and_set_N $preval, $newval, [$mem], #@getAndSet_N_shenandoah" %} - ins_encode %{ - ShenandoahBarrierSet::assembler()->get_and_set_c2(this, masm, - as_Register($preval$$reg), - as_Register($newval$$reg), - as_Register($mem$$base), - as_Register($tmp$$reg), - t0, - t1, - /* acquire */ false); - %} - ins_cost(10 * DEFAULT_COST); - ins_pipe(pipe_slow); -%} diff --git a/src/hotspot/cpu/riscv/gc/z/z_riscv.ad b/src/hotspot/cpu/riscv/gc/z/z_riscv.ad index 3f065f0bf70..e9cbb8a7cc3 100644 --- a/src/hotspot/cpu/riscv/gc/z/z_riscv.ad +++ b/src/hotspot/cpu/riscv/gc/z/z_riscv.ad @@ -168,7 +168,6 @@ instruct zStorePVolatile(indirect mem, iRegP src, iRegPNoSp tmp1, iRegPNoSp tmp2 instruct zCompareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP newval, iRegPNoSp oldval_tmp, iRegPNoSp newval_tmp, iRegPNoSp tmp1, rFlagsReg cr) %{ - match(Set res (CompareAndSwapP mem (Binary oldval newval))); match(Set res (WeakCompareAndSwapP mem (Binary oldval newval))); predicate(UseZGC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); effect(TEMP oldval_tmp, TEMP newval_tmp, TEMP tmp1, TEMP_DEF res, KILL cr); @@ -256,24 +255,6 @@ instruct zCompareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iReg ins_pipe(pipe_slow); %} -instruct zGetAndSetP(indirect mem, iRegP newv, iRegPNoSp prev, iRegPNoSp tmp, rFlagsReg cr) %{ - match(Set prev (GetAndSetP mem newv)); - predicate(UseZGC && !needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); - effect(TEMP_DEF prev, TEMP tmp, KILL cr); - - ins_cost(2 * VOLATILE_REF_COST); - - format %{ "atomic_xchg $prev, $newv, [$mem], #@zGetAndSetP" %} - - ins_encode %{ - z_store_barrier(masm, this, Address($mem$$Register), $newv$$Register, $prev$$Register, $tmp$$Register, true /* is_atomic */); - __ atomic_xchg($prev$$Register, $prev$$Register, $mem$$Register); - z_uncolor(masm, $prev$$Register); - %} - - ins_pipe(pipe_serial); -%} - instruct zGetAndSetPAcq(indirect mem, iRegP newv, iRegPNoSp prev, iRegPNoSp tmp, rFlagsReg cr) %{ match(Set prev (GetAndSetP mem newv)); predicate(UseZGC && needs_acquiring_load_reserved(n) && n->as_LoadStore()->barrier_data() != 0); From c368bac40e185398fbc40dc4d75a457f7a36f954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Jeli=C5=84ski?= Date: Wed, 14 Oct 2026 05:35:38 +0000 Subject: [PATCH 078/101] 6340855: Remove sun.cpu.isalist property Reviewed-by: prr, jpai, alanb --- .../J2DBench/src/j2dbench/ResultSet.java | 3 +- .../jdk/internal/util/SystemProps.java | 6 +-- src/java.base/share/native/libjava/System.c | 3 +- .../share/native/libjava/java_props.h | 4 +- .../unix/native/libjava/java_props_md.c | 17 +-------- .../windows/native/libjava/java_props_md.c | 37 ------------------- 6 files changed, 6 insertions(+), 64 deletions(-) diff --git a/src/demo/share/java2d/J2DBench/src/j2dbench/ResultSet.java b/src/demo/share/java2d/J2DBench/src/j2dbench/ResultSet.java index fecffc302d1..39f1df9ec39 100644 --- a/src/demo/share/java2d/J2DBench/src/j2dbench/ResultSet.java +++ b/src/demo/share/java2d/J2DBench/src/j2dbench/ResultSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -65,7 +65,6 @@ public class ResultSet { "os.version", "user.name", "sun.cpu.endian", - "sun.cpu.isalist", }; // Ignored props - will not be copied to results file diff --git a/src/java.base/share/classes/jdk/internal/util/SystemProps.java b/src/java.base/share/classes/jdk/internal/util/SystemProps.java index 5760f04831e..73ab9e17a1d 100644 --- a/src/java.base/share/classes/jdk/internal/util/SystemProps.java +++ b/src/java.base/share/classes/jdk/internal/util/SystemProps.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 @@ -127,7 +127,6 @@ public static Map initProperties() { putIfAbsent(props, "sun.arch.data.model", raw.propDefault(Raw._sun_arch_data_model_NDX)); putIfAbsent(props, "sun.os.patch.level", raw.propDefault(Raw._sun_os_patch_level_NDX)); putIfAbsent(props, "sun.io.unicode.encoding", raw.propDefault(Raw._sun_io_unicode_encoding_NDX)); - putIfAbsent(props, "sun.cpu.isalist", raw.propDefault(Raw._sun_cpu_isalist_NDX)); putIfAbsent(props, "sun.cpu.endian", raw.propDefault(Raw._sun_cpu_endian_NDX)); /* Construct i18n related options */ @@ -249,8 +248,7 @@ public static class Raw { @Native private static final int _sun_arch_abi_NDX = 1 + _stdout_encoding_NDX; @Native private static final int _sun_arch_data_model_NDX = 1 + _sun_arch_abi_NDX; @Native private static final int _sun_cpu_endian_NDX = 1 + _sun_arch_data_model_NDX; - @Native private static final int _sun_cpu_isalist_NDX = 1 + _sun_cpu_endian_NDX; - @Native private static final int _sun_io_unicode_encoding_NDX = 1 + _sun_cpu_isalist_NDX; + @Native private static final int _sun_io_unicode_encoding_NDX = 1 + _sun_cpu_endian_NDX; @Native private static final int _sun_jnu_encoding_NDX = 1 + _sun_io_unicode_encoding_NDX; @Native private static final int _sun_os_patch_level_NDX = 1 + _sun_jnu_encoding_NDX; @Native private static final int _user_dir_NDX = 1 + _sun_os_patch_level_NDX; diff --git a/src/java.base/share/native/libjava/System.c b/src/java.base/share/native/libjava/System.c index 725c1e30227..19f7922af55 100644 --- a/src/java.base/share/native/libjava/System.c +++ b/src/java.base/share/native/libjava/System.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2025, 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 @@ -161,7 +161,6 @@ Java_jdk_internal_util_SystemProps_00024Raw_platformProperties(JNIEnv *env, jcla /* unicode_encoding specifies the default endianness */ PUTPROP(propArray, _sun_io_unicode_encoding_NDX, sprops->unicode_encoding); PUTPROP(propArray, _sun_cpu_endian_NDX, sprops->cpu_endian); - PUTPROP(propArray, _sun_cpu_isalist_NDX, sprops->cpu_isalist); #ifdef MACOSX /* Proxy setting properties */ diff --git a/src/java.base/share/native/libjava/java_props.h b/src/java.base/share/native/libjava/java_props.h index 774a7435e54..2b444ad4ec7 100644 --- a/src/java.base/share/native/libjava/java_props.h +++ b/src/java.base/share/native/libjava/java_props.h @@ -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 @@ -72,8 +72,6 @@ typedef struct { char *unicode_encoding; /* The default endianness of unicode i.e. UnicodeBig or UnicodeLittle */ - const char *cpu_isalist; /* list of supported instruction sets */ - char *cpu_endian; /* endianness of platform */ char *data_model; /* 32 or 64 bit data model */ diff --git a/src/java.base/unix/native/libjava/java_props_md.c b/src/java.base/unix/native/libjava/java_props_md.c index 1cbc3665ebc..5f2a4ccf628 100644 --- a/src/java.base/unix/native/libjava/java_props_md.c +++ b/src/java.base/unix/native/libjava/java_props_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 @@ -384,21 +384,6 @@ GetJavaProperties(JNIEnv *env) /* patches/service packs installed */ sprops.patch_level = NULL; // leave it undefined -#ifdef SI_ISALIST - /* supported instruction sets */ - { - char list[258]; - int ret = sysinfo(SI_ISALIST, list, sizeof(list)); - if (ret == 0) { - sprops.cpu_isalist = strdup(list); - } else { - sprops.cpu_isalist = NULL; - } - } -#else - sprops.cpu_isalist = NULL; -#endif - /* endianness of platform */ { unsigned int endianTest = 0xff000000; diff --git a/src/java.base/windows/native/libjava/java_props_md.c b/src/java.base/windows/native/libjava/java_props_md.c index 6504891af34..7847b1e78a2 100644 --- a/src/java.base/windows/native/libjava/java_props_md.c +++ b/src/java.base/windows/native/libjava/java_props_md.c @@ -40,10 +40,6 @@ #include "locale_str.h" #include "java_props.h" -#ifndef PROCESSOR_ARCHITECTURE_AMD64 -#define PROCESSOR_ARCHITECTURE_AMD64 9 -#endif - typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO); static BOOL SetupI18nProps(LCID lcid, char** language, char** script, char** country, char** variant); @@ -227,36 +223,6 @@ getHomeFromShell32() return u_path; } -static BOOL -haveMMX(void) -{ - return IsProcessorFeaturePresent(PF_MMX_INSTRUCTIONS_AVAILABLE); -} - -static const char * -cpu_isalist(void) -{ - SYSTEM_INFO info; - GetSystemInfo(&info); - switch (info.wProcessorArchitecture) { -#ifdef PROCESSOR_ARCHITECTURE_AMD64 - case PROCESSOR_ARCHITECTURE_AMD64: return "amd64"; -#endif - case PROCESSOR_ARCHITECTURE_INTEL: - switch (info.wProcessorLevel) { - case 6: return haveMMX() - ? "pentium_pro+mmx pentium_pro pentium+mmx pentium i486 i386 i86" - : "pentium_pro pentium i486 i386 i86"; - case 5: return haveMMX() - ? "pentium+mmx pentium i486 i386 i86" - : "pentium i486 i386 i86"; - case 4: return "i486 i386 i86"; - case 3: return "i386 i86"; - } - } - return NULL; -} - static BOOL SetupI18nProps(LCID lcid, char** language, char** script, char** country, char** variant) { @@ -517,9 +483,6 @@ GetJavaProperties(JNIEnv* env) } } - /* CPU ISA list */ - sprops.cpu_isalist = cpu_isalist(); - /* * User name * We try to avoid calling GetUserName as it turns out to From e344a3723712ab3e42d867a4de6a3acd59be94cf Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Wed, 14 Oct 2026 07:10:45 +0000 Subject: [PATCH 079/101] 8394406: RISC-V: Preserve StoreLoad barriers before CAS and fast locking Reviewed-by: gli, fyang --- src/hotspot/cpu/riscv/riscv.ad | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 8330122899c..26e6f768a77 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -8607,20 +8607,6 @@ instruct membar_volatile_rvtso() %{ ins_pipe(pipe_slow); %} -instruct unnecessary_membar_volatile_rvtso() %{ - predicate(UseZtso && Matcher::post_store_load_barrier(n)); - match(MemBarVolatile); - ins_cost(0); - - size(0); - - format %{ "#@unnecessary_membar_volatile_rvtso (unnecessary so empty encoding)" %} - ins_encode %{ - __ block_comment("unnecessary_membar_volatile_rvtso"); - %} - ins_pipe(real_empty); -%} - instruct membar_full_rvtso() %{ predicate(UseZtso); match(MemBarFull); @@ -8788,8 +8774,7 @@ instruct membar_lock_rvwmo() %{ %} instruct unnecessary_membar_volatile_rvwmo() %{ - predicate(!UseZtso && - (unnecessary_volatile(n) || Matcher::post_store_load_barrier(n))); + predicate(!UseZtso && unnecessary_volatile(n)); match(MemBarVolatile); ins_cost(0); From 446786a3b05d8ff1b9b315ce131ca6e93227be6c Mon Sep 17 00:00:00 2001 From: Daisuke Yamazaki Date: Wed, 14 Oct 2026 08:00:43 +0000 Subject: [PATCH 080/101] 8393016: (fs) FileChannelLinesSpliterator.tryAdvance/forEachRemaining does not check if action is null Reviewed-by: vyazici --- .../nio/file/FileChannelLinesSpliterator.java | 5 ++- .../java/nio/file/Files/StreamLinesTest.java | 45 ++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/nio/file/FileChannelLinesSpliterator.java b/src/java.base/share/classes/java/nio/file/FileChannelLinesSpliterator.java index 36e463ba616..d80aca3fcdd 100644 --- a/src/java.base/share/classes/java/nio/file/FileChannelLinesSpliterator.java +++ b/src/java.base/share/classes/java/nio/file/FileChannelLinesSpliterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -36,6 +36,7 @@ import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.charset.Charset; +import java.util.Objects; import java.util.Set; import java.util.Spliterator; import java.util.concurrent.atomic.AtomicInteger; @@ -114,6 +115,7 @@ private FileChannelLinesSpliterator(FileChannel fc, Charset cs, int index, @Override public boolean tryAdvance(Consumer action) { + Objects.requireNonNull(action); String line = readLine(); if (line != null) { action.accept(line); @@ -125,6 +127,7 @@ public boolean tryAdvance(Consumer action) { @Override public void forEachRemaining(Consumer action) { + Objects.requireNonNull(action); String line; while ((line = readLine()) != null) { action.accept(line); diff --git a/test/jdk/java/nio/file/Files/StreamLinesTest.java b/test/jdk/java/nio/file/Files/StreamLinesTest.java index 7812a4ceb89..ac5d0e70e2b 100644 --- a/test/jdk/java/nio/file/Files/StreamLinesTest.java +++ b/test/jdk/java/nio/file/Files/StreamLinesTest.java @@ -22,7 +22,7 @@ */ /* @test - * @bug 8072773 + * @bug 8072773 8393016 * @library /test/lib * @build jdk.test.lib.RandomFactory * @run junit/othervm StreamLinesTest @@ -46,6 +46,8 @@ import java.util.EnumSet; import java.util.List; import java.util.Random; +import java.util.Spliterator; +import java.util.function.Consumer; import java.util.function.IntFunction; import java.util.stream.Stream; import jdk.test.lib.RandomFactory; @@ -53,8 +55,11 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class StreamLinesTest { @@ -196,4 +201,42 @@ public void test(IntFunction lineGenerator, IntFunction s assertEquals(expected, s.toList()); } } + + @ParameterizedTest + @ValueSource(ints = {0, 2, 5}) + void testTryAdvanceNullAction(int consumed) throws IOException { + testNullAction(consumed, sp -> sp.tryAdvance(null)); + } + + @ParameterizedTest + @ValueSource(ints = {0, 2, 5}) + void testForEachRemainingNullAction(int consumed) throws IOException { + testNullAction(consumed, sp -> sp.forEachRemaining(null)); + } + + private void testNullAction( + int consumed, + Consumer> operation) throws IOException { + + List lines = List.of("L0", "L1", "L2", "L3", "L4"); + Path file = generateTempFileWithLines( + i -> "L" + i, i -> LineSeparator.N, lines.size(), + StandardCharsets.UTF_8, false); + + try (Stream stream = Files.lines(file, StandardCharsets.UTF_8)) { + Spliterator sp = stream.spliterator(); + + // Consume some lines first + for (int i = 0; i < consumed; i++) { + assertTrue(sp.tryAdvance(l -> { })); + } + + assertThrows(NullPointerException.class, () -> operation.accept(sp)); + + // Verify that the remaining lines are still available + List remaining = new ArrayList<>(); + sp.forEachRemaining(remaining::add); + assertEquals(lines.subList(consumed, lines.size()), remaining); + } + } } From 4b9e4a1eb7378356083d0582624246c288737cfe Mon Sep 17 00:00:00 2001 From: Yasumasa Suenaga Date: Wed, 14 Oct 2026 08:01:13 +0000 Subject: [PATCH 081/101] 8390618: Move debuginfo related code to salibelf from symtab Reviewed-by: cjplummer, kevinw --- .../linux/native/libsaproc/salibelf.c | 275 +++++++++++++++++- .../linux/native/libsaproc/salibelf.h | 14 +- .../linux/native/libsaproc/symtab.c | 260 +---------------- 3 files changed, 294 insertions(+), 255 deletions(-) diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.c b/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.c index 6e39b95e7ec..30d4cb9c726 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.c +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, 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 @@ -29,6 +29,279 @@ extern void print_debug(const char*,...); +// Directory that contains global debuginfo files. In theory it +// should be possible to change this, but in a Java environment there +// is no obvious place to put a user interface to do it. Maybe this +// could be set with an environment variable. +static const char debug_file_directory[] = "/usr/lib/debug"; + +/* The CRC used in gnu_debuglink, retrieved from + http://sourceware.org/gdb/current/onlinedocs/gdb/Separate-Debug-Files.html#Separate-Debug-Files. */ +unsigned int gnu_debuglink_crc32 (unsigned int crc, + unsigned char *buf, size_t len) +{ + static const unsigned int crc32_table[256] = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, + 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, + 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, + 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, + 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, + 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, + 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, + 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, + 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, + 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, + 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, + 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, + 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, + 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, + 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, + 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, + 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, + 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, + 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, + 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, + 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, + 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, + 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, + 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, + 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, + 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, + 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, + 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, + 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, + 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, + 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, + 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, + 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, + 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, + 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, + 0x2d02ef8d + }; + unsigned char *end; + + crc = ~crc & 0xffffffff; + for (end = buf + len; buf < end; ++buf) + crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8); + return ~crc & 0xffffffff; +} + +/* Open a debuginfo file and check its CRC. If it exists and the CRC + matches return its fd. */ +static int +open_debug_file (const char *pathname, unsigned int crc) +{ + unsigned int file_crc = 0; + unsigned char buffer[8 * 1024]; + + int fd = pathmap_open(pathname); + + if (fd < 0) + return -1; + + lseek(fd, 0, SEEK_SET); + + for (;;) { + int len = read(fd, buffer, sizeof buffer); + if (len <= 0) + break; + file_crc = gnu_debuglink_crc32(file_crc, buffer, len); + } + + if (crc == file_crc) + return fd; + else { + close(fd); + return -1; + } +} + +/* Look for a ".gnu_debuglink" section. If one exists, try to open a + suitable debuginfo file. */ +static int open_debuginfo_from_debug_link(const char *name, + int fd, + ELF_EHDR *ehdr, + struct elf_section *scn_cache) +{ + int debug_fd; + struct elf_section *debug_link = find_section_by_name(".gnu_debuglink", fd, ehdr, + scn_cache); + if (debug_link == NULL) + return -1; + char *debug_filename = debug_link->c_data; + int offset = (strlen(debug_filename) + 4) >> 2; + static unsigned int crc; + crc = ((unsigned int*)debug_link->c_data)[offset]; + char *debug_pathname = malloc(strlen(debug_filename) + + strlen(name) + + strlen(".debug/") + + strlen(debug_file_directory) + + 2); + if (debug_pathname == NULL) { + return -1; + } + strcpy(debug_pathname, name); + char *last_slash = strrchr(debug_pathname, '/'); + if (last_slash == NULL) { + free(debug_pathname); + return -1; + } + + /* Look in the same directory as the object. */ + strcpy(last_slash+1, debug_filename); + debug_fd = open_debug_file(debug_pathname, crc); + if (debug_fd >= 0) { + free(debug_pathname); + return debug_fd; + } + + /* Look in a subdirectory named ".debug". */ + strcpy(last_slash+1, ".debug/"); + strcat(last_slash, debug_filename); + + debug_fd = open_debug_file(debug_pathname, crc); + if (debug_fd >= 0) { + free(debug_pathname); + return debug_fd; + } + /* Look in /usr/lib/debug + the full pathname. */ + strcpy(debug_pathname, debug_file_directory); + strcat(debug_pathname, name); + last_slash = strrchr(debug_pathname, '/'); + strcpy(last_slash+1, debug_filename); + + debug_fd = open_debug_file(debug_pathname, crc); + if (debug_fd >= 0) { + free(debug_pathname); + return debug_fd; + } + + free(debug_pathname); + return -1; +} + +static char* build_id_to_debug_filename (size_t size, unsigned char* data) { + char *filename, *s; + + filename = malloc(strlen (debug_file_directory) + (sizeof "/.build-id/" - 1) + 1 + + 2 * size + (sizeof ".debug" - 1) + 1); + if (filename == NULL) { + return NULL; + } + s = filename + sprintf (filename, "%s/.build-id/", debug_file_directory); + if (size > 0) { + size--; + s += sprintf (s, "%02x", *data++); + } + if (size > 0) { + *s++ = '/'; + } + while (size-- > 0) { + s += sprintf (s, "%02x", *data++); + } + strcpy (s, ".debug"); + + return filename; +} + +static int open_debuginfo_from_build_id(ELF_SHDR* shbuf, ELF_EHDR* ehdr, struct elf_section* scn_cache) { +#ifdef NT_GNU_BUILD_ID + int cnt = 0; + ELF_SHDR* cursct = NULL; + + // First we look for a Build ID + for (cursct = shbuf, cnt = 0; cnt < ehdr->e_shnum; cnt++) { + if (cursct->sh_type == SHT_NOTE) { + Elf64_Nhdr *note = (Elf64_Nhdr *)scn_cache[cnt].c_data; + if (note->n_type == NT_GNU_BUILD_ID) { + unsigned char *bytes = (unsigned char*)(note+1) + note->n_namesz; + char *filename = build_id_to_debug_filename(note->n_descsz, bytes); + if (filename != NULL) { + int fd = pathmap_open(filename); + free(filename); + return fd; + } + } + } + cursct++; + } +#endif + return -1; +} + +int open_debuginfo(const char* filename, int fd) { + // prepare (load ELF sections) + ELF_EHDR ehdr; + if (!read_elf_header(fd, &ehdr)) { + return -1; + } + + ELF_SHDR* shbuf = NULL; + if ((shbuf = read_section_header_table(fd, &ehdr)) == NULL) { + return -1; + } + if (ehdr.e_shnum == 0) { + // calloc() might return the pointer which should not be accessed + // if the size is zero. (implementation-defined) + free(shbuf); + return -1; + } + + struct elf_section *scn_cache = (struct elf_section *)calloc(ehdr.e_shnum, sizeof(struct elf_section)); + if (scn_cache == NULL) { + free(shbuf); + return -1; + } + + bool read_failure = false; + for (int cnt = 0; cnt < ehdr.e_shnum; cnt++) { + scn_cache[cnt].c_shdr = &shbuf[cnt]; + if (shbuf[cnt].sh_type == SHT_NOTE || shbuf[cnt].sh_type == SHT_STRTAB) { + scn_cache[cnt].c_data = read_section_data(fd, &ehdr, &shbuf[cnt]); + if (scn_cache[cnt].c_data == NULL) { + read_failure = true; + break; + } + } + } + + int debug_fd = -1; + if (!read_failure) { + // attempt to open debuginfo + debug_fd = open_debuginfo_from_debug_link(filename, fd, &ehdr, scn_cache); + if (debug_fd == -1) { + // try again with build id. + debug_fd = open_debuginfo_from_build_id(shbuf, &ehdr, scn_cache); + } + } + + // cleanup + for (int cnt = 0; cnt < ehdr.e_shnum; cnt++) { + free(scn_cache[cnt].c_data); + } + free(scn_cache); + free(shbuf); + + return debug_fd; +} + // ELF file parsing helpers. Note that we do *not* use libelf here. int read_elf_header(int fd, ELF_EHDR* ehdr) { if (pread(fd, ehdr, sizeof (ELF_EHDR), 0) != sizeof (ELF_EHDR) || diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.h b/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.h index 59b5039416e..d82bf794af2 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.h +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/salibelf.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, 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 @@ -34,6 +34,13 @@ struct elf_section { void *c_data; }; +#ifdef __cplusplus +extern "C" { +#endif + +// open debuginfo +int open_debuginfo(const char* filename, int fd); + // read ELF file header. int read_elf_header(int fd, ELF_EHDR* ehdr); @@ -60,4 +67,9 @@ struct elf_section *find_section_by_name(char *name, int fd, ELF_EHDR *ehdr, struct elf_section *scn_cache); + +#ifdef __cplusplus +} +#endif + #endif /* _SALIBELF_H_ */ diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/symtab.c b/src/jdk.hotspot.agent/linux/native/libsaproc/symtab.c index 8f8ce28be1e..153ea1e7313 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/symtab.c +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/symtab.c @@ -49,245 +49,20 @@ typedef struct symtab { } symtab_t; -// Directory that contains global debuginfo files. In theory it -// should be possible to change this, but in a Java environment there -// is no obvious place to put a user interface to do it. Maybe this -// could be set with an environment variable. -static const char debug_file_directory[] = "/usr/lib/debug"; - -/* The CRC used in gnu_debuglink, retrieved from - http://sourceware.org/gdb/current/onlinedocs/gdb/Separate-Debug-Files.html#Separate-Debug-Files. */ -unsigned int gnu_debuglink_crc32 (unsigned int crc, - unsigned char *buf, size_t len) -{ - static const unsigned int crc32_table[256] = - { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, - 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, - 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, - 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, - 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, - 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, - 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, - 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, - 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, - 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, - 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, - 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, - 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, - 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, - 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, - 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, - 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, - 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, - 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, - 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, - 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, - 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, - 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, - 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, - 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, - 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, - 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, - 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, - 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, - 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, - 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, - 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, - 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, - 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, - 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, - 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, - 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, - 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, - 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, - 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, - 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, - 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, - 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, - 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, - 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, - 0x2d02ef8d - }; - unsigned char *end; - - crc = ~crc & 0xffffffff; - for (end = buf + len; buf < end; ++buf) - crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8); - return ~crc & 0xffffffff; -} - -/* Open a debuginfo file and check its CRC. If it exists and the CRC - matches return its fd. */ -static int -open_debug_file (const char *pathname, unsigned int crc) -{ - unsigned int file_crc = 0; - unsigned char buffer[8 * 1024]; - - int fd = pathmap_open(pathname); - - if (fd < 0) - return -1; - - lseek(fd, 0, SEEK_SET); - - for (;;) { - int len = read(fd, buffer, sizeof buffer); - if (len <= 0) - break; - file_crc = gnu_debuglink_crc32(file_crc, buffer, len); - } - - if (crc == file_crc) - return fd; - else { - close(fd); - return -1; - } -} - -/* Look for a ".gnu_debuglink" section. If one exists, try to open a - suitable debuginfo file. */ -static int open_file_from_debug_link(const char *name, - int fd, - ELF_EHDR *ehdr, - struct elf_section *scn_cache) -{ - int debug_fd; - struct elf_section *debug_link = find_section_by_name(".gnu_debuglink", fd, ehdr, - scn_cache); - if (debug_link == NULL) - return -1; - char *debug_filename = debug_link->c_data; - int offset = (strlen(debug_filename) + 4) >> 2; - static unsigned int crc; - crc = ((unsigned int*)debug_link->c_data)[offset]; - char *debug_pathname = malloc(strlen(debug_filename) - + strlen(name) - + strlen(".debug/") - + strlen(debug_file_directory) - + 2); - if (debug_pathname == NULL) { - return -1; - } - strcpy(debug_pathname, name); - char *last_slash = strrchr(debug_pathname, '/'); - if (last_slash == NULL) { - free(debug_pathname); - return -1; - } - - /* Look in the same directory as the object. */ - strcpy(last_slash+1, debug_filename); - debug_fd = open_debug_file(debug_pathname, crc); - if (debug_fd >= 0) { - free(debug_pathname); - return debug_fd; - } - - /* Look in a subdirectory named ".debug". */ - strcpy(last_slash+1, ".debug/"); - strcat(last_slash, debug_filename); - - debug_fd = open_debug_file(debug_pathname, crc); - if (debug_fd >= 0) { - free(debug_pathname); - return debug_fd; - } - - /* Look in /usr/lib/debug + the full pathname. */ - strcpy(debug_pathname, debug_file_directory); - strcat(debug_pathname, name); - last_slash = strrchr(debug_pathname, '/'); - strcpy(last_slash+1, debug_filename); - - debug_fd = open_debug_file(debug_pathname, crc); - if (debug_fd >= 0) { - free(debug_pathname); - return debug_fd; - } - - free(debug_pathname); - return -1; -} - static struct symtab* build_symtab_internal(int fd, const char *filename, bool try_debuginfo); -/* Look for a ".gnu_debuglink" section. If one exists, try to open a - suitable debuginfo file and read a symbol table from it. */ -static struct symtab *build_symtab_from_debug_link(const char *name, - int fd, - ELF_EHDR *ehdr, - struct elf_section *scn_cache) -{ - fd = open_file_from_debug_link(name, fd, ehdr, scn_cache); - - if (fd >= 0) { - struct symtab *symtab = build_symtab_internal(fd, NULL, /* try_debuginfo */ false); - close(fd); +/* Try to open a suitable debuginfo file and read a symbol table from it. */ +static struct symtab *build_symtab_from_debuginfo(const char* filename, int fd) { + int debug_fd = open_debuginfo(filename, fd); + if (debug_fd >= 0) { + struct symtab *symtab = build_symtab_internal(debug_fd, NULL, /* try_debuginfo */ false); + close(debug_fd); return symtab; } return NULL; } -// Given a build_id, find the associated debuginfo file -static char * -build_id_to_debug_filename (size_t size, unsigned char *data) -{ - char *filename, *s; - - filename = malloc(strlen (debug_file_directory) + (sizeof "/.build-id/" - 1) + 1 - + 2 * size + (sizeof ".debug" - 1) + 1); - if (filename == NULL) { - return NULL; - } - s = filename + sprintf (filename, "%s/.build-id/", debug_file_directory); - if (size > 0) - { - size--; - s += sprintf (s, "%02x", *data++); - } - if (size > 0) - *s++ = '/'; - while (size-- > 0) - s += sprintf (s, "%02x", *data++); - strcpy (s, ".debug"); - - return filename; -} - -// Read a build ID note. Try to open any associated debuginfo file -// and return its symtab -static struct symtab* build_symtab_from_build_id(Elf64_Nhdr *note) -{ - int fd; - struct symtab *symtab = NULL; - - unsigned char *bytes - = (unsigned char*)(note+1) + note->n_namesz; - char *filename - = (build_id_to_debug_filename (note->n_descsz, bytes)); - if (filename == NULL) { - return NULL; - } - fd = pathmap_open(filename); - if (fd >= 0) { - symtab = build_symtab_internal(fd, NULL, /* try_debuginfo */ false); - close(fd); - } - free(filename); - - return symtab; -} - // read symbol table from given fd. If try_debuginfo) is true, also // try to open an associated debuginfo file static struct symtab* build_symtab_internal(int fd, const char *filename, bool try_debuginfo) { @@ -464,28 +239,7 @@ static struct symtab* build_symtab_internal(int fd, const char *filename, bool t // the debuginfo file. We stash a copy of the old symtab in case // there is no debuginfo. struct symtab* prev_symtab = symtab; - symtab = NULL; - -#ifdef NT_GNU_BUILD_ID - // First we look for a Build ID - for (cursct = shbuf, cnt = 0; - symtab == NULL && cnt < ehdr.e_shnum; - cnt++) { - if (cursct->sh_type == SHT_NOTE) { - Elf64_Nhdr *note = (Elf64_Nhdr *)scn_cache[cnt].c_data; - if (note->n_type == NT_GNU_BUILD_ID) { - symtab = build_symtab_from_build_id(note); - } - } - cursct++; - } -#endif - - // Then, if that doesn't work, the debug link - if (symtab == NULL) { - symtab = build_symtab_from_debug_link(filename, fd, &ehdr, - scn_cache); - } + symtab = build_symtab_from_debuginfo(filename, fd); // If we still haven't found a symtab, use the object's own symtab. if (symtab != NULL) { From 16c6239e728271ef49e0d820ea82f8ce99539376 Mon Sep 17 00:00:00 2001 From: Harshit Dhiman Date: Wed, 14 Oct 2026 08:15:41 +0000 Subject: [PATCH 082/101] 8394502: [s390x] TestZGCBarrierElision.java test failure Reviewed-by: amitkumar --- src/hotspot/cpu/s390/gc/z/z_s390.ad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/cpu/s390/gc/z/z_s390.ad b/src/hotspot/cpu/s390/gc/z/z_s390.ad index 2638b893f58..a9a75e596b0 100644 --- a/src/hotspot/cpu/s390/gc/z/z_s390.ad +++ b/src/hotspot/cpu/s390/gc/z/z_s390.ad @@ -171,7 +171,7 @@ instruct zCompareAndExchangeP(iRegP res, iRegP mem, iRegP oldval, iRegP newval, ins_pipe(pipe_class_dummy); %} -instruct zCompareAndSwap(iRegI res, iRegP mem, iRegP oldval, iRegP newval, iRegP oldval_temp, iRegP newval_temp, flagsReg cr) %{ +instruct zCompareAndSwapP(iRegI res, iRegP mem, iRegP oldval, iRegP newval, iRegP oldval_temp, iRegP newval_temp, flagsReg cr) %{ match(Set res (CompareAndSwapP mem (Binary oldval newval))); match(Set res (WeakCompareAndSwapP mem (Binary oldval newval))); predicate(UseZGC && n->as_LoadStore()->barrier_data() != 0); From f3f7829d691b0b6e1a0f52d51a44887e979f0d07 Mon Sep 17 00:00:00 2001 From: Richard Reingruber Date: Wed, 14 Oct 2026 08:27:44 +0000 Subject: [PATCH 083/101] 8394343: PPC64: C2: incorrect lshl of positive integer Reviewed-by: mdoerr --- src/hotspot/cpu/ppc/ppc.ad | 6 +- .../jtreg/compiler/codegen/ShiftTest.java | 55 ++++++++++++++++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index cdac272dcd7..a8baa686e9b 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -8323,9 +8323,11 @@ instruct lShiftL_regI_immGE32(iRegLdst dst, iRegIorL2I src1, uimmI6_ge32 src2) % ins_pipe(pipe_class_default); %} -// Shift a postivie int to the left. +// Shift a positive int to the left. // Clrlsldi clears the upper 32 bits and shifts. -instruct scaledPositiveI2L_lShiftL_convI2L_reg_imm6(iRegLdst dst, iRegIorL2I src1, uimmI6 src2) %{ +// Only valid for shift amounts < 32. +// Shift amounts >= 32 are handled by lShiftL_regI_immGE32. +instruct scaledPositiveI2L_lShiftL_convI2L_reg_imm6(iRegLdst dst, iRegIorL2I src1, uimmI5 src2) %{ match(Set dst (LShiftL (ConvI2L src1) src2)); predicate(((ConvI2LNode*)(_kids[0]->_leaf))->type()->is_long()->is_positive_int()); diff --git a/test/hotspot/jtreg/compiler/codegen/ShiftTest.java b/test/hotspot/jtreg/compiler/codegen/ShiftTest.java index 8d051c127c5..6d430d584ce 100644 --- a/test/hotspot/jtreg/compiler/codegen/ShiftTest.java +++ b/test/hotspot/jtreg/compiler/codegen/ShiftTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 4093292 8280511 + * @bug 4093292 8280511 8394343 * @summary Test for correct code generation by the JIT * @library /test/lib * @run main compiler.codegen.ShiftTest @@ -171,6 +171,50 @@ private static long negSignedRightShiftLCorner(long input) { return -(input >> 66); } + private static int[] posInt = { + 0, 1, 2, 3, (1<<16) - 1, 1<<16, Integer.MAX_VALUE + }; + + private static long[] posInt_LeftShift20_expected = { + 0, 1048576, 2097152, 3145728, 68718428160L, 68719476736L, 2251799812636672L + }; + + private static long[] posInt_LeftShift31_expected = { + 0, 2147483648L, 4294967296L, 6442450944L, 140735340871680L, 140737488355328L, 4611686016279904256L + }; + + private static long[] posInt_LeftShift32_expected = { + 0, 4294967296L, 8589934592L, 12884901888L, 281470681743360L, 281474976710656L, 9223372032559808512L + }; + + private static long[] posInt_LeftShift33_expected = { + 0, 8589934592L, 17179869184L, 25769803776L, 562941363486720L, 562949953421312L, -8589934592L + }; + + private static long[] posInt_LeftShift63_expected = { + 0, -9223372036854775808L, 0, -9223372036854775808L, -9223372036854775808L, 0, -9223372036854775808L + }; + + private static long leftShiftPosI2L_20(int input) { + return (long)(input & Integer.MAX_VALUE) << 20; + } + + private static long leftShiftPosI2L_31(int input) { + return (long)(input & Integer.MAX_VALUE) << 31; + } + + private static long leftShiftPosI2L_32(int input) { + return (long)(input & Integer.MAX_VALUE) << 32; + } + + private static long leftShiftPosI2L_33(int input) { + return (long)(input & Integer.MAX_VALUE) << 33; + } + + private static long leftShiftPosI2L_63(int input) { + return (long)(input & Integer.MAX_VALUE) << 63; + } + private static void testNegShift() { for (int i = 0; i < 20_000; i++) { for (int j = 0; j < ispecial.length; j++) { @@ -189,6 +233,13 @@ private static void testNegShift() { Asserts.assertEquals(negUnsignedRightShiftLCorner(lspecial[j]), lspecial_UnsignedRightShiftCorner_expected[j]); Asserts.assertEquals(negSignedRightShiftLCorner(lspecial[j]), lspecial_SignedRightShiftCorner_expected[j]); } + for (int j = 0; j < posInt.length; j++) { + Asserts.assertEquals(leftShiftPosI2L_20(posInt[j]), posInt_LeftShift20_expected[j]); + Asserts.assertEquals(leftShiftPosI2L_31(posInt[j]), posInt_LeftShift31_expected[j]); + Asserts.assertEquals(leftShiftPosI2L_32(posInt[j]), posInt_LeftShift32_expected[j]); + Asserts.assertEquals(leftShiftPosI2L_33(posInt[j]), posInt_LeftShift33_expected[j]); + Asserts.assertEquals(leftShiftPosI2L_63(posInt[j]), posInt_LeftShift63_expected[j]); + } } } From f7360da0fdca3b2ade08b969937f689dea7c99dc Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Wed, 14 Oct 2026 08:31:28 +0000 Subject: [PATCH 084/101] 8393580: C2: "assert(_base >= OopPtr && _base <= AryPtr) failed: Not a Java pointer" with -XX:PerMethodTrapLimit=0 Reviewed-by: mchevalier, thartmann, kvn --- src/hotspot/share/opto/graphKit.cpp | 13 +-- src/hotspot/share/opto/memnode.cpp | 42 ++----- src/hotspot/share/opto/type.cpp | 109 ++++++------------ src/hotspot/share/opto/type.hpp | 34 +++--- .../TestInvalidSpeculativeType.java | 60 ++++++++++ 5 files changed, 135 insertions(+), 123 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInvalidSpeculativeType.java diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 54887860531..8fbabc70930 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -1967,18 +1967,17 @@ Node* GraphKit::cast_to_flat_array(Node* array, ciValueKlass* elem_vk) { return cast_to_flat_array_exact(array, elem_vk, false, true); } - bool is_null_free = false; - if (!elem_vk->has_nullable_atomic_layout()) { - // Element does not have a nullable flat layout, cannot be nullable - is_null_free = true; - } - ciArrayKlass* array_klass = ciObjArrayKlass::make(elem_vk, false); if (!array_klass->is_loaded()) { return top(); } + const TypeAryPtr* arytype = TypeOopPtr::make_from_klass(array_klass)->isa_aryptr(); - arytype = arytype->cast_to_flat(true)->cast_to_null_free(is_null_free); + arytype = arytype->cast_to_flat(); + if (!elem_vk->has_nullable_atomic_layout()) { + arytype = arytype->cast_to_null_free(); + } + return _gvn.transform(new CheckCastPPNode(control(), array, arytype, ConstraintCastNode::DependencyType::NonFloatingNarrowing)); } diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 80693504040..a522ea77064 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -354,38 +354,20 @@ Node *MemNode::optimize_memory_chain(Node *mchain, const TypePtr *t_adr, Node *l if (t_oop == nullptr) return mchain; // don't try to optimize non-oop types Node* result = optimize_simple_memory_chain(mchain, t_oop, load, phase); + + // optimized_memory_chain is called both during IGVN and during split_unique_types in escape + // analysis, so we may need to split_out_instance if result is a memory Phi corresponding to + // TypePtr::BOTTOM or the general oop of the known instance t_oop bool is_instance = t_oop->is_known_instance_field(); - PhaseIterGVN *igvn = phase->is_IterGVN(); + PhaseIterGVN* igvn = phase->is_IterGVN(); if (is_instance && igvn != nullptr && result->is_Phi()) { - PhiNode *mphi = result->as_Phi(); - assert(mphi->bottom_type() == Type::MEMORY, "memory phi required"); - const TypePtr *t = mphi->adr_type(); - bool do_split = false; - // In the following cases, Load memory input can be further optimized based on - // its precise address type - if (t == TypePtr::BOTTOM || t == TypeRawPtr::BOTTOM ) { - do_split = true; - } else if (t->isa_oopptr() && !t->is_oopptr()->is_known_instance()) { - const TypeOopPtr* mem_t = - t->is_oopptr()->cast_to_exactness(true) - ->is_oopptr()->cast_to_ptr_type(t_oop->ptr()) - ->is_oopptr()->cast_to_instance_id(t_oop->instance_id()); - if (t_oop->isa_aryptr()) { - mem_t = mem_t->is_aryptr() - ->cast_to_stable(t_oop->is_aryptr()->is_stable()) - ->cast_to_size(t_oop->is_aryptr()->size()) - ->cast_to_not_flat(t_oop->is_aryptr()->is_not_flat()) - ->cast_to_not_null_free(t_oop->is_aryptr()->is_not_null_free()) - ->with_offset(t_oop->is_aryptr()->offset()) - ->is_aryptr(); - } - do_split = mem_t == t_oop; - } - if (do_split) { - // clone the Phi with our address type - result = mphi->split_out_instance(t_adr, igvn); - } else { - assert(phase->C->get_alias_index(t) == phase->C->get_alias_index(t_adr), "correct memory chain"); + PhiNode* phi = result->as_Phi(); + assert(phi->bottom_type() == Type::MEMORY, "memory phi required"); + const TypePtr* phi_at = phi->adr_type(); + assert(t_oop->higher_equal(phi_at), "a memory Phi must have the a wider adr_type than an access"); + const TypePtr* t_oop_widened = phase->C->get_adr_type(phase->C->get_alias_index(t_oop)); + if (t_oop_widened != phi_at) { + result = phi->split_out_instance(t_oop_widened, igvn); } } return result; diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index 131d1251666..1ec37b9683d 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -716,7 +716,10 @@ void Type::Initialize_shared(Compile* current) { TypeAryPtr::LONGS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeLong::LONG ,TypeInt::POS, false, false, true, false, true, true), ciTypeArrayKlass::make(T_LONG), true, Offset::bottom); TypeAryPtr::FLOATS = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::FLOAT ,TypeInt::POS, false, false, true, false, true, true), ciTypeArrayKlass::make(T_FLOAT), true, Offset::bottom); TypeAryPtr::DOUBLES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(Type::DOUBLE ,TypeInt::POS, false, false, true, false, true, true), ciTypeArrayKlass::make(T_DOUBLE), true, Offset::bottom); - TypeAryPtr::INLINES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM,TypeInt::POS, /* stable= */ false, /* flat= */ true, false, false, false, false), nullptr, false, Offset::bottom); + TypeAryPtr::INLINES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM, TypeInt::POS, false, true, false, false, false, false), nullptr, false, Offset::bottom); + TypeAryPtr::REFERENCES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM, TypeInt::POS, false, false, true, false, false, true), nullptr, false, Offset::bottom); + TypeAryPtr::NULL_FREES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::NOTNULL, TypeInt::POS, false, false, false, true, false, false), nullptr, false, Offset::bottom); + TypeAryPtr::NULLABLES = TypeAryPtr::make(TypePtr::BotPTR, TypeAry::make(TypeInstPtr::BOTTOM, TypeInt::POS, false, false, false, false, true, true), nullptr, false, Offset::bottom); // Nobody should ask _array_body_type[T_NARROWOOP]. Use null as assert. TypeAryPtr::_array_body_type[T_NARROWOOP] = nullptr; @@ -4711,17 +4714,20 @@ const TypeKlassPtr* TypeInstPtr::as_klass_type(bool try_for_exact) const { //============================================================================= // Convenience common pre-built types. const TypeAryPtr* TypeAryPtr::BOTTOM; -const TypeAryPtr *TypeAryPtr::RANGE; -const TypeAryPtr *TypeAryPtr::OOPS; -const TypeAryPtr *TypeAryPtr::NARROWOOPS; -const TypeAryPtr *TypeAryPtr::BYTES; -const TypeAryPtr *TypeAryPtr::SHORTS; -const TypeAryPtr *TypeAryPtr::CHARS; -const TypeAryPtr *TypeAryPtr::INTS; -const TypeAryPtr *TypeAryPtr::LONGS; -const TypeAryPtr *TypeAryPtr::FLOATS; -const TypeAryPtr *TypeAryPtr::DOUBLES; -const TypeAryPtr *TypeAryPtr::INLINES; +const TypeAryPtr* TypeAryPtr::RANGE; +const TypeAryPtr* TypeAryPtr::OOPS; +const TypeAryPtr* TypeAryPtr::NARROWOOPS; +const TypeAryPtr* TypeAryPtr::BYTES; +const TypeAryPtr* TypeAryPtr::SHORTS; +const TypeAryPtr* TypeAryPtr::CHARS; +const TypeAryPtr* TypeAryPtr::INTS; +const TypeAryPtr* TypeAryPtr::LONGS; +const TypeAryPtr* TypeAryPtr::FLOATS; +const TypeAryPtr* TypeAryPtr::DOUBLES; +const TypeAryPtr* TypeAryPtr::INLINES; +const TypeAryPtr* TypeAryPtr::REFERENCES; +const TypeAryPtr* TypeAryPtr::NULL_FREES; +const TypeAryPtr* TypeAryPtr::NULLABLES; //------------------------------make------------------------------------------- const TypeAryPtr* TypeAryPtr::make(PTR ptr, const TypeAry *ary, ciKlass* k, bool xk, Offset offset, Offset field_offset, @@ -4839,80 +4845,41 @@ const TypeAryPtr* TypeAryPtr::cast_to_size(const TypeInt* new_size) const { return make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); } -const TypeAryPtr* TypeAryPtr::cast_to_flat(bool flat) const { - if (flat == is_flat()) { +const TypeAryPtr* TypeAryPtr::cast_to_flat() const { + if (is_flat()) { return this; } - assert(!flat || !is_not_flat(), "inconsistency"); - const TypeAry* new_ary = TypeAry::make(elem(), size(), is_stable(), flat, is_not_flat(), is_null_free(), is_not_null_free(), is_atomic()); - const TypeAryPtr* res = make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); - if (res->speculative() == res->remove_speculative()) { - return res->remove_speculative(); - } - return res; + + assert(!is_not_flat(), "inconsistency"); + return join_speculative(TypeAryPtr::INLINES)->is_aryptr(); } -//-------------------------------cast_to_not_flat------------------------------ -const TypeAryPtr* TypeAryPtr::cast_to_not_flat(bool not_flat) const { - if (not_flat == is_not_flat()) { +const TypeAryPtr* TypeAryPtr::cast_to_not_flat() const { + if (is_not_flat()) { return this; } - assert(!not_flat || !is_flat(), "inconsistency"); - const TypeAry* new_ary = TypeAry::make(elem(), size(), is_stable(), is_flat(), not_flat, is_null_free(), is_not_null_free(), is_atomic()); - const TypeAryPtr* res = make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); - // We keep the speculative part if it contains information about flat-/nullability. - // Make sure it's removed if it's not better than the non-speculative type anymore. - if (res->speculative() == res->remove_speculative()) { - return res->remove_speculative(); - } - return res; + + assert(!is_flat(), "inconsistency"); + return join_speculative(TypeAryPtr::REFERENCES)->is_aryptr(); } -const TypeAryPtr* TypeAryPtr::cast_to_null_free(bool null_free) const { - if (null_free == is_null_free()) { +const TypeAryPtr* TypeAryPtr::cast_to_null_free() const { + if (is_null_free()) { return this; } - assert(!null_free || !is_not_null_free(), "inconsistency"); - const Type* elem = this->elem(); - const Type* new_elem = elem->make_ptr(); - if (null_free) { - new_elem = new_elem->join_speculative(TypePtr::NOTNULL); - } else { - new_elem = new_elem->meet_speculative(TypePtr::NULL_PTR); - } - new_elem = elem->isa_narrowoop() ? new_elem->make_narrowoop() : new_elem; - const TypeAry* new_ary = TypeAry::make(new_elem, size(), is_stable(), is_flat(), is_not_flat(), null_free, is_not_null_free(), is_atomic()); - const TypeAryPtr* res = make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, _instance_id, _speculative, _inline_depth, _is_autobox_cache); - if (res->speculative() == res->remove_speculative()) { - return res->remove_speculative(); - } - assert(res->speculative() == nullptr || res->speculative()->with_inline_depth(res->inline_depth())->higher_equal(res->remove_speculative()), - "speculative type must not be narrower than non-speculative type"); - return res; + + assert(!is_not_null_free(), "inconsistency"); + return join_speculative(TypeAryPtr::NULL_FREES)->is_aryptr(); } //-------------------------------cast_to_not_null_free------------------------- -const TypeAryPtr* TypeAryPtr::cast_to_not_null_free(bool not_null_free) const { - if (not_null_free == is_not_null_free()) { +const TypeAryPtr* TypeAryPtr::cast_to_not_null_free() const { + if (is_not_null_free()) { return this; } - assert(!not_null_free || !is_null_free(), "inconsistency"); - const TypeAry* new_ary = TypeAry::make(elem(), size(), is_stable(), is_flat(), is_not_flat(), is_null_free(), not_null_free, is_atomic()); - const TypePtr* new_spec = _speculative; - if (new_spec != nullptr) { - // Could be 'null free' from profiling, which would contradict the cast. - new_spec = new_spec->is_aryptr()->cast_to_null_free(false)->cast_to_not_null_free(); - } - const TypeAryPtr* res = make(ptr(), const_oop(), new_ary, klass(), klass_is_exact(), _offset, _field_offset, - _instance_id, new_spec, _inline_depth, _is_autobox_cache); - // We keep the speculative part if it contains information about flat-/nullability. - // Make sure it's removed if it's not better than the non-speculative type anymore. - if (res->speculative() == res->remove_speculative()) { - return res->remove_speculative(); - } - assert(res->speculative() == nullptr || res->speculative()->with_inline_depth(res->inline_depth())->higher_equal(res->remove_speculative()), - "speculative type must not be narrower than non-speculative type"); - return res; + + assert(!is_null_free(), "inconsistency"); + return join_speculative(TypeAryPtr::NULLABLES)->is_aryptr(); } //---------------------------------update_properties--------------------------- diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 140fcc55f69..8807bbd973d 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -1797,10 +1797,10 @@ class TypeAryPtr : public TypeOopPtr { virtual const Type* xjoin_helper(const Type* t) const; // Value type array properties - const TypeAryPtr* cast_to_flat(bool flat) const; - const TypeAryPtr* cast_to_not_flat(bool not_flat = true) const; - const TypeAryPtr* cast_to_null_free(bool null_free) const; - const TypeAryPtr* cast_to_not_null_free(bool not_null_free = true) const; + const TypeAryPtr* cast_to_flat() const; + const TypeAryPtr* cast_to_not_flat() const; + const TypeAryPtr* cast_to_null_free() const; + const TypeAryPtr* cast_to_not_null_free() const; const TypeAryPtr* update_properties(const TypeAryPtr* new_type) const; jint flat_layout_helper() const; int flat_elem_size() const; @@ -1826,17 +1826,21 @@ class TypeAryPtr : public TypeOopPtr { // Convenience common pre-built types. static const TypeAryPtr* BOTTOM; - static const TypeAryPtr *RANGE; - static const TypeAryPtr *OOPS; - static const TypeAryPtr *NARROWOOPS; - static const TypeAryPtr *BYTES; - static const TypeAryPtr *SHORTS; - static const TypeAryPtr *CHARS; - static const TypeAryPtr *INTS; - static const TypeAryPtr *LONGS; - static const TypeAryPtr *FLOATS; - static const TypeAryPtr *DOUBLES; - static const TypeAryPtr *INLINES; + static const TypeAryPtr* RANGE; + static const TypeAryPtr* OOPS; + static const TypeAryPtr* NARROWOOPS; + static const TypeAryPtr* BYTES; + static const TypeAryPtr* SHORTS; + static const TypeAryPtr* CHARS; + static const TypeAryPtr* INTS; + static const TypeAryPtr* LONGS; + static const TypeAryPtr* FLOATS; + static const TypeAryPtr* DOUBLES; + static const TypeAryPtr* INLINES; + static const TypeAryPtr* REFERENCES; + static const TypeAryPtr* NULL_FREES; + static const TypeAryPtr* NULLABLES; + // selects one of the above: static const TypeAryPtr *get_array_body_type(BasicType elem) { assert((uint)elem <= T_CONFLICT && _array_body_type[elem] != nullptr, "bad elem type"); diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInvalidSpeculativeType.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInvalidSpeculativeType.java new file mode 100644 index 00000000000..90ab8d69061 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInvalidSpeculativeType.java @@ -0,0 +1,60 @@ +/* + * 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.valhalla.valuetypes; + +import jdk.internal.misc.Unsafe; +import jdk.internal.value.ValueClass; + +/* + * @test + * @bug 8393580 + * @summary For a Type, the speculative part should be a subset of the static part. + * @enablePreview + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * @run main ${test.main.class} + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:+IgnoreUnrecognizedVMOptions -XX:+StressReflectiveCode + * -XX:TypeProfileLevel=222 -XX:PerMethodTrapLimit=0 + * -XX:CompileOnly=${test.main.class}::test + * ${test.main.class} + */ +public class TestInvalidSpeculativeType { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final Integer[] ARRAY = new Integer[4]; + + public static void main(String[] args) { + for (int i = 0; i < 20000; i++) { + test(ARRAY); + } + } + + private static boolean test(Integer[] array) { + if (!ValueClass.isFlatArray(array)) { + Integer _ = (Integer) UNSAFE.getReference(array, Unsafe.ARRAY_OBJECT_BASE_OFFSET); + return array instanceof Number[]; + } else { + return false; + } + } +} From 3dca17791050f764d21922028b21a48e45c396c4 Mon Sep 17 00:00:00 2001 From: Frederic Thevenet Date: Wed, 14 Oct 2026 08:40:25 +0000 Subject: [PATCH 085/101] 8393459: [test] TestDefaultArchiveLoading.java is incorrectly ignored on Windows Reviewed-by: iklam, stuefe --- test/hotspot/jtreg/TEST.ROOT | 1 + .../cds/TestDefaultArchiveLoading.java | 30 +--------- test/jtreg-ext/requires/VMProps.java | 56 +++++++++++++------ 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index 27ef85a967c..58de4871f8c 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -80,6 +80,7 @@ requires.properties= \ vm.cds.default.archive.available \ vm.cds.nocoops.archive.available \ vm.cds.nocoh.archive.available \ + vm.cds.nocoops.nocoh.archive.available \ vm.cds.custom.loaders \ vm.cds.supports.aot.class.linking \ vm.cds.supports.aot.code.caching \ diff --git a/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java b/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java index 5788f302391..7a0103bf803 100644 --- a/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java +++ b/test/hotspot/jtreg/runtime/cds/TestDefaultArchiveLoading.java @@ -27,6 +27,7 @@ * @summary Test Loading of default archives in all configurations (requires --enable-cds-archive-nocoh) * @requires vm.cds * @requires vm.cds.default.archive.available + * @requires vm.cds.nocoops.nocoh.archive.available * @requires vm.cds.write.archived.java.heap * @requires vm.bits == 64 * @library /test/lib @@ -40,6 +41,7 @@ * @summary Test Loading of default archives in all configurations * @requires vm.cds * @requires vm.cds.default.archive.available + * @requires vm.cds.nocoops.archive.available * @requires vm.cds.write.archived.java.heap * @requires vm.bits == 64 * @library /test/lib @@ -53,6 +55,7 @@ * @summary Test Loading of default archives in all configurations (requires --enable-cds-archive-nocoh) * @requires vm.cds * @requires vm.cds.default.archive.available + * @requires vm.cds.nocoh.archive.available * @requires vm.cds.write.archived.java.heap * @requires vm.bits == 64 * @requires vm.gc != "Z" @@ -94,17 +97,6 @@ private static String archiveName(String archiveSuffix) { return "classes" + archiveSuffix + archivePreviewSuffix + ".jsa"; } - private static Path archivePath(String archiveSuffix) { - return Paths.get(System.getProperty("java.home"), "lib", - "server", archiveName(archiveSuffix)); - } - - private static boolean isArchiveAvailable(char coops, char coh, - String archiveSuffix) throws Exception { - Path archive= archivePath(archiveSuffix); - return Files.exists(archive); - } - public static void main(String[] args) throws Exception { if (args.length != 1) { @@ -124,36 +116,20 @@ public static void main(String[] args) throws Exception { case "nocoops_nocoh": coh = coops = '-'; archiveSuffix = "_nocoops_nocoh"; - if (!isArchiveAvailable(coops, coh, archiveSuffix)) { - throw new SkippedException("Skipping test due to " + - archivePath(archiveSuffix).toString() + " not available"); - } break; case "nocoops_coh": coops = '-'; coh = '+'; archiveSuffix = "_nocoops"; - if (!isArchiveAvailable(coops, coh, archiveSuffix)) { - throw new SkippedException("Skipping test due to " + - archivePath(archiveSuffix).toString() + " not available"); - } break; case "coops_nocoh": coops = '+'; coh = '-'; archiveSuffix = "_nocoh"; - if (!isArchiveAvailable(coops, coh, archiveSuffix)) { - throw new SkippedException("Skipping test due to " + - archivePath(archiveSuffix).toString() + " not available"); - } break; case "coops_coh": coh = coops = '+'; archiveSuffix = ""; - if (!isArchiveAvailable(coops, coh, archiveSuffix)) { - throw new SkippedException("Skipping test due to " + - archivePath(archiveSuffix).toString() + " not available"); - } break; default: throw new RuntimeException("Invalid argument " + args[0]); } diff --git a/test/jtreg-ext/requires/VMProps.java b/test/jtreg-ext/requires/VMProps.java index c47b786e48b..491f25bfdce 100644 --- a/test/jtreg-ext/requires/VMProps.java +++ b/test/jtreg-ext/requires/VMProps.java @@ -120,6 +120,7 @@ public Map call() { map.put("vm.cds.default.archive.available", this::vmCDSDefaultArchiveAvailable); map.put("vm.cds.nocoops.archive.available", this::vmCDSNocoopsArchiveAvailable); map.put("vm.cds.nocoh.archive.available", this::vmCDSNocohArchiveAvailable); + map.put("vm.cds.nocoops.nocoh.archive.available", this::vmCDSNocoopsNocohArchiveAvailable); map.put("vm.cds.custom.loaders", this::vmCDSForCustomLoaders); map.put("vm.cds.supports.aot.class.linking", this::vmCDSSupportsAOTClassLinking); map.put("vm.cds.supports.aot.code.caching", this::vmCDSSupportsAOTCodeCaching); @@ -389,29 +390,39 @@ protected String vmHasDTrace() { return "" + WB.isDTraceIncluded(); } - /** - * Check for CDS support. - * - * @return true if CDS is supported by the VM to be tested. - */ - protected String vmCDS() { + // Check if the VM supports CDS. + private boolean isCDSSupported(){ boolean noJvmtiAdded = allFlags() .filter(s -> s.startsWith("-agentpath")) .findAny() .isEmpty(); - return "" + (noJvmtiAdded && WB.isCDSIncluded()); + return noJvmtiAdded && WB.isCDSIncluded(); + } + + /** + * Check for CDS support. + * + * @return "true" if CDS is supported by the VM to be tested. + */ + protected String vmCDS() { + return "" + isCDSSupported(); } - // Returns a platform-aware path for the specified archive file. - private Path archivePath(String archiveName) { + // Returns a platform-aware path for the specified archive file suffix. + private Path archivePath(String archiveSuffix) { String archiveSubdir = (Platform.isWindows() ? "bin" : "lib"); + String archiveName = "classes" + archiveSuffix; + if (PreviewFeatures.isEnabled()) { + archiveName += "_preview"; + } + archiveName += ".jsa"; return Paths.get(System.getProperty("java.home"), archiveSubdir, "server", archiveName); } // Returns true if a CDS archive file with specified name exists. - private boolean archivePathExists(String archiveName) { - return Files.exists(archivePath(archiveName)); + private boolean archivePathExists(String archiveSuffix) { + return Files.exists(archivePath(archiveSuffix)); } /** @@ -420,7 +431,7 @@ private boolean archivePathExists(String archiveName) { * @return true if CDS default archive classes.jsa exists in the JDK to be tested. */ protected String vmCDSDefaultArchiveAvailable() { - return "" + ("true".equals(vmCDS()) && archivePathExists("classes.jsa")); + return "" + (isCDSSupported() && archivePathExists("")); } /** @@ -429,7 +440,7 @@ protected String vmCDSDefaultArchiveAvailable() { * @return true if CDS archive classes_nocoops.jsa exists in the JDK to be tested. */ protected String vmCDSNocoopsArchiveAvailable() { - return "" + ("true".equals(vmCDS()) && archivePathExists("classes_nocoops.jsa")); + return "" + (isCDSSupported() && archivePathExists("_nocoops")); } /** @@ -438,7 +449,16 @@ protected String vmCDSNocoopsArchiveAvailable() { * @return true if CDS archive classes_nocoh.jsa exists in the JDK to be tested. */ protected String vmCDSNocohArchiveAvailable() { - return "" + ("true".equals(vmCDS()) && archivePathExists("classes_nocoh.jsa")); + return "" + (isCDSSupported() && archivePathExists("_nocoh")); + } + + /** + * Check for existence of CDS archive with no compressed oops and no compact object headers enabled. + * + * @return true if CDS archive classes_nocoops_nocoh.jsa exists in the JDK to be tested. + */ + protected String vmCDSNocoopsNocohArchiveAvailable() { + return "" + (isCDSSupported() && archivePathExists("_nocoops_nocoh")); } /** @@ -447,7 +467,7 @@ protected String vmCDSNocohArchiveAvailable() { * @return true if CDS provides support for customer loader in the VM to be tested. */ protected String vmCDSForCustomLoaders() { - return "" + ("true".equals(vmCDS()) && Platform.areCustomLoadersSupportedForCDS()); + return "" + (isCDSSupported() && Platform.areCustomLoadersSupportedForCDS()); } /** @@ -455,7 +475,7 @@ protected String vmCDSForCustomLoaders() { * with the current set of jtreg VM options. */ protected String vmCDSCanWriteArchivedJavaHeap() { - return "" + ("true".equals(vmCDS()) && WB.canWriteJavaHeapArchive()); + return "" + (isCDSSupported() && WB.canWriteJavaHeapArchive()); } /** @@ -463,7 +483,7 @@ protected String vmCDSCanWriteArchivedJavaHeap() { * with the current set of jtreg VM options. */ protected String vmCDSCanWriteMappedArchivedJavaHeap() { - return "" + ("true".equals(vmCDS()) && WB.canWriteMappedJavaHeapArchive()); + return "" + (isCDSSupported() && WB.canWriteMappedJavaHeapArchive()); } /** @@ -471,7 +491,7 @@ protected String vmCDSCanWriteMappedArchivedJavaHeap() { * with the current set of jtreg VM options. */ protected String vmCDSCanWriteStreamedArchivedJavaHeap() { - return "" + ("true".equals(vmCDS()) && WB.canWriteStreamedJavaHeapArchive()); + return "" + (isCDSSupported() && WB.canWriteStreamedJavaHeapArchive()); } /** From 7f0d1d993670b021a9c8333080d53aec1edca82d Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Wed, 14 Oct 2026 09:27:03 +0000 Subject: [PATCH 086/101] 8392541: Remove the internal BufferStack and CaptureStateUtil Reviewed-by: jvernee, mcimadamore, liach --- .../jdk/internal/foreign/BufferStack.java | 214 ---------- .../internal/foreign/CaptureStateUtil.java | 378 ------------------ .../internal/foreign/SlicingAllocator.java | 18 +- .../jdk/internal/foreign/abi/SharedUtils.java | 30 +- test/jdk/java/foreign/TestBufferStack.java | 294 -------------- .../java/foreign/TestBufferStackStress.java | 79 ---- .../java/foreign/TestBufferStackStress2.java | 148 ------- .../java/foreign/TestCaptureStateUtil.java | 154 ------- test/jdk/java/foreign/libTestBufferStack.c | 39 -- .../java/lang/foreign/BufferStackBench.java | 79 ---- .../lang/foreign/CaptureStateUtilBench.java | 116 ------ 11 files changed, 23 insertions(+), 1526 deletions(-) delete mode 100644 src/java.base/share/classes/jdk/internal/foreign/BufferStack.java delete mode 100644 src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java delete mode 100644 test/jdk/java/foreign/TestBufferStack.java delete mode 100644 test/jdk/java/foreign/TestBufferStackStress.java delete mode 100644 test/jdk/java/foreign/TestBufferStackStress2.java delete mode 100644 test/jdk/java/foreign/TestCaptureStateUtil.java delete mode 100644 test/jdk/java/foreign/libTestBufferStack.c delete mode 100644 test/micro/org/openjdk/bench/java/lang/foreign/BufferStackBench.java delete mode 100644 test/micro/org/openjdk/bench/java/lang/foreign/CaptureStateUtilBench.java diff --git a/src/java.base/share/classes/jdk/internal/foreign/BufferStack.java b/src/java.base/share/classes/jdk/internal/foreign/BufferStack.java deleted file mode 100644 index dbf21601c53..00000000000 --- a/src/java.base/share/classes/jdk/internal/foreign/BufferStack.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 jdk.internal.foreign; - -import jdk.internal.misc.CarrierThreadLocal; -import jdk.internal.vm.annotation.ForceInline; - -import java.lang.foreign.Arena; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.ref.Reference; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; - -/** - * A buffer stack that allows efficient reuse of memory segments. This is useful in cases - * where temporary memory is needed. - *

- * Use the factories {@code BufferStack.of(...)} to create new instances of this class. - *

- * Note: The reused segments are neither zeroed out before nor after re-use. - */ -public final class BufferStack { - - private final long byteSize; - private final long byteAlignment; - private final CarrierThreadLocal tl; - - private BufferStack(long byteSize, long byteAlignment) { - this.byteSize = byteSize; - this.byteAlignment = byteAlignment; - this.tl = new CarrierThreadLocal<>() { - @Override - protected BufferStack.PerThread initialValue() { - return BufferStack.PerThread.of(byteSize, byteAlignment); - } - }; - } - - /** - * {@return a new Arena that tries to provide {@code byteSize} and {@code byteAlignment} - * allocations by recycling the BufferStack's internal memory} - * - * @param byteSize to be reserved from this BufferStack's internal memory - * @param byteAlignment to be used for reservation - */ - @ForceInline - public Arena pushFrame(long byteSize, long byteAlignment) { - return tl.get().pushFrame(byteSize, byteAlignment); - } - - /** - * {@return a new Arena that tries to provide {@code byteSize} - * allocations by recycling the BufferStack's internal memory} - * - * @param byteSize to be reserved from this BufferStack's internal memory - */ - @ForceInline - public Arena pushFrame(long byteSize) { - return pushFrame(byteSize, 1); - } - - /** - * {@return a new Arena that tries to provide {@code layout} - * allocations by recycling the BufferStack's internal memory} - * - * @param layout for which to reserve internal memory - */ - @ForceInline - public Arena pushFrame(MemoryLayout layout) { - return pushFrame(layout.byteSize(), layout.byteAlignment()); - } - - @Override - public String toString() { - return "BufferStack[byteSize=" + byteSize + ", byteAlignment=" + byteAlignment + "]"; - } - - private record PerThread(ReentrantLock lock, - Arena arena, - SlicingAllocator stack, - CleanupAction cleanupAction) { - - @ForceInline - public Arena pushFrame(long size, long byteAlignment) { - boolean needsLock = Thread.currentThread().isVirtual() && !lock.isHeldByCurrentThread(); - if (needsLock && !lock.tryLock()) { - // Rare: another virtual thread on the same carrier competed for acquisition. - return Arena.ofConfined(); - } - if (!stack.canAllocate(size, byteAlignment)) { - if (needsLock) lock.unlock(); - return Arena.ofConfined(); - } - return new Frame(needsLock, size, byteAlignment); - } - - static PerThread of(long byteSize, long byteAlignment) { - final Arena arena = Arena.ofAuto(); - return new PerThread(new ReentrantLock(), - arena, - new SlicingAllocator(arena.allocate(byteSize, byteAlignment)), - new CleanupAction(arena)); - } - - private record CleanupAction(Arena arena) implements Consumer { - @Override - public void accept(MemorySegment memorySegment) { - Reference.reachabilityFence(arena); - } - } - - private final class Frame implements Arena { - - private final boolean locked; - private final long parentOffset; - private final long topOfStack; - private final Arena confinedArena; - private final SegmentAllocator frame; - - @SuppressWarnings("restricted") - @ForceInline - public Frame(boolean locked, long byteSize, long byteAlignment) { - this.locked = locked; - this.parentOffset = stack.currentOffset(); - final MemorySegment frameSegment = stack.allocate(byteSize, byteAlignment); - this.topOfStack = stack.currentOffset(); - this.confinedArena = Arena.ofConfined(); - // The cleanup action will keep the original automatic `arena` (from which - // the reusable segment is first allocated) alive even if this Frame - // becomes unreachable but there are reachable segments still alive. - this.frame = new SlicingAllocator(frameSegment.reinterpret(confinedArena, cleanupAction)); - } - - @ForceInline - private void assertOrder() { - if (topOfStack != stack.currentOffset()) - throw new IllegalStateException("Out of order access: frame not top-of-stack"); - } - - @ForceInline - @Override - @SuppressWarnings("restricted") - public MemorySegment allocate(long byteSize, long byteAlignment) { - // Make sure we are on the right thread and not closed - MemorySessionImpl.toMemorySession(confinedArena).checkValidState(); - return frame.allocate(byteSize, byteAlignment); - } - - @ForceInline - @Override - public MemorySegment.Scope scope() { - return confinedArena.scope(); - } - - @ForceInline - @Override - public void close() { - assertOrder(); - // the Arena::close method is called "early" as it checks thread - // confinement and crucially before any mutation of the internal - // state takes place. - confinedArena.close(); - stack.resetTo(parentOffset); - if (locked) { - lock.unlock(); - } - } - } - } - - public static BufferStack of(long byteSize, long byteAlignment) { - if (byteSize < 0) { - throw new IllegalArgumentException("Negative byteSize: " + byteSize); - } - if (byteAlignment < 0) { - throw new IllegalArgumentException("Negative byteAlignment: " + byteAlignment); - } - return new BufferStack(byteSize, byteAlignment); - } - - public static BufferStack of(long byteSize) { - return new BufferStack(byteSize, 1); - } - - public static BufferStack of(MemoryLayout layout) { - // Implicit null check - return of(layout.byteSize(), layout.byteAlignment()); - } -} diff --git a/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java b/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java deleted file mode 100644 index b3a19340fb5..00000000000 --- a/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright (c) 2024, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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 jdk.internal.foreign; - -import jdk.internal.invoke.MhUtil; -import jdk.internal.vm.annotation.ForceInline; - -import java.lang.foreign.Arena; -import java.lang.foreign.Linker; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.StructLayout; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.invoke.VarHandle; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.function.IntFunction; - -/** - * An internal utility class that can be used to adapt system-call-styled method handles - * for efficient and easy use. - */ -public final class CaptureStateUtil { - - private static final StructLayout CAPTURE_LAYOUT = Linker.Option.captureStateLayout(); - private static final BufferStack POOL = BufferStack.of(CAPTURE_LAYOUT); - - // The `BASIC_HANDLE_CACHE` contains the common "basic handles" that can be reused for - // all adapted method handles. Keeping as much as possible reusable reduces the number - // of combinators needed to form an adapted method handle. - // The function is lazily computed. - // - private static final Map SEGMENT_EXTRACTION_HANDLE_CACHE; - - static { - final Set inputs = new HashSet<>(); - // The Cartesian product : (int.class, long.class) x ("errno", ...) - // Do not use Streams in order to enable "early" use in the init sequence. - for (Class c : new Class[]{int.class, long.class}) { - for (MemoryLayout layout : CAPTURE_LAYOUT.memberLayouts()) { - inputs.add(new SegmentExtractorKey(c, layout.name().orElseThrow())); - } - } - - // Do not use a lambda in order to allow early use in the init sequence - final Function segmentExtractionHandle = new Function<>() { - @Override - public MethodHandle apply(SegmentExtractorKey basicKey) { - return makeSegmentExtractionHandle(basicKey); - } - }; - - SEGMENT_EXTRACTION_HANDLE_CACHE = Map.ofLazy(inputs, segmentExtractionHandle); - } - - // A key that holds both the `returnType` and the `stateName` needed to look up a - // specific "basic handle" in the `BASIC_HANDLE_CACHE`. - // returnType in {int.class | long.class} - // stateName can be anything non-null but should be in {"GetLastError" | "WSAGetLastError" | "errno"} - private record SegmentExtractorKey(Class returnType, String stateName) { - - SegmentExtractorKey(MethodHandle target, String stateName) { - this(returnType(target), Objects.requireNonNull(stateName)); - } - - static Class returnType(MethodHandle target) { - // Implicit null check - final MethodType type = target.type(); - final Class returnType = type.returnType(); - - if (!(returnType.equals(int.class) || returnType.equals(long.class))) { - throw illegalArgDoesNot(target, "return an int or a long"); - } - if (type.parameterCount() == 0 || type.parameterType(0) != MemorySegment.class) { - throw illegalArgDoesNot(target, "have a MemorySegment as the first parameter"); - } - return returnType; - } - - private static IllegalArgumentException illegalArgDoesNot(MethodHandle target, String info) { - return new IllegalArgumentException("The provided target " + target - + " does not " + info); - } - - } - - private CaptureStateUtil() {} - - /** - * {@return a new MethodHandle that adapts the provided {@code target} so that it - * directly returns the same value as the {@code target} if it is non-negative, - * otherwise returns the negated captured state defined by the provided - * {@code stateName}} - *

- * This method is suitable for adapting system-call method handles(e.g. - * {@code open()}, {@code read()}, and {@code close()}). Clients can check the return - * value as shown in this example: - * {@snippet lang = java: - * // (MemorySegment capture, MemorySegment pathname, int flags)int - * static final MethodHandle CAPTURING_OPEN = ... - * - * // (MemorySegment pathname, int flags)int - * static final MethodHandle OPEN = CaptureStateUtil - * .adaptSystemCall(CAPTURING_OPEN, "errno"); - * - * try { - * int fh = (int)OPEN.invokeExact(pathName, flags); - * if (fh < 0) { - * throw new IOException("Error opening file: errno = " + (-fh)); - * } - * processFile(fh); - * } catch (Throwable t) { - * throw new RuntimeException(t); - * } - * - *} - * - * For a {@code target} method handle that takes a {@code MemorySegment} and two - * {@code int} parameters and returns an {@code int} value, the method returns a new - * method handle that is doing the equivalent of: - *

- * {@snippet lang = java: - * private static final MemoryLayout CAPTURE_LAYOUT = - * Linker.Option.captureStateLayout(); - * private static final BufferStack POOL = - * BufferStack.of(CAPTURE_LAYOUT); - * - * public int invoke(MethodHandle target, - * String stateName, - * int a, int b) { - * try (var arena = POOL.pushFrame(CAPTURE_LAYOUT)) { - * final MemorySegment segment = arena.allocate(CAPTURE_LAYOUT); - * final int result = (int) handle.invoke(segment, a, b); - * if (result >= 0) { - * return result; - * } - * return -(int) CAPTURE_LAYOUT - * .varHandle(MemoryLayout.PathElement.groupElement(stateName)) - * .get(segment, 0); - * } - * } - *} - * except it is more performant. In the above {@code stateName} is the name of the - * captured state (e.g. {@code errno}). The static {@code CAPTURE_LAYOUT} is shared - * across all target method handles adapted by this method. - * - * @param target method handle that returns an {@code int} or a {@code long} and - * has a capturing state MemorySegment as its first parameter - * @param stateName the name of the capturing state member layout (i.e. "errno", - * "GetLastError", or "WSAGetLastError") - * @throws IllegalArgumentException if the provided {@code target}'s return type is - * not {@code int} or {@code long} - * @throws IllegalArgumentException if the provided {@code target}'s first parameter - * type is not {@linkplain MemorySegment} - * @throws IllegalArgumentException if the provided {@code stateName} is unknown on - * the current platform - */ - public static MethodHandle adaptSystemCall(MethodHandle target, - String stateName) { - // Invariants checked in the BasicKey record - final SegmentExtractorKey key = new SegmentExtractorKey(target, stateName); - - // ((int | long), MemorySegment)(int | long) - final MethodHandle segmentExtractor = SEGMENT_EXTRACTION_HANDLE_CACHE.get(key); - if (segmentExtractor == null) { - throw new IllegalArgumentException("Input not allowed: " + key); - } - - // Make `target` specific adaptations of the basic handle - - // Pre-pend all the parameters from the `target` MH. - // (C0=MemorySegment, C1-Cn, MemorySegment)(int|long) - MethodHandle innerAdapted = MethodHandles.collectArguments(segmentExtractor, 0, target); - - final int[] perm = new int[target.type().parameterCount() + 1]; - for (int i = 0; i < target.type().parameterCount(); i++) { - perm[i] = i; - } - // Last takes first - perm[perm.length - 1] = 0; - // Deduplicate the first and last coordinate and only use the first one. - // (C0=MemorySegment, C1-Cn)(int|long) - innerAdapted = MethodHandles.permuteArguments(innerAdapted, target.type(), perm); - - // Use an `Arena` for the first argument instead and extract a segment from it. - // (C0=Arena, C1-Cn)(int|long) - innerAdapted = MethodHandles.collectArguments(innerAdapted, 0, HANDLES_CACHE.get(ALLOCATE)); - - // Add an identity function for the result of the cleanup action. - // ((int|long))(int|long) - MethodHandle cleanup = MethodHandles.identity(key.returnType()); - // Add a dummy `Throwable` argument for the cleanup action. - // This means, anything thrown will just be propagated. - // (Throwable, (int|long))(int|long) - cleanup = MethodHandles.dropArguments(cleanup, 0, Throwable.class); - // Add the first `Arena` parameter of the `innerAdapted` method handle to the - // cleanup action and invoke `Arena::close` when it is run. The `cleanup` handle - // does not have to have all parameters. It can have zero or more. - // (Throwable, (int|long), Arena)(int|long) - cleanup = MethodHandles.collectArguments(cleanup, 2, HANDLES_CACHE.get(ARENA_CLOSE)); - - // Combine the `innerAdapted` and `cleanup` action into a try/finally block. - // (Arena, C1-Cn)(int|long) - final MethodHandle tryFinally = MethodHandles.tryFinally(innerAdapted, cleanup); - - // Acquire the arena from the global pool. - // With this, we finally arrive at the intended method handle: - // (C1-Cn)(int|long) - return MethodHandles.collectArguments(tryFinally, 0, HANDLES_CACHE.get(ACQUIRE_ARENA)); - } - - private static MethodHandle makeSegmentExtractionHandle(SegmentExtractorKey segmentExtractorKey) { - final VarHandle vh = CAPTURE_LAYOUT.varHandle( - MemoryLayout.PathElement.groupElement(segmentExtractorKey.stateName())); - // This MH is used to extract the named captured state - // from the capturing `MemorySegment`. - // (MemorySegment, long)int - MethodHandle intExtractor = vh.toMethodHandle(VarHandle.AccessMode.GET); - // As the MH is already adapted to use the appropriate - // offset, we just insert `0L` for the offset. - // (MemorySegment)int - intExtractor = MethodHandles.insertArguments(intExtractor, 1, 0L); - - // If X is the `returnType` (either `int` or `long`) then - // the code below is equivalent to: - // - // X handle(X returnValue, MemorySegment segment) - // if (returnValue >= 0) { - // // Ignore the segment - // return returnValue; - // } else { - // // ignore the returnValue - // return -(X)intExtractor.invokeExact(segment); - // } - // } - if (segmentExtractorKey.returnType().equals(int.class)) { - // (int, MemorySegment)int - return MethodHandles.guardWithTest( - HANDLES_CACHE.get(NON_NEGATIVE_INT), - HANDLES_CACHE.get(SUCCESS_INT), - HANDLES_CACHE.get(ERROR_INT).bindTo(intExtractor)); - } else { - // (long, MemorySegment)long - return MethodHandles.guardWithTest( - HANDLES_CACHE.get(NON_NEGATIVE_LONG), - HANDLES_CACHE.get(SUCCESS_LONG), - HANDLES_CACHE.get(ERROR_LONG).bindTo(intExtractor)); - } - } - - // The methods below are reflective used via static MethodHandles - - @ForceInline - private static Arena acquireArena() { - return POOL.pushFrame(CAPTURE_LAYOUT); - } - - @ForceInline - private static MemorySegment allocate(Arena arena) { - return arena.allocate(CAPTURE_LAYOUT.byteSize(), CAPTURE_LAYOUT.byteAlignment()); - } - - @ForceInline - private static boolean nonNegative(int value) { - return value >= 0; - } - - @ForceInline - private static int success(int value, - MemorySegment segment) { - return value; - } - - @ForceInline - private static int error(MethodHandle errorHandle, - int value, - MemorySegment segment) throws Throwable { - return -(int) errorHandle.invokeExact(segment); - } - - @ForceInline - private static boolean nonNegative(long value) { - return value >= 0L; - } - - @ForceInline - private static long success(long value, - MemorySegment segment) { - return value; - } - - @ForceInline - private static long error(MethodHandle errorHandle, - long value, - MemorySegment segment) throws Throwable { - return -(int) errorHandle.invokeExact(segment); - } - - // The method handles below are bound to static methods residing in this class - - private static final int - NON_NEGATIVE_INT = 0, - SUCCESS_INT = 1, - ERROR_INT = 2, - NON_NEGATIVE_LONG = 3, - SUCCESS_LONG = 4, - ERROR_LONG = 5, - ACQUIRE_ARENA = 6, - ALLOCATE = 7, - ARENA_CLOSE = 8; - - // Do not use a lambda in order to allow early use in the init sequence - private static final IntFunction UNDERLYING_MAKE_HANDLE = new IntFunction() { - @Override - public MethodHandle apply(int value) { - return makeHandle(value); - } - }; - - private static final List HANDLES_CACHE = - List.ofLazy(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE); - - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - - private static MethodHandle makeHandle(int index) { - return switch (index) { - case NON_NEGATIVE_INT -> MhUtil.findStatic(LOOKUP, "nonNegative", - MethodType.methodType(boolean.class, int.class)); - case SUCCESS_INT -> MhUtil.findStatic(LOOKUP, "success", - MethodType.methodType(int.class, int.class, MemorySegment.class)); - case ERROR_INT -> MhUtil.findStatic(LOOKUP, "error", - MethodType.methodType(int.class, MethodHandle.class, int.class, MemorySegment.class)); - case NON_NEGATIVE_LONG -> MhUtil.findStatic(LOOKUP, "nonNegative", - MethodType.methodType(boolean.class, long.class)); - case SUCCESS_LONG -> MhUtil.findStatic(LOOKUP, "success", - MethodType.methodType(long.class, long.class, MemorySegment.class)); - case ERROR_LONG -> MhUtil.findStatic(LOOKUP, "error", - MethodType.methodType(long.class, MethodHandle.class, long.class, MemorySegment.class)); - case ACQUIRE_ARENA -> MhUtil.findStatic(LOOKUP, "acquireArena", - MethodType.methodType(Arena.class)); - case ALLOCATE -> MhUtil.findStatic(LOOKUP, "allocate", - MethodType.methodType(MemorySegment.class, Arena.class)); - case ARENA_CLOSE -> MhUtil.findVirtual(LOOKUP, Arena.class, "close", - MethodType.methodType(void.class)); - default -> throw new InternalError("Unknown index: " + index); - }; - } - -} diff --git a/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java b/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java index 6b1a071c2af..3aec538885c 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java +++ b/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 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 @@ -38,22 +38,6 @@ public SlicingAllocator(MemorySegment segment) { this.segment = segment; } - public long currentOffset() { - return sp; - } - - public void resetTo(long offset) { - if (offset < 0 || offset > sp) - throw new IllegalArgumentException(String.format("offset %d should be in [0, %d] ", offset, sp)); - this.sp = offset; - } - - public boolean canAllocate(long byteSize, long byteAlignment) { - long min = segment.address(); - long start = Utils.alignUp(min + sp, byteAlignment) - min; - return start + byteSize <= segment.byteSize(); - } - MemorySegment trySlice(long byteSize, long byteAlignment) { long min = segment.address(); long start = Utils.alignUp(min + sp, byteAlignment) - min; diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java index 37200598d5b..5e4d667b4f6 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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,11 +22,11 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ + package jdk.internal.foreign.abi; import jdk.internal.access.JavaLangInvokeAccess; import jdk.internal.access.SharedSecrets; -import jdk.internal.foreign.BufferStack; import jdk.internal.foreign.CABI; import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker; @@ -390,15 +390,29 @@ static long pickChunkOffset(long chunkOffset, long byteWidth, int chunkWidth) { : chunkOffset; } - private static final int LINKER_STACK_SIZE = Integer.getInteger("jdk.internal.foreign.LINKER_STACK_SIZE", 256); - private static final BufferStack LINKER_STACK = BufferStack.of(LINKER_STACK_SIZE, 1); - @ForceInline - public static Arena newBoundedArena(long size) { - return LINKER_STACK.pushFrame(size, 8); + static Arena newBoundedArena(long size) { + return BoundedArena.of(size); + } + + private record BoundedArena(Arena delegate, SegmentAllocator allocator) implements Arena { + @ForceInline @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { return allocator.allocate(byteSize, byteAlignment); } + @ForceInline @Override + public Scope scope() { return delegate.scope(); } + @ForceInline @Override + public void close() { delegate.close(); } + + @ForceInline + static Arena of(long size) { + final Arena arena = Arena.ofConfined(); + final SegmentAllocator allocator = SegmentAllocator.slicingAllocator(arena.allocate(size)); + return new BoundedArena(arena, allocator); + } + } - public static Arena newEmptyArena() { + static Arena newEmptyArena() { return new Arena() { final Arena arena = Arena.ofConfined(); diff --git a/test/jdk/java/foreign/TestBufferStack.java b/test/jdk/java/foreign/TestBufferStack.java deleted file mode 100644 index 6873cb6415e..00000000000 --- a/test/jdk/java/foreign/TestBufferStack.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * 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 - * @library /test/lib - * @modules java.base/jdk.internal.foreign - * @build NativeTestHelper TestBufferStack - * @run junit/othervm --enable-native-access=ALL-UNNAMED TestBufferStack - */ - -import jdk.internal.foreign.BufferStack; -import org.junit.jupiter.api.Test; - -import java.lang.foreign.Arena; -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.invoke.MethodHandle; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - -import jdk.test.lib.thread.VThreadRunner; - -import static java.lang.foreign.MemoryLayout.structLayout; -import static java.lang.foreign.ValueLayout.*; -import static org.junit.jupiter.api.Assertions.*; - -final class TestBufferStack extends NativeTestHelper { - - private static final long POOL_SIZE = 64; - private static final long SMALL_ALLOC_SIZE = JAVA_LONG.byteSize(); - - @Test - void invariants() { - var exBS = assertThrows(IllegalArgumentException.class, () -> BufferStack.of(-1, 1)); - assertEquals("Negative byteSize: -1", exBS.getMessage()); - var exBA = assertThrows(IllegalArgumentException.class, () -> BufferStack.of(1, -1)); - assertEquals("Negative byteAlignment: -1", exBA.getMessage()); - assertThrows(NullPointerException.class, () -> BufferStack.of(null)); - - BufferStack stack = newBufferStack(); - assertThrows(IllegalArgumentException.class, () -> stack.pushFrame(-1, 8)); - assertThrows(IllegalArgumentException.class, () -> stack.pushFrame(SMALL_ALLOC_SIZE, -1)); - - try (var arena = stack.pushFrame(SMALL_ALLOC_SIZE, 1)) { - assertThrows(IllegalArgumentException.class, () -> arena.allocate(-1)); - assertThrows(IllegalArgumentException.class, () -> arena.allocate(4, -1)); - } - } - - @Test - void invariantsVt() { - VThreadRunner.run(this::invariants); - } - - @Test - void testScopedAllocation() { - int stackSize = 128; - BufferStack stack = newBufferStack(); - try (Arena frame1 = stack.pushFrame(3 * JAVA_INT.byteSize(), JAVA_INT.byteAlignment())) { - // Segments have expected sizes and are accessible and allocated consecutively in the same scope. - MemorySegment segment11 = frame1.allocate(JAVA_INT); - assertEquals(frame1.scope(), segment11.scope()); - assertEquals(JAVA_INT.byteSize(), segment11.byteSize()); - segment11.set(JAVA_INT, 0, 1); - - MemorySegment segment12 = frame1.allocate(JAVA_INT); - assertEquals(segment11.address() + JAVA_INT.byteSize(), segment12.address()); - assertEquals(JAVA_INT.byteSize(), segment12.byteSize()); - assertEquals(frame1.scope(), segment12.scope()); - segment12.set(JAVA_INT, 0, 1); - - MemorySegment segment2; - try (Arena frame2 = stack.pushFrame(JAVA_LONG.byteSize(), JAVA_LONG.byteAlignment())) { - assertNotEquals(frame1.scope(), frame2.scope()); - // same here, but a new scope. - segment2 = frame2.allocate(JAVA_LONG); - assertEquals( segment12.address() + /*segment12 size + frame 1 spare + alignment constraint*/ 3 * JAVA_INT.byteSize(), segment2.address()); - assertEquals(JAVA_LONG.byteSize(), segment2.byteSize()); - assertEquals(frame2.scope(), segment2.scope()); - segment2.set(JAVA_LONG, 0, 1); - - // Frames must be closed in stack order. - assertThrows(IllegalStateException.class, frame1::close); - } - // Scope is closed here, inner segments throw. - assertThrows(IllegalStateException.class, () -> segment2.get(JAVA_INT, 0)); - // A new stack frame allocates at the same location (but different scope) as the previous did. - try (Arena frame3 = stack.pushFrame(2 * JAVA_INT.byteSize(), JAVA_INT.byteAlignment())) { - MemorySegment segment3 = frame3.allocate(JAVA_INT); - assertEquals(frame3.scope(), segment3.scope()); - assertEquals(segment12.address() + 2 * JAVA_INT.byteSize(), segment3.address()); - } - - // Fallback arena behaves like regular stack frame. - MemorySegment outOfStack; - try (Arena hugeFrame = stack.pushFrame(1024, 4)) { - outOfStack = hugeFrame.allocate(4); - assertEquals(hugeFrame.scope(), outOfStack.scope()); - assertTrue(outOfStack.asOverlappingSlice(segment11).isEmpty()); - } - assertThrows(IllegalStateException.class, () -> outOfStack.get(JAVA_INT, 0)); - - // Outer segments are still accessible. - segment11.get(JAVA_INT, 0); - segment12.get(JAVA_INT, 0); - } - } - - @Test - void testScopedAllocationVt() { - VThreadRunner.run(this::testScopedAllocation); - } - - static { - System.loadLibrary("TestBufferStack"); - } - - private static final MemoryLayout HVAPoint3D = structLayout(NativeTestHelper.C_DOUBLE, C_DOUBLE, C_DOUBLE); - private static final MemorySegment UPCALL_MH = upcallStub(TestBufferStack.class, "recurse", FunctionDescriptor.of(HVAPoint3D, C_INT)); - private static final MethodHandle DOWNCALL_MH = downcallHandle("recurse", FunctionDescriptor.of(HVAPoint3D, C_INT, ADDRESS)); - - public static MemorySegment recurse(int depth) { - try { - return (MemorySegment) DOWNCALL_MH.invokeExact((SegmentAllocator) Arena.ofAuto(), depth, UPCALL_MH); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - @Test - void testDeepStack() { - // Each downcall and upcall require 48 bytes of stack. - // After five allocations we start falling back. - MemorySegment point = recurse(10); - assertEquals( 12.0, point.getAtIndex(C_DOUBLE, 0)); - assertEquals(11.0, point.getAtIndex(C_DOUBLE, 1)); - assertEquals( 10.0, point.getAtIndex(C_DOUBLE, 2)); - } - - @Test - void testDeepStackVt() { - VThreadRunner.run(this::testDeepStack); - } - - @Test - void equals() { - var first = newBufferStack(); - var second = newBufferStack(); - assertNotEquals(first, second); - assertEquals(first, first); - } - - @Test - void allocationSameAsPoolSize() { - MemoryLayout twoInts = MemoryLayout.sequenceLayout(2, JAVA_INT); - var pool = newBufferStack(); - long firstAddress; - try (var arena = pool.pushFrame(JAVA_INT)) { - var segment = arena.allocate(JAVA_INT); - firstAddress = segment.address(); - } - for (int i = 0; i < 10; i++) { - try (var arena = pool.pushFrame(twoInts)) { - var segment = arena.allocate(JAVA_INT); - assertEquals(firstAddress, segment.address()); - var segmentTwo = arena.allocate(JAVA_INT); - assertEquals(firstAddress + JAVA_INT.byteSize(), segmentTwo.address()); - // Questionable exception type - assertThrows(IndexOutOfBoundsException.class, () -> arena.allocate(JAVA_INT)); - } - } - } - @Test - void allocationSameAsPoolSizeVt() { - VThreadRunner.run(this::allocationSameAsPoolSize); - } - - @Test - void allocateConfinement() { - var pool = newBufferStack(); - Consumer allocateAction = arena -> - assertThrows(WrongThreadException.class, () -> { - CompletableFuture future = CompletableFuture.supplyAsync(() -> pool.pushFrame(SMALL_ALLOC_SIZE, 1)); - var otherThreadArena = future.get(); - otherThreadArena.allocate(SMALL_ALLOC_SIZE); - // Intentionally do not close the otherThreadArena here. - }); - doInTwoStackedArenas(pool, allocateAction, allocateAction); - } - - @Test - void allocateConfinementVt() { - VThreadRunner.run(this::allocateConfinement); - } - - @Test - void closeConfinement() { - var pool = newBufferStack(); - Consumer closeAction = arena -> { - // Do not use CompletableFuture here as it might accidentally run on the - // same carrier thread as a virtual thread. - AtomicReference otherThreadArena = new AtomicReference<>(); - var thread = Thread.ofPlatform().start(() -> { - otherThreadArena.set(pool.pushFrame(SMALL_ALLOC_SIZE, 1)); - }); - try { - thread.join(); - } catch (InterruptedException ie) { - fail(ie); - } - assertThrows(WrongThreadException.class, otherThreadArena.get()::close); - }; - doInTwoStackedArenas(pool, closeAction, closeAction); - } - - @Test - void closeConfinementVt() { - VThreadRunner.run(this::closeConfinement); - } - - @Test - void toStringTest() { - BufferStack stack = newBufferStack(); - assertEquals("BufferStack[byteSize=" + POOL_SIZE + ", byteAlignment=1]", stack.toString()); - } - - @Test - void allocBounds() { - BufferStack stack = newBufferStack(); - try (var arena = stack.pushFrame(SMALL_ALLOC_SIZE, 1)) { - assertThrows(IllegalArgumentException.class, () -> arena.allocate(-1)); - assertDoesNotThrow(() -> arena.allocate(SMALL_ALLOC_SIZE)); - assertThrows(IndexOutOfBoundsException.class, () -> arena.allocate(SMALL_ALLOC_SIZE + 1)); - } - } - - @Test - void accessBounds() { - BufferStack stack = newBufferStack(); - try (var arena = stack.pushFrame(SMALL_ALLOC_SIZE, 1)) { - var segment = arena.allocate(SMALL_ALLOC_SIZE); - assertThrows(IndexOutOfBoundsException.class, () -> segment.get(JAVA_BYTE, SMALL_ALLOC_SIZE)); - } - } - - @Test - void noInit() { - BufferStack stack = newBufferStack(); - try (var arena = stack.pushFrame(SMALL_ALLOC_SIZE, 1)) { - assertDoesNotThrow(() -> arena.allocateFrom(JAVA_INT, 1)); - } - } - - static void doInTwoStackedArenas(BufferStack pool, - Consumer firstAction, - Consumer secondAction) { - try (var firstArena = pool.pushFrame(SMALL_ALLOC_SIZE, 1)) { - firstAction.accept(firstArena); - try (var secondArena = pool.pushFrame(SMALL_ALLOC_SIZE, 1)) { - secondAction.accept(secondArena); - } - } - } - - private static BufferStack newBufferStack() { - return BufferStack.of(POOL_SIZE, 1); - } - - -} diff --git a/test/jdk/java/foreign/TestBufferStackStress.java b/test/jdk/java/foreign/TestBufferStackStress.java deleted file mode 100644 index a0b7348331c..00000000000 --- a/test/jdk/java/foreign/TestBufferStackStress.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2025, 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 - * @modules java.base/jdk.internal.foreign - * @library /test/lib - * @run junit TestBufferStackStress - */ - -import jdk.internal.foreign.BufferStack; -import jdk.test.lib.Platform; -import org.junit.jupiter.api.Test; - -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.time.Duration; -import java.util.Arrays; -import java.util.stream.IntStream; - -import static java.lang.foreign.ValueLayout.*; -import static java.time.temporal.ChronoUnit.SECONDS; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assumptions.*; - -final class TestBufferStackStress { - - @Test - void stress() throws InterruptedException { - if (Platform.isOSX()) { - // 8350455 was only fixed in macOS versions 26 or higher - assumeFalse(Platform.getOsVersionMajor() < 26, "Unsupported OS version: " + Platform.getOsVersionMajor()); - } - - BufferStack stack = BufferStack.of(256, 1); - Thread[] vThreads = IntStream.range(0, 512).mapToObj(_ -> - Thread.ofVirtual().start(() -> { - long threadId = Thread.currentThread().threadId(); - while (!Thread.interrupted()) { - for (int i = 0; i < 500_000; i++) { - try (Arena arena = stack.pushFrame(JAVA_LONG.byteSize(), JAVA_LONG.byteAlignment())) { - // Try to assert no two vThreads get allocated the same stack space. - MemorySegment segment = arena.allocate(JAVA_LONG); - JAVA_LONG.varHandle().setVolatile(segment, 0L, threadId); - assertEquals(threadId, (long) JAVA_LONG.varHandle().getVolatile(segment, 0L)); - } - } - Thread.yield(); // make sure the driver thread gets a chance. - } - })).toArray(Thread[]::new); - Thread.sleep(Duration.of(10, SECONDS)); - Arrays.stream(vThreads).forEach( - thread -> { - assertTrue(thread.isAlive()); - thread.interrupt(); - }); - } - -} diff --git a/test/jdk/java/foreign/TestBufferStackStress2.java b/test/jdk/java/foreign/TestBufferStackStress2.java deleted file mode 100644 index cf02d5ae608..00000000000 --- a/test/jdk/java/foreign/TestBufferStackStress2.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2025, 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 - * - * @comment If the VM does not have continuations, then VTs will be scheduled on OS threads. - * @requires vm.continuations - * - * @bug 8356114 8356658 - * @modules java.base/jdk.internal.foreign - * @run junit/timeout=480 TestBufferStackStress2 - */ - -import jdk.internal.foreign.BufferStack; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; - -import java.io.FileDescriptor; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; -import java.util.concurrent.ForkJoinWorkerThread; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.junit.jupiter.api.Assertions.*; - -final class TestBufferStackStress2 { - - private static final long POOL_SIZE = 64; - private static final long SMALL_ALLOC_SIZE = 8; - - /** - * The objective with this test is to test the case when a virtual thread VT0 is - * mounted on a carrier thread CT0; VT0 is then suspended; The pool of carrier threads - * are then contracted; VT0 is then remounted on another carrier thread C1. VT0 runs - * for a while when there is a lot of GC activity. - * In other words, we are trying to establish that there is no use-after-free and that - * the original arena, from which reusable segments are initially allocated from, is - * not closed underneath. - *

- * Unfortunately, this test takes about 30 seconds as that is the time it takes for - * the pool of carrier threads to be contracted. - */ - @Test - void movingVirtualThreadWithGc() throws InterruptedException { - - // If the VM is configured such that the main thread is virtual, - // this stress test will not work as the main thread is always alive causing - // us to wait forever for contraction. - // Hence, we will skip this test if the main thread is virtual. - Assumptions.assumeFalse(Thread.currentThread().isVirtual(), - "Skipped because the main thread is a virtual thread"); - - final long begin = System.nanoTime(); - var pool = BufferStack.of(POOL_SIZE, 1); - - System.setProperty("jdk.virtualThreadScheduler.parallelism", "1"); - - var done = new AtomicBoolean(); - var completed = new AtomicBoolean(); - var quiescentLatch = new CountDownLatch(1); - - var executor = Executors.newVirtualThreadPerTaskExecutor(); - - executor.submit(() -> { - while (!done.get()) { - FileDescriptor.out.sync(); - } - return null; - }); - - executor.submit(() -> { - System.out.println(duration(begin) + "ALLOCATING = " + Thread.currentThread()); - try (Arena arena = pool.pushFrame(SMALL_ALLOC_SIZE, 1)) { - MemorySegment segment = arena.allocate(SMALL_ALLOC_SIZE); - done.set(true); - // wait for ForkJoinPool to contract - try { - quiescentLatch.await(); - } catch (Throwable ex) { - throw new AssertionError(ex); - } - System.out.println(duration(begin) + "ACCESSING SEGMENT"); - - for (int i = 0; i < 100_000; i++) { - if (i % 100 == 0) { - System.gc(); - } - segment.get(ValueLayout.JAVA_BYTE, i % SMALL_ALLOC_SIZE); - } - System.out.println(duration(begin) + "DONE ACCESSING SEGMENT"); - } - System.out.println(duration(begin) + "VT DONE"); - completed.set(true); - }); - - long count; - do { - Thread.sleep(1000); - count = Thread.getAllStackTraces().keySet().stream() - .filter(t -> t instanceof ForkJoinWorkerThread) - .count(); - } while (count > 0); - - System.out.println(duration(begin) + "FJP HAS CONTRACTED"); - quiescentLatch.countDown(); // notify the thread that accesses the MemorySegment - - System.out.println(duration(begin) + "CLOSING EXECUTOR"); - executor.close(); - System.out.println(duration(begin) + "EXECUTOR CLOSED"); - - assertTrue(completed.get(), "The VT did not complete properly"); - } - - private static String duration(Long begin) { - var duration = Duration.of(System.nanoTime() - begin, ChronoUnit.NANOS); - long seconds = duration.toSeconds(); - int nanos = duration.toNanosPart(); - return (Thread.currentThread().isVirtual() ? "VT: " : "PT: ") + - String.format("%3d:%09d ", seconds, nanos); - } - -} diff --git a/test/jdk/java/foreign/TestCaptureStateUtil.java b/test/jdk/java/foreign/TestCaptureStateUtil.java deleted file mode 100644 index c4f0e038f09..00000000000 --- a/test/jdk/java/foreign/TestCaptureStateUtil.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2024, 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 - * @summary Test CaptureStateUtil - * @modules java.base/jdk.internal.foreign - * @run junit TestCaptureStateUtil - */ - -import jdk.internal.foreign.CaptureStateUtil; -import org.junit.jupiter.api.Test; - -import java.lang.foreign.Linker; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.invoke.VarHandle; - -import static org.junit.jupiter.api.Assertions.*; - -final class TestCaptureStateUtil { - - private static final String ERRNO_NAME = "errno"; - - private static final VarHandle ERRNO_HANDLE = Linker.Option.captureStateLayout() - .varHandle(MemoryLayout.PathElement.groupElement(ERRNO_NAME)); - - private static final MethodHandle INT_DUMMY_HANDLE; - private static final MethodHandle LONG_DUMMY_HANDLE; - - static { - try { - MethodHandles.Lookup lookup = MethodHandles.lookup(); - INT_DUMMY_HANDLE = lookup - .findStatic(TestCaptureStateUtil.class, "dummy", - MethodType.methodType(int.class, MemorySegment.class, int.class, int.class)); - LONG_DUMMY_HANDLE = lookup - .findStatic(TestCaptureStateUtil.class, "dummy", - MethodType.methodType(long.class, MemorySegment.class, long.class, int.class)); - } catch (ReflectiveOperationException e) { - throw new InternalError(e); - } - } - - private static final MethodHandle ADAPTED_INT = CaptureStateUtil - .adaptSystemCall(INT_DUMMY_HANDLE, ERRNO_NAME); - private static final MethodHandle ADAPTED_LONG = CaptureStateUtil - .adaptSystemCall(LONG_DUMMY_HANDLE, ERRNO_NAME); - - @Test - void successfulInt() throws Throwable { - int r = (int) ADAPTED_INT.invokeExact(1, 0); - assertEquals(1, r); - } - - private static final int EACCES = 13; /* Permission denied */ - - @Test - void errorInt() throws Throwable { - int r = (int) ADAPTED_INT.invokeExact(-1, EACCES); - assertEquals(-EACCES, r); - } - - @Test - void successfulLong() throws Throwable { - long r = (long) ADAPTED_LONG.invokeExact(1L, 0); - assertEquals(1, r); - } - - @Test - void errorLong() throws Throwable { - long r = (long) ADAPTED_LONG.invokeExact(-1L, EACCES); - assertEquals(-EACCES, r); - } - - @Test - void successfulIntPerHandle() throws Throwable { - MethodHandle handle = CaptureStateUtil - .adaptSystemCall(INT_DUMMY_HANDLE, ERRNO_NAME); - int r = (int) handle.invokeExact(1, 0); - assertEquals(1, r); - } - - @Test - void invariants() throws Throwable { - MethodHandle noSegment = MethodHandles.lookup() - .findStatic(TestCaptureStateUtil.class, "wrongType", - MethodType.methodType(long.class, long.class, int.class)); - - var noSegEx = assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(noSegment, ERRNO_NAME)); - assertTrue(noSegEx.getMessage().contains("does not have a MemorySegment as the first parameter")); - - MethodHandle noArgMH = MethodHandles.empty(MethodType.methodType(int.class)); - var emptyEx = assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(noArgMH, ERRNO_NAME)); - assertTrue(emptyEx.getMessage().contains("does not have a MemorySegment as the first parameter")); - - MethodHandle wrongReturnType = MethodHandles.lookup() - .findStatic(TestCaptureStateUtil.class, "wrongType", - MethodType.methodType(short.class, MemorySegment.class, long.class, int.class)); - - var wrongRetEx = assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(wrongReturnType, ERRNO_NAME)); - assertTrue(wrongRetEx.getMessage().contains("does not return an int or a long")); - - var wrongCaptureName = assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(LONG_DUMMY_HANDLE, "foo")); - assertEquals("Input not allowed: SegmentExtractorKey[returnType=long, stateName=foo]", wrongCaptureName.getMessage()); - - assertThrows(NullPointerException.class, () -> CaptureStateUtil.adaptSystemCall(null, ERRNO_NAME)); - assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(noSegment, null)); - } - - // Dummy method that is just returning the provided parameters - private static int dummy(MemorySegment segment, int result, int errno) { - ERRNO_HANDLE.set(segment, 0, errno); - return result; - } - - // Dummy method that is just returning the provided parameters - private static long dummy(MemorySegment segment, long result, int errno) { - ERRNO_HANDLE.set(segment, 0, errno); - return result; - } - - private static long wrongType(long result, int errno) { - return 0; - } - - private static short wrongType(MemorySegment segment, long result, int errno) { - return 0; - } - -} diff --git a/test/jdk/java/foreign/libTestBufferStack.c b/test/jdk/java/foreign/libTestBufferStack.c deleted file mode 100644 index 79eb32bf933..00000000000 --- a/test/jdk/java/foreign/libTestBufferStack.c +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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. - */ - -#include "export.h" - -typedef struct { double x, y, z; } HVAPoint3D; - -EXPORT HVAPoint3D recurse(int depth, HVAPoint3D (*cb)(int)) { - if (depth == 0) { - HVAPoint3D result = { 2, 1, 0}; - return result; - } - - HVAPoint3D result = cb(depth - 1); - result.x += 1; - result.y += 1; - result.z += 1; - return result; -} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/BufferStackBench.java b/test/micro/org/openjdk/bench/java/lang/foreign/BufferStackBench.java deleted file mode 100644 index 06f5a352209..00000000000 --- a/test/micro/org/openjdk/bench/java/lang/foreign/BufferStackBench.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - * - */ - -package org.openjdk.bench.java.lang.foreign; - -import jdk.internal.foreign.BufferStack; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Warmup; - -import java.lang.foreign.Arena; -import java.util.concurrent.TimeUnit; - -@BenchmarkMode(Mode.AverageTime) -@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) -@Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) -@State(Scope.Thread) -@OutputTimeUnit(TimeUnit.NANOSECONDS) -@Fork(value = 3, jvmArgs = {"--add-exports=java.base/jdk.internal.foreign=ALL-UNNAMED"}) -public class BufferStackBench { - - @Param({"8", "16", "32"}) - public int ELEM_SIZE; - - private BufferStack bufferStack; - - @Setup - public void setup() { - bufferStack = BufferStack.of(128); - } - - @Benchmark - public long confined() { - try (Arena arena = Arena.ofConfined()) { - return arena.allocate(ELEM_SIZE).address(); - } - } - - @Benchmark - public long buffer() { - try (Arena arena = bufferStack.pushFrame(64, 1)) { - return arena.allocate(ELEM_SIZE).address(); - } - } - - @Fork(value = 3, jvmArgsAppend = "-Djmh.executor=VIRTUAL") - public static class OfVirtual extends BufferStackBench {} - -} - diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/CaptureStateUtilBench.java b/test/micro/org/openjdk/bench/java/lang/foreign/CaptureStateUtilBench.java deleted file mode 100644 index c833444d713..00000000000 --- a/test/micro/org/openjdk/bench/java/lang/foreign/CaptureStateUtilBench.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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. - */ - -package org.openjdk.bench.java.lang.foreign; - -import jdk.internal.foreign.CaptureStateUtil; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.Warmup; - -import java.lang.foreign.Arena; -import java.lang.foreign.Linker; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.MemorySegment; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.invoke.VarHandle; -import java.util.concurrent.TimeUnit; - -@BenchmarkMode(Mode.AverageTime) -@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) -@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) -@State(Scope.Benchmark) -@OutputTimeUnit(TimeUnit.NANOSECONDS) -@Fork(value = 3, jvmArgs = {"--add-exports=java.base/jdk.internal.foreign=ALL-UNNAMED", - "--enable-native-access=ALL-UNNAMED"}) -public class CaptureStateUtilBench { - - private static final String ERRNO_NAME = "errno"; - - private static final VarHandle ERRNO_HANDLE = Linker.Option.captureStateLayout() - .varHandle(MemoryLayout.PathElement.groupElement(ERRNO_NAME)); - - private static final long SIZE = Linker.Option.captureStateLayout().byteSize(); - - private static final MethodHandle DUMMY_EXPLICIT_ALLOC = dummyExplicitAlloc(); - private static final MethodHandle DUMMY_TL_ALLOC = dummyTlAlloc(); - - @Benchmark - public int explicitAllocationSuccess() throws Throwable { - try (var arena = Arena.ofConfined()) { - return (int) DUMMY_EXPLICIT_ALLOC.invokeExact(arena.allocate(SIZE), 0, 0); - } - } - - @Benchmark - public int explicitAllocationFail() throws Throwable { - try (var arena = Arena.ofConfined()) { - return (int) DUMMY_EXPLICIT_ALLOC.invokeExact(arena.allocate(SIZE), -1, 1); - } - } - - @Benchmark - public int adaptedSysCallSuccess() throws Throwable { - return (int) DUMMY_TL_ALLOC.invokeExact(0, 0); - } - - @Benchmark - public int adaptedSysCallFail() throws Throwable { - return (int) DUMMY_TL_ALLOC.invokeExact( -1, 1); - } - - private static MethodHandle dummyExplicitAlloc() { - try { - return MethodHandles.lookup().findStatic(CaptureStateUtilBench.class, - "dummy", MethodType.methodType(int.class, MemorySegment.class, int.class, int.class)); - } catch (ReflectiveOperationException roe) { - throw new RuntimeException(roe); - } - } - - private static MethodHandle dummyTlAlloc() { - final MethodHandle handle = dummyExplicitAlloc(); - return CaptureStateUtil.adaptSystemCall(handle, ERRNO_NAME); - } - - // Dummy method that is just returning the provided parameters - private static int dummy(MemorySegment segment, int result, int errno) { - if (errno != 0) { - // Assuming the capture state is only modified upon detecting an error. - ERRNO_HANDLE.set(segment, 0, errno); - } - return result; - } - - @Fork(value = 3, jvmArgsAppend = "-Djmh.executor=VIRTUAL") - public static class OfVirtual extends CaptureStateUtilBench {} - -} From a6b5c4ba7ce8036d35c547d705552f7e850fccbd Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Wed, 14 Oct 2026 09:45:15 +0000 Subject: [PATCH 087/101] 8392597: ZGC: Test gc/metaspace/TestMetaspaceFirstGC.java still fails after JDK-8391711 Reviewed-by: mgronlun, stefank --- test/hotspot/jtreg/ProblemList.txt | 1 - .../gc/metaspace/TestMetaspaceFirstGC.java | 288 ++++++++++++------ 2 files changed, 197 insertions(+), 92 deletions(-) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 3c5950c771a..9e4d24efc3b 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -89,7 +89,6 @@ gc/stress/jfr/TestStressAllocationGCEventsWithShenandoah.java#generational 83869 gc/stress/jfr/TestStressAllocationGCEventsWithShenandoah.java#default 8386964 generic-all gc/stress/jfr/TestStressBigAllocationGCEventsWithShenandoah.java#generational 8386964 generic-all gc/stress/jfr/TestStressBigAllocationGCEventsWithShenandoah.java#default 8386964 generic-all -gc/metaspace/TestMetaspaceFirstGC.java 8392597 generic-all ############################################################################# diff --git a/test/hotspot/jtreg/gc/metaspace/TestMetaspaceFirstGC.java b/test/hotspot/jtreg/gc/metaspace/TestMetaspaceFirstGC.java index f832ffdfc2a..d8ba25ca9f5 100644 --- a/test/hotspot/jtreg/gc/metaspace/TestMetaspaceFirstGC.java +++ b/test/hotspot/jtreg/gc/metaspace/TestMetaspaceFirstGC.java @@ -23,14 +23,17 @@ /* * @test TestMetaspaceFirstGC - * @bug 8208250 8391711 - * @summary Verify that the first metaspace GC is triggered when metaspace reaches the MetaspaceSize threshold + * @bug 8208250 8391711 8392597 + * @summary Verify that the first metadata GC request seen after startup is made when metaspace reaches the MetaspaceSize threshold * @requires vm.hasJFR + * @requires vm.gc != "Shenandoah" * @library /test/lib - * @run main/othervm -Xms200m -XX:StartFlightRecording:name=startup TestMetaspaceFirstGC - * @run main/othervm -Xms200m -XX:MetaspaceSize=10m -XX:StartFlightRecording:name=startup TestMetaspaceFirstGC 10m - * @run main/othervm -Xms200m -XX:MetaspaceSize=50m -XX:StartFlightRecording:name=startup TestMetaspaceFirstGC 50m - * @run main/othervm -Xms200m -XX:MetaspaceSize=99m -XX:StartFlightRecording:name=startup TestMetaspaceFirstGC 99m + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xms200m -XX:MinMetaspaceFreeRatio=0 -XX:StartFlightRecording:name=startup TestMetaspaceFirstGC + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xms200m -XX:MinMetaspaceFreeRatio=0 -XX:MetaspaceSize=10m -XX:StartFlightRecording:name=startup TestMetaspaceFirstGC 10m + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xms200m -XX:MinMetaspaceFreeRatio=0 -XX:MetaspaceSize=50m -XX:StartFlightRecording:name=startup TestMetaspaceFirstGC 50m + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xms200m -XX:MinMetaspaceFreeRatio=0 -XX:MetaspaceSize=99m -XX:StartFlightRecording:name=startup TestMetaspaceFirstGC 99m */ import java.lang.management.ManagementFactory; @@ -40,27 +43,59 @@ import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; +import java.time.Instant; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import jdk.jfr.Event; import jdk.jfr.FlightRecorder; +import jdk.jfr.Name; import jdk.jfr.Recording; +import jdk.jfr.StackTrace; import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedFrame; +import jdk.jfr.consumer.RecordedStackTrace; import jdk.jfr.consumer.RecordingFile; import jdk.jfr.consumer.RecordingStream; import jdk.test.lib.Asserts; import jdk.test.lib.jfr.EventNames; +import jdk.test.whitebox.WhiteBox; import jtreg.SkippedException; public class TestMetaspaceFirstGC { private static int classCounter = 0; + // written on the loading thread right before every class load, on the same clock as the + // VM events, the one right before the failed allocation is the closest look at the request + @Name("TestMetaspaceFirstGC.LoadSample") + @StackTrace(false) + private static class LoadSample extends Event { + long committed; + long threshold; + } + + // the candidate allocation failure happened inside loadOneClass + private static boolean fromLoadOneClass(RecordedEvent event) { + RecordedStackTrace stackTrace = event.getStackTrace(); + if (stackTrace == null) { + return false; + } + for (RecordedFrame frame : stackTrace.getFrames()) { + if (frame.getMethod().getType().getName().equals("TestMetaspaceFirstGC") + && frame.getMethod().getName().equals("loadOneClass")) { + return true; + } + } + return false; + } + // kept alive so no collection can unload them before the threshold is reached + private static final List loaders = new ArrayList<>(); - // Counted down from the JFR stream when a collection with cause "Metadata GC Threshold" - // arrives, so the event is already in hand when loading stops. + // Counted down from the JFR stream when a metadata allocation failure inside loadOneClass + // arrives, whether it was the first request is decided afterwards from the recording. private static final CountDownLatch metadataGC = new CountDownLatch(1); public interface Dummy {} @@ -78,117 +113,175 @@ public static void main(String[] args) throws Exception { expectedSize = parseSize(args[0]); } - long committedAtStart = ManagementFactory.getMemoryPoolMXBeans().stream() - .filter(p -> p.getName().equals("Metaspace")) - .mapToLong(p -> p.getUsage().getCommitted()) - .findFirst() - .orElseThrow(() -> new RuntimeException("Metaspace pool not found")); - System.out.println("Metaspace committed at start: " + committedAtStart); - if (expectedSize > 0 && committedAtStart >= expectedSize) { - // the first metadata GC already happened during VM startup, nothing left to observe - throw new SkippedException("metaspace committed at start (" + committedAtStart - + ") already at MetaspaceSize (" + expectedSize + ")"); - } - List events; try (RecordingStream rs = new RecordingStream()) { - rs.enable(EventNames.GarbageCollection); - rs.onEvent(EventNames.GarbageCollection, event -> { - if ("Metadata GC Threshold".equals(event.getString("cause"))) { + // a concurrent collector may fold the requested GC into a cycle that is already + // running and never emit the cause, the failed allocation inside loadOneClass is the + // candidate, whether a GC was really asked for is checked afterwards + rs.enable(EventNames.MetaspaceAllocationFailure).withStackTrace(); + rs.onEvent(EventNames.MetaspaceAllocationFailure, event -> { + if (fromLoadOneClass(event)) { metadataGC.countDown(); } }); rs.startAsync(); + // load what the measured window touches before it opens, the recording is checked + // afterwards for any other allocation failure inside it + WhiteBox.getWhiteBox().metaspaceCapacityUntilGC(); + getMetaspaceCommitted(); + getMetaspaceUsed(); + new LoadSample().commit(); + Instant loadingStart = Instant.now(); + // metaspace commits in 64K granules and one class load allocates a few KB, the sample + // is taken right before the load that failed + long tolerance = 1024 * 1024; + long initialThreshold = WhiteBox.getWhiteBox().metaspaceCapacityUntilGC(); + System.out.println("Initial metaspace GC threshold: " + initialThreshold); + long metaspaceSize = WhiteBox.getWhiteBox().getSizeTVMFlag("MetaspaceSize"); + if (expectedSize > 0) { + Asserts.assertEquals(metaspaceSize, expectedSize, "MetaspaceSize as set on the command line"); + } else { + // the platform dependent ergonomic default + Asserts.assertGreaterThan(metaspaceSize, 11_500_000L, "default MetaspaceSize (" + metaspaceSize + ") too small"); + Asserts.assertLessThan(metaspaceSize, 22_500_000L, "default MetaspaceSize (" + metaspaceSize + ") too large"); + } + if (initialThreshold != metaspaceSize) { + // startup already moved the threshold, the first metadata GC request can't be measured + throw new SkippedException("threshold already at " + initialThreshold + + ", not MetaspaceSize " + metaspaceSize); + } - // Load classes until a metaspace-triggered GC happens - loadClassesUntilGC(50000); + // Load classes until the metadata allocation failure shows up + try { + loadClassesUntilGC(50000); + } catch (RuntimeException e) { + System.out.println("Threshold now " + WhiteBox.getWhiteBox().metaspaceCapacityUntilGC() + + ", metaspace used=" + getMetaspaceUsed()); + for (Recording r : FlightRecorder.getFlightRecorder().getRecordings()) { + if ("startup".equals(r.getName())) { + r.dump(Path.of("metaspace-first-gc-" + (expectedSize > 0 ? args[0] : "default") + "-failed.jfr")); + } + } + throw e; + } + // one more sample closes the window of the last candidate before anything is released + LoadSample end = new LoadSample(); + end.committed = getMetaspaceCommitted(); + end.threshold = WhiteBox.getWhiteBox().metaspaceCapacityUntilGC(); + end.commit(); + loaders.clear(); rs.stop(); - // The startup recording has run since VM start, so it holds the GC's "Before GC" - // summary and the first threshold change even when the first metadata GC happens - // during JFR initialization, before this stream existed. + // The startup recording begins during VM initialization, before this stream existed, + // so it holds the earlier events too, anything before JFR started is out of reach. Recording startup = FlightRecorder.getFlightRecorder().getRecordings().stream() .filter(r -> "startup".equals(r.getName())) .findFirst() .orElseThrow(() -> new RuntimeException("startup recording not found")); startup.stop(); - Path dump = Path.of("metaspace-first-gc.jfr"); + Path dump = Path.of("metaspace-first-gc-" + (expectedSize > 0 ? args[0] : "default") + ".jfr"); startup.dump(dump); events = new ArrayList<>(RecordingFile.readAllEvents(dump)); events.sort(Comparator.comparing(RecordedEvent::getStartTime)); - // Find first GarbageCollection with cause "Metadata GC Threshold" - RecordedEvent gcEvent = null; + // The candidate is the first failed metadata allocation inside loadOneClass. A threshold + // change or a metadata GC starting before the next sample shows the collector was asked, + // the window may hold more failures and the collection may finish later. The sample before + // the failing load gives committed and the threshold, committed has to be at the threshold + // within the tolerance, below it the request was premature. A later threshold change is not + // guaranteed, when there is one it starts at or above the sampled threshold. A failed + // allocation elsewhere or a threshold change before the candidate skips the run, a sampled + // threshold that differs from the initial one with no change recorded is a failure. + // Shenandoah without class unloading expands without asking and is excluded. + RecordedEvent request = null; + int startupFailures = 0; + int earlierFailures = 0; for (RecordedEvent event : events) { - if (event.getEventType().getName().equals(EventNames.GarbageCollection)) { - String cause = event.getString("cause"); - if ("Metadata GC Threshold".equals(cause)) { - gcEvent = event; - break; - } + if (!event.getEventType().getName().equals(EventNames.MetaspaceAllocationFailure)) { + continue; } + if (!event.getStartTime().isAfter(loadingStart)) { + startupFailures++; + continue; + } + if (fromLoadOneClass(event)) { + request = event; + break; + } + earlierFailures++; } - - if (gcEvent == null) { - throw new RuntimeException("No GC with cause 'Metadata GC Threshold' found"); - } - - int gcId = gcEvent.getInt("gcId"); - System.out.println("Found Metadata GC Threshold GC, gcId=" + gcId); - - // Find matching MetaspaceSummary with same gcId and when="Before GC" - RecordedEvent msEvent = null; + Asserts.assertNotNull(request, "no metaspace allocation failure inside loadOneClass"); + Instant nextLoad = null; for (RecordedEvent event : events) { - if (event.getEventType().getName().equals(EventNames.MetaspaceSummary)) { - if (event.getInt("gcId") == gcId && "Before GC".equals(event.getString("when"))) { - msEvent = event; - break; - } + if (event.getEventType().getName().equals("TestMetaspaceFirstGC.LoadSample") + && event.getStartTime().isAfter(request.getStartTime())) { + nextLoad = event.getStartTime(); + break; } } - - if (msEvent == null) { - for (RecordedEvent e : events) { - if (e.getEventType().getName().equals(EventNames.MetaspaceSummary)) { - System.out.println("MetaspaceSummary gcId=" + e.getInt("gcId") + " when=" + e.getString("when")); + long thresholdAfterRequest = -1; + boolean metadataGcSeen = false; + boolean summaryLogged = false; + int changesBefore = 0; + for (RecordedEvent event : events) { + if (!event.getStartTime().isAfter(loadingStart)) { + continue; + } + String type = event.getEventType().getName(); + boolean beforeRequest = event.getStartTime().isBefore(request.getStartTime()); + if (type.equals(EventNames.MetaspaceGCThreshold)) { + if (beforeRequest) { + changesBefore++; + System.out.println("Threshold changed before the candidate: " + event.getLong("oldValue") + + " -> " + event.getLong("newValue") + " by " + event.getString("updater")); + } else if (thresholdAfterRequest < 0 + && (nextLoad == null || !event.getStartTime().isAfter(nextLoad))) { + thresholdAfterRequest = event.getLong("oldValue"); } + } else if (type.equals(EventNames.GarbageCollection) && !beforeRequest + && (nextLoad == null || !event.getStartTime().isAfter(nextLoad)) + && "Metadata GC Threshold".equals(event.getString("cause"))) { + metadataGcSeen = true; + } else if (type.equals(EventNames.MetaspaceSummary) && !beforeRequest && !summaryLogged) { + summaryLogged = true; + System.out.println("Summary after the candidate: " + event.getString("when") + " gcId=" + + event.getLong("gcId") + " committed=" + event.getLong("metaspace.committed") + + " gcThreshold=" + event.getLong("gcThreshold")); } - throw new RuntimeException("No MetaspaceSummary 'Before GC' found for gcId=" + gcId); } - - long committed = msEvent.getLong("metaspace.committed"); - long gcThreshold = msEvent.getLong("gcThreshold"); - System.out.println("MetaspaceSummary: committed=" + committed + " gcThreshold=" + gcThreshold); - - // committed should be reasonably close to gcThreshold - long tolerance = 5 * 1024 * 1024; // 5MB tolerance - Asserts.assertLessThanOrEqual(Math.abs(committed - gcThreshold), tolerance, - "committed (" + committed + ") should be close to gcThreshold (" + gcThreshold + ")"); - // The threshold at which the first metadata GC was requested. A concurrent collector - // expands it and keeps allocating during the GC, the earliest change keeps the original value. - long firstThreshold = gcThreshold; + if (startupFailures > 0 || earlierFailures > 0 || changesBefore > 0) { + throw new SkippedException(startupFailures + " allocation failures before loading started, " + + earlierFailures + " other allocation failures and " + changesBefore + + " threshold changes before the candidate, the first metadata GC request can't be measured"); + } + if (thresholdAfterRequest < 0 && !metadataGcSeen) { + // nothing shows a GC was asked for, the retry may have gone through without one + throw new SkippedException("no threshold change and no metadata GC starting in the candidate's window"); + } + RecordedEvent atRequest = null; for (RecordedEvent event : events) { - if (event.getEventType().getName().equals(EventNames.MetaspaceGCThreshold)) { - firstThreshold = event.getLong("oldValue"); - System.out.println("First threshold change: " + firstThreshold + " -> " - + event.getLong("newValue") + " by " + event.getString("updater")); + if (!event.getEventType().getName().equals("TestMetaspaceFirstGC.LoadSample")) { + continue; + } + if (event.getStartTime().isAfter(request.getStartTime())) { break; } + atRequest = event; } - - // If explicit MetaspaceSize given, the first GC must have been triggered at it - if (expectedSize > 0) { - Asserts.assertLessThanOrEqual(Math.abs(firstThreshold - expectedSize), tolerance, - "first threshold (" + firstThreshold + ") should be close to MetaspaceSize (" + expectedSize + ")"); - System.out.println("first threshold matches expected MetaspaceSize=" + expectedSize); - } else { - // No explicit MetaspaceSize, check default range (~12MB to ~20MB per tuning guide) - Asserts.assertGreaterThan(firstThreshold, 11_500_000L, - "default threshold (" + firstThreshold + ") too small"); - Asserts.assertLessThan(firstThreshold, 22_500_000L, - "default threshold (" + firstThreshold + ") too large"); - System.out.println("first threshold in expected default range"); + Asserts.assertNotNull(atRequest, "no sample before the request"); + long committedAtRequest = atRequest.getLong("committed"); + long thresholdAtRequest = atRequest.getLong("threshold"); + System.out.println("Before the failing load: committed=" + committedAtRequest + + " threshold=" + thresholdAtRequest + + (thresholdAfterRequest < 0 ? ", no threshold change in the window" : ", next change in the window from " + thresholdAfterRequest)); + if (thresholdAfterRequest >= 0) { + Asserts.assertGreaterThanOrEqual(thresholdAfterRequest, thresholdAtRequest, + "the threshold change after the request should start at or above the sampled threshold"); } + Asserts.assertLessThanOrEqual(Math.abs(thresholdAtRequest - committedAtRequest), tolerance, + "committed before the request (" + committedAtRequest + ") should be at the threshold (" + thresholdAtRequest + ")"); + Asserts.assertEquals(thresholdAtRequest, initialThreshold, + "the first metadata GC should have been requested at the initial threshold"); System.out.println("PASSED"); } @@ -196,30 +289,43 @@ public static void main(String[] args) throws Exception { private static void loadClassesUntilGC(int maxIterations) throws InterruptedException { for (int i = 0; i < maxIterations; i++) { + LoadSample sample = new LoadSample(); + sample.committed = getMetaspaceCommitted(); + sample.threshold = WhiteBox.getWhiteBox().metaspaceCapacityUntilGC(); + sample.commit(); loadOneClass(); if (metadataGC.getCount() == 0) { - System.out.println("Metadata GC seen after " + (i + 1) + " class loads, metaspace used=" + getMetaspaceUsed()); + System.out.println("Metadata allocation failure seen after " + (i + 1) + " load attempts, metaspace used=" + getMetaspaceUsed()); return; } } - // a concurrent collector may still be running the collection + // the event may still be on its way from the stream if (metadataGC.await(60, TimeUnit.SECONDS)) { - System.out.println("Metadata GC seen after " + maxIterations + " class loads, metaspace used=" + getMetaspaceUsed()); + System.out.println("Metadata allocation failure seen after " + maxIterations + " load attempts, metaspace used=" + getMetaspaceUsed()); return; } - throw new RuntimeException("No metaspace GC after " + maxIterations + " class loads"); + throw new RuntimeException("No metadata allocation failure inside loadOneClass after " + maxIterations + " load attempts"); } private static void loadOneClass() { try { String jarUrl = "file:" + (classCounter++) + ".jar"; URLClassLoader cl = new URLClassLoader(new URL[]{new URL(jarUrl)}); + loaders.add(cl); Proxy.newProxyInstance(cl, new Class[]{Dummy.class}, new DummyHandler()); } catch (Exception e) { throw new RuntimeException(e); } } + private static long getMetaspaceCommitted() { + return ManagementFactory.getMemoryPoolMXBeans().stream() + .filter(p -> p.getName().equals("Metaspace")) + .mapToLong(p -> p.getUsage().getCommitted()) + .findFirst() + .orElseThrow(() -> new RuntimeException("Metaspace pool not found")); + } + private static long getMetaspaceUsed() { return ManagementFactory.getMemoryPoolMXBeans().stream() .filter(p -> p.getName().equals("Metaspace")) From 82f5b10574b5f103297bb4c1ca1f7ca285b1c0c1 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 14 Oct 2026 10:53:41 +0000 Subject: [PATCH 088/101] 8394303: RISC-V: Simplify C1 compare-and-swap result handling Reviewed-by: fyang, gcao --- src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp | 5 +---- src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp | 5 ++--- src/hotspot/share/c1/c1_LIR.cpp | 4 +++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index 7560d2ed3de..a105faf9137 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -1386,7 +1386,7 @@ void LIR_Assembler::emit_compare_and_swap(LIR_OpCompareAndSwap* op) { if (op->result_opr()->is_valid()) { assert(op->result_opr()->is_register(), "need a register"); - __ mv(as_reg(op->result_opr()), t0); // cas result in t0, and 0 for success + __ mv(as_reg(op->result_opr()), t0); // cas result in t0: 1 on success, 0 otherwise } } @@ -2317,17 +2317,14 @@ address LIR_Assembler::int_constant(jlong n) { void LIR_Assembler::casw(Register addr, Register newval, Register cmpval) { __ cmpxchg(addr, cmpval, newval, Assembler::int32, memory_order_seq_cst, t0, /* result as bool */ true); - __ seqz(t0, t0); // cmpxchg not equal, set t0 to 1 } void LIR_Assembler::caswu(Register addr, Register newval, Register cmpval) { __ cmpxchg(addr, cmpval, newval, Assembler::uint32, memory_order_seq_cst, t0, /* result as bool */ true); - __ seqz(t0, t0); // cmpxchg not equal, set t0 to 1 } void LIR_Assembler::casl(Register addr, Register newval, Register cmpval) { __ cmpxchg(addr, cmpval, newval, Assembler::int64, memory_order_seq_cst, t0, /* result as bool */ true); - __ seqz(t0, t0); // cmpxchg not equal, set t0 to 1 } void LIR_Assembler::deoptimize_trap(CodeEmitInfo *info) { diff --git a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp index 98adfdd7a11..a3594d15fee 100644 --- a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp @@ -596,13 +596,12 @@ LIR_Opr LIRGenerator::atomic_cmpxchg(BasicType type, LIR_Opr addr, LIRItem& cmp_ if (is_reference_type(type)) { __ cas_obj(addr, cmp_value.result(), new_value.result(), new_register(T_INT), new_register(T_INT), result); } else if (type == T_INT) { - __ cas_int(addr->as_address_ptr()->base(), cmp_value.result(), new_value.result(), ill, ill); + __ cas_int(addr->as_address_ptr()->base(), cmp_value.result(), new_value.result(), ill, ill, result); } else if (type == T_LONG) { - __ cas_long(addr->as_address_ptr()->base(), cmp_value.result(), new_value.result(), ill, ill); + __ cas_long(addr->as_address_ptr()->base(), cmp_value.result(), new_value.result(), ill, ill, result); } else { ShouldNotReachHere(); } - __ logical_xor(FrameMap::r5_opr, LIR_OprFact::intConst(1), result); return result; } diff --git a/src/hotspot/share/c1/c1_LIR.cpp b/src/hotspot/share/c1/c1_LIR.cpp index 79b415a60f9..4718f67e27a 100644 --- a/src/hotspot/share/c1/c1_LIR.cpp +++ b/src/hotspot/share/c1/c1_LIR.cpp @@ -1265,8 +1265,10 @@ void LIR_List::set_cmp_oprs(LIR_Op* op) { case lir_cas_long: case lir_cas_obj: case lir_cas_int: + // CAS returns 1 on success and 0 on failure. Compare against 1 so + // a subsequent lir_cond_equal branch or conditional move tests for success. _cmp_opr1 = op->as_OpCompareAndSwap()->result_opr(); - _cmp_opr2 = LIR_OprFact::intConst(0); + _cmp_opr2 = LIR_OprFact::intConst(1); break; #if INCLUDE_ZGC case lir_xloadbarrier_test: From 39c6f4bb130c6d343f1698a565791f3061ae8d15 Mon Sep 17 00:00:00 2001 From: Frederic Thevenet Date: Wed, 14 Oct 2026 12:36:28 +0000 Subject: [PATCH 089/101] 8392686: Don't attempt to reserve address space for compressed classes as unscaled or zero-based encoding with COH enabled Reviewed-by: stuefe, iklam --- src/hotspot/share/memory/metaspace.cpp | 4 +- ...essedCPUSpecificClassSpaceReservation.java | 39 +++++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp index e5f2026e458..8cc67d77dad 100644 --- a/src/hotspot/share/memory/metaspace.cpp +++ b/src/hotspot/share/memory/metaspace.cpp @@ -791,7 +791,9 @@ void Metaspace::global_initialize() { // ...failing that, reserve anywhere, but let platform do optimized placement: if (!rs.is_reserved()) { log_info(metaspace)("Reserving compressed class space anywhere"); - rs = Metaspace::reserve_address_space_for_compressed_classes(size, true); + // No need to optimize for zero based encoding if Compressed Object Headers are enabled + const bool optimize_for_zero_base = !UseCompactObjectHeaders; + rs = Metaspace::reserve_address_space_for_compressed_classes(size, optimize_for_zero_base); } // ...failing that, give up. diff --git a/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java b/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java index 89c39b57435..0bab4669e2c 100644 --- a/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java +++ b/test/hotspot/jtreg/runtime/CompressedOops/CompressedCPUSpecificClassSpaceReservation.java @@ -53,7 +53,7 @@ public class CompressedCPUSpecificClassSpaceReservation { // +SimulateFullAddressSpace (VM won't be able to reserve heap). Therefore we exclude the test for windows // for now. - private static void do_test(boolean CDS) throws IOException { + private static void do_test(boolean CDS, boolean COH) throws IOException { // We start the VM with -XX:+SimulateFullAdressSpace, which means the JVM will go through all motions // of reserving the cds+class space, but never succeed. That means we see every single allocation attempt. // We start with -Xlog options enabled. The expected output goes like this: @@ -62,22 +62,33 @@ private static void do_test(boolean CDS) throws IOException { "-Xshare:" + (CDS ? "on" : "off"), "-Xmx128m", "-XX:CompressedClassSpaceSize=128m", - "-XX:-UseCompactObjectHeaders", + "-XX:" + (COH ? "+" : "-") +"UseCompactObjectHeaders", "-Xlog:metaspace*", "-Xlog:metaspace+map=trace", "-Xlog:os+map=trace", "-XX:+SimulateFullAddressSpace", // So that no resevation attempt will succeed "-version"); OutputAnalyzer output = new OutputAnalyzer(pb.start()); - final String tryReserveForUnscaled = "reserve_between (range [0x0000000000000000-0x0000000100000000)"; + final boolean doNotOptimizeForZeroBased = CDS || COH; final String tryReserveBelow4G = "reserve_between (range [0x0000000000000000-0x0000000100000000)"; - final String tryReserveForZeroBased = "reserve_between (range [0x0000000100000000-0x0000000800000000)"; + // The encoding range for unscaled depends on the size of narrow class pointer, + // which is dictated by the status of Compressed Object Headers: + // +COH: [0, 2^22) + // -COH: [0, 2^32) + final String tryReserveForUnscaled = "reserve_between (range [" + + (COH ? "0x0000000000000000-0x0000000000400000" : "0x0000000000000000-0x0000000100000000") + ")"; + // The encoding range for zero based depends on the size of narrow class pointer and max shift, + // which are dictated by the status of Compressed Object Headers: + // +COH: [2^22, 2^32) + // -COH: [0, 2^35) + final String tryReserveForZeroBased = "reserve_between (range [" + + (COH ? "0x0000000000400000-0x0000000100000000" : "0x0000000100000000-0x0000000800000000") + ")"; // Failing zero-based allocation, platforms will often attempt allocation suitable for a disjointed move: // an insert of the base address bits into the register holding the nK. That requires the base to not // intersect with nK bits (for simplicity, we always assume nK range of 32bits). // The upper limit of this reservation attempt is platform-dependent, though. final String tryReserveFor16bitMoveIntoQ3Regex = "reserve_between.*0x0000000100000000-0x\\d{8}00000000.*alignment 0x100000000"; if (Platform.isAArch64()) { - if (CDS) { + if (doNotOptimizeForZeroBased) { output.shouldNotContain(tryReserveForUnscaled); } else { output.shouldContain(tryReserveForUnscaled); @@ -85,7 +96,7 @@ private static void do_test(boolean CDS) throws IOException { output.shouldContain("Trying to reserve at an EOR-compatible address"); output.shouldNotContain(tryReserveForZeroBased); } else if (Platform.isPPC()) { - if (CDS) { + if (doNotOptimizeForZeroBased) { output.shouldNotContain(tryReserveForUnscaled); output.shouldNotContain(tryReserveForZeroBased); } else { @@ -101,7 +112,7 @@ private static void do_test(boolean CDS) throws IOException { output.shouldContain("reserve_between (range [0x0000100000000000-0xffffffffffffffff)"); } else if (Platform.isS390x()) { output.shouldContain(tryReserveBelow4G); // unconditionally - if (CDS) { + if (doNotOptimizeForZeroBased) { output.shouldNotContain(tryReserveForZeroBased); } else { output.shouldContain(tryReserveForZeroBased); @@ -109,7 +120,7 @@ private static void do_test(boolean CDS) throws IOException { output.shouldMatch(tryReserveFor16bitMoveIntoQ3Regex); } else if (Platform.isX64()) { output.shouldContain(tryReserveBelow4G); - if (CDS) { + if (doNotOptimizeForZeroBased) { output.shouldNotContain(tryReserveForZeroBased); } else { output.shouldContain(tryReserveForZeroBased); @@ -146,9 +157,13 @@ private static void do_test(boolean CDS) throws IOException { } public static void main(String[] args) throws Exception { - System.out.println("Test with CDS"); - do_test(true); - System.out.println("Test without CDS"); - do_test(false); + System.out.println("Test with CDS / without COH"); + do_test(true, false); + System.out.println("Test without CDS / without COH"); + do_test(false, false); + System.out.println("Test with CDS / with COH"); + do_test(true, true); + System.out.println("Test without CDS / with COH"); + do_test(false, true); } } From e2e948ada464cb539ad1e8b25ddcf83ec145dd76 Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Wed, 14 Oct 2026 13:07:07 +0000 Subject: [PATCH 090/101] 8394429: RISC-V: Enable Zalasr extensions in Xuantie vendor at product mode Reviewed-by: fyang, weihe, gli --- src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp index cc814b958e4..bfe77da1919 100644 --- a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp +++ b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp @@ -366,6 +366,8 @@ void VM_Version::xuantie_features() { ext_v.enable_feature(); ext_Zabha.enable_feature(); ext_Zacas.enable_feature(); + // Zalasr will always be available. Note that hwprobe won't report Ztso on C925-C950 CPUs. + ext_Zalasr.enable_feature(); ext_Zawrs.enable_feature(); ext_Zba.enable_feature(); ext_Zbb.enable_feature(); From aff48c4c4acb8d96119478a69537df5d94d9f7fe Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Wed, 14 Oct 2026 13:10:26 +0000 Subject: [PATCH 091/101] 8391921: RISC-V: Add C2 vector support for CompressBitsV and ExpandBitsV Co-authored-by: Dingli Zhang Reviewed-by: gli, ayang, fyang --- .../cpu/riscv/c2_MacroAssembler_riscv.cpp | 119 ++++++++++++++++++ .../cpu/riscv/c2_MacroAssembler_riscv.hpp | 15 +++ src/hotspot/cpu/riscv/riscv.ad | 1 - src/hotspot/cpu/riscv/riscv_v.ad | 112 +++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 10 ++ .../TestVectorCompressExpandBits.java | 113 +++++++++++++++-- 6 files changed, 360 insertions(+), 10 deletions(-) diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index 977554e3b01..72a20ca5001 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -2213,6 +2213,125 @@ void C2_MacroAssembler::enc_cmove_fp_cmp_fp(int cmpFlag, } } +static void vshift_left(C2_MacroAssembler* masm, VectorRegister dst, VectorRegister src, int shift) { + assert(shift >= 0 && shift <= BitsPerInt, "unexpected shift amount"); + if (shift < BitsPerInt) { + masm->vsll_vi(dst, src, shift); + } else { + masm->mv(t0, shift); + masm->vsll_vx(dst, src, t0); + } +} + +static void vshift_right(C2_MacroAssembler* masm, VectorRegister dst, VectorRegister src, int shift) { + assert(shift >= 0 && shift <= BitsPerInt, "unexpected shift amount"); + if (shift < BitsPerInt) { + masm->vsrl_vi(dst, src, shift); + } else { + masm->mv(t0, shift); + masm->vsrl_vx(dst, src, t0); + } +} + +// Compute the parallel suffix XOR used to construct the movement masks in +// Integer.compress, Long.compress, Integer.expand, and Long.expand. Within each +// vector element, dst bit k is src[k] ^ src[k-1] ^ ... ^ src[0]. Each round +// doubles the number of source bits covered. +static void parallel_suffix_v(C2_MacroAssembler* masm, VectorRegister dst, + VectorRegister src, VectorRegister tmp, bool is_int) { + const int bits = is_int ? BitsPerInt : BitsPerLong; + const int rounds = exact_log2(bits); + + masm->vmv_v_v(dst, src); + for (int i = 0; i < rounds; i++) { + vshift_left(masm, tmp, dst, 1 << i); + masm->vxor_vv(dst, dst, tmp); + } +} + +// Compress the bits selected by mask toward the least-significant bit of each +// vector element while preserving their order. This is the vector equivalent +// of Integer.compress and Long.compress and uses the same movement-mask algorithm. +void C2_MacroAssembler::compress_bits_v(VectorRegister dst, VectorRegister src, VectorRegister bit_mask, + VectorRegister src_tmp, VectorRegister mask_tmp, + VectorRegister tmp1, VectorRegister tmp2, + BasicType bt, uint vector_length, VectorMask vm) { + assert(bt == T_INT || bt == T_LONG, "unsupported element type"); + const bool is_int = bt == T_INT; + const int bits = is_int ? BitsPerInt : BitsPerLong; + const int rounds = exact_log2(bits); + + vsetvli_helper(bt, vector_length); + vand_vv(dst, src, bit_mask, vm); + vmv_v_v(mask_tmp, bit_mask); + vnot_v(tmp1, mask_tmp); + vshift_left(this, tmp1, tmp1, 1); + + for (int i = 0; i < rounds; i++) { + const int shift = 1 << i; + parallel_suffix_v(this, tmp2, tmp1, src_tmp, is_int); + vand_vv(src_tmp, tmp2, mask_tmp); + vnot_v(tmp2, tmp2); + vand_vv(tmp1, tmp1, tmp2); + + vxor_vv(mask_tmp, mask_tmp, src_tmp); + vshift_right(this, tmp2, src_tmp, shift); + vor_vv(mask_tmp, mask_tmp, tmp2); + + vand_vv(tmp2, dst, src_tmp); + vxor_vv(dst, dst, tmp2, vm); + vshift_right(this, tmp2, tmp2, shift); + vor_vv(dst, dst, tmp2, vm); + } +} + +// Expand the low-order bits of src into the positions selected by mask in each +// vector element. First record the movement masks used by compression, then +// apply them in reverse order to implement Integer.expand and Long.expand. +void C2_MacroAssembler::expand_bits_v(VectorRegister dst, VectorRegister src, VectorRegister bit_mask, + VectorRegister src_tmp, VectorRegister mask_tmp, + VectorRegister mask_move1, VectorRegister mask_move2, + VectorRegister mask_move3, VectorRegister mask_move4, + VectorRegister mask_move5, VectorRegister mask_move6, + VectorRegister tmp1, VectorRegister tmp2, + BasicType bt, uint vector_length, VectorMask vm) { + assert(bt == T_INT || bt == T_LONG, "unsupported element type"); + const bool is_int = bt == T_INT; + const int bits = is_int ? BitsPerInt : BitsPerLong; + const int rounds = exact_log2(bits); + VectorRegister mask_moves[LogBitsPerLong] = { + mask_move1, mask_move2, mask_move3, mask_move4, mask_move5, mask_move6 + }; + + vsetvli_helper(bt, vector_length); + vmv_v_v(src_tmp, src); + vmv_v_v(mask_tmp, bit_mask); + vor_vv(dst, mask_tmp, mask_tmp, vm); + vnot_v(tmp1, mask_tmp); + vshift_left(this, tmp1, tmp1, 1); + + for (int i = 0; i < rounds; i++) { + const int shift = 1 << i; + parallel_suffix_v(this, tmp2, tmp1, mask_moves[i], is_int); + vand_vv(mask_moves[i], tmp2, mask_tmp); + vnot_v(tmp2, tmp2); + vand_vv(tmp1, tmp1, tmp2); + + vxor_vv(mask_tmp, mask_tmp, mask_moves[i]); + vshift_right(this, tmp2, mask_moves[i], shift); + vor_vv(mask_tmp, mask_tmp, tmp2); + } + + for (int i = rounds - 1; i >= 0; i--) { + const int shift = 1 << i; + vshift_left(this, tmp2, src_tmp, shift); + vxor_vv(tmp2, tmp2, src_tmp); + vand_vv(tmp2, tmp2, mask_moves[i]); + vxor_vv(src_tmp, src_tmp, tmp2); + } + vand_vv(dst, src_tmp, dst, vm); +} + // Set dst to NaN if any NaN input. void C2_MacroAssembler::minmax_fp(FloatRegister dst, FloatRegister src1, FloatRegister src2, FLOAT_TYPE ft, bool is_min) { diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp index 4f6bc00687b..093f367b790 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp @@ -142,6 +142,21 @@ FloatRegister dst, FloatRegister src, bool cmp_single, bool cmov_single); + void compress_bits_v(VectorRegister dst, VectorRegister src, VectorRegister bit_mask, + VectorRegister src_tmp, VectorRegister mask_tmp, + VectorRegister tmp1, VectorRegister tmp2, + BasicType bt, uint vector_length, + VectorMask vm = Assembler::unmasked); + + void expand_bits_v(VectorRegister dst, VectorRegister src, VectorRegister bit_mask, + VectorRegister src_tmp, VectorRegister mask_tmp, + VectorRegister mask_move1, VectorRegister mask_move2, + VectorRegister mask_move3, VectorRegister mask_move4, + VectorRegister mask_move5, VectorRegister mask_move6, + VectorRegister tmp1, VectorRegister tmp2, + BasicType bt, uint vector_length, + VectorMask vm = Assembler::unmasked); + void spill(Register r, bool is64, int offset) { is64 ? sd(r, Address(sp, offset)) : sw(r, Address(sp, offset)); diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 26e6f768a77..8dfc989846e 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -7719,7 +7719,6 @@ instruct regL_not_reg(iRegLNoSp dst, iRegL src1, immL_M1 m1) %{ ins_pipe(ialu_reg_imm); %} - // ============================================================================ // Floating Point Arithmetic Instructions diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index f22c79259fb..f7951a4a2d1 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -90,6 +90,9 @@ source %{ return false; } return UseZvbb; + case Op_CompressBitsV: + case Op_ExpandBitsV: + return bt == T_INT || bt == T_LONG; case Op_LoadVectorGather: case Op_LoadVectorGatherMasked: case Op_StoreVectorScatter: @@ -1633,6 +1636,115 @@ instruct vdiv_masked(vReg dst_src1, vReg src2, vRegMask_V0 v0) %{ ins_pipe(pipe_slow); %} +// vector bit compress + +instruct vcompressBits(vReg dst, vReg src, vReg bit_mask, + vReg tmp1, vReg tmp2, vReg tmp3, vReg tmp4) %{ + predicate(Matcher::vector_element_basic_type(n) == T_INT || + Matcher::vector_element_basic_type(n) == T_LONG); + match(Set dst (CompressBitsV src bit_mask)); + effect(TEMP_DEF dst, TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4); + format %{ "vcompressBits $dst, $src, $bit_mask\t# KILL $tmp1, $tmp2, $tmp3, $tmp4" %} + ins_encode %{ + BasicType bt = Matcher::vector_element_basic_type(this); + __ compress_bits_v(as_VectorRegister($dst$$reg), + as_VectorRegister($src$$reg), + as_VectorRegister($bit_mask$$reg), + as_VectorRegister($tmp1$$reg), + as_VectorRegister($tmp2$$reg), + as_VectorRegister($tmp3$$reg), + as_VectorRegister($tmp4$$reg), + bt, Matcher::vector_length(this)); + %} + ins_pipe(pipe_slow); +%} + +instruct vcompressBits_masked(vReg dst_src, vReg bit_mask, + vReg tmp1, vReg tmp2, vReg tmp3, vReg tmp4, + vRegMask_V0 v0) %{ + predicate(Matcher::vector_element_basic_type(n) == T_INT || + Matcher::vector_element_basic_type(n) == T_LONG); + match(Set dst_src (CompressBitsV (Binary dst_src bit_mask) v0)); + effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4); + format %{ "vcompressBits_masked $dst_src, $dst_src, $bit_mask, $v0\t# KILL $tmp1, $tmp2, $tmp3, $tmp4" %} + ins_encode %{ + BasicType bt = Matcher::vector_element_basic_type(this); + __ compress_bits_v(as_VectorRegister($dst_src$$reg), + as_VectorRegister($dst_src$$reg), + as_VectorRegister($bit_mask$$reg), + as_VectorRegister($tmp1$$reg), + as_VectorRegister($tmp2$$reg), + as_VectorRegister($tmp3$$reg), + as_VectorRegister($tmp4$$reg), + bt, Matcher::vector_length(this), Assembler::v0_t); + %} + ins_pipe(pipe_slow); +%} + +// vector bit expand + +instruct vexpandBits(vReg dst, vReg src, vReg bit_mask, + vReg tmp1, vReg tmp2, vReg tmp3, vReg tmp4, + vReg tmp5, vReg tmp6, vReg tmp7, vReg tmp8, + vReg tmp9, vReg tmp10) %{ + predicate(Matcher::vector_element_basic_type(n) == T_INT || + Matcher::vector_element_basic_type(n) == T_LONG); + match(Set dst (ExpandBitsV src bit_mask)); + effect(TEMP_DEF dst, TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4, + TEMP tmp5, TEMP tmp6, TEMP tmp7, TEMP tmp8, + TEMP tmp9, TEMP tmp10); + format %{ "vexpandBits $dst, $src, $bit_mask\t# KILL $tmp1, $tmp2, $tmp3, $tmp4, $tmp5, $tmp6, $tmp7, $tmp8, $tmp9, $tmp10" %} + ins_encode %{ + BasicType bt = Matcher::vector_element_basic_type(this); + __ expand_bits_v(as_VectorRegister($dst$$reg), + as_VectorRegister($src$$reg), + as_VectorRegister($bit_mask$$reg), + as_VectorRegister($tmp1$$reg), + as_VectorRegister($tmp2$$reg), + as_VectorRegister($tmp3$$reg), + as_VectorRegister($tmp4$$reg), + as_VectorRegister($tmp5$$reg), + as_VectorRegister($tmp6$$reg), + as_VectorRegister($tmp7$$reg), + as_VectorRegister($tmp8$$reg), + as_VectorRegister($tmp9$$reg), + as_VectorRegister($tmp10$$reg), + bt, Matcher::vector_length(this)); + %} + ins_pipe(pipe_slow); +%} + +instruct vexpandBits_masked(vReg dst_src, vReg bit_mask, + vReg tmp1, vReg tmp2, vReg tmp3, vReg tmp4, + vReg tmp5, vReg tmp6, vReg tmp7, vReg tmp8, + vReg tmp9, vReg tmp10, vRegMask_V0 v0) %{ + predicate(Matcher::vector_element_basic_type(n) == T_INT || + Matcher::vector_element_basic_type(n) == T_LONG); + match(Set dst_src (ExpandBitsV (Binary dst_src bit_mask) v0)); + effect(TEMP tmp1, TEMP tmp2, TEMP tmp3, TEMP tmp4, + TEMP tmp5, TEMP tmp6, TEMP tmp7, TEMP tmp8, + TEMP tmp9, TEMP tmp10); + format %{ "vexpandBits_masked $dst_src, $dst_src, $bit_mask, $v0\t# KILL $tmp1, $tmp2, $tmp3, $tmp4, $tmp5, $tmp6, $tmp7, $tmp8, $tmp9, $tmp10" %} + ins_encode %{ + BasicType bt = Matcher::vector_element_basic_type(this); + __ expand_bits_v(as_VectorRegister($dst_src$$reg), + as_VectorRegister($dst_src$$reg), + as_VectorRegister($bit_mask$$reg), + as_VectorRegister($tmp1$$reg), + as_VectorRegister($tmp2$$reg), + as_VectorRegister($tmp3$$reg), + as_VectorRegister($tmp4$$reg), + as_VectorRegister($tmp5$$reg), + as_VectorRegister($tmp6$$reg), + as_VectorRegister($tmp7$$reg), + as_VectorRegister($tmp8$$reg), + as_VectorRegister($tmp9$$reg), + as_VectorRegister($tmp10$$reg), + bt, Matcher::vector_length(this), Assembler::v0_t); + %} + ins_pipe(pipe_slow); +%} + // vector float div instruct vdiv_hfp(vReg dst, vReg src1, vReg src2) %{ diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 9c11f56003b..87ad9f029f7 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -2497,6 +2497,16 @@ public static void anyStoreOfNodes(String irNodePlaceholder, String fieldHolder) machOnlyNameRegex(RISCV_VAND_NOTL_VX_MASKED, "vand_notL_vx_masked"); } + public static final String RISCV_VCOMPRESS_BITS_MASKED = PREFIX + "RISCV_VCOMPRESS_BITS_MASKED" + POSTFIX; + static { + machOnlyNameRegex(RISCV_VCOMPRESS_BITS_MASKED, "vcompressBits_masked"); + } + + public static final String RISCV_VEXPAND_BITS_MASKED = PREFIX + "RISCV_VEXPAND_BITS_MASKED" + POSTFIX; + static { + machOnlyNameRegex(RISCV_VEXPAND_BITS_MASKED, "vexpandBits_masked"); + } + public static final String VECTOR_SLICE_B = VECTOR_PREFIX + "VECTOR_SLICE_B" + POSTFIX; static { vectorNode(VECTOR_SLICE_B, "VectorSlice", TYPE_BYTE); diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorCompressExpandBits.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorCompressExpandBits.java index 8889ccd0d26..729b549218c 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/TestVectorCompressExpandBits.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorCompressExpandBits.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2026, Alibaba Group Holding Limited. All Rights Reserved. * Copyright (c) 2023, Arm Limited. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -29,19 +30,22 @@ import jdk.incubator.vector.IntVector; import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.VectorMask; import jdk.incubator.vector.VectorOperators; import jdk.incubator.vector.VectorSpecies; import jdk.test.lib.Asserts; +import jdk.test.lib.Platform; import jdk.test.lib.Utils; /** * @test - * @bug 8301012 + * @bug 8301012 8391921 * @key randomness * @library /test/lib / - * @requires os.arch == "aarch64" & vm.cpu.features ~= ".*sve2.*" & vm.cpu.features ~= ".*svebitperm.*" - * @summary [vectorapi]: Intrinsify CompressBitsV/ExpandBitsV and add the AArch64 SVE backend implementation + * @requires (os.arch == "aarch64" & vm.cpu.features ~= ".*sve2.*" & vm.cpu.features ~= ".*svebitperm.*") | + * (os.arch == "riscv64" & vm.cpu.features ~= ".*rvv.*") + * @summary [vectorapi]: Test intrinsification of CompressBitsV/ExpandBitsV * @modules jdk.incubator.vector * @run driver compiler.vectorapi.TestVectorCompressExpandBits */ @@ -59,6 +63,7 @@ public class TestVectorCompressExpandBits { private static long[] la; private static long[] lb; private static long[] lr; + private static boolean[] laneMask; static { ia = new int[LENGTH]; @@ -67,16 +72,18 @@ public class TestVectorCompressExpandBits { la = new long[LENGTH]; lb = new long[LENGTH]; lr = new long[LENGTH]; + laneMask = new boolean[LENGTH]; for (int i = 0; i < LENGTH; i++) { ia[i] = RD.nextInt(25); ib[i] = RD.nextInt(25); la[i] = RD.nextLong(25); lb[i] = RD.nextLong(25); + laneMask[i] = RD.nextBoolean(); } } - // Test for vectorized Integer.compress operation in SVE2 + // Test for vectorized Integer.compress operation @Test @IR(counts = {IRNode.COMPRESS_BITS_VI, "> 0"}) public static void testIntCompress() { @@ -95,7 +102,28 @@ public static void testIntCompress_runner() { } } - // Test for vectorized Integer.expand operation in SVE2 + @Test + @IR(counts = {IRNode.COMPRESS_BITS_VI, "> 0"}) + @IR(counts = {IRNode.RISCV_VCOMPRESS_BITS_MASKED, "> 0"}, applyIfPlatform = {"riscv64", "true"}) + public static void testIntCompressMasked() { + for (int i = 0; i < LENGTH; i += I_SPECIES.length()) { + IntVector av = IntVector.fromArray(I_SPECIES, ia, i); + IntVector bv = IntVector.fromArray(I_SPECIES, ib, i); + VectorMask vm = VectorMask.fromArray(I_SPECIES, laneMask, i); + av.lanewise(VectorOperators.COMPRESS_BITS, bv, vm).intoArray(ir, i); + } + } + + @Run(test = "testIntCompressMasked") + public static void testIntCompressMasked_runner() { + testIntCompressMasked(); + for (int i = 0; i < LENGTH; i++) { + int expected = laneMask[i] ? Integer.compress(ia[i], ib[i]) : ia[i]; + Asserts.assertEquals(expected, ir[i]); + } + } + + // Test for vectorized Integer.expand operation @Test @IR(counts = {IRNode.EXPAND_BITS_VI, "> 0"}) public static void testIntExpand() { @@ -114,7 +142,28 @@ public static void testIntExpand_runner() { } } - // Test for vectorized Long.compress operation in SVE2 + @Test + @IR(counts = {IRNode.EXPAND_BITS_VI, "> 0"}) + @IR(counts = {IRNode.RISCV_VEXPAND_BITS_MASKED, "> 0"}, applyIfPlatform = {"riscv64", "true"}) + public static void testIntExpandMasked() { + for (int i = 0; i < LENGTH; i += I_SPECIES.length()) { + IntVector av = IntVector.fromArray(I_SPECIES, ia, i); + IntVector bv = IntVector.fromArray(I_SPECIES, ib, i); + VectorMask vm = VectorMask.fromArray(I_SPECIES, laneMask, i); + av.lanewise(VectorOperators.EXPAND_BITS, bv, vm).intoArray(ir, i); + } + } + + @Run(test = "testIntExpandMasked") + public static void testIntExpandMasked_runner() { + testIntExpandMasked(); + for (int i = 0; i < LENGTH; i++) { + int expected = laneMask[i] ? Integer.expand(ia[i], ib[i]) : ia[i]; + Asserts.assertEquals(expected, ir[i]); + } + } + + // Test for vectorized Long.compress operation @Test @IR(counts = {IRNode.COMPRESS_BITS_VL, "> 0"}) public static void testLongCompress() { @@ -133,7 +182,28 @@ public static void testLongCompress_runner() { } } - // Test for vectorized Long.expand operation in SVE2 + @Test + @IR(counts = {IRNode.COMPRESS_BITS_VL, "> 0"}) + @IR(counts = {IRNode.RISCV_VCOMPRESS_BITS_MASKED, "> 0"}, applyIfPlatform = {"riscv64", "true"}) + public static void testLongCompressMasked() { + for (int i = 0; i < LENGTH; i += L_SPECIES.length()) { + LongVector av = LongVector.fromArray(L_SPECIES, la, i); + LongVector bv = LongVector.fromArray(L_SPECIES, lb, i); + VectorMask vm = VectorMask.fromArray(L_SPECIES, laneMask, i); + av.lanewise(VectorOperators.COMPRESS_BITS, bv, vm).intoArray(lr, i); + } + } + + @Run(test = "testLongCompressMasked") + public static void testLongCompressMasked_runner() { + testLongCompressMasked(); + for (int i = 0; i < LENGTH; i++) { + long expected = laneMask[i] ? Long.compress(la[i], lb[i]) : la[i]; + Asserts.assertEquals(expected, lr[i]); + } + } + + // Test for vectorized Long.expand operation @Test @IR(counts = {IRNode.EXPAND_BITS_VL, "> 0"}) public static void testLongExpand() { @@ -152,8 +222,33 @@ public static void testLongExpand_runner() { } } + @Test + @IR(counts = {IRNode.EXPAND_BITS_VL, "> 0"}) + @IR(counts = {IRNode.RISCV_VEXPAND_BITS_MASKED, "> 0"}, applyIfPlatform = {"riscv64", "true"}) + public static void testLongExpandMasked() { + for (int i = 0; i < LENGTH; i += L_SPECIES.length()) { + LongVector av = LongVector.fromArray(L_SPECIES, la, i); + LongVector bv = LongVector.fromArray(L_SPECIES, lb, i); + VectorMask vm = VectorMask.fromArray(L_SPECIES, laneMask, i); + av.lanewise(VectorOperators.EXPAND_BITS, bv, vm).intoArray(lr, i); + } + } + + @Run(test = "testLongExpandMasked") + public static void testLongExpandMasked_runner() { + testLongExpandMasked(); + for (int i = 0; i < LENGTH; i++) { + long expected = laneMask[i] ? Long.expand(la[i], lb[i]) : la[i]; + Asserts.assertEquals(expected, lr[i]); + } + } + public static void main(String[] args) { - TestFramework.runWithFlags("--add-modules=jdk.incubator.vector", - "-XX:UseSVE=2"); + TestFramework testFramework = new TestFramework(); + testFramework.addFlags("--add-modules=jdk.incubator.vector"); + if (Platform.isAArch64()) { + testFramework.addFlags("-XX:UseSVE=2"); + } + testFramework.start(); } } From 246098019e5aef8eadcc1d28ca1fc1ff8472d865 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Wed, 14 Oct 2026 15:53:17 +0000 Subject: [PATCH 092/101] 8393482: assert(false) failed: PhaseCCP not at fixpoint: analysis result may be unsound for DecodeNKlass Reviewed-by: dlong, chagedorn --- src/hotspot/share/opto/compile.cpp | 20 +- src/hotspot/share/opto/phaseX.cpp | 44 ++++- src/hotspot/share/opto/phaseX.hpp | 4 +- .../TestConcurrentClassLoadingDuringCCP.java | 184 ++++++++++++++++++ test/jdk/ProblemList-AotJdk.txt | 1 - 5 files changed, 238 insertions(+), 15 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/ccp/TestConcurrentClassLoadingDuringCCP.java diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index fdba29b9d61..54fe6c1baf7 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -2608,6 +2608,9 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { current->set_memory_at(index, current->base_memory()); } igvn.optimize(); + if (failing()) { + return; + } #ifdef ASSERT wq.clear(); @@ -3044,6 +3047,9 @@ void Compile::process_late_inline_calls_no_inline(PhaseIterGVN& igvn) { if (failing()) return; inline_incrementally_cleanup(igvn); + if (failing()) { + return; + } } DEBUG_ONLY( _modified_nodes = modified_nodes; ) set_strength_reduction(false); @@ -3191,6 +3197,9 @@ void Compile::Optimize() { // Now that all inlining is over and no PhaseRemoveUseless will run, cut edge from root to loop // safepoints remove_root_to_sfpts_edges(igvn); + if (failing()) { + return; + } // Process value type nodes now that all inlining is over process_value_types(igvn); @@ -3250,10 +3259,10 @@ void Compile::Optimize() { // Optimize out fields loads from scalar replaceable allocations. igvn.optimize(true); - print_method(PHASE_ITER_GVN_AFTER_EA, 2); - if (failing()) return; + print_method(PHASE_ITER_GVN_AFTER_EA, 2); + if (congraph() != nullptr && macro_count() > 0) { TracePhase tp(_t_macroEliminate); PhaseMacroExpand mexp(igvn); @@ -3320,6 +3329,9 @@ void Compile::Optimize() { // Conditional Constant Propagation; print_method(PHASE_BEFORE_CCP1, 2); PhaseCCP ccp( &igvn ); + if (failing()) { + return; + } assert( true, "Break here to ccp.dump_nodes_and_types(_root,999,1)"); { TracePhase tp(_t_ccp); @@ -3335,10 +3347,10 @@ void Compile::Optimize() { igvn.reset_from_igvn(&ccp); igvn.optimize(true); } - print_method(PHASE_ITER_GVN2, 2); - if (failing()) return; + print_method(PHASE_ITER_GVN2, 2); + // Loop transforms on the ideal graph. Range Check Elimination, // peeling, unrolling, etc. if (!optimize_loops(igvn, LoopOptsDefault)) { diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 6f797ac79c4..ffd9ccf1916 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -1225,6 +1225,14 @@ void PhaseIterGVN::optimize(bool deep) { C->print_method(PHASE_AFTER_ITER_GVN, 3); } +static bool klass_bottom_type_invalidated(const Node* n, const Type* new_type) { + // Concurrent class loading can invalidate a klass bottom type. + const Type* bt = n->bottom_type(); + return (bt->isa_klassptr() != nullptr || + bt->isa_narrowklass() != nullptr) && + !new_type->higher_equal_speculative(bt); +} + #ifdef ASSERT void PhaseIterGVN::verify_optimize(bool deep_revisit_converged) { assert(_worklist.size() == 0, "igvn worklist must be empty before verify"); @@ -1244,7 +1252,12 @@ void PhaseIterGVN::verify_optimize(bool deep_revisit_converged) { // in PhaseIterGVN::add_users_to_worklist to update it again or add an exception // in the verification methods below if that is not possible for some reason (like Load nodes). if (is_verify_Value()) { - verify_Value_for(n, deep_revisit_converged /* strict */); + const Type* tnew = n->Value(this); + if (klass_bottom_type_invalidated(n, tnew)) { + C->record_failure("concurrent class loading"); + return; + } + verify_Value_for(n, tnew, deep_revisit_converged /* strict */); } if (is_verify_Ideal()) { verify_Ideal_for(n, false /* can_reshape */, deep_revisit_converged); @@ -1296,11 +1309,10 @@ void PhaseIterGVN::verify_empty_worklist(Node* node) { // (1) Integer "widen" changes, but the range is the same. // (2) LoadNode performs deep traversals. Load is not notified for changes far away. // (3) CmpPNode performs deep traversals if it compares oopptr. CmpP is not notified for changes far away. -void PhaseIterGVN::verify_Value_for(const Node* n, bool strict) { +void PhaseIterGVN::verify_Value_for(const Node* n, const Type* tnew, bool strict) { // If we assert inside type(n), because the type is still a null, then maybe // the node never went through gvn.transform, which would be a bug. const Type* told = type(n); - const Type* tnew = n->Value(this); if (told == tnew) { return; } @@ -3070,7 +3082,9 @@ void PhaseCCP::analyze() { // not reachable from the bottom. Otherwise, infinite loops would be removed. _root_and_safepoints.push(n); } - analyze_step(worklist, n); + if (!analyze_step(worklist, n)) { + return; + } } // More rounds to catch updates far in the graph. @@ -3079,19 +3093,27 @@ void PhaseCCP::analyze() { do { while (worklist.size() != 0) { Node* n = fetch_next_node(worklist); - analyze_step(worklist, n); + if (!analyze_step(worklist, n)) { + return; + } } for (uint t = 0; t < worklist_revisit.size(); t++) { Node* n = worklist_revisit.at(t); - analyze_step(worklist, n); + if (!analyze_step(worklist, n)) { + return; + } } } while (worklist.size() != 0); DEBUG_ONLY(verify_analyze(worklist_verify);) } -void PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) { +bool PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) { const Type* new_type = n->Value(this); + if (klass_bottom_type_invalidated(n, new_type)) { + C->record_failure("concurrent class loading"); + return false; + } if (new_type != type(n)) { DEBUG_ONLY(verify_type(n, new_type, type(n));) dump_type_and_node(n, new_type); @@ -3103,6 +3125,7 @@ void PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) { // nodes that become dead. _maybe_top_type_nodes.push(n); } + return true; } // Some nodes can refine their types due to type change somewhere deep @@ -3136,7 +3159,12 @@ void PhaseCCP::verify_analyze(Unique_Node_List& worklist_verify) { // in PhaseCCP::push_child_nodes_to_worklist() to update their type in the same round, // or that they are added in PhaseCCP::needs_revisit() so that analysis revisits // them at the end of the round. - verify_Value_for(n, true); + const Type* tnew = n->Value(this); + if (klass_bottom_type_invalidated(n, tnew)) { + C->record_failure("concurrent class loading"); + return; + } + verify_Value_for(n, tnew, true); } } #endif diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index b6b76586d43..f6bdce80cd3 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -549,7 +549,7 @@ class PhaseIterGVN : public PhaseGVN { #ifdef ASSERT void verify_optimize(bool deep_revisit_converged); - void verify_Value_for(const Node* n, bool strict = false); + void verify_Value_for(const Node* n, const Type* tnew, bool strict = false); void verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit_converged); void verify_Identity_for(Node* n); void verify_node_invariants_for(const Node* n); @@ -734,7 +734,7 @@ class PhaseCCP : public PhaseIterGVN { // Worklist algorithm identifies constants void analyze(); - void analyze_step(Unique_Node_List& worklist, Node* n); + bool analyze_step(Unique_Node_List& worklist, Node* n); bool needs_revisit(Node* n) const; #ifdef ASSERT void verify_type(Node* n, const Type* tnew, const Type* told); diff --git a/test/hotspot/jtreg/compiler/ccp/TestConcurrentClassLoadingDuringCCP.java b/test/hotspot/jtreg/compiler/ccp/TestConcurrentClassLoadingDuringCCP.java new file mode 100644 index 00000000000..c1783531121 --- /dev/null +++ b/test/hotspot/jtreg/compiler/ccp/TestConcurrentClassLoadingDuringCCP.java @@ -0,0 +1,184 @@ +/* + * 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 8393482 + * @summary Test bailout from PhaseCCP and compilation when CHA changed + * @requires vm.compiler2.enabled & vm.flagless & vm.bits == 64 & vm.debug == true + * @comment The test uses debug flag TracePhaseCCP to check for DecodeNKlass type + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:+TracePhaseCCP -XX:+PrintCompilation + * -XX:+LogVMOutput -XX:LogFile=ccp_test.log + * -XX:CompileCommand=quiet + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * -XX:CompileCommand=inline,${test.main.class}::inline* + * ${test.main.class} + */ + +package compiler.ccp; + +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jdk.test.lib.Utils; +import jdk.test.whitebox.WhiteBox; + +/* + * The loader thread watches the VM log file until CCP prints the exact + * type for DecodeNKlass generated for array.clone(). At that point + * both, klass load and decode, nodes have cached the old exact type. + * The loader thread then loads Child subclass making the cached type + * of DecodeNKlass invalid. Without the fix, CCP verification asserts. + * With the fix, PrintCompilation reports that compilation was skipped + * because of concurrent class loading. + */ + +public class TestConcurrentClassLoadingDuringCCP { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + private static final String TEST_CLASS = TestConcurrentClassLoadingDuringCCP.class.getName(); + private static final String CHILD_CLASS = TEST_CLASS + "$Child"; + private static final Path LOG_FILE = Path.of("ccp_test.log"); + private static final long TIMEOUT_SEC = Utils.adjustTimeout(60); // 60 sec + + private static volatile Throwable failure; + + static class Parent {} + static class Child extends Parent {} + + public static void main(String[] args) throws Exception { + if (WB.isClassAlive(CHILD_CLASS)) { + throw new RuntimeException(CHILD_CLASS + " should not be loaded"); + } + Method testMethod = TestConcurrentClassLoadingDuringCCP.class + .getDeclaredMethod("test", Parent[][].class); + + CountDownLatch loaderReady = new CountDownLatch(1); + Thread loader = new Thread(() -> { + try { + // Unblock await() + loaderReady.countDown(); + // Wait TracePhaseCCP output + waitForLog(" DecodeNKlass === ", "$Parent:Constant"); + // Load subclass + Class.forName(CHILD_CLASS); + } catch (Throwable t) { + failure = t; + } + }, "loader"); + loader.start(); + + if (!loaderReady.await(TIMEOUT_SEC, TimeUnit.SECONDS)) { + throw new RuntimeException("Loader thread did not start"); + } + // Request C2 Tier4 compilation + if (!WB.enqueueMethodForCompilation(testMethod, 4)) { + throw new RuntimeException("Could not enqueue test method for C2 compilation"); + } + loader.join(TimeUnit.SECONDS.toMillis(TIMEOUT_SEC)); + if (loader.isAlive()) { + loader.interrupt(); + throw new RuntimeException("Timeout waiting for loader thread"); + } + if (failure != null) { + throw new RuntimeException(failure); + } + + waitForLog(TEST_CLASS + "::test", "COMPILE SKIPPED: concurrent class loading"); + if (WB.isMethodCompiled(testMethod)) { + throw new RuntimeException("Compilation should bail out"); + } + } + + private static void waitForLog(String... patterns) throws Exception { + long timeout = System.nanoTime() + TimeUnit.SECONDS.toNanos(TIMEOUT_SEC); + do { + String output = Files.readString(LOG_FILE); + if (output.lines().anyMatch(line -> containsAll(line, patterns))) { + return; // Found " DecodeNKlass === .* $Parent:Constant" + } + Thread.sleep(1); + } while (System.nanoTime() < timeout); + throw new RuntimeException("Did not find " + String.join("...", patterns) + + " in " + LOG_FILE); + } + + private static boolean containsAll(String line, String... patterns) { + for (String pattern : patterns) { + if (!line.contains(pattern)) { + return false; + } + } + return true; + } + + // When C2 parses the test() and inlined methods, Child has not been loaded, + // so C2 treats Parent as a leaf class. The klass type generated for `clone` + // thus contains exact Parent class. + // After CCP caches this type, the loader thread loads Child subclass. + // This invalidates the exact Parent and causes LoadNKlass type to widen + // while CCP is still running. + + // Use static variables to avoid folding of inlined methods. + private static volatile int result; + private static volatile int var; + + // Use 2-dims array to force C2 generate LoadNklass -> DecodeNKlass nodes. + static Parent[][] test(Parent[][] array) { + Parent[][] clone = array.clone(); + var = inline0(); + return clone; + } + + private static int inline0() { + return inline1() + inline1() + inline1() + inline1(); + } + + private static int inline1() { + return inline2() + inline2() + inline2() + inline2(); + } + + private static int inline2() { + return inline3() + inline3() + inline3() + inline3(); + } + + private static int inline3() { + return inline4() + inline4() + inline4() + inline4(); + } + + private static int inline4() { + return inline5() + inline5() + inline5() + inline5(); + } + + private static int inline5() { + return result; + } +} diff --git a/test/jdk/ProblemList-AotJdk.txt b/test/jdk/ProblemList-AotJdk.txt index cb26a58a1e6..49d28c04914 100644 --- a/test/jdk/ProblemList-AotJdk.txt +++ b/test/jdk/ProblemList-AotJdk.txt @@ -51,7 +51,6 @@ jdk/incubator/vector/Vector512ConversionTests.java 8337281 linux-aarch64,mac jdk/incubator/vector/Vector64ConversionTests.java#id0 8337281 linux-aarch64,macosx-aarch64 jdk/incubator/vector/VectorMaxConversionTests.java#id0 8337281 linux-aarch64,macosx-aarch64 -java/math/BigInteger/SerializationTests.java 8393482 generic-all java/util/concurrent/locks/Lock/OOMEInAQS.java 8393490 generic-all ############################################################################# From 8efc77d00b4ff63f710460fef485e6cc794aaf55 Mon Sep 17 00:00:00 2001 From: Jayathirth D V Date: Wed, 14 Oct 2026 16:00:47 +0000 Subject: [PATCH 093/101] 8394492: Update FreeType to 2.15.0 Reviewed-by: azvegint, prr --- src/java.desktop/share/legal/freetype.md | 2 +- .../include/freetype/config/ftmodule.h | 1 + .../include/freetype/config/ftoption.h | 38 +- .../libfreetype/include/freetype/freetype.h | 15 +- .../libfreetype/include/freetype/ftimage.h | 4 +- .../libfreetype/include/freetype/ftmoderr.h | 1 + .../libfreetype/include/freetype/fttypes.h | 2 - .../include/freetype/internal/ftcalc.h | 4 + .../include/freetype/internal/ftdebug.h | 1 + .../include/freetype/internal/ftmmtypes.h | 7 + .../include/freetype/internal/ftobjs.h | 4 +- .../include/freetype/internal/ftserv.h | 29 + .../include/freetype/internal/fttrace.h | 6 + .../freetype/internal/services/svfntfmt.h | 1 + .../freetype/internal/services/svttcmap.h | 3 +- .../include/freetype/internal/tttypes.h | 107 ++- .../libfreetype/include/freetype/tttags.h | 12 + .../native/libfreetype/src/autofit/aflatin.c | 8 +- .../native/libfreetype/src/autofit/afranges.c | 17 +- .../native/libfreetype/src/autofit/afshaper.c | 48 +- .../native/libfreetype/src/base/ftbbox.c | 9 +- .../native/libfreetype/src/base/ftbitmap.c | 261 +++--- .../native/libfreetype/src/base/ftcalc.c | 25 +- .../native/libfreetype/src/base/ftobjs.c | 231 ++--- .../native/libfreetype/src/base/ftoutln.c | 102 ++- .../native/libfreetype/src/base/ftstroke.c | 55 +- .../native/libfreetype/src/cff/cffdrivr.c | 142 ++- .../native/libfreetype/src/cff/cffgload.c | 76 +- .../native/libfreetype/src/cff/cffload.c | 6 + .../native/libfreetype/src/cff/cffobjs.c | 8 +- .../native/libfreetype/src/cid/cidload.c | 14 +- .../native/libfreetype/src/psaux/psintrp.c | 30 +- .../native/libfreetype/src/psaux/psstack.c | 13 +- .../native/libfreetype/src/raster/ftraster.c | 93 +- .../native/libfreetype/src/sfnt/sfobjs.c | 200 ++++- .../native/libfreetype/src/sfnt/sfwoff.c | 8 +- .../native/libfreetype/src/sfnt/ttcmap.c | 820 +++++++++++++++++- .../native/libfreetype/src/sfnt/ttcmap.h | 14 +- .../native/libfreetype/src/sfnt/ttcmapc.h | 4 + .../native/libfreetype/src/sfnt/ttcolr.c | 101 ++- .../native/libfreetype/src/sfnt/ttload.c | 59 +- .../share/native/libfreetype/src/sfnt/ttmtx.c | 91 +- .../native/libfreetype/src/sfnt/ttpost.c | 12 +- .../native/libfreetype/src/sfnt/ttsbit.c | 14 +- .../native/libfreetype/src/smooth/ftgrays.c | 234 ++--- .../native/libfreetype/src/smooth/ftsmooth.c | 18 +- .../libfreetype/src/truetype/ttdriver.c | 88 +- .../native/libfreetype/src/truetype/ttgload.c | 131 ++- .../native/libfreetype/src/truetype/ttgxvar.c | 639 ++++++++++---- .../native/libfreetype/src/truetype/ttgxvar.h | 17 + .../libfreetype/src/truetype/ttinterp.c | 343 +++++--- .../libfreetype/src/truetype/ttinterp.h | 8 + .../native/libfreetype/src/truetype/ttobjs.c | 78 +- .../native/libfreetype/src/truetype/ttobjs.h | 16 + .../native/libfreetype/src/truetype/ttpload.c | 15 +- .../native/libfreetype/src/type1/t1load.c | 11 +- .../native/libfreetype/src/type1/t1parse.c | 56 +- 57 files changed, 3338 insertions(+), 1014 deletions(-) diff --git a/src/java.desktop/share/legal/freetype.md b/src/java.desktop/share/legal/freetype.md index 0c972d0fa99..d7c9b0fe002 100644 --- a/src/java.desktop/share/legal/freetype.md +++ b/src/java.desktop/share/legal/freetype.md @@ -1,4 +1,4 @@ -## The FreeType Project: Freetype v2.14.3 +## The FreeType Project: Freetype v2.15.0 ### FreeType Notice diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/config/ftmodule.h b/src/java.desktop/share/native/libfreetype/include/freetype/config/ftmodule.h index d908ce76bd1..8a1fa3a301b 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/config/ftmodule.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/config/ftmodule.h @@ -15,6 +15,7 @@ FT_USE_MODULE( FT_Driver_ClassRec, tt_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, t1_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, cff_driver_class ) FT_USE_MODULE( FT_Driver_ClassRec, t1cid_driver_class ) +// FT_USE_MODULE( FT_Driver_ClassRec, hvf_driver_class ) // FT_USE_MODULE( FT_Driver_ClassRec, pfr_driver_class ) // FT_USE_MODULE( FT_Driver_ClassRec, t42_driver_class ) // FT_USE_MODULE( FT_Driver_ClassRec, winfnt_driver_class ) diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/config/ftoption.h b/src/java.desktop/share/native/libfreetype/include/freetype/config/ftoption.h index a4e9c7f3e83..b74a3bc5578 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/config/ftoption.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/config/ftoption.h @@ -334,6 +334,22 @@ FT_BEGIN_HEADER /* #define FT_CONFIG_OPTION_USE_BROTLI */ + /************************************************************************** + * + * HVF support. + * + * FreeType can use Apple's HVF (Hierarchical Variable Font) library + * to render glyphs from fonts containing 'hvgl' tables. + * + * Define this macro if you want to enable this 'feature'. + * + * If you use a build system like cmake or the `configure` script, + * options set by those programs have precedence, overwriting the value + * here with the configured one. + */ +/* #define FT_CONFIG_OPTION_HVF */ + + /************************************************************************** * * Glyph Postscript Names handling @@ -423,8 +439,10 @@ FT_BEGIN_HEADER /************************************************************************** * - * The size in bytes of the render pool used by the scan-line converter to - * do all of its work. + * The size in bytes of the stack render pool used by the scan-line + * converters. Use this option to limit the stack usage. The memory + * requirements are proportional to size and complexity of a given glyph. + * FreeType's rasterizers switch to dynamic allocations when necessary. */ #define FT_RENDER_POOL_SIZE 16384L @@ -651,6 +669,7 @@ FT_BEGIN_HEADER #define TT_CONFIG_CMAP_FORMAT_12 #define TT_CONFIG_CMAP_FORMAT_13 #define TT_CONFIG_CMAP_FORMAT_14 +#define TT_CONFIG_CMAP_FORMAT_15 /*************************************************************************/ @@ -756,6 +775,21 @@ FT_BEGIN_HEADER /* #define TT_CONFIG_OPTION_NO_BORING_EXPANSION */ + /************************************************************************** + * + * Define `TT_CONFIG_OPTION_VARC` if you want to include support for + * variable composite glyphs (the 'VARC' table). This is part of the + * 'boring' OpenType specification expansions. + * + * https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md + * + * Variable composites enable more efficient encoding of variable glyphs + * by allowing glyphs to be composed from other glyphs with variable + * transformations and axis value overrides. + */ +/* #define TT_CONFIG_OPTION_VARC */ + + /************************************************************************** * * Define `TT_CONFIG_OPTION_BDF` if you want to include support for an diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/freetype.h b/src/java.desktop/share/native/libfreetype/include/freetype/freetype.h index 68a9e0c6bd2..0a330c7c3f6 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/freetype.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/freetype.h @@ -4134,7 +4134,7 @@ FT_BEGIN_HEADER * This function returns an error if the charmap is not part of the face * (i.e., if it is not listed in the `face->charmaps` table). * - * It also fails if an OpenType type~14 charmap is selected (which + * It also fails if an OpenType type 14 or~15 charmap is selected (which * doesn't map character codes to glyph indices at all). */ FT_EXPORT( FT_Error ) @@ -4700,7 +4700,7 @@ FT_BEGIN_HEADER * * @abstract: * The FreeType~2 interface to Unicode Variation Sequences (UVS), using - * the SFNT cmap format~14. + * the SFNT cmap formats 14 and~15. * * @description: * Many characters, especially for CJK scripts, have variant forms. They @@ -4730,9 +4730,10 @@ FT_BEGIN_HEADER * U+9089, you have to write the character sequence `U+9089 U+E0100`. * * Adobe and MS decided to support both standardized and ideographic VS - * with a new cmap subtable (format~14). It is an odd subtable because - * it is not a mapping of input code points to glyphs, but contains lists - * of all variations supported by the font. + * with a new cmap subtable (format~14, or format~15 for glyph indices + * above 65535). It is an odd subtable because it is not a mapping of + * input code points to glyphs, but contains lists of all variations + * supported by the font. * * A variation may be either 'default' or 'non-default' for a given font. * A default variation is the one you will get for that code point if you @@ -5168,8 +5169,8 @@ FT_BEGIN_HEADER * */ #define FREETYPE_MAJOR 2 -#define FREETYPE_MINOR 14 -#define FREETYPE_PATCH 3 +#define FREETYPE_MINOR 15 +#define FREETYPE_PATCH 0 /************************************************************************** diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/ftimage.h b/src/java.desktop/share/native/libfreetype/include/freetype/ftimage.h index 1fff76b20d9..61de54e48cb 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/ftimage.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/ftimage.h @@ -471,7 +471,7 @@ FT_BEGIN_HEADER /* */ -#define FT_CURVE_TAG( flag ) ( flag & 0x03 ) +#define FT_CURVE_TAG( flag ) ( ( flag & 0x01 ) ? 0x01 : ( flag & 0x03 ) ) /* see the `tags` field in `FT_Outline` for a description of the values */ #define FT_CURVE_TAG_ON 0x01 @@ -875,7 +875,7 @@ FT_BEGIN_HEADER */ typedef struct FT_Span_ { - unsigned short x; + short x; unsigned short len; unsigned char coverage; diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/ftmoderr.h b/src/java.desktop/share/native/libfreetype/include/freetype/ftmoderr.h index 7c1b1fd3c04..9010c9fc489 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/ftmoderr.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/ftmoderr.h @@ -172,6 +172,7 @@ FT_MODERRDEF( Winfonts, 0x1500, "Windows FON/FNT module" ) FT_MODERRDEF( GXvalid, 0x1600, "GX validation module" ) FT_MODERRDEF( Sdf, 0x1700, "Signed distance field raster module" ) + FT_MODERRDEF( HVF, 0x1800, "HVF module" ) #ifdef FT_MODERR_END_LIST diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/fttypes.h b/src/java.desktop/share/native/libfreetype/include/freetype/fttypes.h index 97ddcde65aa..2f3ca05955a 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/fttypes.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/fttypes.h @@ -25,8 +25,6 @@ #include #include -#include - FT_BEGIN_HEADER diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftcalc.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftcalc.h index 4c4943164a3..129366f3894 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftcalc.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftcalc.h @@ -77,6 +77,10 @@ FT_BEGIN_HEADER #endif /* FT_INT64 */ +#define ULABS( a ) ( a >= 0 ? (FT_ULong)a : 0UL - (FT_ULong)a ) +#define UABS( a ) ( a >= 0 ? (FT_UInt)a : 0U - (FT_UInt)a ) + + /************************************************************************** * * FT_MulDiv() and FT_MulFix() are declared in freetype.h. diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftdebug.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftdebug.h index 62b9ec2f79c..1baf5f385a8 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftdebug.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftdebug.h @@ -33,6 +33,7 @@ #ifdef FT_DEBUG_LOGGING #define DLG_STATIC +#define DLG_LOG_LEVEL dlg_level_trace #include #include diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftmmtypes.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftmmtypes.h index a4db9c487b7..854798ad3c0 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftmmtypes.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftmmtypes.h @@ -71,6 +71,13 @@ FT_BEGIN_HEADER FT_UInt regionCount; /* total number of regions defined */ GX_VarRegion varRegionList; + /* Cache of per-region scalars for the current normalized coordinates. */ + /* `regionScalars` holds `regionCount` values, `cachedCoords` the */ + /* `axisCount` coordinates they were computed for; both are NULL until */ + /* first use and recomputed whenever the coordinates change. */ + FT_Fixed* regionScalars; + FT_Fixed* cachedCoords; + } GX_ItemVarStoreRec, *GX_ItemVarStore; diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftobjs.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftobjs.h index 4fa4991dd88..f97262ffa95 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftobjs.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftobjs.h @@ -214,8 +214,8 @@ FT_BEGIN_HEADER FT_CMap_CharIndexFunc char_index; FT_CMap_CharNextFunc char_next; - /* Subsequent entries are special ones for format 14 -- the variant */ - /* selector subtable which behaves like no other */ + /* Subsequent entries are special ones for formats 14 and 15 -- */ + /* the variant selector subtables which behave like no other */ FT_CMap_CharVarIndexFunc char_var_index; FT_CMap_CharVarIsDefaultFunc char_var_default; diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftserv.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftserv.h index 9c6406c82d8..3b326627074 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftserv.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/ftserv.h @@ -173,6 +173,7 @@ FT_BEGIN_HEADER * FT_DEFINE_SERVICEDESCREC8 * FT_DEFINE_SERVICEDESCREC9 * FT_DEFINE_SERVICEDESCREC10 + * FT_DEFINE_SERVICEDESCREC11 * * @description: * Used to initialize an array of FT_ServiceDescRec structures. @@ -350,6 +351,34 @@ FT_BEGIN_HEADER { NULL, NULL } \ }; +#define FT_DEFINE_SERVICEDESCREC11( class_, \ + serv_id_1, serv_data_1, \ + serv_id_2, serv_data_2, \ + serv_id_3, serv_data_3, \ + serv_id_4, serv_data_4, \ + serv_id_5, serv_data_5, \ + serv_id_6, serv_data_6, \ + serv_id_7, serv_data_7, \ + serv_id_8, serv_data_8, \ + serv_id_9, serv_data_9, \ + serv_id_10, serv_data_10, \ + serv_id_11, serv_data_11 ) \ + static const FT_ServiceDescRec class_[] = \ + { \ + { serv_id_1, serv_data_1 }, \ + { serv_id_2, serv_data_2 }, \ + { serv_id_3, serv_data_3 }, \ + { serv_id_4, serv_data_4 }, \ + { serv_id_5, serv_data_5 }, \ + { serv_id_6, serv_data_6 }, \ + { serv_id_7, serv_data_7 }, \ + { serv_id_8, serv_data_8 }, \ + { serv_id_9, serv_data_9 }, \ + { serv_id_10, serv_data_10 }, \ + { serv_id_11, serv_data_11 }, \ + { NULL, NULL } \ + }; + /* * Parse a list of FT_ServiceDescRec descriptors and look for a specific diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/fttrace.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/fttrace.h index 798896cd0c8..22291ef7cfe 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/fttrace.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/fttrace.h @@ -79,6 +79,7 @@ FT_TRACE_DEF( ttgxvar ) /* TrueType GX var handler (ttgxvar.c) */ FT_TRACE_DEF( ttinterp ) /* bytecode interpreter (ttinterp.c) */ FT_TRACE_DEF( ttobjs ) /* TT objects manager (ttobjs.c) */ FT_TRACE_DEF( ttpload ) /* TT data/program loader (ttpload.c) */ +FT_TRACE_DEF( ttvarc ) /* TT VARC handler (ttvarc.c) */ /* Type 1 driver components */ FT_TRACE_DEF( t1afm ) @@ -120,6 +121,11 @@ FT_TRACE_DEF( cidload ) FT_TRACE_DEF( cidobjs ) FT_TRACE_DEF( cidparse ) + /* HVF driver component */ +FT_TRACE_DEF( hvfdrv ) +FT_TRACE_DEF( hvfobjs ) +FT_TRACE_DEF( hvfload ) + /* Windows font component */ FT_TRACE_DEF( winfnt ) diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/services/svfntfmt.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/services/svfntfmt.h index 770381da67a..91a6f007aa5 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/services/svfntfmt.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/services/svfntfmt.h @@ -40,6 +40,7 @@ FT_BEGIN_HEADER #define FT_FONT_FORMAT_TYPE_42 "Type 42" #define FT_FONT_FORMAT_CID "CID Type 1" #define FT_FONT_FORMAT_CFF "CFF" +#define FT_FONT_FORMAT_HVF "HVF" #define FT_FONT_FORMAT_PFR "PFR" #define FT_FONT_FORMAT_WINFNT "Windows FNT" diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/services/svttcmap.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/services/svttcmap.h index 14921304ebf..d7ad8066b5e 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/services/svttcmap.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/services/svttcmap.h @@ -52,7 +52,8 @@ FT_BEGIN_HEADER * table), 2~(high-byte mapping through table), 4~(segment mapping to * delta values), 6~(trimmed table mapping), 8~(mixed 16-bit and 32-bit * coverage), 10~(trimmed array), 12~(segmented coverage), 13~(last - * resort font), and 14 (Unicode Variation Sequences). + * resort font), 14~(Unicode Variation Sequences), and 15~(extended + * glyph repertoire Unicode Variation Sequences). */ typedef struct TT_CMapInfo_ { diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/internal/tttypes.h b/src/java.desktop/share/native/libfreetype/include/freetype/internal/tttypes.h index 65341a3a259..ad45da39b99 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/internal/tttypes.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/internal/tttypes.h @@ -1130,11 +1130,29 @@ FT_BEGIN_HEADER * The font's horizontal header ('hhea' table). This field also * contains the associated horizontal metrics table ('hmtx'). * + * horz_metrics_count :: + * The number of horizontal metrics. This can be larger than the + * public `TT_HoriHeader.number_Of_HMetrics` field when the font uses + * the 'HHEA table (instead of 'hhea'). + * + * horz_metrics_is_extended :: + * A boolean which is set when the font uses 'HHEA' and 'HMTX' tables + * (instead of 'hhea' and 'hmtx'). + * * max_profile :: * The font's maximum profile table. Read on font opening. Note that * some maximum values cannot be taken directly from this table. We * thus define additional fields below to hold the computed maxima. * + * maxp_num_glyphs :: + * The glyph count from the font's maximum profile table. This can be + * larger than the public `TT_MaxProfile.numGlyphs` field when the font + * uses the 24-bit 'MAXP' table. + * + * is_extended_glyf :: + * A boolean which is set when the font uses the 'GLYF' and 'LOCA' + * tables (extending 'glyf' and 'loca', respectively). + * * vertical_info :: * A boolean which is set when the font file contains vertical metrics. * If not, the value of the 'vertical' field is undefined. @@ -1145,6 +1163,15 @@ FT_BEGIN_HEADER * IMPORTANT: The contents of this field is undefined if the * `vertical_info` field is unset. * + * vert_metrics_count :: + * The number of vertical metrics. This can be larger than the public + * `TT_VertHeader.number_Of_VMetrics` field when the font uses the + * 'VHEA' table (instead of 'vhea'). + * + * vert_metrics_is_extended :: + * A boolean which is set when the font uses the 'VHEA' and 'VMTX' + * tables (instead of 'vhea' and 'vmtx'). + * * num_names :: * The number of name records within this TrueType font. * @@ -1166,6 +1193,25 @@ FT_BEGIN_HEADER * cmap_size :: * The size in bytes of the `cmap_table` described above. * + * dmap_table :: + * Address of the face's 'DMAP' SFNT table in memory (it's an extracted + * frame). + * + * dmap_size :: + * The size in bytes of the `dmap_table` described above. + * + * dmap_charmaps :: + * The array of 'DMAP' charmaps. + * + * num_dmap_charmaps :: + * The number of elements in `dmap_charmaps`. + * + * dmap_results :: + * An array used while merging a dmap with its corresponding cmap. + * + * dmap_max_results :: + * The current size of `dmap_results`. + * * goto_table :: * A function called by each TrueType table loader to position a * stream's cursor to the start of a given table according to its tag. @@ -1209,6 +1255,13 @@ FT_BEGIN_HEADER * A pointer to the Metrics Variations service for this `TT_Face`'s * driver. * + * tt_varc :: + * A pointer for sharing the VARC services provided by this + * `TT_Face`'s driver. + * + * face_varc :: + * A pointer to handle VARC glyphs by this `TT_Face`'s driver. + * * psaux :: * A pointer to the PostScript Auxiliary service. * @@ -1408,6 +1461,26 @@ FT_BEGIN_HEADER * svg :: * A pointer to data related to the 'SVG' table. `NULL` if the table * is not available. + * + * gpos_table :: + * A pointer to the 'GPOS' table. `NULL` if the table is not + * available. + * + * gpos_lookups_kerning :: + * An array of GPOS kerning lookups. + * + * num_gpos_lookups_kerning :: + * The number of GPOS kerning lookups. + * + * varc :: + * A pointer to data related to the 'VARC' table. `NULL` if the table + * is not available. + * + * varc_context :: + * A context to manage recursion in the 'VARC' table. + * + * varc_loading_components :: + * Set if component checks for 'VARC' table handling can be skipped. */ typedef struct TT_FaceRec_ { @@ -1421,11 +1494,17 @@ FT_BEGIN_HEADER TT_Header header; /* TrueType header table */ TT_HoriHeader horizontal; /* TrueType horizontal header */ + FT_UInt32 horz_metrics_count; + FT_Bool horz_metrics_is_extended; TT_MaxProfile max_profile; + FT_UInt maxp_num_glyphs; + FT_Bool is_extended_glyf; FT_Bool vertical_info; TT_VertHeader vertical; /* TT Vertical header, if present */ + FT_UInt32 vert_metrics_count; + FT_Bool vert_metrics_is_extended; FT_UShort num_names; /* number of name records */ TT_NameTableRec name_table; /* name table */ @@ -1436,6 +1515,13 @@ FT_BEGIN_HEADER FT_Byte* cmap_table; /* extracted `cmap' table */ FT_ULong cmap_size; + FT_Byte* dmap_table; /* extracted 'DMAP' table */ + FT_ULong dmap_size; + FT_CMap* dmap_charmaps; + FT_UInt num_dmap_charmaps; + FT_UInt32* dmap_results; + FT_UInt32 dmap_max_results; + TT_Loader_GotoTableFunc goto_table; TT_Loader_StartGlyphFunc access_glyph_frame; @@ -1468,6 +1554,16 @@ FT_BEGIN_HEADER void* face_var; /* since 2.13.1 */ #endif +#ifdef TT_CONFIG_OPTION_VARC + /* a typeless pointer to the FT_Service_VARCRec table used to */ + /* share VARC handling from the `truetype' driver */ + void* tt_varc; /* since 2.15 */ + + /* a typeless pointer to the FT_Service_VARCRec table used to */ + /* handle VARC glyphs by this TT_Face's driver */ + void* face_varc; /* since 2.15 */ +#endif + /* a typeless pointer to the PostScript Aux service */ void* psaux; @@ -1553,7 +1649,7 @@ FT_BEGIN_HEADER FT_ULong horz_metrics_size; FT_ULong vert_metrics_size; - FT_ULong num_locations; /* up to 0xFFFF + 1 */ + FT_ULong num_locations; /* up to 0xFFFFFF + 1 */ FT_Byte* glyph_locations; FT_Byte* hdmx_table; @@ -1604,6 +1700,15 @@ FT_BEGIN_HEADER FT_UInt num_gpos_lookups_kerning; #endif +#ifdef TT_CONFIG_OPTION_VARC + /* since 2.15 */ + void* varc; + /* Active recursion context. */ + void* varc_context; + /* Whether to skip VARC check for components. */ + FT_Bool varc_loading_components; +#endif + } TT_FaceRec; diff --git a/src/java.desktop/share/native/libfreetype/include/freetype/tttags.h b/src/java.desktop/share/native/libfreetype/include/freetype/tttags.h index 3be3e2ff905..7d431cea490 100644 --- a/src/java.desktop/share/native/libfreetype/include/freetype/tttags.h +++ b/src/java.desktop/share/native/libfreetype/include/freetype/tttags.h @@ -49,6 +49,7 @@ FT_BEGIN_HEADER #define TTAG_CPAL FT_MAKE_TAG( 'C', 'P', 'A', 'L' ) #define TTAG_cvar FT_MAKE_TAG( 'c', 'v', 'a', 'r' ) #define TTAG_cvt FT_MAKE_TAG( 'c', 'v', 't', ' ' ) +#define TTAG_DMAP FT_MAKE_TAG( 'D', 'M', 'A', 'P' ) #define TTAG_DSIG FT_MAKE_TAG( 'D', 'S', 'I', 'G' ) #define TTAG_EBDT FT_MAKE_TAG( 'E', 'B', 'D', 'T' ) #define TTAG_EBLC FT_MAKE_TAG( 'E', 'B', 'L', 'C' ) @@ -60,23 +61,31 @@ FT_BEGIN_HEADER #define TTAG_gasp FT_MAKE_TAG( 'g', 'a', 's', 'p' ) #define TTAG_GDEF FT_MAKE_TAG( 'G', 'D', 'E', 'F' ) #define TTAG_glyf FT_MAKE_TAG( 'g', 'l', 'y', 'f' ) +#define TTAG_GLYF FT_MAKE_TAG( 'G', 'L', 'Y', 'F' ) #define TTAG_GPOS FT_MAKE_TAG( 'G', 'P', 'O', 'S' ) #define TTAG_GSUB FT_MAKE_TAG( 'G', 'S', 'U', 'B' ) #define TTAG_gvar FT_MAKE_TAG( 'g', 'v', 'a', 'r' ) +#define TTAG_GVAR FT_MAKE_TAG( 'G', 'V', 'A', 'R' ) #define TTAG_HVAR FT_MAKE_TAG( 'H', 'V', 'A', 'R' ) #define TTAG_hdmx FT_MAKE_TAG( 'h', 'd', 'm', 'x' ) #define TTAG_head FT_MAKE_TAG( 'h', 'e', 'a', 'd' ) #define TTAG_hhea FT_MAKE_TAG( 'h', 'h', 'e', 'a' ) +#define TTAG_HHEA FT_MAKE_TAG( 'H', 'H', 'E', 'A' ) #define TTAG_hmtx FT_MAKE_TAG( 'h', 'm', 't', 'x' ) +#define TTAG_HMTX FT_MAKE_TAG( 'H', 'M', 'T', 'X' ) +#define TTAG_hvgl FT_MAKE_TAG( 'h', 'v', 'g', 'l' ) +#define TTAG_hvpm FT_MAKE_TAG( 'h', 'v', 'p', 'm' ) #define TTAG_JSTF FT_MAKE_TAG( 'J', 'S', 'T', 'F' ) #define TTAG_just FT_MAKE_TAG( 'j', 'u', 's', 't' ) #define TTAG_kern FT_MAKE_TAG( 'k', 'e', 'r', 'n' ) #define TTAG_lcar FT_MAKE_TAG( 'l', 'c', 'a', 'r' ) #define TTAG_loca FT_MAKE_TAG( 'l', 'o', 'c', 'a' ) +#define TTAG_LOCA FT_MAKE_TAG( 'L', 'O', 'C', 'A' ) #define TTAG_LTSH FT_MAKE_TAG( 'L', 'T', 'S', 'H' ) #define TTAG_LWFN FT_MAKE_TAG( 'L', 'W', 'F', 'N' ) #define TTAG_MATH FT_MAKE_TAG( 'M', 'A', 'T', 'H' ) #define TTAG_maxp FT_MAKE_TAG( 'm', 'a', 'x', 'p' ) +#define TTAG_MAXP FT_MAKE_TAG( 'M', 'A', 'X', 'P' ) #define TTAG_META FT_MAKE_TAG( 'M', 'E', 'T', 'A' ) #define TTAG_MMFX FT_MAKE_TAG( 'M', 'M', 'F', 'X' ) #define TTAG_MMSD FT_MAKE_TAG( 'M', 'M', 'S', 'D' ) @@ -104,7 +113,10 @@ FT_BEGIN_HEADER #define TTAG_typ1 FT_MAKE_TAG( 't', 'y', 'p', '1' ) #define TTAG_VDMX FT_MAKE_TAG( 'V', 'D', 'M', 'X' ) #define TTAG_vhea FT_MAKE_TAG( 'v', 'h', 'e', 'a' ) +#define TTAG_VHEA FT_MAKE_TAG( 'V', 'H', 'E', 'A' ) #define TTAG_vmtx FT_MAKE_TAG( 'v', 'm', 't', 'x' ) +#define TTAG_VMTX FT_MAKE_TAG( 'V', 'M', 'T', 'X' ) +#define TTAG_VARC FT_MAKE_TAG( 'V', 'A', 'R', 'C' ) #define TTAG_VVAR FT_MAKE_TAG( 'V', 'V', 'A', 'R' ) #define TTAG_wOFF FT_MAKE_TAG( 'w', 'O', 'F', 'F' ) #define TTAG_wOF2 FT_MAKE_TAG( 'w', 'O', 'F', '2' ) diff --git a/src/java.desktop/share/native/libfreetype/src/autofit/aflatin.c b/src/java.desktop/share/native/libfreetype/src/autofit/aflatin.c index 73addf28eb4..3c97f91dd93 100644 --- a/src/java.desktop/share/native/libfreetype/src/autofit/aflatin.c +++ b/src/java.desktop/share/native/libfreetype/src/autofit/aflatin.c @@ -2783,8 +2783,8 @@ { do { - point->y += movement; - point = point->next; + point->y = ADD_LONG( point->y, movement ); + point = point->next; } while ( point != first_point ); } @@ -3739,7 +3739,7 @@ pos = ADD_LONG( high_min_y, adjustment_amount ); if ( adjust_below_top && is_top_tilde ) - pos += high_height; + pos = ADD_LONG( pos, high_height ); if ( pos % 64 == 0 && tilde_height < 3 * 64 ) { @@ -3892,7 +3892,7 @@ pos = SUB_LONG( low_max_y, adjustment_amount ); if ( adjust_above_bottom && is_bottom_tilde ) - pos -= low_height; + pos = SUB_LONG( pos, low_height ); if ( pos % 64 == 0 && tilde_height < 3 * 64 ) { diff --git a/src/java.desktop/share/native/libfreetype/src/autofit/afranges.c b/src/java.desktop/share/native/libfreetype/src/autofit/afranges.c index 4d0755decb2..c9f5e0a8aab 100644 --- a/src/java.desktop/share/native/libfreetype/src/autofit/afranges.c +++ b/src/java.desktop/share/native/libfreetype/src/autofit/afranges.c @@ -104,7 +104,8 @@ AF_UNIRANGE_REC( 0xFE7A, 0xFE7A ), AF_UNIRANGE_REC( 0xFE7C, 0xFE7C ), AF_UNIRANGE_REC( 0xFE7E, 0xFE7E ), - AF_UNIRANGE_REC( 0x10EFD, 0x10EFF ), + AF_UNIRANGE_REC( 0x10ECB, 0x10ECF ), + AF_UNIRANGE_REC( 0x10EF0, 0x10EFF ), AF_UNIRANGE_REC( 0, 0 ) }; @@ -118,7 +119,8 @@ const AF_Script_UniRangeRec af_armn_nonbase_uniranges[] = { - AF_UNIRANGE_REC( 0x0559, 0x055F ), + AF_UNIRANGE_REC( 0x0558, 0x055F ), + AF_UNIRANGE_REC( 0x058B, 0x058C ), AF_UNIRANGE_REC( 0, 0 ) }; @@ -156,7 +158,8 @@ const AF_Script_UniRangeRec af_beng_uniranges[] = { - AF_UNIRANGE_REC( 0x0980, 0x09FF ), /* Bengali */ + AF_UNIRANGE_REC( 0x0980, 0x09FF ), /* Bengali */ + AF_UNIRANGE_REC( 0x11DF0, 0x11DFF ), /* Bengali Supplement */ AF_UNIRANGE_REC( 0, 0 ) }; @@ -168,6 +171,7 @@ AF_UNIRANGE_REC( 0x09CD, 0x09CD ), AF_UNIRANGE_REC( 0x09E2, 0x09E3 ), AF_UNIRANGE_REC( 0x09FE, 0x09FE ), + AF_UNIRANGE_REC( 0x11DF0, 0x11DF0 ), AF_UNIRANGE_REC( 0, 0 ) }; @@ -470,7 +474,7 @@ AF_UNIRANGE_REC( 0x0591, 0x05BF ), AF_UNIRANGE_REC( 0x05C1, 0x05C2 ), AF_UNIRANGE_REC( 0x05C4, 0x05C5 ), - AF_UNIRANGE_REC( 0x05C7, 0x05C7 ), + AF_UNIRANGE_REC( 0x05C7, 0x05C9 ), AF_UNIRANGE_REC( 0xFB1E, 0xFB1E ), AF_UNIRANGE_REC( 0, 0 ) }; @@ -586,7 +590,7 @@ AF_UNIRANGE_REC( 0xAB6A, 0xAB6F ), /* ... continued */ AF_UNIRANGE_REC( 0xFB00, 0xFB06 ), /* Alphab. Present. Forms (Latin Ligs) */ AF_UNIRANGE_REC( 0x1D400, 0x1D7FF ), /* Mathematical Alphanumeric Symbols */ - AF_UNIRANGE_REC( 0x1DF00, 0x1DFFF ), /* Latin Extended-G */ + AF_UNIRANGE_REC( 0x1DF00, 0x1DFCC ), /* Latin Extended-G */ AF_UNIRANGE_REC( 0, 0 ) }; @@ -643,7 +647,8 @@ AF_UNIRANGE_REC( 0xA7F8, 0xA7F9 ), /* more modifier letters */ AF_UNIRANGE_REC( 0xAB5C, 0xAB5F ), /* more modifier letters */ AF_UNIRANGE_REC( 0xAB69, 0xAB69 ), /* modifier letter small turned w */ - AF_UNIRANGE_REC( 0x10780, 0x107FB ), /* Latin Extended-F */ + AF_UNIRANGE_REC( 0x10780, 0x107BF ), /* Latin Extended-F */ + AF_UNIRANGE_REC( 0x1DFCD, 0x1DFFF ), /* Latin Extended-G (subset) */ AF_UNIRANGE_REC( 0, 0 ) }; diff --git a/src/java.desktop/share/native/libfreetype/src/autofit/afshaper.c b/src/java.desktop/share/native/libfreetype/src/autofit/afshaper.c index 5c9e5376ad0..3c0ffe09473 100644 --- a/src/java.desktop/share/native/libfreetype/src/autofit/afshaper.c +++ b/src/java.desktop/share/native/libfreetype/src/autofit/afshaper.c @@ -45,12 +45,16 @@ * that actually do the mapping job. Please check the OpenType * specification for more details on features and lookups. * - * 2. Create glyph ID sets from the corresponding lookup sets. + * 2. Create glyph ID sets from the corresponding lookup sets. For + * default coverage, start with glyphs already assigned to the style + * and compute their substitution closure. * * 3. The glyph set corresponding to AF_COVERAGE_DEFAULT is computed - * with all lookups specific to the OpenType script activated. It - * relies on the order of AF_DEFINE_STYLE_CLASS entries so that - * special coverages (like `oldstyle figures') don't get overwritten. + * with all lookups specific to the OpenType script activated. Only + * reachable glyphs are added, since a lookup can be shared by several + * scripts. It relies on the order of AF_DEFINE_STYLE_CLASS entries + * so that special coverages (like `oldstyle figures') don't get + * overwritten. * */ @@ -198,14 +202,34 @@ count++; #endif - /* get output coverage of GSUB feature */ - hb( ot_layout_lookup_collect_glyphs )( face, - HB_OT_TAG_GSUB, - idx, - NULL, - NULL, - NULL, - gsub_glyphs ); + if ( style_class->coverage != AF_COVERAGE_DEFAULT ) + { + /* get output coverage of GSUB feature */ + hb( ot_layout_lookup_collect_glyphs )( face, + HB_OT_TAG_GSUB, + idx, + NULL, + NULL, + NULL, + gsub_glyphs ); + } + } + + if ( style_class->coverage == AF_COVERAGE_DEFAULT ) + { + FT_UInt i; + + + /* A script's lookups can also substitute glyphs of other scripts. */ + /* Seed the closure with this style's glyphs so that their */ + /* substitutes inherit the same style. */ + for ( i = 0; i < globals->glyph_count; i++ ) + if ( ( gstyles[i] & AF_STYLE_MASK ) == style_class->style ) + hb( set_add )( gsub_glyphs, i ); + + hb( ot_layout_lookups_substitute_closure )( face, + gsub_lookups, + gsub_glyphs ); } #ifdef FT_DEBUG_LEVEL_TRACE diff --git a/src/java.desktop/share/native/libfreetype/src/base/ftbbox.c b/src/java.desktop/share/native/libfreetype/src/base/ftbbox.c index 4641ce27e82..4007a9314f0 100644 --- a/src/java.desktop/share/native/libfreetype/src/base/ftbbox.c +++ b/src/java.desktop/share/native/libfreetype/src/base/ftbbox.c @@ -291,11 +291,10 @@ /* It is called with either q2 or q3 positive, which is necessary */ /* for the peak to exist and avoids undefined FT_MSB. */ - shift = 27 - FT_MSB( (FT_UInt32)( FT_ABS( q1 ) | - FT_ABS( q2 ) | - FT_ABS( q3 ) | - FT_ABS( q4 ) ) ); - + shift = 8 * sizeof ( FT_Pos ) - 5 - FT_MSB( ULABS( q1 ) | + ULABS( q2 ) | + ULABS( q3 ) | + ULABS( q4 ) ); if ( shift > 0 ) { /* upscaling too much just wastes time */ diff --git a/src/java.desktop/share/native/libfreetype/src/base/ftbitmap.c b/src/java.desktop/share/native/libfreetype/src/base/ftbitmap.c index f88cbc11e63..1606cbfa090 100644 --- a/src/java.desktop/share/native/libfreetype/src/base/ftbitmap.c +++ b/src/java.desktop/share/native/libfreetype/src/base/ftbitmap.c @@ -776,18 +776,12 @@ FT_Bitmap source_bitmap; const FT_Bitmap* source; - FT_Vector source_offset; - FT_Vector target_offset; - FT_Bool free_source_bitmap = 0; FT_Bool free_target_bitmap_on_error = 0; - FT_Pos source_llx, source_lly, source_urx, source_ury; - FT_Pos target_llx, target_lly, target_urx, target_ury; - FT_Pos final_llx, final_lly, final_urx, final_ury; + FT_BBox sbx, tbx, fbx; unsigned int final_rows, final_width; - long x, y; if ( !library || !target || !source_ || !atarget_offset ) @@ -811,110 +805,32 @@ if ( !( source_->width && source_->rows ) ) return FT_Err_Ok; /* nothing to do */ - /* assure integer pixel offsets */ - source_offset.x = FT_PIX_FLOOR( source_offset_.x ); - source_offset.y = FT_PIX_FLOOR( source_offset_.y ); - target_offset.x = FT_PIX_FLOOR( atarget_offset->x ); - target_offset.y = FT_PIX_FLOOR( atarget_offset->y ); + /* get source bitmap dimensions assuming integer offsets */ + sbx.xMin = ( source_offset_.x >> 6 ); + sbx.yMin = ( source_offset_.y >> 6 ) - source_->rows; + sbx.xMax = ( source_offset_.x >> 6 ) + source_->width; + sbx.yMax = ( source_offset_.y >> 6 ); - /* get source bitmap dimensions */ - source_llx = source_offset.x; - if ( FT_LONG_MIN + (FT_Pos)( source_->rows << 6 ) + 64 > source_offset.y ) - { - FT_TRACE5(( - "FT_Bitmap_Blend: y coordinate overflow in source bitmap\n" )); - return FT_THROW( Invalid_Argument ); - } - source_lly = source_offset.y - ( source_->rows << 6 ); + FT_TRACE5(( "FT_Bitmap_Blend:\n" )); + FT_TRACE5(( " source bitmap: (%ld, %ld) -- (%ld, %ld); %u x %u\n", + sbx.xMin, sbx.yMin, sbx.xMax, sbx.yMax, + source_->width, source_->rows )); - if ( FT_LONG_MAX - (FT_Pos)( source_->width << 6 ) - 64 < source_llx ) + /* sanity check */ + if ( sbx.xMin > sbx.xMax || sbx.yMin > sbx.yMax || + source_->width > 0x7FFFU || source_->rows > 0x7FFFU ) { - FT_TRACE5(( - "FT_Bitmap_Blend: x coordinate overflow in source bitmap\n" )); + FT_TRACE5(( "FT_Bitmap_Blend: source dimension overflow\n" )); return FT_THROW( Invalid_Argument ); } - source_urx = source_llx + ( source_->width << 6 ); - source_ury = source_offset.y; - /* get target bitmap dimensions */ - if ( target->width && target->rows ) - { - target_llx = target_offset.x; - if ( FT_LONG_MIN + (FT_Pos)( target->rows << 6 ) > target_offset.y ) - { - FT_TRACE5(( - "FT_Bitmap_Blend: y coordinate overflow in target bitmap\n" )); - return FT_THROW( Invalid_Argument ); - } - target_lly = target_offset.y - ( target->rows << 6 ); - - if ( FT_LONG_MAX - (FT_Pos)( target->width << 6 ) < target_llx ) - { - FT_TRACE5(( - "FT_Bitmap_Blend: x coordinate overflow in target bitmap\n" )); - return FT_THROW( Invalid_Argument ); - } - target_urx = target_llx + ( target->width << 6 ); - target_ury = target_offset.y; - } - else + if ( !target->width || !target->rows ) { - target_llx = FT_LONG_MAX; - target_lly = FT_LONG_MAX; - target_urx = FT_LONG_MIN; - target_ury = FT_LONG_MIN; - } - - /* compute final bitmap dimensions */ - final_llx = FT_MIN( source_llx, target_llx ); - final_lly = FT_MIN( source_lly, target_lly ); - final_urx = FT_MAX( source_urx, target_urx ); - final_ury = FT_MAX( source_ury, target_ury ); - - final_width = ( final_urx - final_llx ) >> 6; - final_rows = ( final_ury - final_lly ) >> 6; - -#ifdef FT_DEBUG_LEVEL_TRACE - FT_TRACE5(( "FT_Bitmap_Blend:\n" )); - FT_TRACE5(( " source bitmap: (%ld, %ld) -- (%ld, %ld); %u x %u\n", - source_llx / 64, source_lly / 64, - source_urx / 64, source_ury / 64, - source_->width, source_->rows )); - - if ( target->width && target->rows ) - FT_TRACE5(( " target bitmap: (%ld, %ld) -- (%ld, %ld); %u x %u\n", - target_llx / 64, target_lly / 64, - target_urx / 64, target_ury / 64, - target->width, target->rows )); - else FT_TRACE5(( " target bitmap: empty\n" )); - if ( final_width && final_rows ) - FT_TRACE5(( " final bitmap: (%ld, %ld) -- (%ld, %ld); %u x %u\n", - final_llx / 64, final_lly / 64, - final_urx / 64, final_ury / 64, - final_width, final_rows )); - else - FT_TRACE5(( " final bitmap: empty\n" )); -#endif /* FT_DEBUG_LEVEL_TRACE */ - - if ( !( final_width && final_rows ) ) - return FT_Err_Ok; /* nothing to do */ - - /* for blending, set offset vector of final bitmap */ - /* temporarily to (0,0) */ - source_llx -= final_llx; - source_lly -= final_lly; - - if ( target->width && target->rows ) - { - target_llx -= final_llx; - target_lly -= final_lly; - } + final_width = source_->width; + final_rows = source_->rows; - /* set up target bitmap */ - if ( target->pixel_mode == FT_PIXEL_MODE_NONE ) - { /* create new empty bitmap */ target->width = final_width; target->rows = final_rows; @@ -926,72 +842,116 @@ return error; free_target_bitmap_on_error = 1; + + fbx = sbx; } - else if ( target->width != final_width || - target->rows != final_rows ) + else { - /* adjust old bitmap to enlarged size */ - int pitch, new_pitch; + /* get target bitmap dimensions assuming integer offsets */ + tbx.xMin = ( atarget_offset->x >> 6 ); + tbx.yMin = ( atarget_offset->y >> 6 ) - target->rows; + tbx.xMax = ( atarget_offset->x >> 6 ) + target->width; + tbx.yMax = ( atarget_offset->y >> 6 ); - unsigned char* buffer = NULL; + FT_TRACE5(( " target bitmap: (%ld, %ld) -- (%ld, %ld); %u x %u\n", + tbx.xMin, tbx.yMin, tbx.xMax, tbx.yMax, + target->width, target->rows )); + /* sanity check */ + if ( tbx.xMin > tbx.xMax || tbx.yMin > tbx.yMax ) + { + FT_TRACE5(( "FT_Bitmap_Blend: targget dimension overflow\n" )); + return FT_THROW( Invalid_Argument ); + } - pitch = target->pitch; + /* compute final bitmap dimensions */ + fbx.xMin = FT_MIN( sbx.xMin, tbx.xMin ); + fbx.yMin = FT_MIN( sbx.yMin, tbx.yMin ); + fbx.xMax = FT_MAX( sbx.xMax, tbx.xMax ); + fbx.yMax = FT_MAX( sbx.yMax, tbx.yMax ); - if ( pitch < 0 ) - pitch = -pitch; + /* sanity check */ + if ( fbx.xMin < -0x10000 || fbx.xMax >= 0x10000 || + fbx.yMin < -0x10000 || fbx.yMax >= 0x10000 ) + { + FT_TRACE5(( "FT_Bitmap_Blend: final dimension overflow\n" )); + return FT_THROW( Invalid_Argument ); + } - new_pitch = (int)final_width * 4; + final_width = fbx.xMax - fbx.xMin; + final_rows = fbx.yMax - fbx.yMin; - /* TODO: provide an in-buffer solution for large bitmaps */ - /* to avoid allocation of a new buffer */ - if ( FT_ALLOC_MULT( buffer, final_rows, new_pitch ) ) - goto Error; + /* adjust target bitmap to enlarged size */ + if ( target->width < final_width || + target->rows < final_rows ) + { + int pitch, new_pitch; - /* copy data to new buffer */ - x = target_llx >> 6; - y = target_lly >> 6; + unsigned char* buffer = NULL; - /* the bitmap flow is from top to bottom, */ - /* but y is measured from bottom to top */ - if ( target->pitch < 0 ) - { - /* XXX */ - } - else - { - unsigned char* p = - target->buffer; - unsigned char* q = - buffer + - ( final_rows - y - target->rows ) * new_pitch + - x * 4; - unsigned char* limit_p = - p + pitch * (int)target->rows; + pitch = target->pitch; + + if ( pitch < 0 ) + pitch = -pitch; + + new_pitch = (int)final_width * 4; + + /* TODO: provide an in-buffer solution for large bitmaps */ + /* to avoid allocation of a new buffer */ + if ( FT_ALLOC_MULT( buffer, final_rows, new_pitch ) ) + goto Error; + + /* copy data to new buffer */ - while ( p < limit_p ) + /* the bitmap flow is from top to bottom, */ + /* but y is measured from bottom to top */ + if ( target->pitch < 0 ) { - FT_MEM_COPY( q, p, pitch ); + /* XXX */ + } + else + { + FT_Pos x = tbx.xMin - fbx.xMin; + FT_Pos y = tbx.yMin - fbx.yMin; + + unsigned char* p = + target->buffer; + unsigned char* q = + buffer + + ( final_rows - y - target->rows ) * new_pitch + + x * 4; + unsigned char* limit_p = + p + pitch * (int)target->rows; + - p += pitch; - q += new_pitch; + while ( p < limit_p ) + { + FT_MEM_COPY( q, p, pitch ); + + p += pitch; + q += new_pitch; + } } - } - FT_FREE( target->buffer ); + FT_FREE( target->buffer ); - target->width = final_width; - target->rows = final_rows; + target->width = final_width; + target->rows = final_rows; - if ( target->pitch < 0 ) - target->pitch = -new_pitch; - else - target->pitch = new_pitch; + if ( target->pitch < 0 ) + target->pitch = -new_pitch; + else + target->pitch = new_pitch; - target->buffer = buffer; + target->buffer = buffer; + } } + FT_TRACE5(( " final bitmap: (%ld, %ld) -- (%ld, %ld); %u x %u\n", + fbx.xMin, fbx.yMin, fbx.xMax, fbx.yMax, + final_width, final_rows )); + /* adjust source bitmap if necessary */ if ( source_->pixel_mode != FT_PIXEL_MODE_GRAY ) { @@ -1008,8 +968,6 @@ /* do blending; the code below returns pre-multiplied channels, */ /* similar to what FreeType gets from `CBDT' tables */ - x = source_llx >> 6; - y = source_lly >> 6; /* the bitmap flow is from top to bottom, */ /* but y is measured from bottom to top */ @@ -1019,6 +977,9 @@ } else { + FT_Pos x = sbx.xMin - fbx.xMin; + FT_Pos y = sbx.yMin - fbx.yMin; + unsigned char* p = source->buffer; unsigned char* q = @@ -1064,8 +1025,8 @@ } } - atarget_offset->x = final_llx; - atarget_offset->y = final_lly + ( final_rows << 6 ); + atarget_offset->x = fbx.xMin * 64; + atarget_offset->y = fbx.yMax * 64; Error: if ( error && free_target_bitmap_on_error ) diff --git a/src/java.desktop/share/native/libfreetype/src/base/ftcalc.c b/src/java.desktop/share/native/libfreetype/src/base/ftcalc.c index d233dba3d6b..c78d3b6da4b 100644 --- a/src/java.desktop/share/native/libfreetype/src/base/ftcalc.c +++ b/src/java.desktop/share/native/libfreetype/src/base/ftcalc.c @@ -714,9 +714,8 @@ FT_Matrix_Check( const FT_Matrix* matrix ) { FT_Fixed xx, xy, yx, yy; - FT_Fixed val; + FT_ULong val, abs_det, frob_sq; FT_Int shift; - FT_ULong temp1, temp2; if ( !matrix ) @@ -726,16 +725,14 @@ xy = matrix->xy; yx = matrix->yx; yy = matrix->yy; - val = FT_ABS( xx ) | FT_ABS( xy ) | FT_ABS( yx ) | FT_ABS( yy ); + val = ULABS( xx ) | ULABS( xy ) | ULABS( yx ) | ULABS( yy ); /* we only handle non-zero 32-bit values */ - if ( !val || val > 0x7FFFFFFFL ) + if ( !val || val > 0x7FFFFFFFUL ) return 0; - /* Scale matrix to avoid the temp1 overflow, which is */ - /* more stringent than avoiding the temp2 overflow. */ - - shift = FT_MSB( val ) - 12; + /* Scale matrix to avoid overflows */ + shift = FT_MSB( val ) - 4 * sizeof ( FT_ULong ) + 2; if ( shift > 0 ) { @@ -745,11 +742,11 @@ yy >>= shift; } - temp1 = 32U * (FT_ULong)FT_ABS( xx * yy - xy * yx ); - temp2 = (FT_ULong)( xx * xx ) + (FT_ULong)( xy * xy ) + - (FT_ULong)( yx * yx ) + (FT_ULong)( yy * yy ); + abs_det = ULABS( xx * yy - xy * yx ); + frob_sq = (FT_ULong)( xx * xx ) + (FT_ULong)( xy * xy ) + + (FT_ULong)( yx * yx ) + (FT_ULong)( yy * yy ); - if ( temp1 <= temp2 ) + if ( abs_det <= frob_sq / 32 ) return 0; return 1; @@ -966,7 +963,7 @@ FT_Int result; - if ( (FT_ULong)FT_ABS( in_x ) + (FT_ULong)FT_ABS( out_y ) <= 92681UL ) + if ( ULABS( in_x ) + ULABS( out_y ) <= 92681UL ) { z1.lo = (FT_UInt32)in_x * (FT_UInt32)out_y; z1.hi = (FT_UInt32)( (FT_Int32)z1.lo >> 31 ); /* sign-expansion */ @@ -974,7 +971,7 @@ else ft_multo64( (FT_UInt32)in_x, (FT_UInt32)out_y, &z1 ); - if ( (FT_ULong)FT_ABS( in_y ) + (FT_ULong)FT_ABS( out_x ) <= 92681UL ) + if ( ULABS( in_y ) + ULABS( out_x ) <= 92681UL ) { z2.lo = (FT_UInt32)in_y * (FT_UInt32)out_x; z2.hi = (FT_UInt32)( (FT_Int32)z2.lo >> 31 ); /* sign-expansion */ diff --git a/src/java.desktop/share/native/libfreetype/src/base/ftobjs.c b/src/java.desktop/share/native/libfreetype/src/base/ftobjs.c index f9f64c40fec..acefdf46fd9 100644 --- a/src/java.desktop/share/native/libfreetype/src/base/ftobjs.c +++ b/src/java.desktop/share/native/libfreetype/src/base/ftobjs.c @@ -42,6 +42,10 @@ #include #include +#ifdef TT_CONFIG_OPTION_VARC +#include +#endif + #include #ifdef FT_CONFIG_OPTION_MAC_FONTS @@ -503,8 +507,8 @@ pbox.xMin < -0x1000000 || pbox.xMax >= 0x1000000 || pbox.yMin < -0x1000000 || pbox.yMax >= 0x1000000 || ( slot->face && - ( width > 10 * slot->face->size->metrics.x_ppem || - height > 10 * slot->face->size->metrics.y_ppem ) ) ); + ( width > 16 * slot->face->size->metrics.x_ppem || + height > 16 * slot->face->size->metrics.y_ppem ) ) ); if ( ret ) FT_TRACE3(( "ft_glyphslot_preset_bitmap: [%ld %ld %ld %ld]\n", @@ -788,7 +792,7 @@ internal->transform_flags = 0; - if ( !matrix ) + if ( !matrix || !FT_Matrix_Check( matrix ) ) { internal->transform_matrix.xx = 0x10000L; internal->transform_matrix.xy = 0; @@ -1017,6 +1021,22 @@ } } +#ifdef TT_CONFIG_OPTION_VARC + /* Never auto-hint a VARC (variable composite) glyph: its components */ + /* are already loaded unhinted, and the auto-fitter cannot process */ + /* the assembled composite (it also re-enters variation-coordinate */ + /* setting). Use the native hinter / unhinted load instead. */ + if ( autohint && FT_IS_SFNT( face ) ) + { + FT_Service_VARC varc; + + + FT_FACE_FIND_SERVICE( face, varc, VARC ); + if ( varc && varc->has_glyph( face, glyph_index ) ) + autohint = FALSE; + } +#endif + if ( autohint ) { FT_AutoHinter_Interface hinting; @@ -1441,8 +1461,14 @@ { if ( cur[0]->encoding == FT_ENCODING_UNICODE ) { - face->charmap = cur[0]; - return FT_Err_Ok; + FT_Long format = FT_Get_CMap_Format( cur[0] ); + + + if ( format != 14 && format != 15 ) + { + face->charmap = cur[0]; + return FT_Err_Ok; + } } } @@ -1457,7 +1483,7 @@ * * @Description: * This function finds the variant selector charmap, if there is one. - * There can only be one (platform=0, specific=5, format=14). + * There can only be one (platform=0, specific=5, format=14 or 15). */ static FT_CharMap find_variant_selector_charmap( FT_Face face ) @@ -1480,9 +1506,14 @@ for ( cur = first; cur < end; cur++ ) { if ( cur[0]->platform_id == TT_PLATFORM_APPLE_UNICODE && - cur[0]->encoding_id == TT_APPLE_ID_VARIANT_SELECTOR && - FT_Get_CMap_Format( cur[0] ) == 14 ) - return cur[0]; + cur[0]->encoding_id == TT_APPLE_ID_VARIANT_SELECTOR ) + { + FT_Long format = FT_Get_CMap_Format( cur[0] ); + + + if ( format == 14 || format == 15 ) + return cur[0]; + } } return NULL; @@ -3771,11 +3802,16 @@ for ( ; cur < limit; cur++ ) { - if ( cur[0] == charmap && - FT_Get_CMap_Format ( charmap ) != 14 ) + if ( cur[0] == charmap ) { - face->charmap = cur[0]; - return FT_Err_Ok; + FT_Long format = FT_Get_CMap_Format( charmap ); + + + if ( format != 14 && format != 15 ) + { + face->charmap = cur[0]; + return FT_Err_Ok; + } } } @@ -4737,121 +4773,114 @@ FT_Error error = FT_Err_Ok; FT_Face face = slot->face; FT_Renderer renderer; + FT_ListNode node = NULL; - switch ( slot->format ) + /* try to render colored glyph layers as a special case */ + if ( slot->internal->load_flags & FT_LOAD_COLOR && + slot->format == FT_GLYPH_FORMAT_OUTLINE ) { - default: - if ( slot->internal->load_flags & FT_LOAD_COLOR ) - { - FT_LayerIterator iterator; + FT_LayerIterator iterator; - FT_UInt base_glyph = slot->glyph_index; + FT_UInt base_glyph = slot->glyph_index; - FT_Bool have_layers; - FT_UInt glyph_index; - FT_UInt color_index; + FT_Bool have_layers; + FT_UInt glyph_index; + FT_UInt color_index; - /* check whether we have colored glyph layers */ - iterator.p = NULL; - have_layers = FT_Get_Color_Glyph_Layer( face, - base_glyph, - &glyph_index, - &color_index, - &iterator ); - if ( have_layers ) + iterator.p = NULL; + have_layers = FT_Get_Color_Glyph_Layer( face, + base_glyph, + &glyph_index, + &color_index, + &iterator ); + if ( have_layers ) + { + error = FT_New_GlyphSlot( face, NULL ); + if ( !error ) { - error = FT_New_GlyphSlot( face, NULL ); - if ( !error ) - { - TT_Face ttface = (TT_Face)face; - SFNT_Service sfnt = (SFNT_Service)ttface->sfnt; + TT_Face ttface = (TT_Face)face; + SFNT_Service sfnt = (SFNT_Service)ttface->sfnt; - do - { - FT_Int32 load_flags = slot->internal->load_flags; + do + { + FT_Int32 load_flags = slot->internal->load_flags; - /* disable the `FT_LOAD_COLOR' flag to avoid recursion */ - /* right here in this function */ - load_flags &= ~FT_LOAD_COLOR; + /* disable the `FT_LOAD_COLOR' flag to avoid recursion */ + /* right here in this function */ + load_flags &= ~FT_LOAD_COLOR; - /* render into the new `face->glyph' glyph slot */ - load_flags |= FT_LOAD_RENDER; + /* render into the new `face->glyph' glyph slot */ + load_flags |= FT_LOAD_RENDER | FT_LOAD_NO_BITMAP; - error = FT_Load_Glyph( face, glyph_index, load_flags ); - if ( error ) - break; - - /* blend new `face->glyph' into old `slot'; */ - /* at the first call, `slot' is still empty */ - error = sfnt->colr_blend( ttface, - color_index, - slot, - face->glyph ); - if ( error ) - break; - - } while ( FT_Get_Color_Glyph_Layer( face, - base_glyph, - &glyph_index, - &color_index, - &iterator ) ); + error = FT_Load_Glyph( face, glyph_index, load_flags ); + if ( error ) + break; - if ( !error ) - slot->format = FT_GLYPH_FORMAT_BITMAP; + /* blend new `face->glyph' into old `slot'; */ + /* at the first call, `slot' is still empty */ + error = sfnt->colr_blend( ttface, + color_index, + slot, + face->glyph ); + if ( error ) + break; - /* this call also restores `slot' as the glyph slot */ - FT_Done_GlyphSlot( face->glyph ); - } + } while ( FT_Get_Color_Glyph_Layer( face, + base_glyph, + &glyph_index, + &color_index, + &iterator ) ); if ( !error ) - return error; + slot->format = FT_GLYPH_FORMAT_BITMAP; - /* Failed to do the colored layer. Draw outline instead. */ - slot->format = FT_GLYPH_FORMAT_OUTLINE; + /* this call also restores `slot' as the glyph slot */ + FT_Done_GlyphSlot( face->glyph ); } - } - { - FT_ListNode node = NULL; + if ( !error ) + return error; + /* Failed to do the colored layer. Draw outline instead. */ + slot->format = FT_GLYPH_FORMAT_OUTLINE; + } + } - /* small shortcut for the very common case */ - if ( slot->format == FT_GLYPH_FORMAT_OUTLINE ) - { - renderer = library->cur_renderer; - node = library->renderers.head; - } - else - renderer = FT_Lookup_Renderer( library, slot->format, &node ); + /* small shortcut for the very common case */ + if ( slot->format == FT_GLYPH_FORMAT_OUTLINE ) + { + renderer = library->cur_renderer; + node = library->renderers.head; + } + else + renderer = FT_Lookup_Renderer( library, slot->format, &node ); - error = FT_ERR( Cannot_Render_Glyph ); - while ( renderer ) - { - error = renderer->render( renderer, slot, render_mode, NULL ); - if ( !error || - FT_ERR_NEQ( error, Cannot_Render_Glyph ) ) - break; - - /* FT_Err_Cannot_Render_Glyph is returned if the render mode */ - /* is unsupported by the current renderer for this glyph image */ - /* format. */ - - /* now, look for another renderer that supports the same */ - /* format. */ - renderer = FT_Lookup_Renderer( library, slot->format, &node ); - } + error = FT_ERR( Cannot_Render_Glyph ); + while ( renderer ) + { + error = renderer->render( renderer, slot, render_mode, NULL ); + if ( !error || + FT_ERR_NEQ( error, Cannot_Render_Glyph ) ) + break; - /* it is not an error if we cannot render a bitmap glyph */ - if ( FT_ERR_EQ( error, Cannot_Render_Glyph ) && - slot->format == FT_GLYPH_FORMAT_BITMAP ) - error = FT_Err_Ok; - } + /* `FT_Err_Cannot_Render_Glyph` is returned if the render mode */ + /* is unsupported by the current renderer for this glyph image */ + /* format. */ + + /* now, look for another renderer that supports the same */ + /* format. */ + renderer = FT_Lookup_Renderer( library, slot->format, &node ); } + /* it is not an error if we cannot render a bitmap glyph */ + if ( FT_ERR_EQ( error, Cannot_Render_Glyph ) && + slot->format == FT_GLYPH_FORMAT_BITMAP ) + error = FT_Err_Ok; + #ifdef FT_DEBUG_LEVEL_TRACE #undef FT_COMPONENT diff --git a/src/java.desktop/share/native/libfreetype/src/base/ftoutln.c b/src/java.desktop/share/native/libfreetype/src/base/ftoutln.c index a0a1549adaa..b37b53312fc 100644 --- a/src/java.desktop/share/native/libfreetype/src/base/ftoutln.c +++ b/src/java.desktop/share/native/libfreetype/src/base/ftoutln.c @@ -102,15 +102,14 @@ tags = outline->tags + first; tag = FT_CURVE_TAG( tags[0] ); - /* A contour cannot start with a cubic control point! */ - if ( tag == FT_CURVE_TAG_CUBIC ) - goto Invalid_Outline; - /* check first point to determine origin */ - if ( tag == FT_CURVE_TAG_CONIC ) + if ( tag != FT_CURVE_TAG_ON ) { - /* first point is conic control. Yes, this happens. */ - if ( FT_CURVE_TAG( outline->tags[last] ) == FT_CURVE_TAG_ON ) + FT_Int last_tag = FT_CURVE_TAG( outline->tags[last] ); + + + /* first point is a control point. Yes, this happens. */ + if ( last_tag == FT_CURVE_TAG_ON ) { /* start at last point if it is on the curve */ v_start = v_last; @@ -118,13 +117,16 @@ } else { - /* if both first and last points are conic, */ - /* start at their middle and record its position */ - /* for closure */ + if ( last_tag != tag ) + goto Invalid_Outline; + + /* if both first and last points are controls, */ + /* start at their middle and record its position */ + /* for closure */ v_start.x = ( v_start.x + v_last.x ) / 2; v_start.y = ( v_start.y + v_last.y ) / 2; - /* v_last = v_start; */ + /* v_last = v_start; */ } point--; tags--; @@ -242,11 +244,24 @@ if ( point <= limit ) { FT_Vector vec; + FT_Vector v_middle; + tag = FT_CURVE_TAG( tags[0] ); + vec.x = SCALED( point->x ); vec.y = SCALED( point->y ); + if ( tag == FT_CURVE_TAG_CUBIC ) + { + v_middle.x = ( vec2.x + vec.x ) / 2; + v_middle.y = ( vec2.y + vec.y ) / 2; + + vec = v_middle; + } + else if ( tag != FT_CURVE_TAG_ON ) + goto Invalid_Outline; + FT_TRACE5(( " cubic to (%.2f, %.2f)" " with controls (%.2f, %.2f) and (%.2f, %.2f)\n", (double)vec.x / 64, @@ -258,6 +273,12 @@ error = func_interface->cubic_to( &vec1, &vec2, &vec, user ); if ( error ) goto Exit; + + if ( tag == FT_CURVE_TAG_CUBIC ) + { + point--; + tags--; + } continue; } @@ -554,11 +575,15 @@ last = -1; for ( n = 0; n < outline->n_contours; n++ ) { - /* keep the first contour point as is and swap points around it */ - /* to guarantee that the cubic arches stay valid after reverse */ - first = last + 2; + first = last + 1; last = outline->contours[n]; + /* Keep the first point unless it is a cubic control. In that */ + /* case, reverse the entire contour to preserve control pairs, */ + /* including contours with no explicit on-curve points. */ + if ( FT_CURVE_TAG( outline->tags[first] ) != FT_CURVE_TAG_CUBIC ) + first++; + /* reverse point table */ { FT_Vector* p = outline->points + first; @@ -1050,12 +1075,17 @@ FT_EXPORT_DEF( FT_Orientation ) FT_Outline_Get_Orientation( FT_Outline* outline ) { + FT_Vector* points; + FT_Int c, n, first, last; + +#ifdef FT_INT64 + FT_Int64 area = 0; +#else FT_BBox cbox = { 0, 0, 0, 0 }; FT_Int xshift, yshift; - FT_Vector* points; FT_Vector v_prev, v_cur; - FT_Int c, n, first, last; FT_Pos area = 0; +#endif if ( !outline || outline->n_points <= 0 ) @@ -1066,6 +1096,42 @@ /* cubic or quadratic curves, this test deals with the polygon */ /* only that is spanned up by the control points. */ + points = outline->points; + +#ifdef FT_INT64 + + /* Single-pass shoelace. The 64-bit accumulator removes the need */ + /* for the coordinate shift pre-pass (and its `FT_Outline_Get_CBox` */ + /* traversal) that the 32-bit variant below uses. */ + last = -1; + for ( c = 0; c < outline->n_contours; c++ ) + { + FT_Pos prev_x, prev_y; + + + first = last + 1; + last = outline->contours[c]; + + prev_x = points[last].x; + prev_y = points[last].y; + + for ( n = first; n <= last; n++ ) + { + FT_Pos cur_x = points[n].x; + FT_Pos cur_y = points[n].y; + + + area = ADD_INT64( area, + MUL_INT64( SUB_INT64( cur_y, prev_y ), + ADD_INT64( cur_x, prev_x ) ) ); + + prev_x = cur_x; + prev_y = cur_y; + } + } + +#else /* !FT_INT64 */ + FT_Outline_Get_CBox( outline, &cbox ); /* Handle collapsed outlines to avoid undefined FT_MSB. */ @@ -1084,8 +1150,6 @@ yshift = FT_MSB( (FT_UInt32)( cbox.yMax - cbox.yMin ) ) - 14; yshift = FT_MAX( yshift, 0 ); - points = outline->points; - last = -1; for ( c = 0; c < outline->n_contours; c++ ) { @@ -1108,6 +1172,8 @@ } } +#endif /* FT_INT64 */ + if ( area > 0 ) return FT_ORIENTATION_POSTSCRIPT; else if ( area < 0 ) diff --git a/src/java.desktop/share/native/libfreetype/src/base/ftstroke.c b/src/java.desktop/share/native/libfreetype/src/base/ftstroke.c index e599ecaf46f..cc93265b0ba 100644 --- a/src/java.desktop/share/native/libfreetype/src/base/ftstroke.c +++ b/src/java.desktop/share/native/libfreetype/src/base/ftstroke.c @@ -656,12 +656,11 @@ FT_UInt num_contours = 0; FT_UInt count = border->num_points; - FT_Vector* point = border->points; FT_Byte* tags = border->tags; FT_Int in_contour = 0; - for ( ; count > 0; count--, num_points++, point++, tags++ ) + for ( ; count > 0; count--, num_points++, tags++ ) { if ( tags[0] & FT_STROKE_TAG_BEGIN ) { @@ -1875,6 +1874,10 @@ goto Exit; } + /* don't try to end the path if no segments have been generated */ + if ( stroker->first_point ) + goto Exit; + if ( stroker->subpath_open ) { FT_StrokeBorder right = stroker->borders; @@ -2095,15 +2098,14 @@ tags = outline->tags + first; tag = FT_CURVE_TAG( tags[0] ); - /* A contour cannot start with a cubic control point! */ - if ( tag == FT_CURVE_TAG_CUBIC ) - goto Invalid_Outline; - /* check first point to determine origin */ - if ( tag == FT_CURVE_TAG_CONIC ) + if ( tag != FT_CURVE_TAG_ON ) { - /* First point is conic control. Yes, this happens. */ - if ( FT_CURVE_TAG( outline->tags[last] ) == FT_CURVE_TAG_ON ) + FT_Int last_tag = FT_CURVE_TAG( outline->tags[last] ); + + + /* First point is a control point. Yes, this happens. */ + if ( last_tag == FT_CURVE_TAG_ON ) { /* start at last point if it is on the curve */ v_start = v_last; @@ -2111,8 +2113,11 @@ } else { - /* if both first and last points are conic, */ - /* start at their middle */ + if ( last_tag != tag ) + goto Invalid_Outline; + + /* if both first and last points are controls, */ + /* start at their middle */ v_start.x = ( v_start.x + v_last.x ) / 2; v_start.y = ( v_start.y + v_last.y ) / 2; } @@ -2206,13 +2211,31 @@ if ( point <= limit ) { FT_Vector vec; + FT_Vector v_middle; + tag = FT_CURVE_TAG( tags[0] ); vec = point[0]; + if ( tag == FT_CURVE_TAG_CUBIC ) + { + v_middle.x = ( vec2.x + vec.x ) / 2; + v_middle.y = ( vec2.y + vec.y ) / 2; + + vec = v_middle; + } + else if ( tag != FT_CURVE_TAG_ON ) + goto Invalid_Outline; + error = FT_Stroker_CubicTo( stroker, &vec1, &vec2, &vec ); if ( error ) goto Exit; + + if ( tag == FT_CURVE_TAG_CUBIC ) + { + point--; + tags--; + } continue; } @@ -2226,13 +2249,9 @@ if ( error ) goto Exit; - /* don't try to end the path if no segments have been generated */ - if ( !stroker->first_point ) - { - error = FT_Stroker_EndSubPath( stroker ); - if ( error ) - goto Exit; - } + error = FT_Stroker_EndSubPath( stroker ); + if ( error ) + goto Exit; } return FT_Err_Ok; diff --git a/src/java.desktop/share/native/libfreetype/src/cff/cffdrivr.c b/src/java.desktop/share/native/libfreetype/src/cff/cffdrivr.c index 18c7b879048..935548ea647 100644 --- a/src/java.desktop/share/native/libfreetype/src/cff/cffdrivr.c +++ b/src/java.desktop/share/native/libfreetype/src/cff/cffdrivr.c @@ -40,6 +40,10 @@ #include #endif +#ifdef TT_CONFIG_OPTION_VARC +#include +#endif + #include "cfferrs.h" #include @@ -1085,6 +1089,111 @@ #endif +#ifdef TT_CONFIG_OPTION_VARC + + /* + * VARC SERVICE + * + * The implementation belongs to the `truetype' module. The CFF + * driver only forwards calls for OpenType/CFF faces, in the same way + * that it forwards variation-font operations. + */ + + FT_CALLBACK_DEF( FT_Error ) + cff_varc_init_axes( FT_Face face, + FT_UInt axis_count ) + { + CFF_Face cffface = (CFF_Face)face; + FT_Service_VARC varc = (FT_Service_VARC)cffface->tt_varc; + + + return varc && varc->init_axes + ? varc->init_axes( face, axis_count ) + : FT_THROW( Missing_Module ); + } + + + FT_CALLBACK_DEF( FT_Error ) + cff_varc_load( FT_Face face, + FT_Stream stream ) + { + CFF_Face cffface = (CFF_Face)face; + FT_Service_VARC varc = (FT_Service_VARC)cffface->tt_varc; + + + return varc ? varc->load( face, stream ) + : FT_THROW( Missing_Module ); + } + + + FT_CALLBACK_DEF( void ) + cff_varc_done( FT_Face face ) + { + CFF_Face cffface = (CFF_Face)face; + FT_Service_VARC varc = (FT_Service_VARC)cffface->tt_varc; + + + if ( varc ) + varc->done( face ); + } + + + FT_CALLBACK_DEF( FT_Bool ) + cff_varc_has_glyph( FT_Face face, + FT_UInt glyph_index ) + { + CFF_Face cffface = (CFF_Face)face; + FT_Service_VARC varc = (FT_Service_VARC)cffface->tt_varc; + + + return FT_BOOL( varc && varc->has_glyph( face, glyph_index ) ); + } + + + FT_CALLBACK_DEF( FT_Error ) + cff_varc_load_glyph( FT_Face face, + FT_GlyphSlot glyph_slot, + FT_UInt glyph_index, + FT_Int32 load_flags ) + { + CFF_Face cffface = (CFF_Face)face; + FT_Service_VARC varc = (FT_Service_VARC)cffface->tt_varc; + CFF_Font cff = (CFF_Font)cffface->extra.data; + + FT_Error error; + + + if ( !varc ) + return FT_THROW( Missing_Module ); + + if ( !FT_HAS_MULTIPLE_MASTERS( face ) && + cff && + cff->cff2 && + cff->vstore.axisCount ) + { + error = varc->init_axes( face, cff->vstore.axisCount ); + if ( error ) + return error; + } + + return varc->load_glyph( face, glyph_slot, + glyph_index, load_flags ); + } + + + FT_DEFINE_SERVICE_VARCREC( + cff_service_varc, + + cff_varc_init_axes, + cff_varc_load, + cff_varc_done, + cff_varc_has_glyph, + cff_varc_load_glyph + ) + +#endif /* TT_CONFIG_OPTION_VARC */ + + /* * CFFLOAD SERVICE * @@ -1113,7 +1222,24 @@ /*************************************************************************/ /*************************************************************************/ -#if defined TT_CONFIG_OPTION_GX_VAR_SUPPORT +#if defined( TT_CONFIG_OPTION_GX_VAR_SUPPORT ) && \ + defined( TT_CONFIG_OPTION_VARC ) + FT_DEFINE_SERVICEDESCREC11( + cff_services, + + FT_SERVICE_ID_FONT_FORMAT, FT_FONT_FORMAT_CFF, + FT_SERVICE_ID_MULTI_MASTERS, &cff_service_multi_masters, + FT_SERVICE_ID_METRICS_VARIATIONS, &cff_service_metrics_variations, + FT_SERVICE_ID_VARC, &cff_service_varc, + FT_SERVICE_ID_POSTSCRIPT_INFO, &cff_service_ps_info, + FT_SERVICE_ID_POSTSCRIPT_FONT_NAME, &cff_service_ps_name, + FT_SERVICE_ID_GLYPH_DICT, &cff_service_glyph_dict, + FT_SERVICE_ID_TT_CMAP, &cff_service_get_cmap_info, + FT_SERVICE_ID_CID, &cff_service_cid_info, + FT_SERVICE_ID_PROPERTIES, &cff_service_properties, + FT_SERVICE_ID_CFF_LOAD, &cff_service_cff_load + ) +#elif defined( TT_CONFIG_OPTION_GX_VAR_SUPPORT ) FT_DEFINE_SERVICEDESCREC10( cff_services, @@ -1128,6 +1254,20 @@ FT_SERVICE_ID_PROPERTIES, &cff_service_properties, FT_SERVICE_ID_CFF_LOAD, &cff_service_cff_load ) +#elif defined( TT_CONFIG_OPTION_VARC ) + FT_DEFINE_SERVICEDESCREC9( + cff_services, + + FT_SERVICE_ID_FONT_FORMAT, FT_FONT_FORMAT_CFF, + FT_SERVICE_ID_VARC, &cff_service_varc, + FT_SERVICE_ID_POSTSCRIPT_INFO, &cff_service_ps_info, + FT_SERVICE_ID_POSTSCRIPT_FONT_NAME, &cff_service_ps_name, + FT_SERVICE_ID_GLYPH_DICT, &cff_service_glyph_dict, + FT_SERVICE_ID_TT_CMAP, &cff_service_get_cmap_info, + FT_SERVICE_ID_CID, &cff_service_cid_info, + FT_SERVICE_ID_PROPERTIES, &cff_service_properties, + FT_SERVICE_ID_CFF_LOAD, &cff_service_cff_load + ) #else FT_DEFINE_SERVICEDESCREC8( cff_services, diff --git a/src/java.desktop/share/native/libfreetype/src/cff/cffgload.c b/src/java.desktop/share/native/libfreetype/src/cff/cffgload.c index 0c986930cee..331c681b8ae 100644 --- a/src/java.desktop/share/native/libfreetype/src/cff/cffgload.c +++ b/src/java.desktop/share/native/libfreetype/src/cff/cffgload.c @@ -24,6 +24,10 @@ #include #include +#ifdef TT_CONFIG_OPTION_VARC +#include +#endif + #include "cffload.h" #include "cffgload.h" @@ -235,7 +239,7 @@ return FT_THROW( Invalid_Argument ); } } - else if ( glyph_index >= cff->num_glyphs ) + else if ( glyph_index >= (FT_ULong)face->root.num_glyphs ) return FT_THROW( Invalid_Argument ); #ifdef TT_CONFIG_OPTION_EMBEDDED_BITMAPS @@ -408,6 +412,76 @@ #endif /* FT_CONFIG_OPTION_SVG */ +#ifdef TT_CONFIG_OPTION_VARC + + /* check for VARC glyphs */ + { + FT_Service_VARC varc = (FT_Service_VARC)face->face_varc; + + + if ( face->varc && + varc && + varc->has_glyph( (FT_Face)face, glyph_index ) ) + { + FT_TRACE3(( "Loading VARC glyph\n" )); + + error = varc->load_glyph( (FT_Face)face, + (FT_GlyphSlot)glyph, + glyph_index, + load_flags ); + if ( !error ) + { + SFNT_Service sfnt = (SFNT_Service)face->sfnt; + + FT_Short sb; + FT_UShort advanceX = 0; + FT_UShort advanceY = 0; + FT_BBox bbox; + + + FT_TRACE3(( "Successfully loaded VARC glyph\n" )); + + /* The outline comes from the VARC table, so the advance and */ + /* metrics must be supplied here (`get_metrics` applies */ + /* HVAR/VVAR). */ + sfnt->get_metrics( face, FALSE, glyph_index, &sb, &advanceX ); + sfnt->get_metrics( face, TRUE, glyph_index, &sb, &advanceY ); + + glyph->root.linearHoriAdvance = advanceX; + glyph->root.linearVertAdvance = advanceY; + + FT_Outline_Get_CBox( &glyph->root.outline, &bbox ); + glyph->root.metrics.horiBearingX = bbox.xMin; + glyph->root.metrics.horiBearingY = bbox.yMax; + glyph->root.metrics.width = SUB_LONG( bbox.xMax, bbox.xMin ); + glyph->root.metrics.height = SUB_LONG( bbox.yMax, bbox.yMin ); + + if ( ( load_flags & FT_LOAD_NO_SCALE ) || !size ) + { + glyph->root.metrics.horiAdvance = advanceX; + glyph->root.metrics.vertAdvance = advanceY; + } + else + { + glyph->root.metrics.horiAdvance = + FT_MulFix( advanceX, size->root.metrics.x_scale ); + glyph->root.metrics.vertAdvance = + FT_MulFix( advanceY, size->root.metrics.y_scale ); + } + + return FT_Err_Ok; + } + + FT_TRACE3(( "Failed to load VARC glyph, falling back to CFF\n" )); + } + } + +#endif /* TT_CONFIG_OPTION_VARC */ + + /* A VARC-only glyph has no CFF2 fallback CharString. */ + if ( glyph_index >= cff->num_glyphs ) + return FT_THROW( Invalid_Argument ); + /* top-level code ensures that FT_LOAD_NO_HINTING is set */ /* if FT_LOAD_NO_SCALE is active */ hinting = FT_BOOL( ( load_flags & FT_LOAD_NO_HINTING ) == 0 ); diff --git a/src/java.desktop/share/native/libfreetype/src/cff/cffload.c b/src/java.desktop/share/native/libfreetype/src/cff/cffload.c index 9d067cfeeb8..cfd5317ff52 100644 --- a/src/java.desktop/share/native/libfreetype/src/cff/cffload.c +++ b/src/java.desktop/share/native/libfreetype/src/cff/cffload.c @@ -634,6 +634,12 @@ if ( error ) goto Exit; + if ( !bytes ) + { + error = FT_THROW( Invalid_Table ); + goto Exit; + } + if ( !FT_QALLOC( name, byte_len + 1 ) ) { FT_MEM_COPY( name, bytes, byte_len ); diff --git a/src/java.desktop/share/native/libfreetype/src/cff/cffobjs.c b/src/java.desktop/share/native/libfreetype/src/cff/cffobjs.c index c6ba35e2734..8efdd109f95 100644 --- a/src/java.desktop/share/native/libfreetype/src/cff/cffobjs.c +++ b/src/java.desktop/share/native/libfreetype/src/cff/cffobjs.c @@ -624,7 +624,13 @@ /* Note that this is only necessary for pure CFF and CEF fonts; */ /* SFNT based fonts use the `name' table instead. */ - cffface->num_glyphs = (FT_Long)cff->num_glyphs; + /* VARC can define glyphs beyond the CFF2 CharStrings INDEX. */ +#ifdef TT_CONFIG_OPTION_VARC + if ( !cff2 || + !face->varc || + cff->num_glyphs > face->max_profile.numGlyphs ) +#endif + cffface->num_glyphs = (FT_Long)cff->num_glyphs; dict = &cff->top_font.font_dict; diff --git a/src/java.desktop/share/native/libfreetype/src/cid/cidload.c b/src/java.desktop/share/native/libfreetype/src/cid/cidload.c index 1a0e6b1561e..170ae2d1c7d 100644 --- a/src/java.desktop/share/native/libfreetype/src/cid/cidload.c +++ b/src/java.desktop/share/native/libfreetype/src/cid/cidload.c @@ -512,12 +512,14 @@ CID_FaceInfo cid = &face->cid; FT_Memory memory = face->root.memory; FT_Stream stream = face->cid_stream; + PSAux_Service psaux = (PSAux_Service)face->psaux; FT_Error error; + FT_UInt n; CID_Subrs subr; FT_UInt max_offsets = 0; - FT_ULong* offsets = NULL; - PSAux_Service psaux = (PSAux_Service)face->psaux; + FT_ULong* offsets = NULL; + FT_ULong cum_len = 0; if ( FT_NEW_ARRAY( face->subrs, cid->num_dicts ) ) @@ -584,6 +586,14 @@ /* now, compute the size of subrs charstrings, */ /* allocate, and read them */ data_len = offsets[num_subrs] - offsets[0]; + cum_len += data_len; + + if ( cum_len / 4 > stream->size ) + { + FT_ERROR(( "cid_read_subrs: excessive overlaps\n" )); + error = FT_THROW( Invalid_File_Format ); + goto Fail; + } if ( FT_QNEW_ARRAY( subr->code, num_subrs + 1 ) || FT_QALLOC( subr->code[0], data_len ) ) diff --git a/src/java.desktop/share/native/libfreetype/src/psaux/psintrp.c b/src/java.desktop/share/native/libfreetype/src/psaux/psintrp.c index 7e3475e6f58..bb8efc8156f 100644 --- a/src/java.desktop/share/native/libfreetype/src/psaux/psintrp.c +++ b/src/java.desktop/share/native/libfreetype/src/psaux/psintrp.c @@ -415,7 +415,7 @@ /* Blend numOperands on the stack, */ /* store results into the first numBlends values, */ /* then pop remaining arguments. */ - static void + static FT_Error cf2_doBlend( const CFF_Blend blend, CF2_Stack opStack, CF2_UInt numBlends ) @@ -423,10 +423,19 @@ CF2_UInt delta; CF2_UInt base; CF2_UInt i, j; - CF2_UInt numOperands = (CF2_UInt)( numBlends * blend->lenBV ); + CF2_UInt numOperands; + CF2_UInt count = cf2_stack_count( opStack ); + + + if ( blend->lenBV == 0 || + numBlends > count / blend->lenBV ) + return FT_THROW( Stack_Underflow ); + numOperands = numBlends * blend->lenBV; /* cannot overflow here */ + if ( numOperands > count ) + return FT_THROW( Stack_Underflow ); - base = cf2_stack_count( opStack ) - numOperands; + base = count - numOperands; delta = base + numBlends; FT_TRACE6(( " (" )); @@ -455,6 +464,8 @@ /* leave only `numBlends' results on stack */ cf2_stack_pop( opStack, numOperands - numBlends ); + + return FT_Err_Ok; } @@ -769,13 +780,10 @@ /* do the blend */ numBlends = (FT_UInt)cf2_stack_popInt( opStack ); - if ( numBlends > stackSize ) - { - lastError = FT_THROW( Invalid_Glyph_Format ); - goto exit; - } - cf2_doBlend( &font->blend, opStack, numBlends ); + lastError = cf2_doBlend( &font->blend, opStack, numBlends ); + if ( lastError ) + goto exit; font->blend.usedBV = TRUE; } @@ -985,8 +993,8 @@ FT_TRACE4(( "%s", op1 == cf2_cmdCALLGSUBR ? " callgsubr" : " callsubr" )); - if ( ( !font->isT1 && charstringIndex > CF2_MAX_SUBR ) || - ( font->isT1 && charstringIndex > T1_MAX_SUBRS_CALLS ) ) + if ( ( !font->isT1 && charstringIndex >= CF2_MAX_SUBR ) || + ( font->isT1 && charstringIndex >= T1_MAX_SUBRS_CALLS ) ) { /* max subr plus one for charstring */ lastError = FT_THROW( Invalid_Glyph_Format ); diff --git a/src/java.desktop/share/native/libfreetype/src/psaux/psstack.c b/src/java.desktop/share/native/libfreetype/src/psaux/psstack.c index 797486588a3..1360ba30dab 100644 --- a/src/java.desktop/share/native/libfreetype/src/psaux/psstack.c +++ b/src/java.desktop/share/native/libfreetype/src/psaux/psstack.c @@ -211,7 +211,7 @@ CF2_UInt idx, CF2_Fixed val ) { - if ( idx > cf2_stack_count( stack ) ) + if ( idx >= cf2_stack_count( stack ) ) { CF2_SET_ERROR( stack->error, Stack_Overflow ); return; @@ -245,6 +245,7 @@ CF2_StackNumber last = { { 0 }, CF2_NumberInt }; CF2_Int start_idx, idx, i; + CF2_Int offset; if ( count < 2 ) @@ -256,6 +257,8 @@ return; } + offset = (CF2_Int)cf2_stack_count( stack ) - count; + /* before C99 it is implementation-defined whether */ /* the result of `%' is negative if the first operand */ /* is negative */ @@ -303,7 +306,7 @@ { start_idx++; idx = start_idx; - last = stack->buffer[idx]; + last = stack->buffer[idx + offset]; } idx += shift; @@ -312,9 +315,9 @@ else if ( idx < 0 ) idx += count; - tmp = stack->buffer[idx]; - stack->buffer[idx] = last; - last = tmp; + tmp = stack->buffer[idx + offset]; + stack->buffer[idx + offset] = last; + last = tmp; } } diff --git a/src/java.desktop/share/native/libfreetype/src/raster/ftraster.c b/src/java.desktop/share/native/libfreetype/src/raster/ftraster.c index 4165800a1aa..e0121f6ee1a 100644 --- a/src/java.desktop/share/native/libfreetype/src/raster/ftraster.c +++ b/src/java.desktop/share/native/libfreetype/src/raster/ftraster.c @@ -1622,15 +1622,14 @@ tag = FT_CURVE_TAG( tags[0] ); - /* A contour cannot start with a cubic control point! */ - if ( tag == FT_CURVE_TAG_CUBIC ) - goto Invalid_Outline; - /* check first point to determine origin */ - if ( tag == FT_CURVE_TAG_CONIC ) + if ( tag != FT_CURVE_TAG_ON ) { - /* first point is conic control. Yes, this happens. */ - if ( FT_CURVE_TAG( ras.outline.tags[last] ) == FT_CURVE_TAG_ON ) + UInt last_tag = FT_CURVE_TAG( ras.outline.tags[last] ); + + + /* first point is a control point. Yes, this happens. */ + if ( last_tag == FT_CURVE_TAG_ON ) { /* start at last point if it is on the curve */ v_start = v_last; @@ -1638,9 +1637,12 @@ } else { - /* if both first and last points are conic, */ - /* start at their middle and record its position */ - /* for closure */ + if ( last_tag != tag ) + goto Invalid_Outline; + + /* if both first and last points are controls, */ + /* start at their middle and record its position */ + /* for closure */ v_start.x = ( v_start.x + v_last.x ) / 2; v_start.y = ( v_start.y + v_last.y ) / 2; @@ -1755,14 +1757,30 @@ if ( point <= limit ) { + tag = FT_CURVE_TAG( tags[0] ); + x3 = SCALED( point[0].x ); y3 = SCALED( point[0].y ); if ( flipped ) SWAP_( x3, y3 ); + if ( tag == FT_CURVE_TAG_CUBIC ) + { + x3 = ( x2 + x3 ) / 2; + y3 = ( y2 + y3 ) / 2; + } + else if ( tag != FT_CURVE_TAG_ON ) + goto Invalid_Outline; + if ( Cubic_To( RAS_VARS x1, y1, x2, y2, x3, y3 ) ) goto Fail; + + if ( tag == FT_CURVE_TAG_CUBIC ) + { + point--; + tags--; + } continue; } @@ -2447,9 +2465,8 @@ Int y_min, Int y_max ) { - Int y_mid; - Int band_top = 0; - Int band_stack[32]; /* enough to bisect 32-bit int bands */ + Int band_stack[32]; /* enough to bisect 32-bit int bands */ + Int* band = band_stack; FT_TRACE6(( "%s pass [%d..%d]\n", @@ -2476,10 +2493,8 @@ FT_TRACE6(( "band [%d..%d]: to be bisected\n", y_min, y_max )); - y_mid = ( y_min + y_max ) >> 1; - - band_stack[band_top++] = y_min; - y_min = y_mid + 1; + *band++ = y_min; + y_min = ( y_min + y_max + 1 ) >> 1; } else { @@ -2490,11 +2505,11 @@ if ( ras.fProfile ) Draw_Sweep( RAS_VAR ); - if ( --band_top < 0 ) - break; + if ( band == band_stack ) + break; /* done */ y_max = y_min - 1; - y_min = band_stack[band_top]; + y_min = *--band; } } @@ -2517,11 +2532,7 @@ Render_Glyph( RAS_ARG ) { FT_Error error; - Long buffer[FT_MAX_BLACK_POOL]; - - ras.buff = buffer; - ras.sizeBuff = (&buffer)[1]; /* Points to right after buffer. */ Set_High_Precision( RAS_VARS ras.outline.flags & FT_OUTLINE_HIGH_PRECISION ); @@ -2665,6 +2676,9 @@ const FT_Outline* outline = (const FT_Outline*)params->source; const FT_Bitmap* target_map = params->target; + FT_ULong estimate; + int ret; + #ifndef FT_STATIC_RASTER black_TWorker worker[1]; #endif @@ -2712,7 +2726,36 @@ if ( ras.bPitch > 0 ) ras.bOrigin += ras.bTop * ras.bPitch; - return Render_Glyph( RAS_VAR ); + /* allocate memory based on empirical estimate from CJK fonts */ + estimate = ( ras.bTop + ras.bRight ) * 8UL + + 80UL * sizeof ( TProfile ) / sizeof ( Long ); + if ( estimate > FT_MAX_BLACK_POOL ) + { + FT_Error error; + FT_Memory memory = (FT_Memory)((black_PRaster)raster)->memory; + + + if ( FT_QNEW_ARRAY( ras.buff, estimate ) ) + ret = error; + else + { + ras.sizeBuff = ras.buff + estimate; + ret = Render_Glyph( RAS_VAR ); + FT_FREE( ras.buff ); + } + } + else + { + Long buffer[FT_MAX_BLACK_POOL]; /* stack allocation */ + + + ras.buff = buffer; + ras.sizeBuff = (&buffer)[1]; /* Points to right after buffer. */ + + ret = Render_Glyph( RAS_VAR ); + } + + return ret; } diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/sfobjs.c b/src/java.desktop/share/native/libfreetype/src/sfnt/sfobjs.c index fc855cbb6b9..b894331935a 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/sfobjs.c +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/sfobjs.c @@ -34,6 +34,10 @@ #include #endif +#ifdef TT_CONFIG_OPTION_VARC +#include +#endif + #include "sferrors.h" #ifdef TT_CONFIG_OPTION_BDF @@ -440,7 +444,10 @@ if ( tag == TTAG_ttcf ) { - FT_Int n; + FT_ULong version; + FT_ULong count; + FT_Bool has_extended_offsets; + FT_Int n; FT_TRACE3(( "sfnt_open_font: file is a collection\n" )); @@ -448,10 +455,13 @@ if ( FT_STREAM_READ_FIELDS( ttc_header_fields, &face->ttc_header ) ) return error; - FT_TRACE3(( " with %ld subfonts\n", - face->ttc_header.count )); + version = (FT_ULong)face->ttc_header.version; + count = (FT_ULong)face->ttc_header.count; - if ( face->ttc_header.count == 0 ) + has_extended_offsets = ( version >> 16 == 1 || version >> 16 == 2 ) && + ( version & 0xFFFF ) >= 1; + + if ( !has_extended_offsets && face->ttc_header.count == 0 ) return FT_THROW( Invalid_Table ); /* a rough size estimate: let's conservatively assume that there */ @@ -459,7 +469,38 @@ /* 28 bytes), thus we have (at least) `12 + 4*count' bytes for the */ /* size of the TTC header plus `28*count' bytes for all subfont */ /* headers */ - if ( (FT_ULong)face->ttc_header.count > stream->size / ( 28 + 4 ) ) + if ( count > stream->size / ( 28 + 4 ) ) + return FT_THROW( Array_Too_Large ); + + if ( has_extended_offsets ) + { + FT_TRACE3(( " with %lu compatibility subfonts\n", + count )); + + if ( FT_STREAM_SKIP( count * 4L ) ) + return error; + + /* In version 2.1 the extended count and offsets follow the */ + /* three version 2 DSIG fields. */ + if ( version >> 16 == 2 && FT_STREAM_SKIP( 12 ) ) + return error; + + if ( FT_READ_ULONG( count ) ) + return error; + + if ( count > FT_LONG_MAX ) + return FT_THROW( Array_Too_Large ); + + face->ttc_header.count = (FT_Long)count; + } + + FT_TRACE3(( " with %ld subfonts\n", + face->ttc_header.count )); + + if ( face->ttc_header.count == 0 ) + return FT_THROW( Invalid_Table ); + + if ( count > stream->size / ( 28 + 4 ) ) return FT_THROW( Array_Too_Large ); /* now read the offsets of each font in the file */ @@ -557,6 +598,24 @@ 0 ); #endif +#ifdef TT_CONFIG_OPTION_VARC + if ( !face->tt_varc ) + { + /* we want the VARC implementation from the `truetype' module only */ + FT_Module tt_module = FT_Get_Module( library, "truetype" ); + + + face->tt_varc = ft_module_get_service( tt_module, + FT_SERVICE_ID_VARC, + 0 ); + } + + if ( !face->face_varc ) + face->face_varc = ft_module_get_service( &face->root.driver->root, + FT_SERVICE_ID_VARC, + 0 ); +#endif + FT_TRACE2(( "SFNT driver\n" )); error = sfnt_open_font( stream, @@ -719,10 +778,12 @@ FT_FREE( default_values ); FT_FREE( instance_values ); - /* we don't support Multiple Master CFFs yet; */ - /* note that `glyf' or `CFF2' have precedence */ - if ( face->goto_table( face, TTAG_glyf, stream, 0 ) && + /* we don't support Multiple Master CFFs yet; */ + /* note that 'GLYF', 'glyf', or 'CFF2' have precedence */ + if ( face->goto_table( face, TTAG_GLYF, stream, 0 ) && + face->goto_table( face, TTAG_glyf, stream, 0 ) && face->goto_table( face, TTAG_CFF2, stream, 0 ) && + face->goto_table( face, TTAG_hvgl, stream, 0 ) && !face->goto_table( face, TTAG_CFF, stream, 0 ) ) num_instances = 0; @@ -862,12 +923,20 @@ /* do we have outlines in there? */ #ifdef FT_CONFIG_OPTION_INCREMENTAL + face->is_extended_glyf = + FT_BOOL( tt_face_lookup_table( face, TTAG_GLYF ) ); + has_outline = FT_BOOL( face->root.internal->incremental_interface || + face->is_extended_glyf || tt_face_lookup_table( face, TTAG_glyf ) || tt_face_lookup_table( face, TTAG_CFF ) || tt_face_lookup_table( face, TTAG_CFF2 ) ); #else - has_outline = FT_BOOL( tt_face_lookup_table( face, TTAG_glyf ) || + face->is_extended_glyf = + FT_BOOL( tt_face_lookup_table( face, TTAG_GLYF ) ); + + has_outline = FT_BOOL( face->is_extended_glyf || + tt_face_lookup_table( face, TTAG_glyf ) || tt_face_lookup_table( face, TTAG_CFF ) || tt_face_lookup_table( face, TTAG_CFF2 ) ); #endif @@ -950,6 +1019,7 @@ get_glyph_metrics ) { face->horizontal.number_Of_HMetrics = 0; + face->horz_metrics_count = 0; error = FT_Err_Ok; } #endif @@ -977,6 +1047,7 @@ get_glyph_metrics ) { face->horizontal.number_Of_HMetrics = 0; + face->horz_metrics_count = 0; error = FT_Err_Ok; } #endif @@ -1024,6 +1095,27 @@ LOAD_( colr ); } +#ifdef TT_CONFIG_OPTION_VARC + /* variable composite glyph support */ + if ( face->face_varc ) + { + FT_Service_VARC varc = (FT_Service_VARC)face->face_varc; + + + FT_TRACE2(( "'VARC' " )); + FT_TRACE3(( "-->\n" )); + + error = varc->load( (FT_Face)face, stream ); + + FT_TRACE2(( "%s\n", ( !error ) + ? "loaded" + : FT_ERR_EQ( error, Table_Missing ) + ? "missing" + : "failed to load" )); + FT_TRACE3(( "\n" )); + } +#endif + /* OpenType-SVG glyph support */ if ( sfnt->load_svg ) LOAD_( svg ); @@ -1037,7 +1129,7 @@ LOAD_( gpos ); #endif - face->root.num_glyphs = face->max_profile.numGlyphs; + face->root.num_glyphs = face->maxp_num_glyphs; /* Bit 8 of the `fsSelection' field in the `OS/2' table denotes */ /* a WWS-only font face. `WWS' stands for `weight', width', and */ @@ -1190,13 +1282,10 @@ tt_face_build_cmaps( face ); /* ignore errors */ - /* set the encoding fields */ { FT_Int m; -#ifdef FT_CONFIG_OPTION_POSTSCRIPT_NAMES - FT_Bool has_unicode = FALSE; -#endif + FT_UInt n; for ( m = 0; m < root->num_charmaps; m++ ) @@ -1206,38 +1295,58 @@ charmap->encoding = sfnt_find_encoding( charmap->platform_id, charmap->encoding_id ); + } + + for ( n = 0; n < face->num_dmap_charmaps; n++ ) + { + FT_CharMap charmap = &face->dmap_charmaps[n]->charmap; -#ifdef FT_CONFIG_OPTION_POSTSCRIPT_NAMES - if ( charmap->encoding == FT_ENCODING_UNICODE || - charmap->encoding == FT_ENCODING_MS_SYMBOL ) /* PUA */ - has_unicode = TRUE; + charmap->encoding = sfnt_find_encoding( charmap->platform_id, + charmap->encoding_id ); } - /* synthesize Unicode charmap if one is missing */ - if ( !has_unicode && - root->face_flags & FT_FACE_FLAG_GLYPH_NAMES ) + tt_face_build_dmaps( face ); /* ignore errors */ + +#ifdef FT_CONFIG_OPTION_POSTSCRIPT_NAMES { - FT_CharMapRec cmaprec; + FT_Bool has_unicode = FALSE; - cmaprec.face = root; - cmaprec.platform_id = TT_PLATFORM_MICROSOFT; - cmaprec.encoding_id = TT_MS_ID_UNICODE_CS; - cmaprec.encoding = FT_ENCODING_UNICODE; + for ( m = 0; m < root->num_charmaps; m++ ) + { + FT_CharMap charmap = root->charmaps[m]; - error = FT_CMap_New( (FT_CMap_Class)&tt_cmap_unicode_class_rec, - NULL, &cmaprec, NULL ); - if ( error && - FT_ERR_NEQ( error, No_Unicode_Glyph_Name ) && - FT_ERR_NEQ( error, Unimplemented_Feature ) ) - goto Exit; - error = FT_Err_Ok; + if ( charmap->encoding == FT_ENCODING_UNICODE || + charmap->encoding == FT_ENCODING_MS_SYMBOL ) /* PUA */ + has_unicode = TRUE; + } + + /* synthesize Unicode charmap if one is missing */ + if ( !has_unicode && + root->face_flags & FT_FACE_FLAG_GLYPH_NAMES ) + { + FT_CharMapRec cmaprec; + + + cmaprec.face = root; + cmaprec.platform_id = TT_PLATFORM_MICROSOFT; + cmaprec.encoding_id = TT_MS_ID_UNICODE_CS; + cmaprec.encoding = FT_ENCODING_UNICODE; -#endif /* FT_CONFIG_OPTION_POSTSCRIPT_NAMES */ + error = FT_CMap_New( (FT_CMap_Class)&tt_cmap_unicode_class_rec, + NULL, &cmaprec, NULL ); + if ( error && + FT_ERR_NEQ( error, No_Unicode_Glyph_Name ) && + FT_ERR_NEQ( error, Unimplemented_Feature ) ) + goto Exit; + error = FT_Err_Ok; + } } + +#endif /* FT_CONFIG_OPTION_POSTSCRIPT_NAMES */ } #ifdef TT_CONFIG_OPTION_EMBEDDED_BITMAPS @@ -1470,6 +1579,17 @@ sfnt->free_colr( face ); } +#ifdef TT_CONFIG_OPTION_VARC + /* free VARC data */ + if ( face->face_varc ) + { + FT_Service_VARC varc = (FT_Service_VARC)face->face_varc; + + + varc->done( (FT_Face)face ); + } +#endif + #ifdef FT_CONFIG_OPTION_SVG /* free SVG data */ if ( sfnt->free_svg ) @@ -1502,9 +1622,14 @@ FT_Stream stream = FT_FACE_STREAM( face ); - /* simply release the 'cmap' table frame */ + tt_face_done_dmaps( face ); + + /* simply release the 'cmap' and 'DMAP' table frames */ FT_FRAME_RELEASE( face->cmap_table ); face->cmap_size = 0; + + FT_FRAME_RELEASE( face->dmap_table ); + face->dmap_size = 0; } face->horz_metrics_size = 0; @@ -1549,6 +1674,11 @@ FT_FREE( face->palette ); face->sfnt = NULL; + +#ifdef TT_CONFIG_OPTION_VARC + face->tt_varc = NULL; + face->face_varc = NULL; +#endif } diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/sfwoff.c b/src/java.desktop/share/native/libfreetype/src/sfnt/sfwoff.c index b8a3030d9cb..02125e3cba5 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/sfwoff.c +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/sfwoff.c @@ -310,8 +310,12 @@ woff_offset += woff.privLength; } - if ( sfnt_offset != woff.totalSfntSize || - woff_offset != woff.length ) + /* Reject unrealistic size or compression factor */ + if ( sfnt_offset > 0x4000000UL || + ( sfnt_offset >> 6 ) > woff_offset || + sfnt_offset != woff.totalSfntSize || + woff_offset != woff.length ) + { FT_ERROR(( "woff_font_open: invalid `sfnt' table structure\n" )); error = FT_THROW( Invalid_Table ); diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmap.c b/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmap.c index f5837e65e86..6fae7041b8e 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmap.c +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmap.c @@ -17,6 +17,7 @@ #include +#include #include "sferrors.h" /* must come before `ftvalid.h' */ @@ -2936,7 +2937,8 @@ * Ranges are sorted by `uniStart'. */ -#ifdef TT_CONFIG_CMAP_FORMAT_14 +#if defined( TT_CONFIG_CMAP_FORMAT_14 ) || \ + defined( TT_CONFIG_CMAP_FORMAT_15 ) typedef struct TT_CMap14Rec_ { @@ -3009,9 +3011,23 @@ } - FT_CALLBACK_DEF( FT_Error ) - tt_cmap14_validate( FT_Byte* table, - FT_Validator valid ) + static FT_Bool + tt_cmap14_is_extended( FT_CMap cmap ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + TT_CMap_Class clazz = (TT_CMap_Class)( ttcmap->cmap_class + ? ttcmap->cmap_class + : cmap->clazz ); + + + return FT_BOOL( clazz->format == 15 ); + } + + + static FT_Error + tt_cmap14_validate_internal( FT_Byte* table, + FT_Validator valid, + FT_Bool is_extended ) { FT_Byte* p; FT_ULong length; @@ -3092,9 +3108,10 @@ /* and the non-default table (these glyphs are specified here) */ if ( nondefOff != 0 ) { - FT_Byte* ndp = table + nondefOff; + FT_Byte* ndp = table + nondefOff; FT_ULong numMappings; - FT_ULong i, lastUni = 0; + FT_UInt record_size = is_extended ? 6 : 5; + FT_ULong i, lastUni = 0; if ( ndp + 4 > valid->limit ) @@ -3102,14 +3119,15 @@ numMappings = TT_NEXT_ULONG( ndp ); - /* numMappings * 5 > (FT_ULong)( valid->limit - ndp ) ? */ - if ( numMappings > ( (FT_ULong)( valid->limit - ndp ) ) / 5 ) + if ( numMappings > + ( (FT_ULong)( valid->limit - ndp ) ) / record_size ) FT_INVALID_TOO_SHORT; for ( i = 0; i < numMappings; i++ ) { FT_ULong uni = TT_NEXT_UINT24( ndp ); - FT_ULong gid = TT_NEXT_USHORT( ndp ); + FT_ULong gid = is_extended ? TT_NEXT_UINT24( ndp ) + : TT_NEXT_USHORT( ndp ); if ( uni >= 0x110000UL ) /* end of Unicode */ @@ -3132,6 +3150,30 @@ } +#ifdef TT_CONFIG_CMAP_FORMAT_14 + + FT_CALLBACK_DEF( FT_Error ) + tt_cmap14_validate( FT_Byte* table, + FT_Validator valid ) + { + return tt_cmap14_validate_internal( table, valid, FALSE ); + } + +#endif /* TT_CONFIG_CMAP_FORMAT_14 */ + + +#ifdef TT_CONFIG_CMAP_FORMAT_15 + + FT_CALLBACK_DEF( FT_Error ) + tt_cmap15_validate( FT_Byte* table, + FT_Validator valid ) + { + return tt_cmap14_validate_internal( table, valid, TRUE ); + } + +#endif /* TT_CONFIG_CMAP_FORMAT_15 */ + + FT_CALLBACK_DEF( FT_UInt ) tt_cmap14_char_index( FT_CMap cmap, FT_UInt32 char_code ) @@ -3160,9 +3202,13 @@ tt_cmap14_get_info( FT_CharMap cmap, TT_CMapInfo *cmap_info ) { - FT_UNUSED( cmap ); + TT_CMap ttcmap = (TT_CMap)cmap; + TT_CMap_Class clazz = (TT_CMap_Class)( ttcmap->cmap_class + ? ttcmap->cmap_class + : FT_CMAP( cmap )->clazz ); + - cmap_info->format = 14; + cmap_info->format = clazz->format; /* subtable 14 does not define a language field */ cmap_info->language = 0xFFFFFFFFUL; @@ -3206,10 +3252,12 @@ static FT_UInt tt_cmap14_char_map_nondef_binary( FT_Byte *base, - FT_UInt32 char_code ) + FT_UInt32 char_code, + FT_Bool is_extended ) { FT_UInt32 numMappings = TT_PEEK_ULONG( base ); FT_UInt32 max, min; + FT_UInt record_size = is_extended ? 6 : 5; min = 0; @@ -3221,7 +3269,7 @@ while ( min < max ) { FT_UInt32 mid = ( min + max ) >> 1; - FT_Byte* p = base + 5 * mid; + FT_Byte* p = base + record_size * mid; FT_UInt32 uni = (FT_UInt32)TT_NEXT_UINT24( p ); @@ -3230,7 +3278,7 @@ else if ( char_code > uni ) min = mid + 1; else - return TT_PEEK_USHORT( p ); + return is_extended ? TT_PEEK_UINT24( p ) : TT_PEEK_USHORT( p ); } return 0; @@ -3278,6 +3326,7 @@ { TT_CMap ttcmap = (TT_CMap)cmap; TT_CMap ttucmap = (TT_CMap)ucmap; + FT_Bool is_extended = tt_cmap14_is_extended( cmap ); FT_Byte* p = tt_cmap14_find_variant( ttcmap->data + 6, variantSelector ); @@ -3301,7 +3350,8 @@ if ( nondefOff != 0 ) return tt_cmap14_char_map_nondef_binary( ttcmap->data + nondefOff, - charcode ); + charcode, + is_extended ); return 0; } @@ -3313,6 +3363,7 @@ FT_UInt32 variantSelector ) { TT_CMap ttcmap = (TT_CMap)cmap; + FT_Bool is_extended = tt_cmap14_is_extended( cmap ); FT_Byte* p = tt_cmap14_find_variant( ttcmap->data + 6, variantSelector ); FT_ULong defOff; @@ -3331,7 +3382,8 @@ if ( nondefOff != 0 && tt_cmap14_char_map_nondef_binary( ttcmap->data + nondefOff, - charcode ) != 0 ) + charcode, + is_extended ) != 0 ) return 0; return -1; @@ -3375,6 +3427,7 @@ FT_UInt32 count = cmap14->num_selectors; FT_Byte* p = ttcmap->data + 10; FT_UInt32* q; + FT_Bool is_extended = tt_cmap14_is_extended( cmap ); if ( tt_cmap14_ensure( cmap14, ( count + 1 ), memory ) ) @@ -3392,7 +3445,8 @@ charCode ) ) || ( nondefOff != 0 && tt_cmap14_char_map_nondef_binary( ttcmap->data + nondefOff, - charCode ) != 0 ) ) + charCode, + is_extended ) != 0 ) ) { q[0] = varSel; q++; @@ -3462,7 +3516,8 @@ static FT_UInt32* tt_cmap14_get_nondef_chars( TT_CMap cmap, FT_Byte *p, - FT_Memory memory ) + FT_Memory memory, + FT_Bool is_extended ) { TT_CMap14 cmap14 = (TT_CMap14) cmap; FT_UInt32 numMappings; @@ -3479,7 +3534,7 @@ for ( i = 0; i < numMappings; i++ ) { ret[i] = (FT_UInt32)TT_NEXT_UINT24( p ); - p += 2; + p += is_extended ? 3 : 2; } ret[i] = 0; @@ -3495,6 +3550,7 @@ TT_CMap ttcmap = (TT_CMap)cmap; FT_Byte *p = tt_cmap14_find_variant( ttcmap->data + 6, variantSelector ); + FT_Bool is_extended = tt_cmap14_is_extended( cmap ); FT_Int i; FT_ULong defOff; FT_ULong nondefOff; @@ -3511,7 +3567,7 @@ if ( defOff == 0 ) return tt_cmap14_get_nondef_chars( ttcmap, ttcmap->data + nondefOff, - memory ); + memory, is_extended ); else if ( nondefOff == 0 ) return tt_cmap14_get_def_chars( ttcmap, ttcmap->data + defOff, memory ); @@ -3543,7 +3599,7 @@ memory ); if ( dcnt == 0 ) return tt_cmap14_get_nondef_chars( ttcmap, ttcmap->data + nondefOff, - memory ); + memory, is_extended ); if ( tt_cmap14_ensure( cmap14, ( dcnt + numMappings + 1 ), memory ) ) return NULL; @@ -3553,7 +3609,7 @@ dcnt = FT_NEXT_BYTE( dp ); di = 1; nuni = (FT_UInt32)TT_NEXT_UINT24( p ); - p += 2; + p += is_extended ? 3 : 2; ni = 1; i = 0; @@ -3583,7 +3639,7 @@ break; nuni = (FT_UInt32)TT_NEXT_UINT24( p ); - p += 2; + p += is_extended ? 3 : 2; } } @@ -3596,7 +3652,7 @@ while ( ni < numMappings ) { ret[i++] = (FT_UInt32)TT_NEXT_UINT24( p ); - p += 2; + p += is_extended ? 3 : 2; ni++; } } @@ -3626,6 +3682,8 @@ } +#ifdef TT_CONFIG_CMAP_FORMAT_14 + FT_DEFINE_TT_CMAP( tt_cmap14_class_rec, @@ -3651,6 +3709,35 @@ #endif /* TT_CONFIG_CMAP_FORMAT_14 */ +#ifdef TT_CONFIG_CMAP_FORMAT_15 + + FT_DEFINE_TT_CMAP( + tt_cmap15_class_rec, + + sizeof ( TT_CMap14Rec ), + + (FT_CMap_InitFunc) tt_cmap14_init, /* init */ + (FT_CMap_DoneFunc) tt_cmap14_done, /* done */ + (FT_CMap_CharIndexFunc)tt_cmap14_char_index, /* char_index */ + (FT_CMap_CharNextFunc) tt_cmap14_char_next, /* char_next */ + + /* Format 15 extension functions */ + (FT_CMap_CharVarIndexFunc) tt_cmap14_char_var_index, + (FT_CMap_CharVarIsDefaultFunc)tt_cmap14_char_var_isdefault, + (FT_CMap_VariantListFunc) tt_cmap14_variants, + (FT_CMap_CharVariantListFunc) tt_cmap14_char_variants, + (FT_CMap_VariantCharListFunc) tt_cmap14_variant_chars, + + 15, + (TT_CMap_ValidateFunc)tt_cmap15_validate, /* validate */ + (TT_CMap_Info_GetFunc)tt_cmap14_get_info /* get_cmap_info */ + ) + +#endif /* TT_CONFIG_CMAP_FORMAT_15 */ + +#endif /* TT_CONFIG_CMAP_FORMAT_14 || TT_CONFIG_CMAP_FORMAT_15 */ + + /*************************************************************************/ /*************************************************************************/ /***** *****/ @@ -3773,22 +3860,506 @@ }; - /* parse the `cmap' table and build the corresponding TT_CMap objects */ - /* in the current face */ - /* */ - FT_LOCAL_DEF( FT_Error ) - tt_face_build_cmaps( TT_Face face ) + static void + tt_cmap_free( FT_CMap cmap ) + { + FT_Memory memory = FT_FACE_MEMORY( cmap->charmap.face ); + + + if ( cmap->clazz->done ) + cmap->clazz->done( cmap ); + + FT_FREE( cmap ); + } + + + static FT_Error + tt_cmap_new( FT_CMap_Class clazz, + FT_Pointer init_data, + FT_CharMap charmap, + FT_CMap *acmap ) + { + FT_Error error = FT_Err_Ok; + FT_Memory memory = FT_FACE_MEMORY( charmap->face ); + FT_CMap cmap = NULL; + + + if ( !FT_ALLOC( cmap, clazz->size ) ) + { + cmap->charmap = *charmap; + cmap->clazz = clazz; + + if ( clazz->init ) + { + error = clazz->init( cmap, init_data ); + if ( error ) + goto Fail; + } + } + + *acmap = cmap; + return error; + + Fail: + tt_cmap_free( cmap ); + *acmap = NULL; + return error; + } + + + static FT_Error + tt_face_add_dmap( TT_Face face, + FT_CMap_Class clazz, + FT_Pointer init_data, + FT_CharMap charmap, + FT_Int flags ) + { + FT_Error error; + FT_Memory memory = FT_FACE_MEMORY( face ); + FT_CMap cmap; + FT_UInt count = face->num_dmap_charmaps; + + + error = tt_cmap_new( clazz, init_data, charmap, &cmap ); + if ( error ) + return error; + + ((TT_CMap)cmap)->flags = flags; + + if ( FT_QRENEW_ARRAY( face->dmap_charmaps, count, count + 1 ) ) + { + tt_cmap_free( cmap ); + return error; + } + + face->dmap_charmaps[count] = cmap; + face->num_dmap_charmaps = count + 1; + + return FT_Err_Ok; + } + + + static FT_Bool + tt_cmap_is_variation( FT_CMap cmap ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + TT_CMap_Class clazz = (TT_CMap_Class)( ttcmap->cmap_class + ? ttcmap->cmap_class + : cmap->clazz ); + + + return FT_BOOL( clazz->format == 14 || clazz->format == 15 ); + } + + + static FT_CMap + tt_face_find_unicode_dmap( TT_Face face ) + { + FT_CMap* first = face->dmap_charmaps; + FT_CMap* cur; + + + if ( !first ) + return NULL; + + cur = first + face->num_dmap_charmaps; + for ( ; --cur >= first; ) + { + FT_CMap cmap = cur[0]; + + + if ( !cmap || + tt_cmap_is_variation( cmap ) || + cmap->charmap.encoding != FT_ENCODING_UNICODE ) + continue; + + if ( ( cmap->charmap.platform_id == TT_PLATFORM_MICROSOFT && + cmap->charmap.encoding_id == TT_MS_ID_UCS_4 ) || + ( cmap->charmap.platform_id == TT_PLATFORM_APPLE_UNICODE && + cmap->charmap.encoding_id == TT_APPLE_ID_UNICODE_32 ) || + ( cmap->charmap.platform_id == TT_PLATFORM_APPLE_UNICODE && + cmap->charmap.encoding_id == TT_APPLE_ID_FULL_UNICODE && + ( (TT_CMap_Class)cmap->clazz )->format == 13 ) ) + return cmap; + } + + cur = first + face->num_dmap_charmaps; + for ( ; --cur >= first; ) + { + FT_CMap cmap = cur[0]; + + + if ( cmap && + !tt_cmap_is_variation( cmap ) && + cmap->charmap.encoding == FT_ENCODING_UNICODE ) + return cmap; + } + + return NULL; + } + + + static FT_CMap + tt_face_find_dmap( TT_Face face, + FT_CharMap charmap ) + { + FT_CMap* first = face->dmap_charmaps; + FT_CMap* cur; + + + if ( !first ) + return NULL; + + if ( tt_cmap_is_variation( FT_CMAP( charmap ) ) ) + { + cur = first + face->num_dmap_charmaps; + for ( ; --cur >= first; ) + if ( cur[0] && tt_cmap_is_variation( cur[0] ) ) + return cur[0]; + + return NULL; + } + + if ( charmap->encoding == FT_ENCODING_UNICODE ) + return tt_face_find_unicode_dmap( face ); + + cur = first + face->num_dmap_charmaps; + for ( ; --cur >= first; ) + { + FT_CMap cmap = cur[0]; + + + if ( cmap && + !tt_cmap_is_variation( cmap ) && + cmap->charmap.platform_id == charmap->platform_id && + cmap->charmap.encoding_id == charmap->encoding_id ) + return cmap; + } + + cur = first + face->num_dmap_charmaps; + for ( ; --cur >= first; ) + { + FT_CMap cmap = cur[0]; + + + if ( cmap && + !tt_cmap_is_variation( cmap ) && + cmap->charmap.encoding == charmap->encoding ) + return cmap; + } + + return NULL; + } + + + FT_CALLBACK_DEF( void ) + tt_cmap_dmap_done( FT_CMap cmap ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + FT_CMap_Class clazz = ttcmap->cmap_class; + + + cmap->clazz = clazz; + if ( clazz->done ) + clazz->done( cmap ); + } + + + FT_CALLBACK_DEF( FT_UInt ) + tt_cmap_dmap_char_index( FT_CMap cmap, + FT_UInt32 char_code ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + FT_UInt result; + + + result = ttcmap->dmap->clazz->char_index( ttcmap->dmap, char_code ); + if ( !result ) + result = ttcmap->cmap_class->char_index( cmap, char_code ); + + return result; + } + + + FT_CALLBACK_DEF( FT_UInt ) + tt_cmap_dmap_char_next( FT_CMap cmap, + FT_UInt32 *achar_code ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + + FT_UInt32 dmap_char = *achar_code; + FT_UInt32 cmap_char = *achar_code; + + FT_UInt dmap_glyph; + FT_UInt cmap_glyph; + + + dmap_glyph = ttcmap->dmap->clazz->char_next( ttcmap->dmap, &dmap_char ); + cmap_glyph = ttcmap->cmap_class->char_next( cmap, &cmap_char ); + + if ( dmap_glyph && ( !cmap_glyph || dmap_char <= cmap_char ) ) + { + *achar_code = dmap_char; + return dmap_glyph; + } + + *achar_code = cmap_char; + return cmap_glyph; + } + + + FT_CALLBACK_DEF( FT_UInt ) + tt_cmap_dmap_char_var_index( FT_CMap cmap, + FT_CMap unicode_cmap, + FT_UInt32 char_code, + FT_UInt32 variant_selector ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + FT_CMap_Class clazz = ttcmap->dmap->clazz; + FT_Int is_default; + + + if ( clazz->char_var_default ) + { + is_default = clazz->char_var_default( ttcmap->dmap, + char_code, + variant_selector ); + if ( is_default == 1 ) + return unicode_cmap->clazz->char_index( unicode_cmap, char_code ); + if ( is_default == 0 ) + return clazz->char_var_index( ttcmap->dmap, unicode_cmap, + char_code, variant_selector ); + } + else if ( clazz->char_var_index ) + { + FT_UInt result = clazz->char_var_index( ttcmap->dmap, + unicode_cmap, + char_code, + variant_selector ); + + + if ( result ) + return result; + } + + clazz = ttcmap->cmap_class; + if ( clazz->char_var_index ) + return clazz->char_var_index( cmap, unicode_cmap, + char_code, variant_selector ); + + return 0; + } + + + FT_CALLBACK_DEF( FT_Int ) + tt_cmap_dmap_char_var_isdefault( FT_CMap cmap, + FT_UInt32 char_code, + FT_UInt32 variant_selector ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + FT_CMap_Class clazz = ttcmap->dmap->clazz; + FT_Int result = -1; + + + if ( clazz->char_var_default ) + result = clazz->char_var_default( ttcmap->dmap, + char_code, variant_selector ); + + clazz = ttcmap->cmap_class; + if ( result == -1 && clazz->char_var_default ) + result = clazz->char_var_default( cmap, char_code, + variant_selector ); + + return result; + } + + + static FT_UInt32* + tt_cmap_dmap_merge( TT_CMap cmap, + FT_UInt32 *dmap_list, + FT_UInt32 *cmap_list ) + { + TT_Face face = (TT_Face)cmap->cmap.charmap.face; + FT_Memory memory = FT_FACE_MEMORY( face ); + FT_Error error = FT_Err_Ok; + + FT_UInt32 dmap_count = 0; + FT_UInt32 cmap_count = 0; + FT_UInt32 old_max = face->dmap_max_results; + + FT_UInt32 i = 0; + FT_UInt32 j = 0; + FT_UInt32 k = 0; + + + if ( dmap_list ) + while ( dmap_list[dmap_count] ) + dmap_count++; + if ( cmap_list ) + while ( cmap_list[cmap_count] ) + cmap_count++; + + if ( !dmap_count && !cmap_count ) + return NULL; + + if ( dmap_count >= 0xFFFFFFFFUL - cmap_count ) + return NULL; + + if ( dmap_count + cmap_count + 1 > face->dmap_max_results ) + { + if ( FT_QRENEW_ARRAY( face->dmap_results, old_max, + dmap_count + cmap_count + 1 ) ) + return NULL; + + face->dmap_max_results = dmap_count + cmap_count + 1; + } + + while ( i < dmap_count && j < cmap_count ) + { + if ( dmap_list[i] < cmap_list[j] ) + face->dmap_results[k++] = dmap_list[i++]; + else if ( cmap_list[j] < dmap_list[i] ) + face->dmap_results[k++] = cmap_list[j++]; + else + { + face->dmap_results[k++] = dmap_list[i++]; + j++; + } + } + + while ( i < dmap_count ) + face->dmap_results[k++] = dmap_list[i++]; + while ( j < cmap_count ) + face->dmap_results[k++] = cmap_list[j++]; + + face->dmap_results[k] = 0; + return face->dmap_results; + } + + + FT_CALLBACK_DEF( FT_UInt32* ) + tt_cmap_dmap_variants( FT_CMap cmap, + FT_Memory memory ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + + FT_CMap_Class dclazz = ttcmap->dmap->clazz; + FT_CMap_Class cclazz = ttcmap->cmap_class; + + FT_UInt32* dmap_list = NULL; + FT_UInt32* cmap_list = NULL; + + + if ( dclazz->variant_list ) + dmap_list = dclazz->variant_list( ttcmap->dmap, memory ); + if ( cclazz->variant_list ) + cmap_list = cclazz->variant_list( cmap, memory ); + + return tt_cmap_dmap_merge( ttcmap, dmap_list, cmap_list ); + } + + + FT_CALLBACK_DEF( FT_UInt32* ) + tt_cmap_dmap_char_variants( FT_CMap cmap, + FT_Memory memory, + FT_UInt32 char_code ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + + FT_CMap_Class dclazz = ttcmap->dmap->clazz; + FT_CMap_Class cclazz = ttcmap->cmap_class; + + FT_UInt32* dmap_list = NULL; + FT_UInt32* cmap_list = NULL; + + + if ( dclazz->charvariant_list ) + dmap_list = dclazz->charvariant_list( ttcmap->dmap, memory, + char_code ); + if ( cclazz->charvariant_list ) + cmap_list = cclazz->charvariant_list( cmap, memory, char_code ); + + return tt_cmap_dmap_merge( ttcmap, dmap_list, cmap_list ); + } + + + FT_CALLBACK_DEF( FT_UInt32* ) + tt_cmap_dmap_variant_chars( FT_CMap cmap, + FT_Memory memory, + FT_UInt32 variant_selector ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + + FT_CMap_Class dclazz = ttcmap->dmap->clazz; + FT_CMap_Class cclazz = ttcmap->cmap_class; + + FT_UInt32* dmap_list = NULL; + FT_UInt32* cmap_list = NULL; + + + if ( dclazz->variantchar_list ) + dmap_list = dclazz->variantchar_list( ttcmap->dmap, memory, + variant_selector ); + if ( cclazz->variantchar_list ) + cmap_list = cclazz->variantchar_list( cmap, memory, + variant_selector ); + + return tt_cmap_dmap_merge( ttcmap, dmap_list, cmap_list ); + } + + + FT_CALLBACK_DEF( FT_Error ) + tt_cmap_dmap_get_info( FT_CharMap charmap, + TT_CMapInfo *cmap_info ) + { + TT_CMap cmap = (TT_CMap)charmap; + TT_CMap_Class clazz = (TT_CMap_Class)cmap->cmap_class; + + + if ( clazz->get_cmap_info ) + return clazz->get_cmap_info( charmap, cmap_info ); + + return FT_THROW( Invalid_CharMap_Format ); + } + + + FT_DEFINE_TT_CMAP( + tt_cmap_dmap_class_rec, + + sizeof ( TT_CMapRec ), + + (FT_CMap_InitFunc) NULL, /* init */ + (FT_CMap_DoneFunc) tt_cmap_dmap_done, /* done */ + (FT_CMap_CharIndexFunc)tt_cmap_dmap_char_index, /* char_index */ + (FT_CMap_CharNextFunc) tt_cmap_dmap_char_next, /* char_next */ + + (FT_CMap_CharVarIndexFunc) tt_cmap_dmap_char_var_index, + (FT_CMap_CharVarIsDefaultFunc)tt_cmap_dmap_char_var_isdefault, + (FT_CMap_VariantListFunc) tt_cmap_dmap_variants, + (FT_CMap_CharVariantListFunc) tt_cmap_dmap_char_variants, + (FT_CMap_VariantCharListFunc) tt_cmap_dmap_variant_chars, + + 0, + (TT_CMap_ValidateFunc)NULL, + (TT_CMap_Info_GetFunc)tt_cmap_dmap_get_info + ) + + + /* parse a `cmap'-shaped table and build the corresponding TT_CMap */ + /* objects in the current face */ + /* */ + static FT_Error + tt_face_build_cmaps_from_table( TT_Face face, + FT_Byte* table, + FT_ULong table_size, + FT_Bool is_dmap ) { - FT_Byte* const table = face->cmap_table; FT_Byte* limit; FT_UInt volatile num_cmaps; - FT_Byte* volatile p = table; - FT_Library library = FT_FACE_LIBRARY( face ); - - FT_UNUSED( library ); + FT_Byte* volatile p = table; - if ( !p || face->cmap_size < 4 ) + if ( !p || table_size < 4 ) return FT_THROW( Invalid_Table ); /* Version 1.8.3 of the OpenType specification contains the following */ @@ -3805,7 +4376,7 @@ num_cmaps = TT_NEXT_USHORT( p ); FT_TRACE4(( "tt_face_build_cmaps: %u cmaps\n", num_cmaps )); - limit = table + face->cmap_size; + limit = table + table_size; for ( ; num_cmaps > 0 && p + 8 <= limit; num_cmaps-- ) { FT_CharMapRec charmap; @@ -3818,7 +4389,7 @@ charmap.encoding = FT_ENCODING_NONE; /* will be filled later */ offset = TT_NEXT_ULONG( p ); - if ( offset && offset <= face->cmap_size - 2 ) + if ( offset && offset <= table_size - 2 ) { FT_Byte* volatile cmap = table + offset; volatile FT_UInt format = TT_PEEK_USHORT( cmap ); @@ -3838,7 +4409,7 @@ ft_validator_init( FT_VALIDATOR( &valid ), cmap, limit, FT_VALIDATE_DEFAULT ); - valid.num_glyphs = (FT_UInt)face->max_profile.numGlyphs; + valid.num_glyphs = face->maxp_num_glyphs; if ( ft_setjmp( FT_VALIDATOR( &valid )->jump_buffer) == 0 ) { @@ -3848,20 +4419,31 @@ if ( !valid.validator.error ) { - FT_CMap ttcmap; + FT_CMap ttcmap; + FT_Error new_error; /* It might make sense to store the single variation */ /* selector cmap somewhere special. But it would have to be */ /* in the public FT_FaceRec, and we can't change that. */ - if ( !FT_CMap_New( (FT_CMap_Class)clazz, - cmap, &charmap, &ttcmap ) ) + if ( is_dmap ) + new_error = tt_face_add_dmap( face, (FT_CMap_Class)clazz, + cmap, &charmap, + (FT_Int)error ); + else + new_error = FT_CMap_New( (FT_CMap_Class)clazz, + cmap, &charmap, &ttcmap ); + + if ( !new_error && !is_dmap ) { /* it is simpler to directly set `flags' than adding */ /* a parameter to FT_CMap_New */ ((TT_CMap)ttcmap)->flags = (FT_Int)error; } + + if ( new_error && FT_ERR_EQ( new_error, Out_Of_Memory ) ) + return new_error; } else { @@ -3884,6 +4466,162 @@ } + /* parse the 'cmap' and 'DMAP' tables */ + FT_LOCAL_DEF( FT_Error ) + tt_face_build_cmaps( TT_Face face ) + { + FT_Error error; + + + error = tt_face_build_cmaps_from_table( face, + face->cmap_table, + face->cmap_size, + FALSE ); + if ( error || !face->dmap_table ) + return error; + + return tt_face_build_cmaps_from_table( face, + face->dmap_table, + face->dmap_size, + TRUE ); + } + + + static FT_Error + tt_face_expose_dmap( TT_Face face, + FT_CMap dmap ) + { + FT_Face root = FT_FACE( face ); + FT_Memory memory = FT_FACE_MEMORY( face ); + FT_Error error = FT_Err_Ok; + + FT_Int count = root->num_charmaps; + FT_UInt i; + + + if ( FT_QRENEW_ARRAY( root->charmaps, count, count + 1 ) ) + return error; + + root->charmaps[count] = &dmap->charmap; + root->num_charmaps = count + 1; + + for ( i = 0; i < face->num_dmap_charmaps; i++ ) + if ( face->dmap_charmaps[i] == dmap ) + { + face->dmap_charmaps[i] = NULL; + break; + } + + return FT_Err_Ok; + } + + + FT_LOCAL_DEF( FT_Error ) + tt_face_build_dmaps( TT_Face face ) + { + FT_Face root = FT_FACE( face ); + FT_Error error; + + FT_Int num_cmaps = root->num_charmaps; + FT_Int i; + FT_UInt j; + + + if ( !face->num_dmap_charmaps ) + return FT_Err_Ok; + + /* Apply the best matching DMAP subtable to every selectable cmap. */ + for ( i = 0; i < num_cmaps; i++ ) + { + FT_CMap cmap = FT_CMAP( root->charmaps[i] ); + FT_CMap dmap = tt_face_find_dmap( face, &cmap->charmap ); + + + if ( dmap ) + { + TT_CMap ttcmap = (TT_CMap)cmap; + + + ttcmap->cmap_class = cmap->clazz; + ttcmap->dmap = dmap; + cmap->clazz = (FT_CMap_Class)&tt_cmap_dmap_class_rec; + } + } + + /* If DMAP supplies an encoding absent from cmap, expose it directly. */ + for ( j = 0; j < face->num_dmap_charmaps; j++ ) + { + FT_CMap dmap = face->dmap_charmaps[j]; + FT_Bool found = FALSE; + + + if ( !dmap || tt_cmap_is_variation( dmap ) ) + continue; + + for ( i = 0; i < root->num_charmaps; i++ ) + { + FT_CMap cmap = FT_CMAP( root->charmaps[i] ); + + + if ( !tt_cmap_is_variation( cmap ) && + cmap->charmap.encoding == dmap->charmap.encoding ) + { + found = TRUE; + break; + } + } + + if ( !found ) + { + FT_CMap best = dmap; + + + if ( dmap->charmap.encoding == FT_ENCODING_UNICODE ) + best = tt_face_find_unicode_dmap( face ); + + error = tt_face_expose_dmap( face, best ); + if ( error ) + return error; + } + } + + /* A variation subtable is not selectable as an ordinary charmap. */ + for ( i = 0; i < root->num_charmaps; i++ ) + if ( tt_cmap_is_variation( FT_CMAP( root->charmaps[i] ) ) ) + return FT_Err_Ok; + + for ( j = 0; j < face->num_dmap_charmaps; j++ ) + { + FT_CMap dmap = face->dmap_charmaps[j]; + + + if ( dmap && tt_cmap_is_variation( dmap ) ) + return tt_face_expose_dmap( face, dmap ); + } + + return FT_Err_Ok; + } + + + FT_LOCAL_DEF( void ) + tt_face_done_dmaps( TT_Face face ) + { + FT_Memory memory = FT_FACE_MEMORY( face ); + FT_UInt i; + + + for ( i = 0; i < face->num_dmap_charmaps; i++ ) + if ( face->dmap_charmaps[i] ) + tt_cmap_free( face->dmap_charmaps[i] ); + + FT_FREE( face->dmap_charmaps ); + face->num_dmap_charmaps = 0; + + FT_FREE( face->dmap_results ); + face->dmap_max_results = 0; + } + + FT_LOCAL_DEF( FT_Error ) tt_get_cmap_info( FT_CharMap charmap, TT_CMapInfo *cmap_info ) diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmap.h b/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmap.h index fdb34f525e3..4b8cbb3197a 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmap.h +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmap.h @@ -32,9 +32,11 @@ FT_BEGIN_HEADER typedef struct TT_CMapRec_ { - FT_CMapRec cmap; - FT_Byte* data; /* pointer to in-memory cmap table */ - FT_Int flags; /* for format 4 only */ + FT_CMapRec cmap; + FT_Byte* data; /* pointer to in-memory cmap table */ + FT_Int flags; /* for format 4 only */ + FT_CMap_Class cmap_class; /* class before applying 'DMAP' */ + FT_CMap dmap; /* overriding 'DMAP' subtable */ } TT_CMapRec, *TT_CMap; @@ -112,6 +114,12 @@ FT_BEGIN_HEADER FT_LOCAL( FT_Error ) tt_face_build_cmaps( TT_Face face ); + FT_LOCAL( FT_Error ) + tt_face_build_dmaps( TT_Face face ); + + FT_LOCAL( void ) + tt_face_done_dmaps( TT_Face face ); + /* used in tt-cmaps service */ FT_LOCAL( FT_Error ) tt_get_cmap_info( FT_CharMap charmap, diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmapc.h b/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmapc.h index c17ac5bba15..a704690dae6 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmapc.h +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/ttcmapc.h @@ -52,5 +52,9 @@ TTCMAPCITEM( tt_cmap14_class_rec ) #endif +#ifdef TT_CONFIG_CMAP_FORMAT_15 + TTCMAPCITEM( tt_cmap15_class_rec ) +#endif + /* END */ diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/ttcolr.c b/src/java.desktop/share/native/libfreetype/src/sfnt/ttcolr.c index b22e279b346..2272ec3739a 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/ttcolr.c +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/ttcolr.c @@ -85,6 +85,7 @@ FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW = 29, FT_COLR_PAINTFORMAT_INTERNAL_SKEW_CENTER = 30, FT_COLR_PAINTFORMAT_INTERNAL_VAR_SKEW_CENTER = 31, + FT_COLR_PAINTFORMAT_INTERNAL_GLYPH2 = 33, } FT_PaintFormat_Internal; @@ -645,7 +646,9 @@ apaint->format = (FT_PaintFormat)FT_NEXT_BYTE( p ); - if ( apaint->format >= FT_COLR_PAINT_FORMAT_MAX ) + if ( apaint->format >= FT_COLR_PAINT_FORMAT_MAX && + (FT_PaintFormat_Internal)apaint->format != + FT_COLR_PAINTFORMAT_INTERNAL_GLYPH2 ) return 0; if ( apaint->format == FT_COLR_PAINTFORMAT_COLR_LAYERS ) @@ -879,12 +882,21 @@ return 1; } - if ( apaint->format == FT_COLR_PAINTFORMAT_GLYPH ) + if ( apaint->format == FT_COLR_PAINTFORMAT_GLYPH || + (FT_PaintFormat_Internal)apaint->format == + FT_COLR_PAINTFORMAT_INTERNAL_GLYPH2 ) { - ENSURE_READ_BYTES( 2 ); + FT_Bool is_glyph2 = (FT_PaintFormat_Internal)apaint->format == + FT_COLR_PAINTFORMAT_INTERNAL_GLYPH2; + + + ENSURE_READ_BYTES( ( is_glyph2 ? 3 : 2 ) ); apaint->u.glyph.paint.p = child_table_p; apaint->u.glyph.paint.insert_root_transform = 0; - apaint->u.glyph.glyphID = FT_NEXT_USHORT( p ); + apaint->u.glyph.glyphID = is_glyph2 ? FT_NEXT_UOFF3( p ) + : FT_NEXT_USHORT( p ); + + apaint->format = FT_COLR_PAINTFORMAT_GLYPH; return 1; } @@ -1862,39 +1874,78 @@ alpha = face->palette[color_index].alpha; } - /* XXX Convert if srcSlot.bitmap is not grey? */ src = srcSlot->bitmap.buffer; dst = dstSlot->bitmap.buffer + dstSlot->bitmap.pitch * ( dstSlot->bitmap_top - srcSlot->bitmap_top ) + 4 * ( srcSlot->bitmap_left - dstSlot->bitmap_left ); - for ( y = 0; y < srcSlot->bitmap.rows; y++ ) + switch ( srcSlot->bitmap.pixel_mode ) { - for ( x = 0; x < srcSlot->bitmap.width; x++ ) - { - int aa = src[x]; - int fa = alpha * aa / 255; + case FT_PIXEL_MODE_GRAY: + for ( y = 0; y < srcSlot->bitmap.rows; y++ ) + { + for ( x = 0; x < srcSlot->bitmap.width; x++ ) + { + int aa = src[x]; + int fa = alpha * aa / 255; - int fb = b * fa / 255; - int fg = g * fa / 255; - int fr = r * fa / 255; + int fb = b * fa / 255; + int fg = g * fa / 255; + int fr = r * fa / 255; - int ba2 = 255 - fa; + int ba2 = 255 - fa; - int bb = dst[4 * x + 0]; - int bg = dst[4 * x + 1]; - int br = dst[4 * x + 2]; - int ba = dst[4 * x + 3]; + int bb = dst[4 * x + 0]; + int bg = dst[4 * x + 1]; + int br = dst[4 * x + 2]; + int ba = dst[4 * x + 3]; + dst[4 * x + 0] = (FT_Byte)( bb * ba2 / 255 + fb ); + dst[4 * x + 1] = (FT_Byte)( bg * ba2 / 255 + fg ); + dst[4 * x + 2] = (FT_Byte)( br * ba2 / 255 + fr ); + dst[4 * x + 3] = (FT_Byte)( ba * ba2 / 255 + fa ); + } - dst[4 * x + 0] = (FT_Byte)( bb * ba2 / 255 + fb ); - dst[4 * x + 1] = (FT_Byte)( bg * ba2 / 255 + fg ); - dst[4 * x + 2] = (FT_Byte)( br * ba2 / 255 + fr ); - dst[4 * x + 3] = (FT_Byte)( ba * ba2 / 255 + fa ); - } + src += srcSlot->bitmap.pitch; + dst += dstSlot->bitmap.pitch; + } + break; + + case FT_PIXEL_MODE_MONO: + { + int fb = b * alpha / 255; + int fg = g * alpha / 255; + int fr = r * alpha / 255; + + int ba2 = 255 - alpha; + + for ( y = 0; y < srcSlot->bitmap.rows; y++ ) + { + for ( x = 0; x < srcSlot->bitmap.width; x++ ) + { + if ( src[x >> 3] & ( 0x80 >> ( x & 7 ) ) ) + { + int bb = dst[4 * x + 0]; + int bg = dst[4 * x + 1]; + int br = dst[4 * x + 2]; + int ba = dst[4 * x + 3]; + + dst[4 * x + 0] = (FT_Byte)( bb * ba2 / 255 + fb ); + dst[4 * x + 1] = (FT_Byte)( bg * ba2 / 255 + fg ); + dst[4 * x + 2] = (FT_Byte)( br * ba2 / 255 + fr ); + dst[4 * x + 3] = (FT_Byte)( ba * ba2 / 255 + alpha ); + } + } + + src += srcSlot->bitmap.pitch; + dst += dstSlot->bitmap.pitch; + } + } + break; - src += srcSlot->bitmap.pitch; - dst += dstSlot->bitmap.pitch; + default: + /* Reject pixel modes other than GRAY/MONO. */ + return FT_Err_Invalid_Glyph_Format; } return FT_Err_Ok; diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/ttload.c b/src/java.desktop/share/native/libfreetype/src/sfnt/ttload.c index e17d07a971c..74d319e01f1 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/ttload.c +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/ttload.c @@ -227,7 +227,9 @@ /* ignore in general). */ if ( table.Tag == TTAG_hmtx || - table.Tag == TTAG_vmtx ) + table.Tag == TTAG_HMTX || + table.Tag == TTAG_vmtx || + table.Tag == TTAG_VMTX ) valid_entries++; else { @@ -439,7 +441,9 @@ else if ( entry.Length > stream->size - entry.Offset ) { if ( entry.Tag == TTAG_hmtx || - entry.Tag == TTAG_vmtx ) + entry.Tag == TTAG_HMTX || + entry.Tag == TTAG_vmtx || + entry.Tag == TTAG_VMTX ) { #ifdef FT_DEBUG_LEVEL_TRACE FT_ULong old_length = entry.Length; @@ -749,7 +753,8 @@ FT_Stream stream ) { FT_Error error; - TT_MaxProfile* maxProfile = &face->max_profile; + TT_MaxProfile* maxProfile = &face->max_profile; + FT_Bool is_extended = FALSE; static const FT_Frame_Field maxp_fields[] = { @@ -782,12 +787,37 @@ }; - error = face->goto_table( face, TTAG_maxp, stream, 0 ); + face->maxp_num_glyphs = 0; + + error = face->goto_table( face, TTAG_MAXP, stream, 0 ); + if ( !error ) + is_extended = TRUE; + else if ( FT_ERR_EQ( error, Table_Missing ) ) + error = face->goto_table( face, TTAG_maxp, stream, 0 ); + if ( error ) goto Exit; - if ( FT_STREAM_READ_FIELDS( maxp_fields, maxProfile ) ) - goto Exit; + if ( is_extended ) + { + FT_UInt32 num_glyphs; + + + if ( FT_READ_LONG( maxProfile->version ) || + FT_READ_UOFF3( num_glyphs ) ) + goto Exit; + + face->maxp_num_glyphs = num_glyphs; + maxProfile->numGlyphs = num_glyphs > 0xFFFFU ? 0xFFFFU + : (FT_UShort)num_glyphs; + } + else + { + if ( FT_STREAM_READ_FIELDS( maxp_fields, maxProfile ) ) + goto Exit; + + face->maxp_num_glyphs = maxProfile->numGlyphs; + } maxProfile->maxPoints = 0; maxProfile->maxContours = 0; @@ -829,7 +859,7 @@ } } - FT_TRACE3(( "numGlyphs: %hu\n", maxProfile->numGlyphs )); + FT_TRACE3(( "numGlyphs: %u\n", face->maxp_num_glyphs )); Exit: return error; @@ -1135,6 +1165,21 @@ if ( FT_FRAME_EXTRACT( face->cmap_size, face->cmap_table ) ) face->cmap_size = 0; + if ( error ) + goto Exit; + + error = face->goto_table( face, TTAG_DMAP, stream, &face->dmap_size ); + if ( error ) + { + face->dmap_size = 0; + if ( FT_ERR_EQ( error, Table_Missing ) ) + error = FT_Err_Ok; + goto Exit; + } + + if ( FT_FRAME_EXTRACT( face->dmap_size, face->dmap_table ) ) + face->dmap_size = 0; + Exit: return error; } diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/ttmtx.c b/src/java.desktop/share/native/libfreetype/src/sfnt/ttmtx.c index 60dce87d841..e52df3f583e 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/ttmtx.c +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/ttmtx.c @@ -81,13 +81,15 @@ if ( vertical ) { - tag = TTAG_vmtx; + tag = face->vert_metrics_is_extended ? TTAG_VMTX + : TTAG_vmtx; ptable_offset = &face->vert_metrics_offset; ptable_size = &face->vert_metrics_size; } else { - tag = TTAG_hmtx; + tag = face->horz_metrics_is_extended ? TTAG_HMTX + : TTAG_hmtx; ptable_offset = &face->horz_metrics_offset; ptable_size = &face->horz_metrics_size; } @@ -132,13 +134,17 @@ { FT_Error error; TT_HoriHeader* header; + FT_UInt32* metrics_count; + FT_Bool* is_extended; + FT_ULong lower_tag; + FT_ULong upper_tag; static const FT_Frame_Field metrics_header_fields[] = { #undef FT_STRUCTURE #define FT_STRUCTURE TT_HoriHeader - FT_FRAME_START( 36 ), + FT_FRAME_START( 34 ), FT_FRAME_ULONG ( Version ), FT_FRAME_SHORT ( Ascender ), FT_FRAME_SHORT ( Descender ), @@ -155,7 +161,6 @@ FT_FRAME_SHORT ( Reserved[2] ), FT_FRAME_SHORT ( Reserved[3] ), FT_FRAME_SHORT ( metric_Data_Format ), - FT_FRAME_USHORT( number_Of_HMetrics ), FT_FRAME_END }; @@ -165,27 +170,71 @@ void *v = &face->vertical; - error = face->goto_table( face, TTAG_vhea, stream, 0 ); + header = (TT_HoriHeader*)v; + metrics_count = &face->vert_metrics_count; + is_extended = &face->vert_metrics_is_extended; + lower_tag = TTAG_vhea; + upper_tag = TTAG_VHEA; + } + else + { + header = &face->horizontal; + metrics_count = &face->horz_metrics_count; + is_extended = &face->horz_metrics_is_extended; + lower_tag = TTAG_hhea; + upper_tag = TTAG_HHEA; + } + + *metrics_count = 0; + *is_extended = FALSE; + + if ( face->is_extended_glyf ) + { + error = face->goto_table( face, upper_tag, stream, 0 ); if ( error ) goto Fail; - header = (TT_HoriHeader*)v; + *is_extended = TRUE; } else { - error = face->goto_table( face, TTAG_hhea, stream, 0 ); + error = face->goto_table( face, lower_tag, stream, 0 ); + if ( FT_ERR_EQ( error, Table_Missing ) ) + { + error = face->goto_table( face, upper_tag, stream, 0 ); + if ( !error ) + *is_extended = TRUE; + } if ( error ) goto Fail; - - header = &face->horizontal; } if ( FT_STREAM_READ_FIELDS( metrics_header_fields, header ) ) goto Fail; + if ( *is_extended ) + { + if ( FT_READ_ULONG( *metrics_count ) ) + goto Fail; + } + else + { + FT_UShort count; + + + if ( FT_READ_USHORT( count ) ) + goto Fail; + + *metrics_count = count; + } + + header->number_Of_HMetrics = *metrics_count > 0xFFFFU + ? 0xFFFFU + : (FT_UShort)*metrics_count; + FT_TRACE3(( "Ascender: %5d\n", header->Ascender )); FT_TRACE3(( "Descender: %5d\n", header->Descender )); - FT_TRACE3(( "number_Of_Metrics: %5u\n", header->number_Of_HMetrics )); + FT_TRACE3(( "number_Of_Metrics: %5lu\n", (FT_ULong)*metrics_count )); header->long_metrics = NULL; header->short_metrics = NULL; @@ -231,11 +280,10 @@ FT_Short *abearing, FT_UShort *aadvance ) { - FT_Error error; - FT_Stream stream = face->root.stream; - TT_HoriHeader* header; - FT_ULong table_pos, table_size, table_end; - FT_UShort k; + FT_Error error; + FT_Stream stream = face->root.stream; + FT_ULong table_pos, table_size, table_end; + FT_UInt32 k; #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT FT_Service_MetricsVariations var = @@ -245,24 +293,19 @@ if ( vertical ) { - void* v = &face->vertical; - - - header = (TT_HoriHeader*)v; table_pos = face->vert_metrics_offset; table_size = face->vert_metrics_size; + k = face->vert_metrics_count; } else { - header = &face->horizontal; table_pos = face->horz_metrics_offset; table_size = face->horz_metrics_size; + k = face->horz_metrics_count; } table_end = table_pos + table_size; - k = header->number_Of_HMetrics; - if ( k > 0 ) { if ( gindex < (FT_UInt)k ) @@ -306,7 +349,9 @@ } #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT - if ( var && FT_IS_VARIATION( &face->root ) ) + if ( var && + ( FT_IS_VARIATION( &face->root ) || + FT_IS_NAMED_INSTANCE( &face->root ) ) ) { FT_Face f = FT_FACE( face ); FT_Int a = (FT_Int)*aadvance; diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/ttpost.c b/src/java.desktop/share/native/libfreetype/src/sfnt/ttpost.c index 98603e115ea..0e0bd0fd366 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/ttpost.c +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/ttpost.c @@ -337,10 +337,10 @@ /* UNDOCUMENTED! The number of glyphs in this table can be smaller */ /* than the value in the maxp table (cf. cyberbit.ttf). */ - if ( post_len < 34 || - FT_STREAM_SKIP( 32 ) || - FT_READ_USHORT( num_glyphs ) || - num_glyphs > face->max_profile.numGlyphs || + if ( post_len < 34 || + FT_STREAM_SKIP( 32 ) || + FT_READ_USHORT( num_glyphs ) || + num_glyphs > face->maxp_num_glyphs || num_glyphs == 0 ) goto Exit; @@ -422,7 +422,7 @@ if ( !face ) return FT_THROW( Invalid_Face_Handle ); - if ( idx >= (FT_UInt)face->max_profile.numGlyphs ) + if ( idx >= face->maxp_num_glyphs ) return FT_THROW( Invalid_Glyph_Index ); #ifdef FT_CONFIG_OPTION_POSTSCRIPT_NAMES @@ -463,7 +463,7 @@ /* version 1.0 is only valid with 258 glyphs */ else if ( format == 0x00010000L && - face->max_profile.numGlyphs == 258 ) + face->maxp_num_glyphs == 258 ) *PSname = MAC_NAME( idx ); /* nothing to do for format == 0x00030000L */ diff --git a/src/java.desktop/share/native/libfreetype/src/sfnt/ttsbit.c b/src/java.desktop/share/native/libfreetype/src/sfnt/ttsbit.c index 1655e40b681..d479b6a0b78 100644 --- a/src/java.desktop/share/native/libfreetype/src/sfnt/ttsbit.c +++ b/src/java.desktop/share/native/libfreetype/src/sfnt/ttsbit.c @@ -1602,9 +1602,10 @@ metrics->horiBearingY = (FT_Short)( originOffsetY + metrics->height ); metrics->vertBearingY = (FT_Short)originOffsetY; - metrics->horiAdvance = (FT_UShort)( aadvance * - face->root.size->metrics.x_ppem / - face->header.Units_Per_EM ); + metrics->horiAdvance = + (FT_UShort)FT_MulDiv( aadvance, + face->root.size->metrics.x_ppem, + face->header.Units_Per_EM ); if ( face->vertical_info ) tt_face_get_metrics( face, TRUE, glyph_index, &abearing, &aadvance ); @@ -1615,9 +1616,10 @@ aadvance = (FT_UShort)FT_ABS( face->horizontal.Ascender - face->horizontal.Descender ); - metrics->vertAdvance = (FT_UShort)( aadvance * - face->root.size->metrics.x_ppem / - face->header.Units_Per_EM ); + metrics->vertAdvance = + (FT_UShort)FT_MulDiv( aadvance, + face->root.size->metrics.x_ppem, + face->header.Units_Per_EM ); } return error; diff --git a/src/java.desktop/share/native/libfreetype/src/smooth/ftgrays.c b/src/java.desktop/share/native/libfreetype/src/smooth/ftgrays.c index 0f7b63937d7..69805f08a27 100644 --- a/src/java.desktop/share/native/libfreetype/src/smooth/ftgrays.c +++ b/src/java.desktop/share/native/libfreetype/src/smooth/ftgrays.c @@ -68,11 +68,14 @@ * * This renderer has the following advantages: * - * - It doesn't need an intermediate bitmap. Instead, one can supply a - * callback function that will be called by the renderer to draw gray - * spans on any target surface. You can thus do direct composition on - * any kind of bitmap, provided that you give the renderer the right - * callback. + * - It doesn't need an intermediate bitmap. Instead, linked lists + * of cells (pixels visited by an outline) are stored, which requires + * less memory and can be maintained on the stack for small glyphs. + * + * - One can supply a callback function that will be called by the + * renderer to draw gray spans on any target surface. You can thus + * do direct composition on any kind of bitmap, provided that you give + * the renderer the right callback. * * - A perfect anti-aliaser, i.e., it computes the _exact_ coverage on * each pixel cell by straight segments. @@ -464,7 +467,10 @@ typedef ptrdiff_t FT_PtrDist; } TPixmap; - /* maximum number of gray cells in the buffer */ + /* Maximum number of gray cells in the stack buffer; */ + /* for example, a buffer of 680 cells can accommodate */ + /* a glyph with the taxicab perimeter of 680 pixels. */ + /* A larger buffer can be allocated when necessary. */ #if FT_RENDER_POOL_SIZE > 2048 #define FT_MAX_GRAY_POOL ( FT_RENDER_POOL_SIZE / sizeof ( TCell ) ) #else @@ -485,16 +491,14 @@ typedef ptrdiff_t FT_PtrDist; typedef struct gray_TWorker_ { - FT_BBox cbox; - - TCoord min_ex, max_ex; /* min and max integer pixel coordinates */ - TCoord min_ey, max_ey; - TCoord count_ey; /* same as (max_ey - min_ey) */ + TCoord min_ex, min_ey; /* min and max integer pixel coordinates */ + TCoord max_ex, max_ey; int error; /* pool overflow exception */ + PCell buffer; /* buffer */ + PCell cell_null; /* last cell, used as dumpster and limit */ PCell cell; /* current cell */ PCell cell_free; /* call allocation next free slot */ - PCell cell_null; /* last cell, used as dumpster and limit */ PCell* ycells; /* array of cell linked-lists; one per */ /* vertical coordinate in the current band */ @@ -581,14 +585,11 @@ typedef ptrdiff_t FT_PtrDist; /* Note that if a cell is to the left of the clipping region, it is */ /* actually set to the (min_ex-1) horizontal position. */ - TCoord ey_index = ey - ras.min_ey; - - - if ( ey_index < 0 || ey_index >= ras.count_ey || ex >= ras.max_ex ) + if ( ey < ras.min_ey || ey >= ras.max_ey || ex >= ras.max_ex ) ras.cell = ras.cell_null; else { - PCell* pcell = ras.ycells + ey_index; + PCell* pcell = ras.ycells + ( ey - ras.min_ey ); PCell cell; @@ -1502,15 +1503,14 @@ typedef ptrdiff_t FT_PtrDist; tags = outline->tags + first; tag = FT_CURVE_TAG( tags[0] ); - /* A contour cannot start with a cubic control point! */ - if ( tag == FT_CURVE_TAG_CUBIC ) - goto Invalid_Outline; - /* check first point to determine origin */ - if ( tag == FT_CURVE_TAG_CONIC ) + if ( tag != FT_CURVE_TAG_ON ) { - /* first point is conic control. Yes, this happens. */ - if ( FT_CURVE_TAG( outline->tags[last] ) == FT_CURVE_TAG_ON ) + char last_tag = FT_CURVE_TAG( outline->tags[last] ); + + + /* first point is a control point. Yes, this happens. */ + if ( last_tag == FT_CURVE_TAG_ON ) { /* start at last point if it is on the curve */ v_start = v_last; @@ -1518,9 +1518,12 @@ typedef ptrdiff_t FT_PtrDist; } else { - /* if both first and last points are conic, */ - /* start at their middle and record its position */ - /* for closure */ + if ( last_tag != tag ) + goto Invalid_Outline; + + /* if both first and last points are controls, */ + /* start at their middle and record its position */ + /* for closure */ v_start.x = ( v_start.x + v_last.x ) / 2; v_start.y = ( v_start.y + v_last.y ) / 2; @@ -1636,11 +1639,24 @@ typedef ptrdiff_t FT_PtrDist; if ( point <= limit ) { FT_Vector vec; + FT_Vector v_middle; + + tag = FT_CURVE_TAG( tags[0] ); vec.x = SCALED( point->x ); vec.y = SCALED( point->y ); + if ( tag == FT_CURVE_TAG_CUBIC ) + { + v_middle.x = ( vec2.x + vec.x ) / 2; + v_middle.y = ( vec2.y + vec.y ) / 2; + + vec = v_middle; + } + else if ( tag != FT_CURVE_TAG_ON ) + goto Invalid_Outline; + FT_TRACE5(( " cubic to (%.2f, %.2f)" " with controls (%.2f, %.2f) and (%.2f, %.2f)\n", vec.x / 64.0, vec.y / 64.0, @@ -1649,6 +1665,12 @@ typedef ptrdiff_t FT_PtrDist; error = func_interface->cubic_to( &vec1, &vec2, &vec, user ); if ( error ) goto Exit; + + if ( tag == FT_CURVE_TAG_CUBIC ) + { + point--; + tags--; + } continue; } @@ -1804,7 +1826,7 @@ typedef ptrdiff_t FT_PtrDist; FT_FILL_RULE( coverage, cover, fill ); span[n].coverage = (unsigned char)coverage; - span[n].x = (unsigned short)x; + span[n].x = (short)x; span[n].len = (unsigned short)( cell->x - x ); if ( ++n == FT_MAX_GRAY_SPANS ) @@ -1823,7 +1845,7 @@ typedef ptrdiff_t FT_PtrDist; FT_FILL_RULE( coverage, area, fill ); span[n].coverage = (unsigned char)coverage; - span[n].x = (unsigned short)cell->x; + span[n].x = (short)cell->x; span[n].len = 1; if ( ++n == FT_MAX_GRAY_SPANS ) @@ -1861,101 +1883,75 @@ typedef ptrdiff_t FT_PtrDist; static int gray_convert_glyph( RAS_ARG ) { - TCell buffer[FT_MAX_GRAY_POOL]; - size_t height = (size_t)( ras.cbox.yMax - ras.cbox.yMin ); - size_t n = FT_MAX_GRAY_POOL / 8; - TCoord y; TCoord bands[32]; /* enough to accommodate bisections */ - TCoord* band; + TCoord* band = bands; int continued = 0; int error = Smooth_Err_Ok; /* Initialize the null cell at the end of the poll. */ - ras.cell_null = buffer + FT_MAX_GRAY_POOL - 1; ras.cell_null->x = CELL_MAX_X_VALUE; ras.cell_null->area = 0; ras.cell_null->cover = 0; ras.cell_null->next = NULL; /* set up vertical bands */ - ras.ycells = (PCell*)buffer; - - if ( height > n ) - { - /* two divisions rounded up */ - n = ( height + n - 1 ) / n; - height = ( height + n - 1 ) / n; - } + ras.ycells = (PCell*)ras.buffer; - for ( y = ras.cbox.yMin; y < ras.cbox.yMax; ) + while ( 1 ) { - ras.min_ey = y; - y += height; - ras.max_ey = FT_MIN( y, ras.cbox.yMax ); - - ras.count_ey = ras.max_ey - ras.min_ey; + size_t n; + TCoord i, count = ras.max_ey - ras.min_ey; - band = bands; - band[1] = ras.cbox.xMin; - band[0] = ras.cbox.xMax; - - do - { - TCoord i; + /* memory management: zero out and skip ycells */ + for ( i = 0; i < count; ++i ) + ras.ycells[i] = ras.cell_null; - ras.min_ex = band[1]; - ras.max_ex = band[0]; + n = ( (size_t)count * sizeof ( PCell ) + sizeof ( TCell ) - 1 ) + / sizeof ( TCell ); - /* memory management: zero out and skip ycells */ - for ( i = 0; i < ras.count_ey; ++i ) - ras.ycells[i] = ras.cell_null; + ras.cell_free = ras.buffer + n; + ras.cell = ras.cell_null; + ras.error = Smooth_Err_Ok; - n = ( (size_t)ras.count_ey * sizeof ( PCell ) + sizeof ( TCell ) - 1 ) - / sizeof ( TCell ); + error = gray_convert_glyph_inner( RAS_VAR_ continued ); + continued = 1; - ras.cell_free = buffer + n; - ras.cell = ras.cell_null; - ras.error = Smooth_Err_Ok; + if ( !error ) + { + if ( ras.render_span ) /* for FT_RASTER_FLAG_DIRECT only */ + gray_sweep_direct( RAS_VAR ); + else + gray_sweep( RAS_VAR ); - error = gray_convert_glyph_inner( RAS_VAR_ continued ); - continued = 1; + if ( band == bands ) + break; /* done */ - if ( !error ) - { - if ( ras.render_span ) /* for FT_RASTER_FLAG_DIRECT only */ - gray_sweep_direct( RAS_VAR ); - else - gray_sweep( RAS_VAR ); - band--; - continue; - } - else if ( error != Smooth_Err_Raster_Overflow ) - goto Exit; + ras.max_ey = ras.min_ey; + ras.min_ey = *--band; + continue; + } + else if ( error != Smooth_Err_Raster_Overflow ) + goto Exit; - /* render pool overflow; we will reduce the render band by half */ - i = ( band[0] - band[1] ) >> 1; + /* render pool overflow, reduce the render band by half */ + count >>= 1; - /* this should never happen even with tiny rendering pool */ - if ( i == 0 ) - { - FT_TRACE7(( "gray_convert_glyph: rotten glyph\n" )); - error = FT_THROW( Raster_Overflow ); - goto Exit; - } + /* this happens only if the rendering pool is too small */ + if ( count == 0 ) + { + FT_TRACE7(( "gray_convert_glyph: rotten glyph\n" )); + error = FT_THROW( Raster_Overflow ); + goto Exit; + } - band++; - band[1] = band[0]; - band[0] += i; - } while ( band >= bands ); + *band++ = ras.min_ey; + ras.min_ey += count; } Exit: - ras.cell = ras.cell_free = ras.cell_null = NULL; - ras.ycells = NULL; - return error; } @@ -1964,6 +1960,9 @@ typedef ptrdiff_t FT_PtrDist; gray_raster_render( FT_Raster raster, const FT_Raster_Params* params ) { + FT_ULong estimate; + int ret; + const FT_Outline* outline = (const FT_Outline*)params->source; const FT_Bitmap* target_map = params->target; @@ -2003,7 +2002,10 @@ typedef ptrdiff_t FT_PtrDist; ras.render_span = (FT_Raster_Span_Func)params->gray_spans; ras.render_span_data = params->user; - ras.cbox = params->clip_box; + ras.min_ex = (TCoord)params->clip_box.xMin; + ras.min_ey = (TCoord)params->clip_box.yMin; + ras.max_ex = (TCoord)params->clip_box.xMax; + ras.max_ey = (TCoord)params->clip_box.yMax; } else { @@ -2029,17 +2031,45 @@ typedef ptrdiff_t FT_PtrDist; ras.render_span = (FT_Raster_Span_Func)NULL; ras.render_span_data = NULL; - ras.cbox.xMin = 0; - ras.cbox.yMin = 0; - ras.cbox.xMax = (FT_Pos)target_map->width; - ras.cbox.yMax = (FT_Pos)target_map->rows; + ras.min_ex = 0; + ras.min_ey = 0; + ras.max_ex = (TCoord)target_map->width; + ras.max_ey = (TCoord)target_map->rows; } /* exit if nothing to do */ - if ( ras.cbox.xMin >= ras.cbox.xMax || ras.cbox.yMin >= ras.cbox.yMax ) + if ( ras.min_ex >= ras.max_ex || ras.min_ey >= ras.max_ey ) return Smooth_Err_Ok; - return gray_convert_glyph( RAS_VAR ); + /* allocate memory based on empirical estimate from CJK fonts */ + estimate = ( ras.max_ex - ras.min_ex + ras.max_ey - ras.min_ey ) * 10UL; + if ( estimate > FT_MAX_GRAY_POOL ) + { + FT_Error error; + FT_Memory memory = (FT_Memory)((gray_PRaster)raster)->memory; + + + if ( FT_QNEW_ARRAY( ras.buffer, estimate ) ) + ret = error; + else + { + ras.cell_null = ras.buffer + estimate - 1; + ret = gray_convert_glyph( RAS_VAR ); + FT_FREE( ras.buffer ); + } + } + else + { + TCell buffer[FT_MAX_GRAY_POOL]; /* stack allocation */ + + + ras.buffer = buffer; + ras.cell_null = ras.buffer + FT_MAX_GRAY_POOL - 1; + + ret = gray_convert_glyph( RAS_VAR ); + } + + return ret; } diff --git a/src/java.desktop/share/native/libfreetype/src/smooth/ftsmooth.c b/src/java.desktop/share/native/libfreetype/src/smooth/ftsmooth.c index 7378e6adc46..f5a3de65d80 100644 --- a/src/java.desktop/share/native/libfreetype/src/smooth/ftsmooth.c +++ b/src/java.desktop/share/native/libfreetype/src/smooth/ftsmooth.c @@ -124,7 +124,8 @@ for ( ; count--; spans++ ) - for ( dst = dst_line + spans->x * 3, w = spans->len; w--; dst += 3 ) + for ( dst = dst_line + (unsigned short)spans->x * 3, + w = spans->len; w--; dst += 3 ) *dst = spans->coverage; } @@ -151,7 +152,7 @@ params.clip_box.xMin = 0; params.clip_box.yMin = 0; - params.clip_box.xMax = bitmap->width; + params.clip_box.xMax = bitmap->width / 3; params.clip_box.yMax = bitmap->rows; if ( bitmap->pitch < 0 ) @@ -290,7 +291,8 @@ for ( ; count--; spans++ ) - for ( dst = dst_line + spans->x, w = spans->len; w--; dst++ ) + for ( dst = dst_line + (unsigned short)spans->x, + w = spans->len; w--; dst++ ) { dst[0] += ( spans->coverage * target->wght[0] + 85 ) >> 8; dst[1] += ( spans->coverage * target->wght[1] + 85 ) >> 8; @@ -385,7 +387,8 @@ for ( ; count--; spans++ ) - for ( dst = dst_line + spans->x, w = spans->len; w--; dst++ ) + for ( dst = dst_line + (unsigned short)spans->x, + w = spans->len; w--; dst++ ) { dst[ 0] += ( spans->coverage * target->wght[0] + 85 ) >> 8; dst[ pitch] += ( spans->coverage * target->wght[1] + 85 ) >> 8; @@ -473,7 +476,7 @@ unsigned char* dst = target->origin - ( y / SCALE ) * target->pitch; - unsigned short x; + unsigned int x, i; unsigned int cover, sum; @@ -487,8 +490,9 @@ cover = ( spans->coverage + SCALE * SCALE / 2 ) / ( SCALE * SCALE ); for ( x = 0; x < spans->len; x++ ) { - sum = dst[( spans->x + x ) / SCALE] + cover; - dst[( spans->x + x ) / SCALE] = (unsigned char)( sum - ( sum >> 8 ) ); + i = ( (unsigned short)spans->x + x ) / SCALE; + sum = dst[i] + cover; + dst[i] = (unsigned char)( sum - ( sum >> 8 ) ); } } } diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttdriver.c b/src/java.desktop/share/native/libfreetype/src/truetype/ttdriver.c index 90d6e810578..b9530b518bc 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttdriver.c +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttdriver.c @@ -32,6 +32,10 @@ #include #include +#ifdef TT_CONFIG_OPTION_VARC +#include +#endif + #include "ttdriver.h" #include "ttgload.h" #include "ttpload.h" @@ -40,6 +44,10 @@ #include "ttgxvar.h" #endif +#ifdef TT_CONFIG_OPTION_VARC +#include "ttvarc.h" +#endif + #include "tterrors.h" @@ -456,15 +464,6 @@ FT_Error error; - if ( !slot ) - return FT_THROW( Invalid_Slot_Handle ); - - if ( !size ) - return FT_THROW( Invalid_Size_Handle ); - - if ( !face ) - return FT_THROW( Invalid_Face_Handle ); - #ifdef FT_CONFIG_OPTION_INCREMENTAL if ( glyph_index >= (FT_UInt)face->num_glyphs && !face->internal->incremental_interface ) @@ -493,6 +492,39 @@ load_flags |= FT_LOAD_NO_HINTING; } +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* + * DirectWrite treats ClearType symmetric grid fitting independently + * from symmetric smoothing. Resolve the grid-fitting decision from the + * same policy source that GETINFO uses for symmetric smoothing. + * + * Only native v40 horizontal-LCD hinting participates. Caller-disabled + * native hinting and the other interpreter/rendering modes are left + * alone. + */ + if ( FT_LOAD_TARGET_MODE( load_flags ) == FT_RENDER_MODE_LCD && + !( load_flags & FT_LOAD_NO_HINTING ) && + !FT_IS_TRICKY( face ) ) + { + TT_Driver driver = (TT_Driver)FT_FACE_DRIVER( face ); + + + if ( driver->interpreter_version == TT_INTERPRETER_VERSION_40 ) + { + FT_Bool grid_fit; + + + tt_face_get_cleartype_policy( (TT_Face)face, + size->metrics.y_ppem, + NULL, + &grid_fit ); + + if ( !grid_fit ) + load_flags |= FT_LOAD_NO_HINTING; + } + } +#endif + /* use hinted metrics only if we load a glyph with hinting */ ttsize->metrics = ( load_flags & FT_LOAD_NO_HINTING ) ? &size->metrics @@ -571,6 +603,21 @@ #endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */ +#ifdef TT_CONFIG_OPTION_VARC + + FT_DEFINE_SERVICE_VARCREC( + tt_service_varc, + + tt_face_init_varc_axes, + tt_face_load_varc, + tt_face_free_varc, + tt_face_has_varc_glyph, + tt_face_load_varc_glyph + ) + +#endif /* TT_CONFIG_OPTION_VARC */ + + static const FT_Service_TrueTypeEngineRec tt_service_truetype_engine = { #ifdef TT_USE_BYTECODE_INTERPRETER @@ -592,7 +639,19 @@ ) -#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT +#if defined( TT_CONFIG_OPTION_GX_VAR_SUPPORT ) && \ + defined( TT_CONFIG_OPTION_VARC ) + FT_DEFINE_SERVICEDESCREC7( + tt_services, + + FT_SERVICE_ID_FONT_FORMAT, FT_FONT_FORMAT_TRUETYPE, + FT_SERVICE_ID_MULTI_MASTERS, &tt_service_gx_multi_masters, + FT_SERVICE_ID_METRICS_VARIATIONS, &tt_service_metrics_variations, + FT_SERVICE_ID_VARC, &tt_service_varc, + FT_SERVICE_ID_TRUETYPE_ENGINE, &tt_service_truetype_engine, + FT_SERVICE_ID_TT_GLYF, &tt_service_truetype_glyf, + FT_SERVICE_ID_PROPERTIES, &tt_service_properties ) +#elif defined( TT_CONFIG_OPTION_GX_VAR_SUPPORT ) FT_DEFINE_SERVICEDESCREC6( tt_services, @@ -602,6 +661,15 @@ FT_SERVICE_ID_TRUETYPE_ENGINE, &tt_service_truetype_engine, FT_SERVICE_ID_TT_GLYF, &tt_service_truetype_glyf, FT_SERVICE_ID_PROPERTIES, &tt_service_properties ) +#elif defined( TT_CONFIG_OPTION_VARC ) + FT_DEFINE_SERVICEDESCREC5( + tt_services, + + FT_SERVICE_ID_FONT_FORMAT, FT_FONT_FORMAT_TRUETYPE, + FT_SERVICE_ID_VARC, &tt_service_varc, + FT_SERVICE_ID_TRUETYPE_ENGINE, &tt_service_truetype_engine, + FT_SERVICE_ID_TT_GLYF, &tt_service_truetype_glyf, + FT_SERVICE_ID_PROPERTIES, &tt_service_properties ) #else FT_DEFINE_SERVICEDESCREC4( tt_services, diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttgload.c b/src/java.desktop/share/native/libfreetype/src/truetype/ttgload.c index dcff7c3bb71..f665361646d 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttgload.c +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttgload.c @@ -34,6 +34,10 @@ #include "ttgxvar.h" #endif +#ifdef TT_CONFIG_OPTION_VARC +#include "ttvarc.h" +#endif + #include "tterrors.h" @@ -51,7 +55,7 @@ * * Simple glyph flags. */ -#define ON_CURVE_POINT 0x01 /* same value as FT_CURVE_TAG_ON */ +#define ON_CURVE_POINT 0x01 #define X_SHORT_VECTOR 0x02 #define Y_SHORT_VECTOR 0x04 #define REPEAT_FLAG 0x08 @@ -60,6 +64,7 @@ #define Y_POSITIVE 0x20 /* two meanings depending on Y_SHORT_VECTOR */ #define SAME_Y 0x20 #define OVERLAP_SIMPLE 0x40 /* retained as FT_OUTLINE_OVERLAP */ +#define CUBIC_FLAG 0x80 /************************************************************************** @@ -79,6 +84,7 @@ #define OVERLAP_COMPOUND 0x0400 /* retained as FT_OUTLINE_OVERLAP */ #define SCALED_COMPONENT_OFFSET 0x0800 #define UNSCALED_COMPONENT_OFFSET 0x1000 +#define GID_IS_24_BIT 0x2000 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT @@ -345,8 +351,8 @@ FT_Byte* limit = load->limit; FT_GlyphLoader gloader = load->gloader; FT_Outline* outline = &gloader->current.outline; - FT_Int n_contours = load->n_contours; - FT_Int n_points; + FT_UInt n_contours = (FT_UInt)load->n_contours; + FT_UInt n_points; FT_UShort n_ins; FT_Byte *flag, *flag_limit; @@ -354,7 +360,6 @@ FT_Vector *vec, *vec_limit; FT_Pos x, y; FT_UShort *cont, *cont_limit; - FT_Int last; /* check that we can add the contours to the glyph */ @@ -363,27 +368,25 @@ goto Fail; /* check space for contours array + instructions count */ - if ( n_contours >= 0xFFF || p + 2 * n_contours + 2 > limit ) + if ( n_contours >= 0xFFFU || p + 2 * n_contours + 2 > limit ) goto Invalid_Outline; /* reading the contours' endpoints & number of points */ cont = outline->contours; cont_limit = cont + n_contours; - last = -1; + n_points = 0; for ( ; cont < cont_limit; cont++ ) { *cont = FT_NEXT_USHORT( p ); - if ( *cont <= last ) + if ( *cont < n_points ) goto Invalid_Outline; - last = *cont; + n_points = *cont + 1U; } - n_points = last + 1; - - FT_TRACE5(( " # of points: %d\n", n_points )); + FT_TRACE5(( " # of points: %u\n", n_points )); /* note that we will add four phantom points later */ error = FT_GLYPHLOADER_CHECK_POINTS( gloader, n_points + 4, 0 ); @@ -527,8 +530,9 @@ y += delta; vec->y = y; - /* the cast is for stupid compilers */ - *flag = (FT_Byte)( f & ON_CURVE_POINT ); + *flag = (FT_Byte)( f & ON_CURVE_POINT ); + if ( load->face->is_extended_glyf && ( f & CUBIC_FLAG ) ) + *flag |= FT_CURVE_TAG_CUBIC; } outline->n_points = (FT_UShort)n_points; @@ -570,7 +574,7 @@ if ( error ) goto Fail; - /* check space */ + /* check space for the flags and a 16-bit glyph ID */ if ( p + 4 > limit ) goto Invalid_Composite; @@ -579,7 +583,16 @@ subglyph->arg1 = subglyph->arg2 = 0; subglyph->flags = FT_NEXT_USHORT( p ); - subglyph->index = FT_NEXT_USHORT( p ); + if ( loader->face->is_extended_glyf && + ( subglyph->flags & GID_IS_24_BIT ) != 0 ) + { + if ( p + 3 > limit ) + goto Invalid_Composite; + + subglyph->index = (FT_Int)FT_NEXT_UOFF3( p ); + } + else + subglyph->index = FT_NEXT_USHORT( p ); /* we reject composites that have components */ /* with invalid glyph indices */ @@ -1148,7 +1161,15 @@ IS_HINTED( loader->load_flags ) ) { #ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL - if ( !loader->exec->backward_compatibility ) + TT_Driver driver = + (TT_Driver)FT_FACE_DRIVER( (FT_Face)loader->face ); + + + /* v40 approximates native ClearType's fine horizontal grid by */ + /* leaving X offsets of components unrounded. */ + if ( driver->interpreter_version != TT_INTERPRETER_VERSION_40 || + loader->exec->mode == FT_RENDER_MODE_MONO || + FT_IS_TRICKY( (FT_Face)loader->face ) ) #endif x = FT_PIX_ROUND( x ); @@ -2539,6 +2560,84 @@ #endif /* FT_CONFIG_OPTION_SVG */ +#ifdef TT_CONFIG_OPTION_VARC + + /* check for VARC glyphs */ + if ( face->varc && + tt_face_has_varc_glyph( (FT_Face)face, glyph_index ) ) + { + FT_TRACE3(( "Loading VARC glyph\n" )); + + error = tt_face_load_varc_glyph( (FT_Face)face, + (FT_GlyphSlot)glyph, + glyph_index, + load_flags ); + if ( !error ) + { + FT_Short left_bearing = 0; + FT_Short top_bearing = 0; + FT_UShort advance_width = 0; + FT_UShort advance_height = 0; + + + /* Get advance width and height. */ + TT_Get_HMetrics( face, glyph_index, + &left_bearing, + &advance_width ); + TT_Get_VMetrics( face, glyph_index, + 0, + &top_bearing, + &advance_height ); + + glyph->linearHoriAdvance = advance_width; + glyph->linearVertAdvance = advance_height; + +#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT + { + FT_Int advance = (FT_Int)advance_width; + + + tt_hadvance_adjust( (FT_Face)face, glyph_index, &advance ); + advance_width = (FT_UShort)advance; + } +#endif + + { + FT_BBox bbox; + + + FT_Outline_Get_CBox( &glyph->outline, &bbox ); + + glyph->metrics.horiBearingX = bbox.xMin; + glyph->metrics.horiBearingY = bbox.yMax; + glyph->metrics.width = SUB_LONG( bbox.xMax, bbox.xMin ); + glyph->metrics.height = SUB_LONG( bbox.yMax, bbox.yMin ); + } + + if ( load_flags & FT_LOAD_NO_SCALE ) + { + /* Under NO_SCALE the advance is in font units (no 26.6 shift), */ + /* like the normal glyf path (pp2.x - pp1.x). */ + glyph->metrics.horiAdvance = advance_width; + glyph->metrics.vertAdvance = advance_height; + } + else + { + glyph->metrics.horiAdvance = FT_MulFix( advance_width, + size->metrics->x_scale ); + glyph->metrics.vertAdvance = FT_MulFix( advance_height, + size->metrics->y_scale ); + } + + FT_TRACE3(( "Successfully loaded VARC glyph\n" )); + goto Exit; + } + + FT_TRACE3(( "Failed to load VARC glyph, falling back to glyf\n" )); + } + +#endif /* TT_CONFIG_OPTION_VARC */ + error = tt_loader_init( &loader, size, glyph, load_flags, FALSE ); if ( error ) goto Exit; diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttgxvar.c b/src/java.desktop/share/native/libfreetype/src/truetype/ttgxvar.c index d392e9bc1c2..4ae7d4805dc 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttgxvar.c +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttgxvar.c @@ -129,6 +129,12 @@ * stream :: * The data stream. * + * scratch :: + * A caller-provided scratch buffer for temporary storage. + * + * scratch_cap :: + * The size of `scratch`. + * * @Output: * point_cnt :: * The number of points read. A zero value means that @@ -140,8 +146,10 @@ * special value ALL_POINTS. */ static FT_UShort* - ft_var_readpackedpoints( FT_Stream stream, - FT_UInt *point_cnt ) + ft_var_readpackedpoints( FT_Stream stream, + FT_UInt *point_cnt, + FT_UShort* scratch, + FT_UInt scratch_cap ) { FT_UShort *points = NULL; FT_UInt n; @@ -166,7 +174,11 @@ n |= FT_GET_BYTE(); } - if ( FT_QNEW_ARRAY( points, n ) ) + /* Re-use `scratch` if it is large enough; this avoids */ + /* a heap allocation per tuple in the common case. */ + if ( scratch && n <= scratch_cap ) + points = scratch; + else if ( FT_QNEW_ARRAY( points, n ) ) return NULL; p = stream->cursor; @@ -218,7 +230,8 @@ Fail: FT_TRACE1(( "ft_var_readpackedpoints: invalid table\n" )); - FT_FREE( points ); + if ( points != scratch ) + FT_FREE( points ); return NULL; } @@ -244,6 +257,12 @@ * delta_cnt :: * The number of deltas to be read. * + * scratch :: + * A caller-provided scratch buffer for temporary storage. + * + * scratch_cap :: + * The size of `scratch`. + * * @Return: * An array of FT_Fixed containing the deltas for the affected * points. (This only gets the deltas for one dimension. It will @@ -256,7 +275,9 @@ */ static FT_Fixed* ft_var_readpackeddeltas( FT_Stream stream, - FT_UInt delta_cnt ) + FT_UInt delta_cnt, + FT_Fixed* scratch, + FT_UInt scratch_cap ) { FT_Fixed *deltas = NULL; FT_UInt runcnt, cnt; @@ -266,7 +287,11 @@ FT_Error error; - if ( FT_QNEW_ARRAY( deltas, delta_cnt ) ) + /* Re-use `scratch` if it is large enough; this avoids */ + /* a heap allocation per tuple in the common case. */ + if ( scratch && delta_cnt <= scratch_cap ) + deltas = scratch; + else if ( FT_QNEW_ARRAY( deltas, delta_cnt ) ) return NULL; p = stream->cursor; @@ -314,7 +339,8 @@ Fail: FT_TRACE1(( "ft_var_readpackeddeltas: invalid table\n" )); - FT_FREE( deltas ); + if ( deltas != scratch ) + FT_FREE( deltas ); return NULL; } @@ -1175,11 +1201,12 @@ GX_ItemVarData varData; - FT_UInt master; - FT_Int64 returnValue = FT_INT64_ZERO; - FT_UInt shift_base = 1; - FT_UInt per_region_size; - FT_Byte* bytes; + FT_UInt master; + FT_Int64 returnValue = FT_INT64_ZERO; + FT_UInt shift_base = 1; + FT_UInt per_region_size; + FT_Byte* bytes; + FT_Fixed* regionScalars; if ( !ttface->blend || !ttface->blend->normalizedcoords ) @@ -1217,6 +1244,67 @@ bytes = varData->deltaSet + per_region_size * innerIndex; + /* The region scalars depend only on the normalized coordinates, which */ + /* are constant while glyphs are loaded; yet this function is called */ + /* once per item (e.g., per glyph for 'HVAR' advances). Cache the */ + /* scalars per store and recompute only when the coordinates change. */ + /* `regionScalars` stays NULL (falling back to recomputation) if the */ + /* cache cannot be allocated. */ + regionScalars = itemStore->regionScalars; + { + FT_Fixed* ncoords = ttface->blend->normalizedcoords; + FT_Bool fresh = FT_BOOL( regionScalars != NULL ); + FT_UInt a; + + + if ( fresh ) + { + for ( a = 0; a < itemStore->axisCount; a++ ) + if ( itemStore->cachedCoords[a] != ncoords[a] ) + { + fresh = FALSE; + break; + } + } + + if ( !fresh ) + { + FT_Memory memory = FT_FACE_MEMORY( face ); + FT_Error error = FT_Err_Ok; + + FT_UNUSED( error ); + + + if ( !regionScalars ) + { + if ( !FT_QNEW_ARRAY( itemStore->regionScalars, + itemStore->regionCount ) && + !FT_QNEW_ARRAY( itemStore->cachedCoords, + itemStore->axisCount ) ) + regionScalars = itemStore->regionScalars; + else + { + FT_FREE( itemStore->regionScalars ); + FT_FREE( itemStore->cachedCoords ); + } + } + + if ( regionScalars ) + { + FT_UInt r; + + + for ( r = 0; r < itemStore->regionCount; r++ ) + regionScalars[r] = tt_calculate_scalar( + itemStore->varRegionList[r].axisList, + itemStore->axisCount, + ncoords ); + for ( a = 0; a < itemStore->axisCount; a++ ) + itemStore->cachedCoords[a] = ncoords[a]; + } + } + } + /* outer loop steps through master designs to be blended */ for ( master = 0; master < varData->regionIdxCount; master++ ) { @@ -1224,10 +1312,12 @@ GX_AxisCoords axis = itemStore->varRegionList[regionIndex].axisList; - FT_Fixed scalar = tt_calculate_scalar( - axis, - itemStore->axisCount, - ttface->blend->normalizedcoords ); + FT_Fixed scalar = regionScalars + ? regionScalars[regionIndex] + : tt_calculate_scalar( + axis, + itemStore->axisCount, + ttface->blend->normalizedcoords ); if ( scalar ) @@ -1755,7 +1845,7 @@ FT_UShort axisCount; FT_UShort globalCoordCount; FT_ULong offsetToCoord; - FT_UShort glyphCount; + FT_UInt32 glyphCount; FT_UShort flags; FT_ULong offsetToData; @@ -1781,16 +1871,24 @@ static FT_Error ft_var_load_gvar( TT_Face face ) { - FT_Stream stream = FT_FACE_STREAM( face ); - FT_Memory memory = stream->memory; - GX_Blend blend = face->blend; - FT_Error error; - FT_UInt i, j; - FT_Byte* bytes; - FT_ULong table_len; - FT_ULong gvar_start; - FT_ULong offsetToData; - FT_ULong offsets_len; + FT_Error error; + + FT_Stream stream = FT_FACE_STREAM( face ); + FT_Memory memory = stream->memory; + + GX_Blend blend = face->blend; + FT_Bool blend_created = FALSE; + FT_Bool is_extended = FALSE; + + FT_UInt i, j; + FT_Byte* bytes; + FT_ULong table_len; + FT_ULong gvar_start; + FT_ULong header_size; + + FT_ULong offsetToData; + FT_ULong offsets_len; + GX_GVar_Head gvar_head; static const FT_Frame_Field gvar_fields[] = @@ -1799,33 +1897,68 @@ #undef FT_STRUCTURE #define FT_STRUCTURE GX_GVar_Head - FT_FRAME_START( 20 ), + FT_FRAME_START( 12 ), FT_FRAME_LONG ( version ), FT_FRAME_USHORT( axisCount ), FT_FRAME_USHORT( globalCoordCount ), FT_FRAME_ULONG ( offsetToCoord ), - FT_FRAME_USHORT( glyphCount ), - FT_FRAME_USHORT( flags ), - FT_FRAME_ULONG ( offsetToData ), FT_FRAME_END }; FT_TRACE2(( "GVAR " )); - if ( FT_SET_ERROR( face->goto_table( face, - TTAG_gvar, - stream, - &table_len ) ) ) + error = face->goto_table( face, + face->is_extended_glyf ? TTAG_GVAR + : TTAG_gvar, + stream, + &table_len ); + if ( FT_ERR_EQ( error, Table_Missing ) && !face->is_extended_glyf ) + { + error = face->goto_table( face, TTAG_GVAR, stream, &table_len ); + if ( !error ) + is_extended = TRUE; + } + else if ( !error && face->is_extended_glyf ) + is_extended = TRUE; + + if ( error ) { FT_TRACE2(( "is missing\n" )); goto Exit; } + header_size = is_extended ? 21 : 20; + if ( table_len < header_size ) + { + error = FT_THROW( Invalid_Table ); + goto Exit; + } + gvar_start = FT_STREAM_POS( ); if ( FT_STREAM_READ_FIELDS( gvar_fields, &gvar_head ) ) goto Exit; + if ( is_extended ) + { + if ( FT_READ_UOFF3( gvar_head.glyphCount ) ) + goto Exit; + } + else + { + FT_UShort glyph_count; + + + if ( FT_READ_USHORT( glyph_count ) ) + goto Exit; + + gvar_head.glyphCount = glyph_count; + } + + if ( FT_READ_USHORT( gvar_head.flags ) || + FT_READ_ULONG( gvar_head.offsetToData ) ) + goto Exit; + if ( gvar_head.version != 0x00010000L ) { FT_TRACE1(( "bad table version\n" )); @@ -1833,7 +1966,25 @@ goto Exit; } - if ( gvar_head.axisCount != (FT_UShort)blend->mmvar->num_axis ) + /* + * VARC can use normalized coordinates for component-internal axes in a + * static font, where 'gvar' is present without 'fvar'. Set up the + * private part of the blend machinery in that case; it deliberately has + * no `mmvar`, so the axes remain invisible through the public MM API. + */ + if ( !blend ) + { + if ( FT_SET_ERROR( tt_var_init_blend( FT_FACE( face ), + gvar_head.axisCount ) ) ) + goto Exit; + + blend = face->blend; + blend_created = TRUE; + } + + blend->gvar_is_extended = is_extended; + + if ( gvar_head.axisCount != (FT_UShort)blend->num_axis ) { FT_TRACE1(( "ft_var_load_gvar:" " number of axes in `gvar' and `cvar'\n" )); @@ -1858,7 +2009,7 @@ ( ( gvar_head.flags & 1 ) ? 4L : 2L ); /* rough sanity check */ - if (offsets_len > table_len ) + if ( offsets_len > table_len - header_size ) { FT_TRACE1(( "ft_var_load_gvar: invalid number of glyphs\n" )); error = FT_THROW( Invalid_Table ); @@ -1999,14 +2150,32 @@ gvar_head.globalCoordCount ) ) goto Fail2; - blend->tuplecount = gvar_head.globalCoordCount; + /* The tuple index in each glyph variation tuple header is only 12 */ + /* bits wide (`GX_TI_TUPLE_INDEX_MASK`), so shared tuples with a */ + /* higher index can never be referenced. Clamp the count to the */ + /* largest addressable value, `GX_TI_TUPLE_INDEX_MASK + 1`. This */ + /* also lets the fast path in `TT_Vary_Apply_Glyph_Deltas` rely on */ + /* a set tuple flag forcing the masked value out of range. */ + blend->tuplecount = + gvar_head.globalCoordCount > GX_TI_TUPLE_INDEX_MASK + 1 + ? GX_TI_TUPLE_INDEX_MASK + 1 + : gvar_head.globalCoordCount; FT_TRACE5(( "\n" )); FT_FRAME_EXIT(); } + if ( blend_created ) + { + for ( i = 0; i < blend->tuplecount; i++ ) + blend->tuplescalars[i] = (FT_Fixed)-0x20000L; + } + Exit: + if ( error && blend_created ) + tt_done_blend( FT_FACE( face ) ); + return error; Fail2: @@ -2019,6 +2188,70 @@ } + /************************************************************************** + * + * @Function: + * tt_var_init_gvar + * + * @Description: + * Initialize the private 'gvar' blend state. This also supports the + * static-font case where VARC addresses component-internal axes and no + * 'fvar' table is present. + */ + FT_LOCAL_DEF( FT_Error ) + tt_var_init_gvar( FT_Face face ) + { + TT_Face ttface = (TT_Face)face; + + + if ( ttface->blend && ttface->blend->glyphoffsets ) + return FT_Err_Ok; + + return ft_var_load_gvar( ttface ); + } + + + /************************************************************************** + * + * @Function: + * tt_var_init_blend + * + * @Description: + * Initialize a private normalized-coordinate vector without 'fvar'. + * CFF2 VARC glyphs use this with the axis count from their variation + * store; TrueType VARC glyphs get the count from 'gvar' above. + */ + FT_LOCAL_DEF( FT_Error ) + tt_var_init_blend( FT_Face face, + FT_UInt axis_count ) + { + TT_Face ttface = (TT_Face)face; + FT_Memory memory = FT_FACE_MEMORY( face ); + FT_Error error = FT_Err_Ok; + GX_Blend blend = ttface->blend; + + + if ( blend ) + return blend->num_axis == axis_count + ? FT_Err_Ok + : FT_THROW( Invalid_Table ); + + if ( FT_NEW( blend ) ) + return error; + + blend->num_axis = axis_count; + ttface->blend = blend; + + if ( FT_NEW_ARRAY( blend->normalizedcoords, axis_count ) ) + { + FT_FREE( blend ); + ttface->blend = NULL; + } + + return error; + } + + /************************************************************************** * * @Function: @@ -2507,6 +2740,11 @@ FT_FRAME_END }; + /* A static VARC face can have a private blend initialized from 'gvar' */ + /* without exposing variation axes through the public MM API. */ + if ( ttface->blend && !ttface->blend->mmvar ) + return FT_THROW( Table_Missing ); + /* `num_instances` holds the number of all named instances including */ /* the default instance, which might be missing in the table of named */ /* instances (in 'fvar'). This value is validated in `sfobjs.c` and */ @@ -3453,9 +3691,7 @@ TT_Face ttface = (TT_Face)face; FT_Error error = FT_Err_Ok; GX_Blend blend; - FT_MM_Var* mmvar; - FT_Var_Axis* a; - FT_UInt i, nc; + FT_UInt i; if ( !ttface->blend ) @@ -3474,31 +3710,30 @@ return error; } - nc = num_coords; if ( num_coords > blend->num_axis ) { FT_TRACE2(( "TT_Get_Var_Design:" " only using first %u of %u coordinates\n", blend->num_axis, num_coords )); - nc = blend->num_axis; + + FT_ARRAY_ZERO( coords + blend->num_axis, num_coords - blend->num_axis ); + num_coords = blend->num_axis; } - mmvar = blend->mmvar; - a = mmvar->axis; if ( ttface->doblend ) { - for ( i = 0; i < nc; i++, a++ ) + for ( i = 0; i < num_coords; i++ ) coords[i] = blend->coords[i]; } else { - for ( i = 0; i < nc; i++, a++ ) + FT_Var_Axis* a = blend->mmvar->axis; + + + for ( i = 0; i < num_coords; i++, a++ ) coords[i] = a->def; } - for ( ; i < num_coords; i++, a++ ) - coords[i] = a->def; - return FT_Err_Ok; } @@ -3804,7 +4039,8 @@ FT_Stream_SeekSet( stream, offsetToData ); - sharedpoints = ft_var_readpackedpoints( stream, &spoint_count ); + sharedpoints = ft_var_readpackedpoints( stream, &spoint_count, + NULL, 0 ); offsetToData = FT_Stream_FTell( stream ); @@ -3879,7 +4115,8 @@ if ( tupleIndex & GX_TI_PRIVATE_POINT_NUMBERS ) { - localpoints = ft_var_readpackedpoints( stream, &point_count ); + localpoints = ft_var_readpackedpoints( stream, &point_count, + NULL, 0 ); points = localpoints; } else @@ -3891,7 +4128,8 @@ deltas = ft_var_readpackeddeltas( stream, point_count == 0 ? face->cvt_size - : point_count ); + : point_count, + NULL, 0 ); if ( !points || !deltas ) ; /* failure, ignore it */ @@ -4025,13 +4263,13 @@ /* modeled after `af_iup_shift' */ static void - tt_delta_shift( int p1, - int p2, - int ref, + tt_delta_shift( FT_UInt p1, + FT_UInt p2, + FT_UInt ref, FT_Vector* in_points, FT_Vector* out_points ) { - int p; + FT_UInt p; FT_Vector delta; @@ -4062,14 +4300,14 @@ /* modeled after `af_iup_interp', `_iup_worker_interpolate', and */ /* `Ins_IUP' with spec differences in handling ill-defined cases. */ static void - tt_delta_interpolate( int p1, - int p2, - int ref1, - int ref2, + tt_delta_interpolate( FT_UInt p1, + FT_UInt p2, + FT_UInt ref1, + FT_UInt ref2, FT_Vector* in_points, FT_Vector* out_points ) { - int p, i; + FT_UInt p, i; FT_Pos out, in1, in2, out1, out2, d1, d2; @@ -4135,24 +4373,18 @@ FT_Vector* in_points, FT_Bool* has_delta ) { - FT_Int first_point; - FT_Int end_point; - - FT_Int first_delta; - FT_Int cur_delta; - - FT_Int point; - FT_Short contour; + FT_UInt first_point; + FT_UInt end_point; + FT_UInt first_delta; + FT_UInt cur_delta; - /* ignore empty outlines */ - if ( !outline->n_contours ) - return; + FT_UInt point; + FT_UInt contour; - contour = 0; - point = 0; - do + for ( point = 0, contour = 0; + contour < outline->n_contours; contour++ ) { end_point = outline->contours[contour]; first_point = point; @@ -4213,9 +4445,7 @@ out_points ); } } - contour++; - - } while ( contour < outline->n_contours ); + } } @@ -4269,6 +4499,8 @@ FT_UInt peak_coords_size; FT_UInt point_deltas_x_size; + FT_UInt deltas_buf_size; + FT_UInt points_buf_size; FT_UInt points_org_size; FT_UInt points_out_size; FT_UInt has_delta_size; @@ -4286,15 +4518,25 @@ FT_UInt point_count; FT_UInt spoint_count = 0; - FT_UShort* sharedpoints = NULL; - FT_UShort* localpoints = NULL; + FT_UShort* sharedpoints = NULL; + FT_UShort* localpoints = NULL; FT_UShort* points; + /* scratch buffer pool for `sharedpoints` */ + FT_UShort* shared_points_buf = NULL; + /* scratch buffer pool for `localpoints` */ + FT_UShort* local_points_buf; FT_Fixed* deltas_x = NULL; FT_Fixed* deltas_y = NULL; + /* scratch buffer pool for `deltas_x` */ + FT_Fixed* deltas_x_buf; + /* scratch buffer pool for `deltas_y` */ + FT_Fixed* deltas_y_buf; FT_Fixed* point_deltas_x = NULL; FT_Fixed* point_deltas_y = NULL; + FT_Fixed* tupleScalars; + for ( i = 0; i < n_points; i++ ) { @@ -4328,8 +4570,15 @@ /* each set of glyph variation data is formatted similarly to `cvar' */ + if ( dataSize < ( blend->gvar_is_extended ? 5U : 4U ) ) + { + error = FT_THROW( Invalid_Table ); + goto FExit; + } + tupleCount = FT_GET_USHORT(); - offsetToData = FT_GET_USHORT(); + offsetToData = blend->gvar_is_extended ? FT_GET_UOFF3() + : FT_GET_USHORT(); /* rough sanity test */ if ( offsetToData > dataSize || @@ -4344,57 +4593,62 @@ offsetToData += glyph_start; - if ( tupleCount & GX_TC_TUPLES_SHARE_POINT_NUMBERS ) - { - here = FT_Stream_FTell( stream ); - - FT_Stream_SeekSet( stream, offsetToData ); - - sharedpoints = ft_var_readpackedpoints( stream, &spoint_count ); - - offsetToData = FT_Stream_FTell( stream ); - - FT_Stream_SeekSet( stream, here ); - } - - FT_TRACE5(( "gvar: there %s %u tuple%s:\n", - ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) == 1 ? "is" : "are", - tupleCount & GX_TC_TUPLE_COUNT_MASK, - ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) == 1 ? "" : "s" )); - + /* Re-use a per-face scratch pool, grown on demand, instead of */ + /* allocating (and freeing) it on every glyph. This pool is freed */ + /* in `tt_done_blend`. The shared/local point-number lists are */ + /* carved out of it too, so it must be set up before they are read. */ peak_coords_size = ALIGN_SIZE( 3 * blend->num_axis * sizeof ( *peak_coords ) ); point_deltas_x_size = ALIGN_SIZE( 2 * n_points * sizeof ( *point_deltas_x ) ); + deltas_buf_size = ALIGN_SIZE( 2 * n_points * + sizeof ( *deltas_x ) ); + points_buf_size = ALIGN_SIZE( 2 * n_points * + sizeof ( *shared_points_buf ) ); points_org_size = ALIGN_SIZE( n_points * sizeof ( *points_org ) ); points_out_size = ALIGN_SIZE( n_points * sizeof ( *points_out ) ); has_delta_size = ALIGN_SIZE( n_points * sizeof ( *has_delta ) ); pool_size = peak_coords_size + point_deltas_x_size + + deltas_buf_size + + points_buf_size + points_org_size + points_out_size + has_delta_size; - if ( FT_ALLOC( pool, pool_size ) ) - goto Exit; - - p = pool; - peak_coords = (FT_Fixed*)p; - p += peak_coords_size; - point_deltas_x = (FT_Fixed*)p; - p += point_deltas_x_size; - points_org = (FT_Vector*)p; - p += points_org_size; - points_out = (FT_Vector*)p; - p += points_out_size; - has_delta = (FT_Bool*)p; + if ( pool_size > blend->glyph_delta_pool_size ) + { + if ( FT_QREALLOC( blend->glyph_delta_pool, + blend->glyph_delta_pool_size, + pool_size ) ) + goto Exit; + blend->glyph_delta_pool_size = pool_size; + } + pool = blend->glyph_delta_pool; + + p = pool; + peak_coords = (FT_Fixed*)p; + p += peak_coords_size; + point_deltas_x = (FT_Fixed*)p; + p += point_deltas_x_size; + deltas_x_buf = (FT_Fixed*)p; + p += deltas_buf_size; + shared_points_buf = (FT_UShort*)p; + p += points_buf_size; + points_org = (FT_Vector*)p; + p += points_org_size; + points_out = (FT_Vector*)p; + p += points_out_size; + has_delta = (FT_Bool*)p; FT_ARRAY_ZERO( point_deltas_x, 2 * n_points ); - im_start_coords = peak_coords + blend->num_axis; - im_end_coords = im_start_coords + blend->num_axis; - point_deltas_y = point_deltas_x + n_points; + im_start_coords = peak_coords + blend->num_axis; + im_end_coords = im_start_coords + blend->num_axis; + point_deltas_y = point_deltas_x + n_points; + deltas_y_buf = deltas_x_buf + n_points; + local_points_buf = shared_points_buf + n_points; for ( j = 0; j < n_points; j++ ) { @@ -4402,39 +4656,102 @@ points_org[j].y = FT_intToFixed( outline->points[j].y ); } - p = stream->cursor; + if ( tupleCount & GX_TC_TUPLES_SHARE_POINT_NUMBERS ) + { + here = FT_Stream_FTell( stream ); + + FT_Stream_SeekSet( stream, offsetToData ); + + sharedpoints = ft_var_readpackedpoints( stream, &spoint_count, + shared_points_buf, n_points ); + + offsetToData = FT_Stream_FTell( stream ); + + FT_Stream_SeekSet( stream, here ); + } + + FT_TRACE5(( "gvar: there %s %u tuple%s:\n", + ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) == 1 ? "is" : "are", + tupleCount & GX_TC_TUPLE_COUNT_MASK, + ( tupleCount & GX_TC_TUPLE_COUNT_MASK ) == 1 ? "" : "s" )); + + p = stream->cursor; + tupleScalars = blend->tuplescalars; tupleCount &= GX_TC_TUPLE_COUNT_MASK; + + /* The tuple-variation headers are walked sequentially through `p`. */ + /* Each header is at least four bytes (`tupleDataSize` and */ + /* `tupleIndex`); embedded and intermediate tuples store additional */ + /* coordinate arrays after that. Bounds-check the four-byte minimum */ + /* of *all* tuples here, once, so the common path inside the loop */ + /* needs no per-tuple check. This is sound because `p` only ever */ + /* advances over these headers -- the variation data of active tuples */ + /* is read further below through a separate stream cursor -- and */ + /* `stream->limit` stays constant within the frame entered above. */ + /* Whenever a tuple consumes more than the four-byte minimum, the */ + /* embedded/intermediate branches re-check the surplus (see below). */ + if ( 4 * tupleCount > (FT_UInt)( stream->limit - p ) ) + { + FT_TRACE2(( "TT_Vary_Apply_Glyph_Deltas:" + " invalid glyph variation array header\n" )); + error = FT_THROW( Invalid_Table ); + goto Exit; + } + for ( i = 0; i < tupleCount; i++ ) { FT_UInt tupleDataSize; FT_UInt tupleIndex; FT_Fixed apply; - FT_Fixed* tupleScalars; FT_TRACE6(( " tuple %u:\n", i )); - tupleScalars = blend->tuplescalars; + /* The four-byte header is already covered by the up-front check. */ + tupleDataSize = FT_NEXT_USHORT( p ); + tupleIndex = FT_NEXT_USHORT( p ); - /* Enter frame for four bytes. */ - if ( 4 > stream->limit - p ) + /* Fast path for the common shared-tuple case. */ + /* */ + /* `blend->tuplecount` is clamped to `GX_TI_TUPLE_INDEX_MASK + 1` at */ + /* load time, so it is at most 0x1000. Of the tuple-index flags */ + /* only `GX_TI_PRIVATE_POINT_NUMBERS` (0x2000) may legitimately */ + /* accompany a shared-tuple index, so it is masked off here; the */ + /* embedded (0x8000), intermediate (0x4000), and reserved (0x1000) */ + /* flags each yield a value >= 0x1000 >= `blend->tuplecount', so if */ + /* any is set the comparison is false. Entering the body therefore */ + /* guarantees a plain shared tuple with an in-range index whose */ + /* scalar lives in the cache. */ + if ( ( tupleIndex & ~GX_TI_PRIVATE_POINT_NUMBERS ) + < blend->tuplecount ) { - FT_TRACE2(( "TT_Vary_Apply_Glyph_Deltas:" - " invalid glyph variation array header\n" )); - error = FT_THROW( Invalid_Table ); - goto Exit; - } + FT_Fixed scalar = tupleScalars[tupleIndex & GX_TI_TUPLE_INDEX_MASK]; - tupleDataSize = FT_NEXT_USHORT( p ); - tupleIndex = FT_NEXT_USHORT( p ); - if ( tupleIndex & GX_TI_INTERMEDIATE_TUPLE ) - tupleScalars = NULL; + if ( scalar == 0 ) + { + offsetToData += tupleDataSize; + continue; + } + + if ( scalar != (FT_Fixed)-0x20000 ) + { + apply = scalar; + goto apply_found; + } + } if ( tupleIndex & GX_TI_EMBEDDED_TUPLE_COORD ) { - if ( 2 * blend->num_axis > (FT_UInt)( stream->limit - p ) ) + /* This tuple stores its peak coordinates inline. `p` has */ + /* advanced past the four-byte header, so the up-front check now */ + /* only accounts for the four-byte minimum of the tuples still to */ + /* come, `4 * ( tupleCount - i - 1 )`. Ensure the embedded */ + /* coordinates fit on top of that reserve; this bounds the read */ + /* and keeps the invariant intact for the following tuples. */ + if ( 2 * blend->num_axis + 4 * ( tupleCount - i - 1 ) > + (FT_UInt)( stream->limit - p ) ) { FT_TRACE2(( "TT_Vary_Apply_Glyph_Deltas:" " invalid glyph variation array header\n" )); @@ -4446,22 +4763,9 @@ peak_coords[j] = FT_fdot14ToFixed( FT_NEXT_SHORT( p ) ); tuple_coords = peak_coords; - tupleScalars = NULL; } else if ( ( tupleIndex & GX_TI_TUPLE_INDEX_MASK ) < blend->tuplecount ) { - FT_Fixed scalar = - tupleScalars - ? tupleScalars[tupleIndex & GX_TI_TUPLE_INDEX_MASK] - : (FT_Fixed)-0x20000; - - - if ( scalar != (FT_Fixed)-0x20000 ) - { - apply = scalar; - goto apply_found; - } - tuple_coords = blend->tuplecoords + ( tupleIndex & GX_TI_TUPLE_INDEX_MASK ) * blend->num_axis; @@ -4477,7 +4781,12 @@ if ( tupleIndex & GX_TI_INTERMEDIATE_TUPLE ) { - if ( 4 * blend->num_axis > (FT_UInt)( stream->limit - p ) ) + /* Intermediate tuples store inline start and end coordinates, */ + /* `4 * num_axis` bytes in total. As above, check them against */ + /* the four-byte reserve of the remaining tuples so the invariant */ + /* survives into the next iteration. */ + if ( 4 * blend->num_axis + 4 * ( tupleCount - i - 1 ) > + (FT_UInt)( stream->limit - p ) ) { FT_TRACE2(( "TT_Vary_Apply_Glyph_Deltas:" " invalid glyph variation array header\n" )); @@ -4497,7 +4806,9 @@ im_start_coords, im_end_coords ); - if ( tupleScalars ) + /* Cache the scalar for shared tuples only (see fast path above). */ + if ( ( tupleIndex & ~GX_TI_PRIVATE_POINT_NUMBERS ) + < blend->tuplecount ) tupleScalars[tupleIndex & GX_TI_TUPLE_INDEX_MASK] = apply; apply_found: @@ -4514,7 +4825,8 @@ if ( tupleIndex & GX_TI_PRIVATE_POINT_NUMBERS ) { - localpoints = ft_var_readpackedpoints( stream, &point_count ); + localpoints = ft_var_readpackedpoints( stream, &point_count, + local_points_buf, n_points ); points = localpoints; } else @@ -4525,10 +4837,14 @@ deltas_x = ft_var_readpackeddeltas( stream, point_count == 0 ? n_points - : point_count ); + : point_count, + deltas_x_buf, + n_points ); deltas_y = ft_var_readpackeddeltas( stream, point_count == 0 ? n_points - : point_count ); + : point_count, + deltas_y_buf, + n_points ); if ( !points || !deltas_y || !deltas_x ) ; /* failure, ignore it */ @@ -4653,10 +4969,14 @@ #endif } - if ( localpoints != ALL_POINTS ) + /* Only free deltas that fell back to a heap allocation; */ + /* the common case reuses the pooled scratch buffers. */ + if ( localpoints != ALL_POINTS && localpoints != local_points_buf ) FT_FREE( localpoints ); - FT_FREE( deltas_x ); - FT_FREE( deltas_y ); + if ( deltas_x != deltas_x_buf ) + FT_FREE( deltas_x ); + if ( deltas_y != deltas_y_buf ) + FT_FREE( deltas_y ); offsetToData += tupleDataSize; @@ -4711,9 +5031,10 @@ } Exit: - if ( sharedpoints != ALL_POINTS ) + if ( sharedpoints != ALL_POINTS && sharedpoints != shared_points_buf ) FT_FREE( sharedpoints ); - FT_FREE( pool ); + /* The persistent per-face scratch buffer (`blend->glyph_delta_pool`) */ + /* is freed in `tt_done_blend`, not here. */ FExit: FT_FRAME_EXIT(); @@ -4793,6 +5114,9 @@ FT_FREE( itemStore->varRegionList ); } + + FT_FREE( itemStore->regionScalars ); + FT_FREE( itemStore->cachedCoords ); } @@ -4829,13 +5153,13 @@ FT_UInt i, num_axes; - /* blend->num_axis might not be set up yet */ - num_axes = blend->mmvar->num_axis; + num_axes = blend->num_axis; FT_FREE( blend->coords ); FT_FREE( blend->normalizedcoords ); FT_FREE( blend->normalized_stylecoords ); FT_FREE( blend->mmvar ); + FT_FREE( blend->glyph_delta_pool ); if ( blend->avar_table ) { @@ -4888,6 +5212,9 @@ FT_FREE( blend->tuplecoords ); FT_FREE( blend->glyphoffsets ); FT_FREE( blend ); + + /* the face may outlive its blend (e.g., after a failed setup) */ + ttface->blend = NULL; } } diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttgxvar.h b/src/java.desktop/share/native/libfreetype/src/truetype/ttgxvar.h index 8a59c0cd6e6..c8446e134b2 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttgxvar.h +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttgxvar.h @@ -262,6 +262,10 @@ FT_BEGIN_HEADER * gv_glyphcnt :: * The number of glyphs handled in the `gvar' table. * + * gvar_is_extended :: + * A boolean which is set when glyph variation data comes from the + * 'GVAR' table (instead of 'gvar'). + * * glyphoffsets :: * Offsets into the glyph variation data array. * @@ -300,10 +304,16 @@ FT_BEGIN_HEADER FT_Fixed* tuplescalars; /* tuplescalars[tuplecount] */ FT_UInt gv_glyphcnt; + FT_Bool gvar_is_extended; FT_ULong* glyphoffsets; /* glyphoffsets[gv_glyphcnt + 1] */ FT_ULong gvar_size; + /* Scratch pool reused across glyphs by `TT_Vary_Apply_Glyph_Deltas`, */ + /* grown on demand, to avoid per-glyph/per-tuple heap allocations. */ + FT_Byte* glyph_delta_pool; + FT_ULong glyph_delta_pool_size; + } GX_BlendRec; @@ -443,6 +453,13 @@ FT_BEGIN_HEADER FT_Fixed* *normalizedcoords, FT_MM_Var* *mm_var ); + FT_LOCAL( FT_Error ) + tt_var_init_gvar( FT_Face face ); + + FT_LOCAL( FT_Error ) + tt_var_init_blend( FT_Face face, + FT_UInt axis_count ); + FT_LOCAL( void ) tt_done_blend( FT_Face face ); diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttinterp.c b/src/java.desktop/share/native/libfreetype/src/truetype/ttinterp.c index f850bc418e7..cc99b2ebd42 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttinterp.c +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttinterp.c @@ -1104,7 +1104,7 @@ /* documentation is in freetype.h */ static __inline FT_Int32 - TT_MulFixi14_i386( FT_Int32 a, + TT_MulFix14_i386( FT_Int32 a, FT_Int32 b ) { FT_Int32 result; @@ -1499,7 +1499,9 @@ * * @Note: * See `ttinterp.h' for details on backward compatibility mode. - * `Touches' the point. + * `Touches' the point. When both freedom and projection vectors + * are aligned along the axes, the moves are handled by simplified + * versions of this function; more complex moves are handled here. */ static void Direct_Move( TT_ExecContext exc, @@ -1514,10 +1516,10 @@ if ( v != 0 ) { #ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL - /* Exception to the post-IUP curfew: Allow the x component of */ - /* diagonal moves, but only post-IUP. DejaVu tries to adjust */ - /* diagonal stems like on `Z' and `z' post-IUP. */ - if ( !exc->backward_compatibility ) + /* See `ttinterp.h' for details on backward compatibility mode. */ + /* Exception for horizontal projection is made for Comic Sans MS. */ + if ( !exc->backward_compatibility || + exc->GS.projVector.y == 0 ) #endif zone->cur[point].x = ADD_LONG( zone->cur[point].x, FT_MulFix( distance, v ) ); @@ -1529,8 +1531,10 @@ if ( v != 0 ) { #ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL - /* See `ttinterp.h' for details on backward compatibility mode. */ - if ( exc->backward_compatibility != 0x7 ) + /* See `ttinterp.h' for details on backward compatibility mode. */ + /* Exception for horizontal projection is made for Comic Sans MS. */ + if ( exc->backward_compatibility != 0x7 || + exc->GS.projVector.y == 0 ) #endif zone->cur[point].y = ADD_LONG( zone->cur[point].y, FT_MulFix( distance, v ) ); @@ -1660,6 +1664,27 @@ zone->org[point].y = ADD_LONG( zone->org[point].y, distance ); } + +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + + static void + Update_Native_ClearType_X_State( TT_ExecContext exc ) + { + /* `backward_compatibility' is also zero in v35, the CVT program, */ + /* monochrome rendering, and tricky-font execution. */ + exc->native_cleartype_x = FT_BOOL( + !exc->backward_compatibility && + exc->GS.projVector.y == 0 && + exc->GS.freeVector.y == 0 && + SUBPIXEL_HINTING_MINIMAL && + exc->iniRange == tt_coderange_glyph && + exc->mode != FT_RENDER_MODE_MONO && + !FT_IS_TRICKY( (FT_Face)exc->face ) ); + } + +#endif /* TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL */ + + /************************************************************************** * * @Function: @@ -2250,9 +2275,12 @@ exc->moveVector.x = exc->GS.freeVector.x * 4; exc->moveVector.y = exc->GS.freeVector.y * 4; } - else if ( -0x400L < F_dot_P && F_dot_P < 0x400L ) + else if ( -0x1555L < F_dot_P && F_dot_P < 0x1555L ) { - /* prohibitively orthogonal */ + /* prohibitively near-orthogonal; the empirical limit avoids */ + /* bad rendering in Palatino without negative consequences */ + FT_TRACE3(( "F_dot_P = %.2f, too small for valid movement.\n", + F_dot_P / 16384.)); exc->moveVector.x = 0; exc->moveVector.y = 0; } @@ -2292,6 +2320,12 @@ else exc->func_dualproj = (TT_Project_Func)Dual_Project; +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* Projection/freedom-vector changes can enter or leave the */ + /* native ClearType direction. */ + Update_Native_ClearType_X_State( exc ); +#endif + /* Disable cached aspect ratio */ exc->tt_metrics.ratio = 0; } @@ -2841,7 +2875,13 @@ ARRAY_BOUND_ERROR; } else + { + Modify_CVT_Check( exc ); + if ( exc->error ) + return; + exc->cvt[I] = FT_MulFix( args[1], exc->tt_metrics.scale ); + } } @@ -2908,8 +2948,19 @@ Ins_ROUND( TT_ExecContext exc, FT_Long* args ) { - args[0] = exc->func_round( exc, args[0], - exc->GS.compensation[exc->opcode & 3] ); +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* + * Native ClearType applies ROUND to the 1/16-pixel virtual + * grid in the ClearType direction. Approximate this in v40 + * by leaving the result unrounded. + */ + if ( exc->native_cleartype_x ) + args[0] = Round_None( exc, args[0], + exc->GS.compensation[exc->opcode & 3] ); + else +#endif + args[0] = exc->func_round( exc, args[0], + exc->GS.compensation[exc->opcode & 3] ); } @@ -4722,7 +4773,13 @@ /* compatibility hacks and lets them program points to the grid like */ /* it's 1996. They might sign a waiver for just one glyph, though. */ if ( SUBPIXEL_HINTING_MINIMAL ) + { exc->backward_compatibility = ( L & 4 ) ^ 4; + + /* A glyph can temporarily switch between backward-compatible */ + /* and native ClearType, so update the native-X state too. */ + Update_Native_ClearType_X_State( exc ); + } #endif } else if ( exc->pedantic_hinting ) @@ -4923,37 +4980,38 @@ Compute_Point_Displacement( TT_ExecContext exc, FT_F26Dot6* x, FT_F26Dot6* y, - TT_GlyphZone zone, - FT_UShort* refp ) + FT_Vector* cur, + FT_UInt* refp ) { - TT_GlyphZoneRec zp; - FT_UShort p; - FT_F26Dot6 d; + TT_GlyphZone zp; + FT_UShort p; + FT_F26Dot6 d; if ( exc->opcode & 1 ) { - zp = exc->zp0; + zp = &exc->zp0; p = exc->GS.rp1; } else { - zp = exc->zp1; + zp = &exc->zp1; p = exc->GS.rp2; } - if ( BOUNDS( p, zp.n_points ) ) + if ( BOUNDS( p, zp->n_points ) ) { if ( exc->pedantic_hinting ) exc->error = FT_THROW( Invalid_Reference ); - *refp = 0; return FAILURE; } - *zone = zp; - *refp = p; + /* return reference if zones match */ + if ( refp ) + *refp = cur == zp->cur ? p + : ~0U; /* nan */ - d = PROJECT( zp.cur + p, zp.org + p ); + d = PROJECT( zp->cur + p, zp->org + p ); *x = FT_MulFix( d, exc->moveVector.x ); *y = FT_MulFix( d, exc->moveVector.y ); @@ -4965,10 +5023,9 @@ /* See `ttinterp.h' for details on backward compatibility mode. */ static void Move_Zp2_Point( TT_ExecContext exc, - FT_UShort point, + FT_UInt point, FT_F26Dot6 dx, - FT_F26Dot6 dy, - FT_Bool touch ) + FT_F26Dot6 dy ) { if ( exc->GS.freeVector.x != 0 ) { @@ -4978,8 +5035,7 @@ #endif exc->zp2.cur[point].x = ADD_LONG( exc->zp2.cur[point].x, dx ); - if ( touch ) - exc->zp2.tags[point] |= FT_CURVE_TAG_TOUCH_X; + exc->zp2.tags[point] |= FT_CURVE_TAG_TOUCH_X; } if ( exc->GS.freeVector.y != 0 ) @@ -4990,8 +5046,7 @@ #endif exc->zp2.cur[point].y = ADD_LONG( exc->zp2.cur[point].y, dy ); - if ( touch ) - exc->zp2.tags[point] |= FT_CURVE_TAG_TOUCH_Y; + exc->zp2.tags[point] |= FT_CURVE_TAG_TOUCH_Y; } } @@ -5007,11 +5062,9 @@ FT_Long* args ) { FT_Long loop = exc->GS.loop; - TT_GlyphZoneRec zp; - FT_UShort refp; FT_F26Dot6 dx, dy; - FT_UShort point; + FT_UInt point; if ( exc->new_top < loop ) @@ -5023,12 +5076,12 @@ exc->new_top -= loop; - if ( Compute_Point_Displacement( exc, &dx, &dy, &zp, &refp ) ) + if ( Compute_Point_Displacement( exc, &dx, &dy, NULL, NULL ) ) return; while ( loop-- ) { - point = (FT_UShort)*(--args); + point = (FT_UInt)*(--args); if ( BOUNDS( point, exc->zp2.n_points ) ) { @@ -5039,7 +5092,7 @@ } } else - Move_Zp2_Point( exc, point, dx, dy, TRUE ); + Move_Zp2_Point( exc, point, dx, dy ); } Fail: @@ -5061,12 +5114,10 @@ Ins_SHC( TT_ExecContext exc, FT_Long* args ) { - TT_GlyphZoneRec zp; - FT_UShort refp; + FT_UInt refp, start, limit, i; FT_F26Dot6 dx, dy; FT_UShort contour, bounds; - FT_UShort start, limit, i; contour = (FT_UShort)args[0]; @@ -5079,7 +5130,7 @@ return; } - if ( Compute_Point_Displacement( exc, &dx, &dy, &zp, &refp ) ) + if ( Compute_Point_Displacement( exc, &dx, &dy, exc->zp2.cur, &refp ) ) return; if ( contour == 0 ) @@ -5095,8 +5146,8 @@ for ( i = start; i < limit; i++ ) { - if ( zp.cur != exc->zp2.cur || refp != i ) - Move_Zp2_Point( exc, i, dx, dy, TRUE ); + if ( refp != i ) + Move_Zp2_Point( exc, i, dx, dy ); } } @@ -5111,40 +5162,59 @@ Ins_SHZ( TT_ExecContext exc, FT_Long* args ) { - TT_GlyphZoneRec zp; - FT_UShort refp; - FT_F26Dot6 dx, - dy; - - FT_UShort limit, i; + FT_Vector* cur; + FT_UInt refp, i, limit; + FT_F26Dot6 dx, dy; - if ( BOUNDS( args[0], 2 ) ) + /* XXX: UNDOCUMENTED! SHZ doesn't move the phantom points, */ + /* which must be subtracted. */ + switch ( (FT_Int)args[0] ) { + case 0: + cur = exc->twilight.cur; + limit = exc->twilight.n_points; + break; + + case 1: + cur = exc->pts.cur; + limit = exc->pts.n_points > 4U ? exc->pts.n_points - 4U : 0; + break; + + default: if ( exc->pedantic_hinting ) exc->error = FT_THROW( Invalid_Reference ); return; } - if ( Compute_Point_Displacement( exc, &dx, &dy, &zp, &refp ) ) + if ( Compute_Point_Displacement( exc, &dx, &dy, cur, &refp ) ) return; - /* XXX: UNDOCUMENTED! SHZ doesn't move the phantom points. */ - /* Twilight zone has no real contours, so use `n_points'. */ - /* Normal zone's `n_points' includes phantoms, so must */ - /* use end of last contour. */ - if ( exc->GS.gep2 == 0 ) - limit = exc->zp2.n_points; - else if ( exc->GS.gep2 == 1 && exc->zp2.n_contours > 0 ) - limit = exc->zp2.contours[exc->zp2.n_contours - 1] + 1; - else - limit = 0; + /* XXX: UNDOCUMENTED! SHZ doesn't touch the points. */ + if ( dx ) + { +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* See `ttinterp.h' for details on backward compatibility mode. */ + if ( !exc->backward_compatibility ) +#endif + for ( i = 0; i < limit; i++ ) + { + if ( refp != i ) + cur[i].x = ADD_LONG( cur[i].x, dx ); + } + } - /* XXX: UNDOCUMENTED! SHZ doesn't touch the points */ - for ( i = 0; i < limit; i++ ) + if ( dy ) { - if ( zp.cur != exc->zp2.cur || refp != i ) - Move_Zp2_Point( exc, i, dx, dy, FALSE ); +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* See `ttinterp.h' for details on backward compatibility mode. */ + if ( exc->backward_compatibility != 0x7 ) +#endif + for ( i = 0; i < limit; i++ ) + { + if ( refp != i ) + cur[i].y = ADD_LONG( cur[i].y, dy ); + } } } @@ -5161,7 +5231,7 @@ { FT_Long loop = exc->GS.loop; FT_F26Dot6 dx, dy; - FT_UShort point; + FT_UInt point; #ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL FT_Bool in_twilight = FT_BOOL( exc->GS.gep0 == 0 || exc->GS.gep1 == 0 || @@ -5183,7 +5253,7 @@ while ( loop-- ) { - point = (FT_UShort)*(--args); + point = (FT_UInt)*(--args); if ( BOUNDS( point, exc->zp2.n_points ) ) { @@ -5206,11 +5276,11 @@ ( exc->backward_compatibility != 0x7 && ( ( exc->is_composite && exc->GS.freeVector.y != 0 ) || ( exc->zp2.tags[point] & FT_CURVE_TAG_TOUCH_Y ) ) ) ) - Move_Zp2_Point( exc, point, 0, dy, TRUE ); + Move_Zp2_Point( exc, point, 0, dy ); } else #endif - Move_Zp2_Point( exc, point, dx, dy, TRUE ); + Move_Zp2_Point( exc, point, dx, dy ); } Fail: @@ -5293,7 +5363,12 @@ if ( ( exc->opcode & 1 ) != 0 ) { cur_dist = FAST_PROJECT( &exc->zp0.cur[point] ); - distance = SUB_LONG( exc->func_round( exc, cur_dist, 0 ), cur_dist ); +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + if ( exc->native_cleartype_x ) + distance = 0; + else +#endif + distance = SUB_LONG( exc->func_round( exc, cur_dist, 0 ), cur_dist ); } else distance = 0; @@ -5371,6 +5446,13 @@ FT_F26Dot6 delta; +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* Native ClearType reduces CVT cut-in to 1/16 in the */ + /* ClearType direction. */ + if ( exc->native_cleartype_x ) + control_value_cutin >>= 4; +#endif + delta = SUB_LONG( distance, org_dist ); if ( delta < 0 ) delta = NEG_LONG( delta ); @@ -5378,7 +5460,10 @@ if ( delta > control_value_cutin ) distance = org_dist; - distance = exc->func_round( exc, distance, 0 ); +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + if ( !exc->native_cleartype_x ) +#endif + distance = exc->func_round( exc, distance, 0 ); } exc->func_move( exc, &exc->zp0, point, SUB_LONG( distance, org_dist ) ); @@ -5472,7 +5557,14 @@ compensation = exc->GS.compensation[exc->opcode & 3]; if ( ( exc->opcode & 4 ) != 0 ) - distance = exc->func_round( exc, org_dist, compensation ); + { +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + if ( exc->native_cleartype_x ) + distance = Round_None( exc, org_dist, compensation ); + else +#endif + distance = exc->func_round( exc, org_dist, compensation ); + } else distance = Round_None( exc, org_dist, compensation ); @@ -5483,6 +5575,13 @@ FT_F26Dot6 minimum_distance = exc->GS.minimum_distance; +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* Native ClearType reduces minimum distance to 1/2 in the */ + /* ClearType direction. */ + if ( exc->native_cleartype_x ) + minimum_distance >>= 1; +#endif + if ( org_dist >= 0 ) { if ( distance < minimum_distance ) @@ -5605,6 +5704,13 @@ FT_F26Dot6 control_value_cutin = exc->GS.control_value_cutin; +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* Native ClearType reduces CVT cut-in to 1/16 in the */ + /* ClearType direction. */ + if ( exc->native_cleartype_x ) + control_value_cutin >>= 4; +#endif + /* XXX: According to Greg Hitchcock, the following wording is */ /* the right one: */ /* */ @@ -5625,7 +5731,12 @@ cvt_dist = org_dist; } - distance = exc->func_round( exc, cvt_dist, compensation ); +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + if ( exc->native_cleartype_x ) + distance = Round_None( exc, cvt_dist, compensation ); + else +#endif + distance = exc->func_round( exc, cvt_dist, compensation ); } else distance = Round_None( exc, cvt_dist, compensation ); @@ -5637,6 +5748,13 @@ FT_F26Dot6 minimum_distance = exc->GS.minimum_distance; +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + /* Native ClearType reduces minimum distance to 1/2 in the */ + /* ClearType direction. */ + if ( exc->native_cleartype_x ) + minimum_distance >>= 1; +#endif + if ( org_dist >= 0 ) { if ( distance < minimum_distance ) @@ -6191,14 +6309,14 @@ IUP_WorkerRec V; FT_Byte mask; - FT_UInt first_point; /* first point of contour */ - FT_UInt end_point; /* end point (last+1) of contour */ + FT_UInt first_point; /* first point of contour */ + FT_UInt end_point; /* end point (last+1) of contour */ - FT_UInt first_touched; /* first touched point in contour */ - FT_UInt cur_touched; /* current touched point in contour */ + FT_UInt first_touched; /* first touched point in contour */ + FT_UInt cur_touched; /* current touched point in contour */ - FT_UInt point; /* current point */ - FT_Short contour; /* current contour */ + FT_UInt point; /* current point */ + FT_UInt contour; /* current contour */ #ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL @@ -6566,11 +6684,23 @@ * Selector Bit: 11 * Return Bit(s): 18 * - * The only smoothing method FreeType supports unless someone sets - * FT_LOAD_TARGET_MONO. + * Report the smoothing mode selected by the DirectWrite-style + * ClearType policy for this face and PPEM. */ - if ( ( args[0] & 2048 ) != 0 && exc->mode != FT_RENDER_MODE_MONO ) - K |= 1 << 18; + if ( ( args[0] & 2048 ) != 0 && + exc->mode != FT_RENDER_MODE_MONO ) + { + FT_Bool symmetric_smoothing; + + + tt_face_get_cleartype_policy( exc->face, + exc->metrics.y_ppem, + &symmetric_smoothing, + NULL ); + + if ( symmetric_smoothing ) + K |= 1 << 18; + } /********************************* * CLEARTYPE HINTING AND @@ -7339,56 +7469,11 @@ } if ( exc->error ) - { - switch ( exc->error ) - { - case FT_ERR( Invalid_Opcode ): - { - TT_DefRecord* def = exc->IDefs; - TT_DefRecord* limit = FT_OFFSET( def, exc->numIDefs ); - - - /* looking for redefined instructions */ - for ( ; def < limit; def++ ) - { - if ( def->active && exc->opcode == (FT_Byte)def->opc ) - { - TT_CallRec* callrec; - - - if ( exc->callTop >= exc->callSize ) - { - exc->error = FT_THROW( Invalid_Reference ); - goto LErrorLabel_; - } - - callrec = &exc->callStack[exc->callTop]; - - callrec->Caller_Range = exc->curRange; - callrec->Caller_IP = exc->IP + 1; - callrec->Cur_Count = 1; - callrec->Def = def; - - if ( Ins_Goto_CodeRange( exc, - def->range, - def->start ) == FAILURE ) - goto LErrorLabel_; - - goto LSuiteLabel_; - } - } - } - FALL_THROUGH; - - default: - goto LErrorLabel_; - } - } + goto LErrorLabel_; exc->top = exc->new_top; exc->IP += exc->length; - LSuiteLabel_: if ( exc->IP >= exc->codeSize ) { if ( exc->callTop > 0 ) diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttinterp.h b/src/java.desktop/share/native/libfreetype/src/truetype/ttinterp.h index 05ac831dfd1..5b71bb82d80 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttinterp.h +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttinterp.h @@ -378,6 +378,14 @@ FT_BEGIN_HEADER * */ + /* + * Native ClearType horizontal execution state. Microsoft ClearType + * uses a 1/16-pixel grid in the ClearType direction; version 40 + * approximates this by leaving rounded horizontal point moves + * unrounded. + */ + FT_Bool native_cleartype_x; + /* Activate backward compatibility (bit 2) and track IUP (bits 0-1). */ /* If this is zero, it means that the interpreter is either in v35 */ /* or in native ClearType mode. */ diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttobjs.c b/src/java.desktop/share/native/libfreetype/src/truetype/ttobjs.c index dc897688584..8c76e23dbf8 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttobjs.c +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttobjs.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "ttgload.h" #include "ttpload.h" @@ -852,11 +853,74 @@ #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT tt_done_blend( ttface ); - face->blend = NULL; #endif } +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + + FT_LOCAL_DEF( void ) + tt_face_get_cleartype_policy( TT_Face face, + FT_UInt ppem, + FT_Bool* symmetric_smoothing, + FT_Bool* grid_fit ) + { + FT_Bool symmetric; + FT_Bool fit; + FT_Bool found = FALSE; + FT_UShort flags = 0; + FT_UInt i; + + + /* + * DirectWrite fallback for hinted fonts without an applicable + * version-1 ClearType 'gasp' range: + * + * <= 20 ppem NATURAL + * > 20 ppem NATURAL_SYMMETRIC + * + * Fonts without glyph bytecode use symmetric rendering. + * + * `maxSizeOfInstructions` is also what Skia uses to determine + * whether a TrueType face is hinted for this DirectWrite policy. + */ + fit = FT_BOOL( face->max_profile.maxSizeOfInstructions != 0 ); + + symmetric = FT_BOOL( !fit || ppem > 20 ); + + /* + * A valid version-1 `gasp' range explicitly controls the two + * independent ClearType properties. + */ + if ( face->gasp.version >= 1 ) + { + for ( i = 0; i < face->gasp.numRanges; i++ ) + { + if ( ppem <= face->gasp.gaspRanges[i].maxPPEM ) + { + flags = face->gasp.gaspRanges[i].gaspFlag; + found = TRUE; + break; + } + } + + if ( found ) + { + symmetric = FT_BOOL( flags & FT_GASP_SYMMETRIC_SMOOTHING ); + fit = FT_BOOL( flags & FT_GASP_SYMMETRIC_GRIDFIT ); + } + } + + if ( symmetric_smoothing ) + *symmetric_smoothing = symmetric; + + if ( grid_fit ) + *grid_fit = fit; + } + +#endif + + /************************************************************************** * * SIZE FUNCTIONS @@ -909,15 +973,15 @@ error = TT_Run_Context( exec, size ); FT_TRACE4(( error ? " failed (error code 0x%x)\n" : "", error )); + + if ( !error ) + TT_Save_Context( exec, size ); } else error = FT_Err_Ok; size->bytecode_ready = error; - if ( !error ) - TT_Save_Context( exec, size ); - return error; } @@ -990,15 +1054,15 @@ error = TT_Run_Context( exec, size ); FT_TRACE4(( error ? " failed (error code 0x%x)\n" : "", error )); + + if ( !error ) + TT_Save_Context( exec, size ); } else error = FT_Err_Ok; size->cvt_ready = error; - if ( !error ) - TT_Save_Context( exec, size ); - return error; } diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttobjs.h b/src/java.desktop/share/native/libfreetype/src/truetype/ttobjs.h index 9b2f412ac47..01e581e1c1a 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttobjs.h +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttobjs.h @@ -325,6 +325,22 @@ FT_BEGIN_HEADER tt_slot_init( FT_GlyphSlot slot ); +#ifdef TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL + + /* + * Return the DirectWrite-style ClearType policy for a face and PPEM. + * + * Either output pointer may be NULL. + */ + FT_LOCAL( void ) + tt_face_get_cleartype_policy( TT_Face face, + FT_UInt ppem, + FT_Bool* symmetric_smoothing, + FT_Bool* grid_fit ); + +#endif + + /* auxiliary */ #define IS_HINTED( flags ) ( ( flags & FT_LOAD_NO_HINTING ) == 0 ) diff --git a/src/java.desktop/share/native/libfreetype/src/truetype/ttpload.c b/src/java.desktop/share/native/libfreetype/src/truetype/ttpload.c index 5b0a3944d6a..ec85f48ce2c 100644 --- a/src/java.desktop/share/native/libfreetype/src/truetype/ttpload.c +++ b/src/java.desktop/share/native/libfreetype/src/truetype/ttpload.c @@ -69,7 +69,11 @@ /* we need the size of the `glyf' table for malformed `loca' tables */ - error = face->goto_table( face, TTAG_glyf, stream, &face->glyf_len ); + error = face->goto_table( face, + face->is_extended_glyf ? TTAG_GLYF + : TTAG_glyf, + stream, + &face->glyf_len ); /* it is possible that a font doesn't have a glyf table at all */ /* or its size is zero */ @@ -91,7 +95,11 @@ } FT_TRACE2(( "Locations " )); - error = face->goto_table( face, TTAG_loca, stream, &table_len ); + error = face->goto_table( face, + face->is_extended_glyf ? TTAG_LOCA + : TTAG_loca, + stream, + &table_len ); if ( error ) { error = FT_THROW( Locations_Missing ); @@ -100,7 +108,8 @@ shift = face->header.Index_To_Loc_Format != 0 ? 2 : 1; - if ( table_len > 0x10000UL << shift ) + if ( !face->is_extended_glyf && + table_len > 0x10000UL << shift ) { FT_TRACE2(( "table too large\n" )); table_len = 0x10000UL << shift; diff --git a/src/java.desktop/share/native/libfreetype/src/type1/t1load.c b/src/java.desktop/share/native/libfreetype/src/type1/t1load.c index 1d095f8ac1f..2ed327ba2e7 100644 --- a/src/java.desktop/share/native/libfreetype/src/type1/t1load.c +++ b/src/java.desktop/share/native/libfreetype/src/type1/t1load.c @@ -677,7 +677,7 @@ PS_Blend blend = t1face->blend; FT_Fixed axiscoords[4]; - FT_UInt i, nc; + FT_UInt i; if ( !blend ) @@ -687,19 +687,18 @@ axiscoords, blend->num_axis ); - nc = num_coords; if ( num_coords > blend->num_axis ) { FT_TRACE2(( "T1_Get_Var_Design:" " only using first %u of %u coordinates\n", blend->num_axis, num_coords )); - nc = blend->num_axis; + + FT_ARRAY_ZERO( coords + blend->num_axis, num_coords - blend->num_axis ); + num_coords = blend->num_axis; } - for ( i = 0; i < nc; i++ ) + for ( i = 0; i < num_coords; i++ ) coords[i] = mm_axis_unmap( &blend->design_map[i], axiscoords[i] ); - for ( ; i < num_coords; i++ ) - coords[i] = 0; return FT_Err_Ok; } diff --git a/src/java.desktop/share/native/libfreetype/src/type1/t1parse.c b/src/java.desktop/share/native/libfreetype/src/type1/t1parse.c index a9b8d18a6ff..73bfb0cdecc 100644 --- a/src/java.desktop/share/native/libfreetype/src/type1/t1parse.c +++ b/src/java.desktop/share/native/libfreetype/src/type1/t1parse.c @@ -268,33 +268,34 @@ if ( parser->in_pfb ) { - /* in the case of the PFB format, the private dictionary can be */ - /* made of several segments. We thus first read the number of */ - /* segments to compute the total size of the private dictionary */ - /* then re-read them into memory. */ - FT_ULong start_pos = FT_STREAM_POS(); + /* in the case of the PFB format, the private dictionary can be */ + /* made of several adjacent segments. */ FT_UShort tag; + FT_Byte* priv_dict = NULL; + FT_ULong priv_len = 0; - parser->private_len = 0; for (;;) { error = read_pfb_tag( stream, &tag, &size ); if ( error ) - goto Fail; + break; - if ( tag != 0x8002U ) + if ( tag != 0x8002U || size > stream->size - stream->pos ) break; - parser->private_len += size; + if ( FT_QREALLOC( priv_dict, priv_len, priv_len + size ) || + FT_STREAM_READ( priv_dict + priv_len, size ) ) + break; - if ( FT_STREAM_SKIP( size ) ) - goto Fail; + priv_len += size; } + parser->private_dict = priv_dict; + parser->private_len = priv_len; + /* Check that we have a private dictionary there */ - /* and allocate private dictionary buffer */ - if ( parser->private_len == 0 ) + if ( priv_len == 0 ) { FT_ERROR(( "T1_Get_Private_Dict:" " invalid private dictionary section\n" )); @@ -302,26 +303,8 @@ goto Fail; } - if ( FT_STREAM_SEEK( start_pos ) || - FT_QALLOC( parser->private_dict, parser->private_len ) ) - goto Fail; - - parser->private_len = 0; - for (;;) - { - error = read_pfb_tag( stream, &tag, &size ); - if ( error || tag != 0x8002U ) - { - error = FT_Err_Ok; - break; - } - - if ( FT_STREAM_READ( parser->private_dict + parser->private_len, - size ) ) - goto Fail; - - parser->private_len += size; - } + /* discard any errors from truncated files */ + error = FT_Err_Ok; } else { @@ -339,11 +322,8 @@ FT_Bool test_cr; - parser->root.cursor = parser->base_dict; - parser->root.limit = parser->base_dict + parser->base_len; - - cur = parser->root.cursor; - limit = parser->root.limit; + parser->root.cursor = cur; + parser->root.limit = limit; while ( cur < limit ) { From b002439716e76fe9e5c21063f6369eba567187be Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Wed, 14 Oct 2026 16:01:22 +0000 Subject: [PATCH 094/101] 8394419: [PPC64] Cleanup after JDK-8331859 Reviewed-by: rrich --- src/hotspot/cpu/ppc/assembler_ppc.hpp | 28 +++++++-------- src/hotspot/cpu/ppc/assembler_ppc.inline.hpp | 9 +++-- src/hotspot/cpu/ppc/globals_ppc.hpp | 2 +- src/hotspot/cpu/ppc/macroAssembler_ppc.cpp | 34 +++++++----------- src/hotspot/cpu/ppc/macroAssembler_ppc.hpp | 38 ++++++++------------ src/hotspot/cpu/ppc/ppc.ad | 24 ++++++------- src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp | 8 ++--- src/hotspot/cpu/ppc/vm_version_ppc.cpp | 20 ++++------- src/hotspot/cpu/ppc/vm_version_ppc.hpp | 2 +- 9 files changed, 67 insertions(+), 98 deletions(-) diff --git a/src/hotspot/cpu/ppc/assembler_ppc.hpp b/src/hotspot/cpu/ppc/assembler_ppc.hpp index 366f2ce0e1f..37eee6e670d 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.hpp @@ -771,24 +771,24 @@ class Assembler : public AbstractAssembler { MTVSCR_OPCODE = (4u << OPCODE_SHIFT | 1604u ), MFVSCR_OPCODE = (4u << OPCODE_SHIFT | 1540u ), - // AES (introduced with Power 8) + // AES VCIPHER_OPCODE = (4u << OPCODE_SHIFT | 1288u), VCIPHERLAST_OPCODE = (4u << OPCODE_SHIFT | 1289u), VNCIPHER_OPCODE = (4u << OPCODE_SHIFT | 1352u), VNCIPHERLAST_OPCODE = (4u << OPCODE_SHIFT | 1353u), VSBOX_OPCODE = (4u << OPCODE_SHIFT | 1480u), - // SHA (introduced with Power 8) + // SHA VSHASIGMAD_OPCODE = (4u << OPCODE_SHIFT | 1730u), VSHASIGMAW_OPCODE = (4u << OPCODE_SHIFT | 1666u), - // Vector Binary Polynomial Multiplication (introduced with Power 8) + // Vector Binary Polynomial Multiplication VPMSUMB_OPCODE = (4u << OPCODE_SHIFT | 1032u), VPMSUMD_OPCODE = (4u << OPCODE_SHIFT | 1224u), VPMSUMH_OPCODE = (4u << OPCODE_SHIFT | 1096u), VPMSUMW_OPCODE = (4u << OPCODE_SHIFT | 1160u), - // Vector Permute and Xor (introduced with Power 8) + // Vector Permute and Xor VPERMXOR_OPCODE = (4u << OPCODE_SHIFT | 45u), // Icache and dcache related instructions @@ -917,7 +917,7 @@ class Assembler : public AbstractAssembler { bcondCRbiIs1_bhintIsTaken = bcondCRbiIs1 | bhintatIsTaken, }; - // Elemental Memory Barriers (>=Power 8) + // Elemental Memory Barriers enum Elemental_Membar_mask_bits { StoreStore = 1 << 0, StoreLoad = 1 << 1, @@ -1838,7 +1838,6 @@ class Assembler : public AbstractAssembler { inline void mfvrsave(Register d); // Timebase inline void mftb(Register d); - // Introduced with Power 8: // Data Stream Control Register inline void mtdscr(Register s1); inline void mfdscr(Register d ); @@ -2015,18 +2014,18 @@ class Assembler : public AbstractAssembler { inline void ptesync(); inline void eieio(); inline void isync(); - inline void elemental_membar(int e); // Elemental Memory Barriers (>=Power 8) + inline void elemental_membar(int e); // Elemental Memory Barriers // Wait instructions for polling. Attention: May result in SIGILL. inline void wait(); inline void waitrsv(); // atomics - inline void lbarx_unchecked(Register d, Register a, Register b, int eh1 = 0); // >=Power 8 - inline void lharx_unchecked(Register d, Register a, Register b, int eh1 = 0); // >=Power 8 + inline void lbarx_unchecked(Register d, Register a, Register b, int eh1 = 0); + inline void lharx_unchecked(Register d, Register a, Register b, int eh1 = 0); inline void lwarx_unchecked(Register d, Register a, Register b, int eh1 = 0); inline void ldarx_unchecked(Register d, Register a, Register b, int eh1 = 0); - inline void lqarx_unchecked(Register d, Register a, Register b, int eh1 = 0); // >=Power 8 + inline void lqarx_unchecked(Register d, Register a, Register b, int eh1 = 0); inline void lbarx( Register d, Register a, Register b, bool hint_exclusive_access = false); inline void lharx( Register d, Register a, Register b, bool hint_exclusive_access = false); inline void lwarx( Register d, Register a, Register b, bool hint_exclusive_access = false); @@ -2052,7 +2051,6 @@ class Assembler : public AbstractAssembler { inline void smt_yield(); inline void smt_mdoio(); inline void smt_mdoom(); - // >= Power8 inline void smt_miso(); // trap instructions @@ -2486,24 +2484,24 @@ class Assembler : public AbstractAssembler { // Deliver A Random Number (introduced with POWER9) inline void darn( Register d, int l = 1 /*L=CRN*/); - // AES (introduced with Power 8) + // AES inline void vcipher( VectorRegister d, VectorRegister a, VectorRegister b); inline void vcipherlast( VectorRegister d, VectorRegister a, VectorRegister b); inline void vncipher( VectorRegister d, VectorRegister a, VectorRegister b); inline void vncipherlast(VectorRegister d, VectorRegister a, VectorRegister b); inline void vsbox( VectorRegister d, VectorRegister a); - // SHA (introduced with Power 8) + // SHA inline void vshasigmad(VectorRegister d, VectorRegister a, bool st, int six); inline void vshasigmaw(VectorRegister d, VectorRegister a, bool st, int six); - // Vector Binary Polynomial Multiplication (introduced with Power 8) + // Vector Binary Polynomial Multiplication inline void vpmsumb( VectorRegister d, VectorRegister a, VectorRegister b); inline void vpmsumd( VectorRegister d, VectorRegister a, VectorRegister b); inline void vpmsumh( VectorRegister d, VectorRegister a, VectorRegister b); inline void vpmsumw( VectorRegister d, VectorRegister a, VectorRegister b); - // Vector Permute and Xor (introduced with Power 8) + // Vector Permute and Xor inline void vpermxor( VectorRegister d, VectorRegister a, VectorRegister b, VectorRegister c); // The following encoders use r0 as second operand. These instructions diff --git a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp index df2ef98de5c..75a2e35d940 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp @@ -468,7 +468,6 @@ inline void Assembler::mtvrsave(Register s1) { emit_int32(MTVRSAVE_OPCODE | inline void Assembler::mfvrsave(Register d ) { emit_int32(MFVRSAVE_OPCODE | rt(d)); } // Timebase inline void Assembler::mftb(Register d ) { emit_int32(MFTB_OPCODE | rt(d)); } -// Introduced with Power 8: // Data Stream Control Register inline void Assembler::mtdscr(Register s1) { emit_int32(MTDSCR_OPCODE | rs(s1)); } inline void Assembler::mfdscr(Register d ) { emit_int32(MFDSCR_OPCODE | rt(d)); } @@ -1098,24 +1097,24 @@ inline void Assembler::mfvscr( VectorRegister d) // Vector Negate Word (introduced with Power 9) inline void Assembler::vnegw( VectorRegister d, VectorRegister b) { emit_int32( VNEGW_OPCODE | vrt(d) | vrb(b)); } -// AES (introduced with Power 8) +// AES inline void Assembler::vcipher( VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VCIPHER_OPCODE | vrt(d) | vra(a) | vrb(b)); } inline void Assembler::vcipherlast( VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VCIPHERLAST_OPCODE | vrt(d) | vra(a) | vrb(b)); } inline void Assembler::vncipher( VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VNCIPHER_OPCODE | vrt(d) | vra(a) | vrb(b)); } inline void Assembler::vncipherlast(VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VNCIPHERLAST_OPCODE | vrt(d) | vra(a) | vrb(b)); } inline void Assembler::vsbox( VectorRegister d, VectorRegister a) { emit_int32( VSBOX_OPCODE | vrt(d) | vra(a) ); } -// SHA (introduced with Power 8) +// SHA inline void Assembler::vshasigmad(VectorRegister d, VectorRegister a, bool st, int six) { emit_int32( VSHASIGMAD_OPCODE | vrt(d) | vra(a) | vst(st) | vsix(six)); } inline void Assembler::vshasigmaw(VectorRegister d, VectorRegister a, bool st, int six) { emit_int32( VSHASIGMAW_OPCODE | vrt(d) | vra(a) | vst(st) | vsix(six)); } -// Vector Binary Polynomial Multiplication (introduced with Power 8) +// Vector Binary Polynomial Multiplication inline void Assembler::vpmsumb( VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VPMSUMB_OPCODE | vrt(d) | vra(a) | vrb(b)); } inline void Assembler::vpmsumd( VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VPMSUMD_OPCODE | vrt(d) | vra(a) | vrb(b)); } inline void Assembler::vpmsumh( VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VPMSUMH_OPCODE | vrt(d) | vra(a) | vrb(b)); } inline void Assembler::vpmsumw( VectorRegister d, VectorRegister a, VectorRegister b) { emit_int32( VPMSUMW_OPCODE | vrt(d) | vra(a) | vrb(b)); } -// Vector Permute and Xor (introduced with Power 8) +// Vector Permute and Xor inline void Assembler::vpermxor( VectorRegister d, VectorRegister a, VectorRegister b, VectorRegister c) { emit_int32( VPERMXOR_OPCODE | vrt(d) | vra(a) | vrb(b) | vrc(c)); } // Deliver A Random Number (introduced with POWER9) diff --git a/src/hotspot/cpu/ppc/globals_ppc.hpp b/src/hotspot/cpu/ppc/globals_ppc.hpp index 49b7eb4cc55..8c2ba8547b6 100644 --- a/src/hotspot/cpu/ppc/globals_ppc.hpp +++ b/src/hotspot/cpu/ppc/globals_ppc.hpp @@ -96,7 +96,7 @@ define_pd_global(bool, ValueTypeReturnedAsFields, false); product(bool, ReoptimizeCallSequences, true, DIAGNOSTIC, \ "Reoptimize code-sequences of calls at runtime.") \ \ - /* Power 8: Configure Data Stream Control Register. */ \ + /* Configure Data Stream Control Register. */ \ product(uint64_t, DSCR_PPC64, (uint64_t)-1, \ "Power8 or later: Specify encoded value for Data Stream Control " \ "Register") \ diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index c42cf60a1a5..e3294587f0d 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -1603,27 +1603,22 @@ void MacroAssembler::getandaddd(Register dest_current_value, Register inc_value, // Word/sub-word atomic helper functions -// Temps and addr_base are killed if size < 4 and processor does not support respective instructions. // Only signed types are supported with size < 4. -// Atomic add always kills tmp1. +// Only atomic add kills tmp. void MacroAssembler::atomic_get_and_modify_generic(Register dest_current_value, Register exchange_value, - Register addr_base, Register tmp1, Register tmp2, Register tmp3, + Register addr_base, Register tmp, bool cmpxchgx_hint, bool is_add, int size) { - // Sub-word instructions are available since Power 8. Label retry; - Register shift_amount = noreg, - val32 = dest_current_value, - modval = is_add ? tmp1 : exchange_value; - + Register modval = is_add ? tmp : exchange_value; // atomic emulation loop bind(retry); switch (size) { - case 4: lwarx(val32, addr_base, cmpxchgx_hint); break; - case 2: lharx(val32, addr_base, cmpxchgx_hint); break; - case 1: lbarx(val32, addr_base, cmpxchgx_hint); break; + case 4: lwarx(dest_current_value, addr_base, cmpxchgx_hint); break; + case 2: lharx(dest_current_value, addr_base, cmpxchgx_hint); break; + case 1: lbarx(dest_current_value, addr_base, cmpxchgx_hint); break; default: ShouldNotReachHere(); } @@ -1651,23 +1646,18 @@ void MacroAssembler::atomic_get_and_modify_generic(Register dest_current_value, }; } -// Temps, addr_base and exchange_value are killed if size < 4 and processor does not support respective instructions. // Only signed types are supported with size < 4. void MacroAssembler::cmpxchg_loop_body(ConditionRegister flag, Register dest_current_value, RegisterOrConstant compare_value, Register exchange_value, Register addr_base, Label &retry, Label &failed, bool cmpxchgx_hint, int size) { - // Sub-word instructions are available since Power 8. - Register shift_amount = noreg, - val32 = dest_current_value, - modval = exchange_value; // atomic emulation loop bind(retry); switch (size) { - case 4: lwarx(val32, addr_base, cmpxchgx_hint); break; - case 2: lharx(val32, addr_base, cmpxchgx_hint); break; - case 1: lbarx(val32, addr_base, cmpxchgx_hint); break; + case 4: lwarx(dest_current_value, addr_base, cmpxchgx_hint); break; + case 2: lharx(dest_current_value, addr_base, cmpxchgx_hint); break; + case 1: lbarx(dest_current_value, addr_base, cmpxchgx_hint); break; default: ShouldNotReachHere(); } @@ -1687,9 +1677,9 @@ void MacroAssembler::cmpxchg_loop_body(ConditionRegister flag, Register dest_cur // fall through => (flag == eq), (dest_current_value == compare_value) switch (size) { - case 4: stwcx_(modval, addr_base); break; - case 2: sthcx_(modval, addr_base); break; - case 1: stbcx_(modval, addr_base); break; + case 4: stwcx_(exchange_value, addr_base); break; + case 2: sthcx_(exchange_value, addr_base); break; + case 1: stbcx_(exchange_value, addr_base); break; default: ShouldNotReachHere(); } } diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp index 5a65f91f1ff..d336ef81dd7 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp @@ -509,7 +509,7 @@ class MacroAssembler: public Assembler { private: // Helper functions for word/sub-word atomics. void atomic_get_and_modify_generic(Register dest_current_value, Register exchange_value, - Register addr_base, Register tmp1, Register tmp2, Register tmp3, + Register addr_base, Register tmp, bool cmpxchgx_hint, bool is_add, int size); void cmpxchg_loop_body(ConditionRegister flag, Register dest_current_value, RegisterOrConstant compare_value, Register exchange_value, @@ -519,43 +519,34 @@ class MacroAssembler: public Assembler { Register addr_base, int semantics, bool cmpxchgx_hint, Register int_flag_success, Label* failed_ext, bool contention_hint, bool weak, int size); public: - // Temps and addr_base are killed if processor does not support Power 8 instructions. // Result will be sign extended. - void getandsetb(Register dest_current_value, Register exchange_value, Register addr_base, - Register tmp1, Register tmp2, Register tmp3, bool cmpxchgx_hint) { - atomic_get_and_modify_generic(dest_current_value, exchange_value, addr_base, tmp1, tmp2, tmp3, cmpxchgx_hint, false, 1); + void getandsetb(Register dest_current_value, Register exchange_value, Register addr_base, bool cmpxchgx_hint) { + atomic_get_and_modify_generic(dest_current_value, exchange_value, addr_base, noreg, cmpxchgx_hint, false, 1); } - // Temps and addr_base are killed if processor does not support Power 8 instructions. // Result will be sign extended. - void getandseth(Register dest_current_value, Register exchange_value, Register addr_base, - Register tmp1, Register tmp2, Register tmp3, bool cmpxchgx_hint) { - atomic_get_and_modify_generic(dest_current_value, exchange_value, addr_base, tmp1, tmp2, tmp3, cmpxchgx_hint, false, 2); + void getandseth(Register dest_current_value, Register exchange_value, Register addr_base, bool cmpxchgx_hint) { + atomic_get_and_modify_generic(dest_current_value, exchange_value, addr_base, noreg, cmpxchgx_hint, false, 2); } - void getandsetw(Register dest_current_value, Register exchange_value, Register addr_base, - bool cmpxchgx_hint) { - atomic_get_and_modify_generic(dest_current_value, exchange_value, addr_base, noreg, noreg, noreg, cmpxchgx_hint, false, 4); + void getandsetw(Register dest_current_value, Register exchange_value, Register addr_base, bool cmpxchgx_hint) { + atomic_get_and_modify_generic(dest_current_value, exchange_value, addr_base, noreg, cmpxchgx_hint, false, 4); } - void getandsetd(Register dest_current_value, Register exchange_value, Register addr_base, - bool cmpxchgx_hint); - // tmp2/3 and addr_base are killed if processor does not support Power 8 instructions (tmp1 is always needed). + void getandsetd(Register dest_current_value, Register exchange_value, Register addr_base, bool cmpxchgx_hint); // Result will be sign extended. void getandaddb(Register dest_current_value, Register inc_value, Register addr_base, - Register tmp1, Register tmp2, Register tmp3, bool cmpxchgx_hint) { - atomic_get_and_modify_generic(dest_current_value, inc_value, addr_base, tmp1, tmp2, tmp3, cmpxchgx_hint, true, 1); + Register tmp, bool cmpxchgx_hint) { + atomic_get_and_modify_generic(dest_current_value, inc_value, addr_base, tmp, cmpxchgx_hint, true, 1); } - // tmp2/3 and addr_base are killed if processor does not support Power 8 instructions (tmp1 is always needed). // Result will be sign extended. void getandaddh(Register dest_current_value, Register inc_value, Register addr_base, - Register tmp1, Register tmp2, Register tmp3, bool cmpxchgx_hint) { - atomic_get_and_modify_generic(dest_current_value, inc_value, addr_base, tmp1, tmp2, tmp3, cmpxchgx_hint, true, 2); + Register tmp, bool cmpxchgx_hint) { + atomic_get_and_modify_generic(dest_current_value, inc_value, addr_base, tmp, cmpxchgx_hint, true, 2); } void getandaddw(Register dest_current_value, Register inc_value, Register addr_base, - Register tmp1, bool cmpxchgx_hint) { - atomic_get_and_modify_generic(dest_current_value, inc_value, addr_base, tmp1, noreg, noreg, cmpxchgx_hint, true, 4); + Register tmp, bool cmpxchgx_hint) { + atomic_get_and_modify_generic(dest_current_value, inc_value, addr_base, tmp, cmpxchgx_hint, true, 4); } void getandaddd(Register dest_current_value, Register exchange_value, Register addr_base, Register tmp, bool cmpxchgx_hint); - // Temps, addr_base and exchange_value are killed if processor does not support Power 8 instructions. // compare_value must be at least 32 bit sign extended. Result will be sign extended. void cmpxchgb(ConditionRegister flag, Register dest_current_value, RegisterOrConstant compare_value, Register exchange_value, @@ -565,7 +556,6 @@ class MacroAssembler: public Assembler { cmpxchg_generic(flag, dest_current_value, compare_value, exchange_value, addr_base, semantics, cmpxchgx_hint, int_flag_success, failed, contention_hint, weak, 1); } - // Temps, addr_base and exchange_value are killed if processor does not support Power 8 instructions. // compare_value must be at least 32 bit sign extended. Result will be sign extended. void cmpxchgh(ConditionRegister flag, Register dest_current_value, RegisterOrConstant compare_value, Register exchange_value, diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index a8baa686e9b..1126d888461 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -7384,7 +7384,7 @@ instruct getAndAddB(iRegIdst res, iRegPdst mem_ptr, iRegIorL2I src, flagsRegCR0 format %{ "GetAndAddB $res, $mem_ptr, $src" %} ins_encode %{ __ getandaddb($res$$Register, $src$$Register, $mem_ptr$$Register, - R0, noreg, noreg, MacroAssembler::cmpxchgx_hint_atomic_update()); + R0, MacroAssembler::cmpxchgx_hint_atomic_update()); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7400,7 +7400,7 @@ instruct getAndAddS(iRegIdst res, iRegPdst mem_ptr, iRegIorL2I src, flagsRegCR0 format %{ "GetAndAddS $res, $mem_ptr, $src" %} ins_encode %{ __ getandaddh($res$$Register, $src$$Register, $mem_ptr$$Register, - R0, noreg, noreg, MacroAssembler::cmpxchgx_hint_atomic_update()); + R0, MacroAssembler::cmpxchgx_hint_atomic_update()); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7449,7 +7449,7 @@ instruct getAndSetB(iRegIdst res, iRegPdst mem_ptr, iRegIorL2I src, flagsRegCR0 format %{ "GetAndSetB $res, $mem_ptr, $src" %} ins_encode %{ __ getandsetb($res$$Register, $src$$Register, $mem_ptr$$Register, - noreg, noreg, noreg, MacroAssembler::cmpxchgx_hint_atomic_update()); + MacroAssembler::cmpxchgx_hint_atomic_update()); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -7465,7 +7465,7 @@ instruct getAndSetS(iRegIdst res, iRegPdst mem_ptr, iRegIorL2I src, flagsRegCR0 format %{ "GetAndSetS $res, $mem_ptr, $src" %} ins_encode %{ __ getandseth($res$$Register, $src$$Register, $mem_ptr$$Register, - noreg, noreg, noreg, MacroAssembler::cmpxchgx_hint_atomic_update()); + MacroAssembler::cmpxchgx_hint_atomic_update()); if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ isync(); } else { @@ -9737,7 +9737,7 @@ instruct convD2IRaw_regD(regD dst, regD src) %{ ins_pipe(pipe_class_default); %} -// Double to Int conversion, NaN is mapped to 0. Special version for Power8. +// Double to Int conversion, NaN is mapped to 0. instruct convD2I_reg_mffprd_ExEx(iRegIdst dst, regD src) %{ match(Set dst (ConvD2I src)); ins_cost(DEFAULT_COST); @@ -9765,7 +9765,7 @@ instruct convF2IRaw_regF(regF dst, regF src) %{ %} -// Float to Int conversion, NaN is mapped to 0. Special version for Power8. +// Float to Int conversion, NaN is mapped to 0. instruct convF2I_regF_mffprd_ExEx(iRegIdst dst, regF src) %{ match(Set dst (ConvF2I src)); ins_cost(DEFAULT_COST); @@ -9858,7 +9858,7 @@ instruct convF2LRaw_regF(regF dst, regF src) %{ ins_pipe(pipe_class_default); %} -// Float to Long conversion, NaN is mapped to 0. Special version for Power8. +// Float to Long conversion, NaN is mapped to 0. instruct convF2L_reg_mffprd_ExEx(iRegLdst dst, regF src) %{ match(Set dst (ConvF2L src)); ins_cost(DEFAULT_COST); @@ -9885,7 +9885,7 @@ instruct convD2LRaw_regD(regD dst, regD src) %{ ins_pipe(pipe_class_default); %} -// Double to Long conversion, NaN is mapped to 0. Special version for Power8. +// Double to Long conversion, NaN is mapped to 0. instruct convD2L_reg_mffprd_ExEx(iRegLdst dst, regD src) %{ match(Set dst (ConvD2L src)); ins_cost(DEFAULT_COST); @@ -9940,7 +9940,7 @@ instruct convL2FRaw_regF(regF dst, regD src) %{ %} -// Integer to Float conversion. Special version for Power8. +// Integer to Float conversion. instruct convI2F_ireg_mtfprd_Ex(regF dst, iRegIorL2I src) %{ match(Set dst (ConvI2F src)); ins_cost(DEFAULT_COST); @@ -9953,7 +9953,7 @@ instruct convI2F_ireg_mtfprd_Ex(regF dst, iRegIorL2I src) %{ %} -// L2F to avoid runtime call. Special version for Power8. +// L2F to avoid runtime call. instruct convL2F_ireg_mtfprd_Ex(regF dst, iRegLsrc src) %{ match(Set dst (ConvL2F src)); ins_cost(DEFAULT_COST); @@ -9971,7 +9971,7 @@ instruct convL2F_ireg_mtfprd_Ex(regF dst, iRegLsrc src) %{ // Convert to Double -// Integer to Double conversion. Special version for Power8. +// Integer to Double conversion. instruct convI2D_reg_mtfprd_Ex(regD dst, iRegIorL2I src) %{ match(Set dst (ConvI2D src)); ins_cost(DEFAULT_COST); @@ -9984,7 +9984,7 @@ instruct convI2D_reg_mtfprd_Ex(regD dst, iRegIorL2I src) %{ %} -// Long to Double conversion. Special version for Power8. +// Long to Double conversion. instruct convL2D_reg_mtfprd_Ex(regD dst, iRegLsrc src) %{ match(Set dst (ConvL2D src)); ins_cost(DEFAULT_COST); diff --git a/src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp b/src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp index 3b7ee66348a..cfc90a78136 100644 --- a/src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2019 SAP SE. All rights reserved. + * Copyright (c) 2002, 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 @@ -93,7 +93,7 @@ address StubRoutines::crc32c_table_addr() { // Constants to fold n words as needed by macroAssembler. address StubRoutines::ppc::generate_crc_constants(juint reverse_poly) { // Layout of constant table: - // >= Power8: 1 table for single byte folding + constants for fast vector implementation + // 1 table for single byte folding + constants for fast vector implementation const int vector_size = 16 * (CRC32_UNROLL_FACTOR2 + CRC32_UNROLL_FACTOR / CRC32_UNROLL_FACTOR2); const int size = CRC32_TABLE_SIZE + vector_size; @@ -108,7 +108,7 @@ address StubRoutines::ppc::generate_crc_constants(juint reverse_poly) { ptr[i] = fold_byte(i, reverse_poly); } - // >= Power8: vector constants + // vector constants juint* ptr1 = (juint*)(consts + CRC32_TABLE_SIZE); guarantee(((intptr_t)ptr1 & 0xF) == 0, "16-byte alignment needed"); diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.cpp b/src/hotspot/cpu/ppc/vm_version_ppc.cpp index a42e9492c13..fb6df36ccac 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.cpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.cpp @@ -80,7 +80,7 @@ void VM_Version::initialize() { guarantee(PowerArchitecturePPC64_ok, "PowerArchitecturePPC64 cannot be set to " "%zu on this machine", PowerArchitecturePPC64); - // Power 8: Configure Data Stream Control Register. + // Configure Data Stream Control Register. if (VM_Version::has_mfdscr()) { config_dscr(); } @@ -233,20 +233,12 @@ void VM_Version::initialize() { ContendedPaddingWidth = cache_line_size; } - // If running on Power8 or newer hardware, the implementation uses the available vector instructions. - // In all other cases, the implementation uses only generally available instructions. - if (!UseCRC32Intrinsics) { - if (FLAG_IS_DEFAULT(UseCRC32Intrinsics)) { - FLAG_SET_DEFAULT(UseCRC32Intrinsics, true); - } + if (FLAG_IS_DEFAULT(UseCRC32Intrinsics)) { + FLAG_SET_DEFAULT(UseCRC32Intrinsics, true); } - // Implementation does not use any of the vector instructions available with Power8. - // Their exploitation is still pending (aka "work in progress"). - if (!UseCRC32CIntrinsics) { - if (FLAG_IS_DEFAULT(UseCRC32CIntrinsics)) { - FLAG_SET_DEFAULT(UseCRC32CIntrinsics, true); - } + if (FLAG_IS_DEFAULT(UseCRC32CIntrinsics)) { + FLAG_SET_DEFAULT(UseCRC32CIntrinsics, true); } if (FLAG_IS_DEFAULT(UseAdler32Intrinsics)) { @@ -539,7 +531,7 @@ void VM_Version::determine_features() { "processors with smaller cache line size are no longer supported"); } -// Power 8: Configure Data Stream Control Register. +// Configure Data Stream Control Register. void VM_Version::config_dscr() { // 7 InstWords for each call (function descriptor + blr instruction). const int code_size = (2+2*7)*BytesPerInstWord; diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.hpp b/src/hotspot/cpu/ppc/vm_version_ppc.hpp index 4f3b6d37911..d0314c47d8b 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.hpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.hpp @@ -49,7 +49,7 @@ class VM_Version: public Abstract_VM_Version { static void print_features(); static void determine_features(); // also measures cache line size - static void config_dscr(); // Power 8: Configure Data Stream Control Register. + static void config_dscr(); // Configure Data Stream Control Register. public: // Initialization From cfb8c9cf0a6c21dc648a880d0dd7dbbfc0009678 Mon Sep 17 00:00:00 2001 From: Kelvin Nilsen Date: Wed, 14 Oct 2026 18:45:12 +0000 Subject: [PATCH 095/101] 8391086: Shenandoah: Remembered set scan needs to balance queues better Reviewed-by: shade, wkemper --- .../gc/shenandoah/shenandoahGeneration.cpp | 2 +- .../shenandoah/shenandoahGenerationalHeap.cpp | 4 +- .../gc/shenandoah/shenandoahHeapRegion.cpp | 1 - .../shenandoah/shenandoahScanRemembered.cpp | 219 +----------------- .../shenandoah/shenandoahScanRemembered.hpp | 136 +++-------- .../shenandoahScanRemembered.inline.hpp | 93 ++++---- .../generational/TestRemSetBalance.java | 186 +++++++++++++++ 7 files changed, 270 insertions(+), 371 deletions(-) create mode 100644 test/hotspot/jtreg/gc/shenandoah/generational/TestRemSetBalance.java diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index f171e2984a1..fb99825bef9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -400,7 +400,7 @@ void ShenandoahGeneration::scan_remembered_set(bool is_concurrent) { uint nworkers = heap->workers()->active_workers(); ShenandoahReferenceProcessor* rp = ref_processor(); - ShenandoahRegionChunkIterator work_list(nworkers); + ShenandoahRegionChunkIterator work_list(heap); ShenandoahScanRememberedTask task(task_queues(), old_gen_task_queues(), rp, &work_list, is_concurrent); heap->assert_gc_workers(nworkers); heap->workers()->run_task(&task); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 703c04f6a7c..c640fc01046 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -893,8 +893,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { void ShenandoahGenerationalHeap::update_heap_references(ShenandoahGeneration* generation, bool concurrent) { assert(!is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); - const uint nworkers = workers()->active_workers(); - ShenandoahRegionChunkIterator work_list(nworkers); + ShenandoahRegionChunkIterator work_list(this); if (concurrent) { ShenandoahGenerationalUpdateHeapRefsTask task(generation, &_update_refs_iterator, &work_list); workers()->run_task(&task); @@ -905,6 +904,7 @@ void ShenandoahGenerationalHeap::update_heap_references(ShenandoahGeneration* ge if (ShenandoahEnableCardStats) { // Only do this if we are collecting card stats + const uint nworkers = workers()->active_workers(); ShenandoahScanRemembered* card_scan = old_generation()->card_scan(); assert(card_scan != nullptr, "Card table must exist when card stats are enabled"); card_scan->log_card_stats(nworkers, CARD_STAT_UPDATE_REFS); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 84582ce07c3..3aac7f21efa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -518,7 +518,6 @@ size_t get_card_count(size_t words) { void ShenandoahHeapRegion::oop_iterate_humongous_slice_dirty(OopIterateClosure* blk, HeapWord* start, size_t words, bool write_table) const { assert(is_humongous(), "only humongous region here"); - ShenandoahHeapRegion* r = humongous_start_region(); oop obj = cast_to_oop(r->bottom()); size_t num_cards = get_card_count(words); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index 57899154ff7..0affa3e4617 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -30,6 +30,7 @@ #include "gc/shenandoah/shenandoahScanRemembered.inline.hpp" #include "logging/log.hpp" #include "runtime/threads.hpp" +#include "utilities/powerOfTwo.hpp" // A closure that takes an oop in the old generation and, if it's pointing // into the young generation, dirties the corresponding remembered set entry. @@ -572,7 +573,7 @@ bool ShenandoahScanRemembered::verify_registration(HeapWord* address, Shenandoah } } else { // This is a mixed evacuation or a global collect: rely on mark bits to identify which objects need to be properly registered - assert(!ShenandoahHeap::heap()->is_concurrent_old_mark_in_progress(), "Cannot rely on mark context here."); + assert(!heap->is_concurrent_old_mark_in_progress(), "Cannot rely on mark context here."); // If the object reaching or spanning the end of this card's memory is marked, then last_offset for this card // should represent this object. Otherwise, last_offset is a don't care. ShenandoahHeapRegion* region = heap->heap_region_containing(base_addr + offset); @@ -844,221 +845,17 @@ void ShenandoahScanRememberedTask::do_work(uint worker_id) { } } -size_t ShenandoahRegionChunkIterator::calc_regular_group_size() { - // The group size is calculated from the number of regions. Suppose the heap has N regions. The first group processes - // N/2 regions. The second group processes N/4 regions, the third group N/8 regions and so on. - // Note that infinite series N/2 + N/4 + N/8 + N/16 + ... sums to N. - // - // The normal group size is the number of regions / 2. - // - // In the case that the region_size_words is greater than _maximum_chunk_size_words, the first group_size is - // larger than the normal group size because each chunk in the group will be smaller than the region size. - // - // The last group also has more than the normal entries because it finishes the total scanning effort. The chunk sizes are - // different for each group. The intention is that the first group processes roughly half of the heap, the second processes - // half of the remaining heap, the third processes half of what remains and so on. The smallest chunk size - // is represented by _smallest_chunk_size_words. We do not divide work any smaller than this. - // - size_t group_size = _heap->num_regions() / 2; - return group_size; -} - -size_t ShenandoahRegionChunkIterator::calc_first_group_chunk_size_b4_rebalance() { - size_t words_in_first_chunk = ShenandoahHeapRegion::region_size_words(); - return words_in_first_chunk; -} - -size_t ShenandoahRegionChunkIterator::calc_num_groups() { - size_t total_heap_size = _heap->num_regions() * ShenandoahHeapRegion::region_size_words(); - size_t num_groups = 0; - size_t cumulative_group_span = 0; - size_t current_group_span = _first_group_chunk_size_b4_rebalance * _regular_group_size; - size_t smallest_group_span = smallest_chunk_size_words() * _regular_group_size; - while ((num_groups < _maximum_groups) && (cumulative_group_span + current_group_span <= total_heap_size)) { - num_groups++; - cumulative_group_span += current_group_span; - if (current_group_span <= smallest_group_span) { - break; - } else { - current_group_span /= 2; // Each group spans half of what the preceding group spanned. - } - } - // Loop post condition: - // num_groups <= _maximum_groups - // cumulative_group_span is the memory spanned by num_groups - // current_group_span is the span of the last fully populated group (assuming loop iterates at least once) - // each of num_groups is fully populated with _regular_group_size chunks in each - // Non post conditions: - // cumulative_group_span may be less than total_heap size for one or more of the folowing reasons - // a) The number of regions remaining to be spanned is smaller than a complete group, or - // b) We have filled up all groups through _maximum_groups and still have not spanned all regions - - if (cumulative_group_span < total_heap_size) { - // We've got more regions to span - if ((num_groups < _maximum_groups) && (current_group_span > smallest_group_span)) { - num_groups++; // Place all remaining regions into a new not-full group (chunk_size half that of previous group) - } - // Else we are unable to create a new group because we've exceed the number of allowed groups or have reached the - // minimum chunk size. - - // Any remaining regions will be treated as if they are part of the most recently created group. This group will - // have more than _regular_group_size chunks within it. - } - assert (num_groups <= _maximum_groups, "Cannot have more than %zu groups", _maximum_groups); - return num_groups; -} - size_t ShenandoahRegionChunkIterator::calc_total_chunks() { - size_t region_size_words = ShenandoahHeapRegion::region_size_words(); - size_t unspanned_heap_size = _heap->num_regions() * region_size_words; - size_t num_chunks = 0; - size_t cumulative_group_span = 0; - size_t current_group_span = _first_group_chunk_size_b4_rebalance * _regular_group_size; - size_t smallest_group_span = smallest_chunk_size_words() * _regular_group_size; - - // The first group gets special handling because the first chunk size can be no larger than _maximum_chunk_size_words - if (region_size_words > _maximum_chunk_size_words) { - // In the case that we shrink the first group's chunk size, certain other groups will also be subsumed within the first group - size_t effective_chunk_size = _first_group_chunk_size_b4_rebalance; - uint coalesced_groups = 0; - while (effective_chunk_size >= _maximum_chunk_size_words) { - // Each iteration of this loop subsumes one original group into a new rebalanced initial group. - num_chunks += current_group_span / _maximum_chunk_size_words; - unspanned_heap_size -= current_group_span; - effective_chunk_size /= 2; - current_group_span /= 2; - coalesced_groups++; - } - assert(effective_chunk_size * 2 == _maximum_chunk_size_words, - "We assume _first_group_chunk_size_b4_rebalance is _maximum_chunk_size_words * a power of two"); - _largest_chunk_size_words = _maximum_chunk_size_words; - _adjusted_num_groups = _num_groups - (coalesced_groups - 1); - } else { - num_chunks = _regular_group_size; - unspanned_heap_size -= current_group_span; - _largest_chunk_size_words = current_group_span / num_chunks; - _adjusted_num_groups = _num_groups; - current_group_span /= 2; - } - - size_t spanned_groups = 1; - while (unspanned_heap_size > 0) { - if (current_group_span <= unspanned_heap_size) { - unspanned_heap_size -= current_group_span; - num_chunks += _regular_group_size; - spanned_groups++; - - // _num_groups is the number of groups required to span the configured heap size. We are not allowed - // to change the number of groups. The last group is responsible for spanning all chunks not spanned - // by previously processed groups. - if (spanned_groups >= _num_groups) { - // The last group has more than _regular_group_size entries. - size_t chunk_span = current_group_span / _regular_group_size; - size_t extra_chunks = unspanned_heap_size / chunk_span; - assert (extra_chunks * chunk_span == unspanned_heap_size, "Chunks must precisely span regions"); - num_chunks += extra_chunks; - return num_chunks; - } else if (current_group_span <= smallest_group_span) { - // We cannot introduce new groups because we've reached the lower bound on group size. So this last - // group may hold extra chunks. - size_t chunk_span = smallest_chunk_size_words(); - size_t extra_chunks = unspanned_heap_size / chunk_span; - assert (extra_chunks * chunk_span == unspanned_heap_size, "Chunks must precisely span regions"); - num_chunks += extra_chunks; - return num_chunks; - } else { - current_group_span /= 2; - } - } else { - // This last group has fewer than _regular_group_size entries. - size_t chunk_span = current_group_span / _regular_group_size; - size_t last_group_size = unspanned_heap_size / chunk_span; - assert (last_group_size * chunk_span == unspanned_heap_size, "Chunks must precisely span regions"); - num_chunks += last_group_size; - return num_chunks; - } - } - return num_chunks; -} - -ShenandoahRegionChunkIterator::ShenandoahRegionChunkIterator(size_t worker_count) : - ShenandoahRegionChunkIterator(ShenandoahHeap::heap(), worker_count) -{ + return (_heap->num_regions() * ShenandoahHeapRegion::region_size_words()) / _chunk_size; } -ShenandoahRegionChunkIterator::ShenandoahRegionChunkIterator(ShenandoahHeap* heap, size_t worker_count) : +// Configure with a single group that spans the entire heap with equal-sized chunks of work. +ShenandoahRegionChunkIterator::ShenandoahRegionChunkIterator(ShenandoahHeap* heap) : _heap(heap), - _regular_group_size(calc_regular_group_size()), - _first_group_chunk_size_b4_rebalance(calc_first_group_chunk_size_b4_rebalance()), - _num_groups(calc_num_groups()), + _chunk_size(chunk_size_words()), + _chunk_shift(log2i_exact(_chunk_size)), _total_chunks(calc_total_chunks()), - _index(0) -{ -#ifdef ASSERT - size_t expected_chunk_size_words = _clusters_in_smallest_chunk * CardTable::card_size_in_words() * ShenandoahCardCluster::CardsPerCluster; - assert(smallest_chunk_size_words() == expected_chunk_size_words, "_smallest_chunk_size (%zu) is not valid because it does not equal (%zu)", - smallest_chunk_size_words(), expected_chunk_size_words); - assert(_num_groups <= _maximum_groups, - "The number of remembered set scanning groups must be less than or equal to maximum groups"); - assert(smallest_chunk_size_words() << (_adjusted_num_groups - 1) == _largest_chunk_size_words, - "The number of groups (%zu) needs to span smallest chunk size (%zu) to largest chunk size (%zu)", - _adjusted_num_groups, smallest_chunk_size_words(), _largest_chunk_size_words); -#endif - - size_t words_in_region = ShenandoahHeapRegion::region_size_words(); - _region_index[0] = 0; - _group_offset[0] = 0; - if (words_in_region > _maximum_chunk_size_words) { - // In the case that we shrink the first group's chunk size, certain other groups will also be subsumed within the first group - size_t num_chunks = 0; - size_t effective_chunk_size = _first_group_chunk_size_b4_rebalance; - size_t current_group_span = effective_chunk_size * _regular_group_size; - while (effective_chunk_size >= _maximum_chunk_size_words) { - num_chunks += current_group_span / _maximum_chunk_size_words; - effective_chunk_size /= 2; - current_group_span /= 2; - } - _group_entries[0] = num_chunks; - _group_chunk_size[0] = _maximum_chunk_size_words; - } else { - _group_entries[0] = _regular_group_size; - _group_chunk_size[0] = _first_group_chunk_size_b4_rebalance; - } - - size_t previous_group_span = _group_entries[0] * _group_chunk_size[0]; - for (size_t i = 1; i < _adjusted_num_groups; i++) { - _group_chunk_size[i] = _group_chunk_size[i-1] / 2; - size_t chunks_in_group = _regular_group_size; - size_t this_group_span = _group_chunk_size[i] * chunks_in_group; - size_t total_span_of_groups = previous_group_span + this_group_span; - _region_index[i] = previous_group_span / words_in_region; - _group_offset[i] = previous_group_span % words_in_region; - _group_entries[i] = _group_entries[i-1] + _regular_group_size; - previous_group_span = total_span_of_groups; - } - if (_group_entries[_adjusted_num_groups-1] < _total_chunks) { - assert((_total_chunks - _group_entries[_adjusted_num_groups-1]) * _group_chunk_size[_adjusted_num_groups-1] + previous_group_span == - heap->num_regions() * words_in_region, "Total region chunks (%zu" - ") do not span total heap regions (%zu)", _total_chunks, _heap->num_regions()); - previous_group_span += (_total_chunks - _group_entries[_adjusted_num_groups-1]) * _group_chunk_size[_adjusted_num_groups-1]; - _group_entries[_adjusted_num_groups-1] = _total_chunks; - } - assert(previous_group_span == heap->num_regions() * words_in_region, "Total region chunks (%zu" - ") do not span total heap regions (%zu): %zu does not equal %zu", - _total_chunks, _heap->num_regions(), previous_group_span, heap->num_regions() * words_in_region); - - // Not necessary, but keeps things tidy - for (size_t i = _adjusted_num_groups; i < _maximum_groups; i++) { - _region_index[i] = 0; - _group_offset[i] = 0; - _group_entries[i] = _group_entries[i-1]; - _group_chunk_size[i] = 0; - } -} - -void ShenandoahRegionChunkIterator::reset() { - _index.store_relaxed(0); -} + _index(0) {} ShenandoahReconstructRememberedSetTask::ShenandoahReconstructRememberedSetTask(ShenandoahRegionIterator* regions) : WorkerTask("Shenandoah Reset Bitmap") diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp index e5ed4c056a2..59e8db07f8e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp @@ -134,52 +134,25 @@ // be responsible for processing the portion of the object // in this cluster. // -// Though an initial division of labor between marking threads may -// assign equal numbers of clusters to be scanned by each thread, it -// should be expected that some threads will finish their assigned -// work before others. Therefore, some amount of the full remembered -// set scanning effort should be held back and assigned incrementally -// to the threads that end up with excess capacity. Consider the -// following strategy for dividing labor: -// -// 1. Assume there are 8 marking threads and 1024 remembered -// set clusters to be scanned. -// 2. Assign each thread to scan 64 clusters. This leaves -// 512 (1024 - (8*64)) clusters to still be scanned. -// 3. As the 8 server threads complete previous cluster -// scanning assignments, issue each of the next 8 scanning -// assignments as units of 32 additional cluster each. -// In the case that there is high variance in effort -// associated with previous cluster scanning assignments, -// multiples of these next assignments may be serviced by -// the server threads that were previously assigned lighter -// workloads. -// 4. Make subsequent scanning assignments as follows: -// a) 8 assignments of size 16 clusters -// b) 8 assignments of size 8 clusters -// c) 16 assignments of size 4 clusters -// -// When there is no more remembered set processing work to be -// assigned to a newly idled worker thread, that thread can move -// on to work on other tasks associated with root scanning until such -// time as all clusters have been examined. +// Each work assignment represents the same number of spanned bytes. +// Because some spans are easier to scan than others, it is expected +// that some workers will process more work assignments than others. // // Remembered set scanning is designed to run concurrently with // mutator threads, with multiple concurrent workers. Furthermore, the // current implementation of remembered set scanning never clears a // card once it has been marked. -// -// These limitations will be addressed in future enhancements to the -// existing implementation. #include "gc/shared/gc_globals.hpp" #include "gc/shared/workerThread.hpp" #include "gc/shenandoah/shenandoahCardStats.hpp" #include "gc/shenandoah/shenandoahCardTable.hpp" +#include "gc/shenandoah/shenandoahHeapRegion.hpp" #include "gc/shenandoah/shenandoahNumberSeq.hpp" #include "gc/shenandoah/shenandoahTaskqueue.hpp" #include "memory/iterator.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/powerOfTwo.hpp" class ShenandoahReferenceProcessor; class ShenandoahConcurrentMark; @@ -919,105 +892,48 @@ struct ShenandoahRegionChunk { // that are assigned one at a time to worker threads. (Here, we use the terms `assignments` and `chunks` // interchangeably.) Note that the effort required to scan a range of memory is not necessarily a linear // function of the size of the range. Some memory ranges hold only a small number of live objects. -// Some ranges hold primarily primitive (non-pointer) data. We start with larger chunk sizes because larger chunks -// reduce coordination overhead. We expect that the GC worker threads that receive more difficult assignments -// will work longer on those chunks. Meanwhile, other worker threads will repeatedly accept and complete multiple -// easier chunks. As the total amount of work remaining to be completed decreases, we decrease the size of chunks -// given to individual threads. This reduces the likelihood of significant imbalance between worker thread assignments -// when there is less meaningful work to be performed by the remaining worker threads while they wait for -// worker threads with difficult assignments to finish, reducing the overall duration of the phase. - +// Some ranges hold primarily primitive (non-pointer) data. class ShenandoahRegionChunkIterator : public StackObj { private: - // The largest chunk size is 4 MiB, measured in words. Otherwise, remembered set scanning may become too unbalanced. - // If the largest chunk size is too small, there is too much overhead sifting out assignments to individual worker threads. - static const size_t _maximum_chunk_size_words = (4 * 1024 * 1024) / HeapWordSize; - static const size_t _clusters_in_smallest_chunk = 4; - - size_t _largest_chunk_size_words; - - // smallest_chunk_size is 4 clusters. Each cluster spans 128 KiB. - // This is computed from CardTable::card_size_in_words() * ShenandoahCardCluster::CardsPerCluster; - static size_t smallest_chunk_size_words() { - return _clusters_in_smallest_chunk * CardTable::card_size_in_words() * ShenandoahCardCluster::CardsPerCluster; + // The number of clusters in a chunk is chosen empirically: larger values would require less synchronization at the risk + // of less even distribution of work between cooperating worker threads. The value must be a power of two. + static const size_t _clusters_in_chunk = 8; + static size_t chunk_size_words() { + size_t max_size = ShenandoahHeapRegion::region_size_words(); + // In default configuration, the standard work assignment is: + // 8 (clusters) * 64 (words/card) * 64 (cards/Cluster) = 32K words = 256K bytes. + size_t planned_size = _clusters_in_chunk * CardTable::card_size_in_words() * ShenandoahCardCluster::CardsPerCluster; + static_assert(is_power_of_2(_clusters_in_chunk), "Precondition"); + if (planned_size > max_size) { + planned_size = max_size; + } + assert(is_power_of_2(planned_size), "Invariant"); + return planned_size; } - // The total remembered set scanning effort is divided into chunks of work that are assigned to individual worker tasks. - // The chunks of assigned work are divided into groups, where the size of the typical group (_regular_group_size) is half the - // total number of regions. The first group may be larger than - // _regular_group_size in the case that the first group's chunk - // size is less than the region size. The last group may be larger - // than _regular_group_size because no group is allowed to - // have smaller assignments than _smallest_chunk_size, which is 128 KB. - - // Under normal circumstances, no configuration needs more than _maximum_groups (default value of 16). - // The first group "effectively" processes chunks of size 1 MiB (or smaller for smaller region sizes). - // The last group processes chunks of size 128 KiB. There are four groups total. - - // group[ 0] is 4 MiB chunk size (_maximum_chunk_size_words) - // group[ 1] is 2 MiB chunk size - // group[ 2] is 1 MiB chunk size - // group[ 3] is 512 KiB chunk size - // group[ 4] is 256 KiB chunk size - // group[ 5] is 128 KiB chunk size - // group[ 6] is 64 KiB chunk size - // group[ 7] is 32 KiB chunk size - // group[ 8] is 16 KiB chunk size - // group[ 9] is 8 KiB chunk size - // group[10] is 4 KiB chunk size - // Note: 4 KiB is smallest possible chunk_size, computed from: - // _clusters_in_smallest_chunk * MinimumCardSizeInWords * ShenandoahCardCluster::CardsPerCluster, which is - // 4 * 16 * 64 = 4096 - - // We set aside arrays to represent the maximum number of groups that may be required for any heap configuration - static const size_t _maximum_groups = 11; - const ShenandoahHeap* _heap; - const size_t _regular_group_size; // Number of chunks in each group - const size_t _first_group_chunk_size_b4_rebalance; - const size_t _num_groups; // Number of groups in this configuration - size_t _adjusted_num_groups; // Rebalancing may coalesce groups + const size_t _chunk_size; + const size_t _chunk_shift; + // Total chunks is HeapSizeWords / chunk_size_words() const size_t _total_chunks; shenandoah_padding(0); Atomic _index; shenandoah_padding(1); - size_t _region_index[_maximum_groups]; // The region index for the first region spanned by this group - size_t _group_offset[_maximum_groups]; // The offset at which group begins within first region spanned by this group - size_t _group_chunk_size[_maximum_groups]; // The size of each chunk within this group - size_t _group_entries[_maximum_groups]; // Total chunks spanned by this group and the ones before it. - // No implicit copying: iterators should be passed by reference to capture the state NONCOPYABLE(ShenandoahRegionChunkIterator); - // Makes use of _heap. - size_t calc_regular_group_size(); - - // Makes use of _regular_group_size, which must be initialized before call. - size_t calc_first_group_chunk_size_b4_rebalance(); - - // Makes use of _regular_group_size and _first_group_chunk_size_b4_rebalance, both of which must be initialized before call. - size_t calc_num_groups(); - - // Makes use of _regular_group_size, _first_group_chunk_size_b4_rebalance, which must be initialized before call. size_t calc_total_chunks(); public: - ShenandoahRegionChunkIterator(size_t worker_count); - ShenandoahRegionChunkIterator(ShenandoahHeap* heap, size_t worker_count); - - // Reset iterator to default state - void reset(); + ShenandoahRegionChunkIterator(ShenandoahHeap* heap); // Fills in assignment with next chunk of work and returns true iff there is more work. - // Otherwise, returns false. This is multi-thread-safe. + // Otherwise, returns false. This is multi-thread-safe. Chunk assignments pertain only to + // memory affiliated with the Old generation. inline bool next(struct ShenandoahRegionChunk* assignment); - - // This is *not* MT safe. However, in the absence of multithreaded access, it - // can be used to determine if there is more work to do. - inline bool has_next() const; }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp index 3544a15c6c0..2e7e4e0ee9a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp @@ -44,7 +44,9 @@ void ShenandoahScanRemembered::mark_card_as_dirty(HeapWord* p) const { // Process all objects starting within count clusters beginning with first_cluster and for which the start address is // less than end_of_range. For any non-array object whose header lies on a dirty card, scan the entire object, // even if its end reaches beyond end_of_range. Object arrays, on the other hand, are precisely dirtied and -// only the portions of the array on dirty cards need to be scanned. +// only the portions of the array on dirty cards need to be scanned. An invocation of process_clusters only scans +// the portion of an Object array that overlaps with dirty cards within its assigned range, both for Object arrays +// that start within its range and for Object arrays that start before its range and extend into its assigned range. // // Do not CANCEL within process_clusters. It is assumed that if a worker thread accepts responsibility for processing // a chunk of work, it will finish the work it starts. Otherwise, the chunk of work will be lost in the transition to @@ -53,7 +55,8 @@ template void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t count, HeapWord* end_of_range, ClosureType* cl, bool use_write_table, uint worker_id) { - assert(ShenandoahHeap::heap()->old_generation()->is_parsable(), "Old generation regions must be parsable for remembered set scan"); + assert(ShenandoahHeap::heap()->old_generation()->is_parsable(), + "Old generation regions must be parsable for remembered set scan"); // If old-gen evacuation is active, then MarkingContext for old-gen heap regions is valid. We use the MarkingContext // bits to determine which objects within a DIRTY card need to be scanned. This is necessary because old-gen heap // regions that are in the candidate collection set have not been coalesced and filled. Thus, these heap regions @@ -93,7 +96,7 @@ void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t cou // The region we will scan is the half-open interval [start_addr, end_addr), // and lies entirely within a single region. - const ShenandoahHeapRegion* region = ShenandoahHeap::heap()->heap_region_containing(start_addr); + const ShenandoahHeapRegion* region = heap->heap_region_containing(start_addr); assert(region->contains(end_addr - 1), "Slice shouldn't cross regions"); // This code may have implicit assumptions of examining only old gen regions. @@ -236,7 +239,7 @@ void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t cou // Scan the object in its entirety p += obj->oop_iterate_size(cl); } else { - assert(p < tams, "Error 1 in ctx/marking/tams logic"); + assert(p < tams, "Objects past TAMS should have been marked"); // Skip over any intermediate dead objects p = ctx->get_next_marked_addr(p, tams); assert(p <= tams, "Error 2 in ctx/marking/tams logic"); @@ -245,6 +248,8 @@ void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t cou assert(p > left, "Should have processed into interior of dirty range"); } } + // If p < left, then either p is a reference array or p starts on a card that is not dirty. + // In both cases, p is processed by BODY below. size_t i = 0; HeapWord* last_p = nullptr; @@ -318,7 +323,7 @@ void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t cou template inline void ShenandoahScanRemembered::process_humongous_clusters(ShenandoahHeapRegion* r, size_t first_cluster, size_t count, - HeapWord *end_of_range, ClosureType *cl, bool use_write_table) { + HeapWord *end_of_range, ClosureType *cl, bool use_write_table) { ShenandoahHeapRegion* start_region = r->humongous_start_region(); HeapWord* p = start_region->bottom(); oop obj = cast_to_oop(p); @@ -337,8 +342,8 @@ ShenandoahScanRemembered::process_humongous_clusters(ShenandoahHeapRegion* r, si template inline void ShenandoahScanRemembered::process_region_slice(ShenandoahHeapRegion *region, size_t start_offset, size_t clusters, - HeapWord *end_of_range, ClosureType *cl, bool use_write_table, - uint worker_id) { + HeapWord *end_of_range, ClosureType *cl, bool use_write_table, + uint worker_id) { // This is called only for young gen collection, when we scan old gen regions assert(region->is_old(), "Expecting an old region"); @@ -385,47 +390,43 @@ ShenandoahScanRemembered::process_region_slice(ShenandoahHeapRegion *region, siz } } -inline bool ShenandoahRegionChunkIterator::has_next() const { - return _index.load_relaxed() < _total_chunks; -} - inline bool ShenandoahRegionChunkIterator::next(struct ShenandoahRegionChunk *assignment) { - if (_index.load_relaxed() >= _total_chunks) { - return false; - } - size_t new_index = _index.add_then_fetch((size_t) 1, memory_order_relaxed); - if (new_index > _total_chunks) { - // First worker that hits new_index == _total_chunks continues, other - // contending workers return false. - return false; + while (true) { + size_t cur_index = _index.load_relaxed(); + if (cur_index >= _total_chunks) { + break; + } + + size_t global_offset = cur_index << _chunk_shift; + size_t region_offset = global_offset & ShenandoahHeapRegion::region_size_words_mask(); + size_t region_index = global_offset >> ShenandoahHeapRegion::region_size_words_shift(); + if (_heap->region_affiliation(region_index) == OLD_GENERATION) { + // Passable candidate, try to claim it. + if (_index.compare_set(cur_index, cur_index + 1, memory_order_relaxed)) { + assert(_heap->region_affiliation(region_index) == OLD_GENERATION, "affiliations do not change during chunk iteration"); + assignment->_r = _heap->get_region(region_index); + assignment->_chunk_offset = region_offset; + assignment->_chunk_size = _chunk_size; + return true; + } + // Someone else claimed this region. I'll iterate and try to claim a different region. + } else { + // Misfit. Try to advance cursor to next OLD region. It is fine to lose the update race, as long as it completes. + while (region_index < _heap->num_regions() && _heap->region_affiliation(region_index) != OLD_GENERATION) { + region_index++; + } + size_t skip_index = (region_index << ShenandoahHeapRegion::region_size_words_shift()) >> _chunk_shift; + _index.compare_set(cur_index, skip_index, memory_order_relaxed); +#ifdef ASSERT + for (size_t i = cur_index; i < skip_index; i++) { + region_index = (i << _chunk_shift) >> ShenandoahHeapRegion::region_size_words_shift(); + assert(_heap->region_affiliation(region_index) != OLD_GENERATION, "affiliations do not change during chunk iteration"); + } +#endif + } } - // convert to zero-based indexing - new_index--; - assert(new_index < _total_chunks, "Error"); - - // Find the group number for the assigned chunk index - size_t group_no; - for (group_no = 0; new_index >= _group_entries[group_no]; group_no++) - ; - assert(group_no < _num_groups, "Cannot have group no greater or equal to _num_groups"); - - // All size computations measured in HeapWord - size_t region_size_words = ShenandoahHeapRegion::region_size_words(); - size_t group_region_index = _region_index[group_no]; - size_t group_region_offset = _group_offset[group_no]; - - size_t index_within_group = (group_no == 0)? new_index: new_index - _group_entries[group_no - 1]; - size_t group_chunk_size = _group_chunk_size[group_no]; - size_t offset_of_this_chunk = group_region_offset + index_within_group * group_chunk_size; - size_t regions_spanned_by_chunk_offset = offset_of_this_chunk / region_size_words; - size_t offset_within_region = offset_of_this_chunk % region_size_words; - - size_t region_index = group_region_index + regions_spanned_by_chunk_offset; - - assignment->_r = _heap->get_region(region_index); - assignment->_chunk_offset = offset_within_region; - assignment->_chunk_size = group_chunk_size; - return true; + // We break if _index is greater than or equal to _total_chunks. All scanning is done. + return false; } void ShenandoahDirectCardMarkRememberedSet::mark_card_as_dirty(HeapWord* p) const { diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestRemSetBalance.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestRemSetBalance.java new file mode 100644 index 00000000000..41a9cb83cc6 --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestRemSetBalance.java @@ -0,0 +1,186 @@ +/* + * Copyright Amazon.com Inc. 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. + * + */ + +/* This test is marked manual because it is known to require human + * interpretation of the results. The data gathered and reported by + * the test can be informative, though failure or passing of the test + * is not conclusive. + */ + +/* + * @test id=generational + * @summary Test that scanning of the remembered set is well balanced + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @requires os.maxMemory >= 10g + * @library /test/lib + * @run main/manual TestRemSetBalance + */ + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.Scanner; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +public class TestRemSetBalance { + + private static boolean parse_input(List stdout) { + Object[] lines = stdout.toArray(); + for (int index = 0; index < lines.length; index++) { + String line = (String) lines[index]; + int line_no = index + 1; + String[] tokens = line.split("\\s+"); + if (tokens.length > 8) { + // We are looking for output lines of the following format: + // [21.947s][info][gc,stats] RS: Work 662295 us total, per worker: 169895, 155334, 167446, 169620, ---, ---, ---, ---, + // We want to enforce low variance in the us consumed by individual GC workers, representing good balance. + if (tokens[1].equals("RS:") && tokens[2].equals("Work") && tokens[4].equals("us") + && tokens[5].equals("total,") && tokens[6].equals("per") && tokens[7].equals("worker:")) { + int min_work = 0x7fff_ffff; + int max_work = 0; + int total_work = 0; + int num_workers = 0; + for (int i = 8; i < tokens.length; i++) { + if (!tokens[i].equals("---,")) { + num_workers++; + // Need to strip the "," from end of tokens[i] + int value = Integer.parseInt(tokens[i].substring(0, tokens[i].length() - 1)); + total_work += value; + if (value > max_work) { + max_work = value; + } + if (value < min_work) { + min_work = value; + } + } + } + if (min_work < 10) { + if (max_work > 100) { + System.out.println("Out of balance: max work: " + max_work + " is greater than 100 when min work: " + min_work + + " is smaller than 10 at line " + line_no); + System.out.println(" line: " + line); + return false; + } + } else { + int average = total_work / num_workers; + // Allow deviation from average plus or minus 9.375%) + int deviation_bound = (3 * average) / 32; + if (max_work > average + deviation_bound) { + System.out.println("Out of balance: max work: " + max_work + " is greater than average work: " + average + + " plus bound: " + deviation_bound + " at line " + line_no); + System.out.println(" line: " + line); + return false; + } else if (min_work < average - deviation_bound) { + System.out.println("Out of balance: min work: " + min_work + " is less than average work: " + average + + " minus bound: " + deviation_bound + " at line " + line_no); + System.out.println(" line: " + line); + return false; + } + } + } + } + } + return true; + } + + static int counter = 1_000_000_000; + + private static String newString() { + counter++; + if (counter >= 1_000_010_000) counter = 1_000_000_000; + return "str" + counter; + } + + static final Random r = new Random(); + static Object[] retain; + + // Runs the allocation workload if args[0] matches "test", returning true. args[1], args[2], and args[3] are assumed + // to represent integers. + // Otherwise, returns false. + static boolean run_workload(String[] args) throws Exception { + if (args[0].equals("test")) { + int total = 1000 * Integer.parseInt(args[1]); + int rewrite = total / 100 * Integer.parseInt(args[2]); + long overwrites = 1L * total * Integer.parseInt(args[3]); + if (rewrite <= 0) { + rewrite = 1; + } + retain = new Object[total]; + for (int c = 0; c < total; c++) { + retain[c] = newString(); + } + for (long c = 0; c < overwrites; c++) { + int i = r.nextInt(total) % rewrite; + retain[i] = newString(); + } + return true; + } else { + return false; + } + } + + public static void testRemSet(String... args) throws Exception { + String[] cmds = Arrays.copyOf(args, args.length + 5); + cmds[args.length] = TestRemSetBalance.class.getName(); + cmds[args.length + 1] = "test"; + cmds[args.length + 2] = "3000"; + cmds[args.length + 3] = "50"; + cmds[args.length + 4] = "1000"; + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(cmds); + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldHaveExitValue(0); + List stdout = output.asLines(); + Asserts.assertTrue(parse_input(stdout), "Log has too much variation in remembered set scan times"); + } + + public static void main(String[] args) throws Exception { + if (args.length > 0 && args[0].equals("test")) { + run_workload(args); + return; + } + + testRemSet("-Xlog:gc", + "-Xms8g", + "-Xmx8g", + "-XX:+UseShenandoahGC", + "-XX:ShenandoahGCMode=generational", + "-Xlog:gc+stats", + "-XX:+UseCompactObjectHeaders" + ); + + testRemSet("-Xlog:gc", + "-Xms8g", + "-Xmx8g", + "-XX:+UseShenandoahGC", + "-XX:ShenandoahGCMode=generational", + "-Xlog:gc+stats", + "-XX:-UseCompactObjectHeaders" + ); + } +} From 76310cbf63459861fd99199d9856bee8272111bf Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 14 Oct 2026 19:19:03 +0000 Subject: [PATCH 096/101] 8394461: Rename ClassLoaderDataShared to AOTModules Reviewed-by: kvn, matsaave --- .../share/cds/aotLinkedClassBulkLoader.cpp | 4 +- src/hotspot/share/cds/aotMappedHeapLoader.cpp | 2 +- src/hotspot/share/cds/aotMetaspace.cpp | 12 ++-- .../aotModules.cpp} | 56 ++++++++++--------- .../aotModules.hpp} | 11 ++-- .../share/cds/aotStreamedHeapLoader.cpp | 2 +- src/hotspot/share/cds/archiveBuilder.cpp | 2 +- src/hotspot/share/cds/cdsConfig.cpp | 6 +- src/hotspot/share/classfile/moduleEntry.cpp | 6 +- src/hotspot/share/classfile/modules.cpp | 14 ++--- src/hotspot/share/classfile/vmClasses.cpp | 4 +- src/hotspot/share/memory/universe.cpp | 4 +- 12 files changed, 64 insertions(+), 59 deletions(-) rename src/hotspot/share/{classfile/classLoaderDataShared.cpp => cds/aotModules.cpp} (85%) rename src/hotspot/share/{classfile/classLoaderDataShared.hpp => cds/aotModules.hpp} (89%) diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp index b6dad1e2513..c7911294e99 100644 --- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp @@ -26,10 +26,10 @@ #include "cds/aotClassLinker.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotLinkedClassTable.hpp" +#include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/heapShared.hpp" #include "classfile/classLoaderData.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaStackTraceClasses.hpp" #include "classfile/systemDictionary.hpp" @@ -68,7 +68,7 @@ void AOTLinkedClassBulkLoader::preload_classes(JavaThread* current) { void AOTLinkedClassBulkLoader::preload_classes_impl(TRAPS) { precond(CDSConfig::is_using_aot_linked_classes()); - ClassLoaderDataShared::restore_archived_modules_for_preloading_classes(THREAD); + AOTModules::restore_archived_modules_for_preloading_classes(THREAD); Handle h_platform_loader(THREAD, SystemDictionary::java_platform_loader()); Handle h_system_loader(THREAD, SystemDictionary::java_system_loader()); diff --git a/src/hotspot/share/cds/aotMappedHeapLoader.cpp b/src/hotspot/share/cds/aotMappedHeapLoader.cpp index 4d08781ebab..b2552bea744 100644 --- a/src/hotspot/share/cds/aotMappedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotMappedHeapLoader.cpp @@ -27,9 +27,9 @@ #include "cds/aotMappedHeapLoader.inline.hpp" #include "cds/aotMappedHeapWriter.hpp" #include "cds/aotMetaspace.hpp" +#include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/heapShared.inline.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/stringTable.hpp" #include "classfile/systemDictionaryShared.hpp" #include "gc/shared/collectedHeap.hpp" diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index f198f14da13..415a5853ccf 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -33,6 +33,7 @@ #include "cds/aotMapLogger.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/aotMetaspace.hpp" +#include "cds/aotModules.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cds_globals.hpp" @@ -49,7 +50,6 @@ #include "cds/lambdaFormInvokers.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "classfile/classLoaderDataGraph.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/javaStackTraceClasses.hpp" #include "classfile/loaderConstraints.hpp" @@ -539,7 +539,7 @@ void AOTMetaspace::serialize(SerializeClosure* soc) { SystemDictionaryShared::serialize_vm_classes(soc); soc->do_tag(--tag); - CDS_JAVA_HEAP_ONLY(ClassLoaderDataShared::serialize(soc);) + CDS_JAVA_HEAP_ONLY(AOTModules::serialize(soc);) soc->do_ptr((void**)&_archived_method_handle_intrinsics); LambdaFormInvokers::serialize(soc); @@ -717,7 +717,7 @@ class StaticArchiveBuilder : public ArchiveBuilder { vmSymbols::metaspace_pointers_do(it); TrainingData::iterate_roots(it); if (CDSConfig::is_dumping_full_module_graph()) { - ClassLoaderDataShared::iterate_roots(it); + AOTModules::iterate_roots(it); } // The above code should find all the symbols that are referenced by the @@ -817,7 +817,7 @@ void VM_PopulateDumpSharedSpace::doit() { AOTMetaspace::make_method_handle_intrinsics_shareable(); if (CDSConfig::is_dumping_full_module_graph()) { - ClassLoaderDataShared::remove_unshareable_info(); + AOTModules::remove_unshareable_info(); } dump_java_heap_objects(); @@ -1181,8 +1181,8 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS if (CDSConfig::is_dumping_heap()) { HeapShared::init_heap_writer(); if (CDSConfig::is_dumping_full_module_graph()) { - ClassLoaderDataShared::ensure_module_entry_tables_exist(); - ClassLoaderDataShared::build_tables(CHECK); + AOTModules::ensure_module_entry_tables_exist(); + AOTModules::build_tables(CHECK); HeapShared::prepare_for_archiving(CHECK); } diff --git a/src/hotspot/share/classfile/classLoaderDataShared.cpp b/src/hotspot/share/cds/aotModules.cpp similarity index 85% rename from src/hotspot/share/classfile/classLoaderDataShared.cpp rename to src/hotspot/share/cds/aotModules.cpp index d974d65772b..ce179c03190 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.cpp +++ b/src/hotspot/share/cds/aotModules.cpp @@ -23,11 +23,11 @@ */ #include "cds/aotLogging.hpp" +#include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/heapShared.inline.hpp" #include "cds/serializeClosure.hpp" #include "classfile/classLoaderData.inline.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/modules.hpp" #include "classfile/packageEntry.hpp" @@ -39,9 +39,11 @@ #if INCLUDE_CDS_JAVA_HEAP -bool ClassLoaderDataShared::_full_module_graph_loaded = false; +bool AOTModules::_full_module_graph_loaded = false; -class ArchivedClassLoaderData { +// This class is used for storing information of a ClassLoaderData into the AOT cache. In +// the production run, information is copied from here into a live ClassLoaderData, +class AOTClassLoaderData { Array* _packages; Array* _modules; ModuleEntry* _unnamed_module; @@ -55,7 +57,7 @@ class ArchivedClassLoaderData { } } public: - ArchivedClassLoaderData() : _packages(nullptr), _modules(nullptr), _unnamed_module(nullptr) {} + AOTClassLoaderData() : _packages(nullptr), _modules(nullptr), _unnamed_module(nullptr) {} void iterate_roots(MetaspaceClosure* closure); void build_tables(ClassLoaderData* loader_data, TRAPS); @@ -74,21 +76,21 @@ class ArchivedClassLoaderData { void clear_archived_oops(); }; -static ArchivedClassLoaderData _archived_boot_loader_data; -static ArchivedClassLoaderData _archived_platform_loader_data; -static ArchivedClassLoaderData _archived_system_loader_data; +static AOTClassLoaderData _archived_boot_loader_data; +static AOTClassLoaderData _archived_platform_loader_data; +static AOTClassLoaderData _archived_system_loader_data; static ModuleEntry* _archived_javabase_moduleEntry = nullptr; static int _platform_loader_root_index = -1; static int _system_loader_root_index = -1; -void ArchivedClassLoaderData::iterate_roots(MetaspaceClosure* it) { +void AOTClassLoaderData::iterate_roots(MetaspaceClosure* it) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); it->push(&_packages); it->push(&_modules); it->push(&_unnamed_module); } -void ArchivedClassLoaderData::build_tables(ClassLoaderData* loader_data, TRAPS) { +void AOTClassLoaderData::build_tables(ClassLoaderData* loader_data, TRAPS) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); assert_valid(loader_data); if (loader_data != nullptr) { @@ -102,7 +104,7 @@ void ArchivedClassLoaderData::build_tables(ClassLoaderData* loader_data, TRAPS) } } -void ArchivedClassLoaderData::remove_unshareable_info() { +void AOTClassLoaderData::remove_unshareable_info() { if (_packages != nullptr) { _packages = ArchiveBuilder::current()->get_buffered_addr(_packages); for (int i = 0; i < _packages->length(); i++) { @@ -121,7 +123,7 @@ void ArchivedClassLoaderData::remove_unshareable_info() { } } -void ArchivedClassLoaderData::restore(ClassLoaderData* loader_data, bool do_entries, bool do_oops) { +void AOTClassLoaderData::restore(ClassLoaderData* loader_data, bool do_entries, bool do_oops) { assert(CDSConfig::is_using_full_module_graph(), "must be"); assert_valid(loader_data); if (_modules != nullptr) { // Could be null if we have archived no modules for platform/system loaders @@ -145,7 +147,7 @@ void ArchivedClassLoaderData::restore(ClassLoaderData* loader_data, bool do_entr } } -void ArchivedClassLoaderData::clear_archived_oops() { +void AOTClassLoaderData::clear_archived_oops() { assert(!CDSConfig::is_using_full_module_graph(), "must be"); if (_modules != nullptr) { for (int i = 0; i < _modules->length(); i++) { @@ -159,7 +161,7 @@ void ArchivedClassLoaderData::clear_archived_oops() { // ------------------------------ -void ClassLoaderDataShared::load_archived_platform_and_system_class_loaders() { +void AOTModules::load_archived_platform_and_system_class_loaders() { // The streaming object loader prefers loading the class loader related objects before // the CLD constructor which has a NoSafepointVerifier. if (!HeapShared::is_loading_streaming_mode()) { @@ -203,33 +205,33 @@ static ClassLoaderData* java_system_loader_data_or_null() { // ModuleEntryTables (even if empty) are required for iterate_symbols() to scan the // platform/system loaders inside the CDS safepoint, but the tables can be created only // when outside of safepoints. Let's do that now. -void ClassLoaderDataShared::ensure_module_entry_tables_exist() { +void AOTModules::ensure_module_entry_tables_exist() { assert(!SafepointSynchronize::is_at_safepoint(), "sanity"); ensure_module_entry_table_exists(SystemDictionary::java_platform_loader()); ensure_module_entry_table_exists(SystemDictionary::java_system_loader()); } -void ClassLoaderDataShared::ensure_module_entry_table_exists(oop class_loader) { +void AOTModules::ensure_module_entry_table_exists(oop class_loader) { Handle h_loader(JavaThread::current(), class_loader); ModuleEntryTable* met = Modules::get_module_entry_table(h_loader); assert(met != nullptr, "sanity"); } -void ClassLoaderDataShared::build_tables(TRAPS) { +void AOTModules::build_tables(TRAPS) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); _archived_boot_loader_data.build_tables(null_class_loader_data(), CHECK); _archived_platform_loader_data.build_tables(java_platform_loader_data_or_null(), CHECK); _archived_system_loader_data.build_tables(java_system_loader_data_or_null(), CHECK); } -void ClassLoaderDataShared::iterate_roots(MetaspaceClosure* it) { +void AOTModules::iterate_roots(MetaspaceClosure* it) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); _archived_boot_loader_data.iterate_roots(it); _archived_platform_loader_data.iterate_roots(it); _archived_system_loader_data.iterate_roots(it); } -void ClassLoaderDataShared::remove_unshareable_info() { +void AOTModules::remove_unshareable_info() { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); _archived_boot_loader_data.remove_unshareable_info(); _archived_platform_loader_data.remove_unshareable_info(); @@ -241,7 +243,7 @@ void ClassLoaderDataShared::remove_unshareable_info() { _system_loader_root_index = HeapShared::append_root(SystemDictionary::java_system_loader()); } -void ClassLoaderDataShared::serialize(SerializeClosure* f) { +void AOTModules::serialize(SerializeClosure* f) { _archived_boot_loader_data.serialize(f); _archived_platform_loader_data.serialize(f); _archived_system_loader_data.serialize(f); @@ -250,7 +252,7 @@ void ClassLoaderDataShared::serialize(SerializeClosure* f) { f->do_int(&_system_loader_root_index); } -ModuleEntry* ClassLoaderDataShared::archived_boot_unnamed_module() { +ModuleEntry* AOTModules::archived_boot_unnamed_module() { if (CDSConfig::is_using_full_module_graph()) { return _archived_boot_loader_data.unnamed_module(); } else { @@ -258,7 +260,7 @@ ModuleEntry* ClassLoaderDataShared::archived_boot_unnamed_module() { } } -ModuleEntry* ClassLoaderDataShared::archived_unnamed_module(ClassLoaderData* loader_data) { +ModuleEntry* AOTModules::archived_unnamed_module(ClassLoaderData* loader_data) { ModuleEntry* archived_module = nullptr; if (!Universe::is_module_initialized() && CDSConfig::is_using_full_module_graph()) { @@ -275,7 +277,7 @@ ModuleEntry* ClassLoaderDataShared::archived_unnamed_module(ClassLoaderData* loa return archived_module; } -void ClassLoaderDataShared::clear_archived_oops() { +void AOTModules::clear_archived_oops() { assert(!CDSConfig::is_using_full_module_graph(), "must be"); _archived_boot_loader_data.clear_archived_oops(); _archived_platform_loader_data.clear_archived_oops(); @@ -287,7 +289,7 @@ void ClassLoaderDataShared::clear_archived_oops() { } // Must be done before ClassLoader::create_javabase() -void ClassLoaderDataShared::restore_archived_entries_for_null_class_loader_data() { +void AOTModules::restore_archived_entries_for_null_class_loader_data() { precond(CDSConfig::is_using_full_module_graph()); _archived_boot_loader_data.restore(null_class_loader_data(), true, false); ModuleEntryTable::set_javabase_moduleEntry(_archived_javabase_moduleEntry); @@ -295,18 +297,18 @@ void ClassLoaderDataShared::restore_archived_entries_for_null_class_loader_data( p2i(_archived_javabase_moduleEntry)); } -oop ClassLoaderDataShared::restore_archived_oops_for_null_class_loader_data() { +oop AOTModules::restore_archived_oops_for_null_class_loader_data() { assert(CDSConfig::is_using_full_module_graph(), "must be"); _archived_boot_loader_data.restore(null_class_loader_data(), false, true); return _archived_javabase_moduleEntry->module_oop(); } -void ClassLoaderDataShared::restore_java_platform_loader_from_archive(ClassLoaderData* loader_data) { +void AOTModules::restore_java_platform_loader_from_archive(ClassLoaderData* loader_data) { assert(CDSConfig::is_using_full_module_graph(), "must be"); _archived_platform_loader_data.restore(loader_data, true, true); } -void ClassLoaderDataShared::restore_java_system_loader_from_archive(ClassLoaderData* loader_data) { +void AOTModules::restore_java_system_loader_from_archive(ClassLoaderData* loader_data) { assert(CDSConfig::is_using_full_module_graph(), "must be"); _archived_system_loader_data.restore(loader_data, true, true); _full_module_graph_loaded = true; @@ -314,7 +316,7 @@ void ClassLoaderDataShared::restore_java_system_loader_from_archive(ClassLoaderD // This is called before AOTLinkedClassBulkLoader starts preloading classes. It makes sure that // when we preload any class, its module is already valid. -void ClassLoaderDataShared::restore_archived_modules_for_preloading_classes(JavaThread* current) { +void AOTModules::restore_archived_modules_for_preloading_classes(JavaThread* current) { precond(CDSConfig::is_using_aot_linked_classes()); precond(_platform_loader_root_index >= 0); diff --git a/src/hotspot/share/classfile/classLoaderDataShared.hpp b/src/hotspot/share/cds/aotModules.hpp similarity index 89% rename from src/hotspot/share/classfile/classLoaderDataShared.hpp rename to src/hotspot/share/cds/aotModules.hpp index 2cf37310e50..f81a54acb78 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.hpp +++ b/src/hotspot/share/cds/aotModules.hpp @@ -22,11 +22,12 @@ * */ -#ifndef SHARE_CLASSFILE_CLASSLOADERDATASHARED_HPP -#define SHARE_CLASSFILE_CLASSLOADERDATASHARED_HPP +#ifndef SHARE_CDS_AOTMODULES_HPP +#define SHARE_CDS_AOTMODULES_HPP #include "memory/allStatic.hpp" #include "oops/oopsHierarchy.hpp" +#include "utilities/exceptions.hpp" #include "utilities/macros.hpp" class ClassLoaderData; @@ -34,7 +35,9 @@ class MetaspaceClosure; class ModuleEntry; class SerializeClosure; -class ClassLoaderDataShared : AllStatic { +// This class manages the information (such as module oops, ModuleEntry's, and ClassLoaderData's) +// for the archived module system. +class AOTModules : AllStatic { static bool _full_module_graph_loaded; CDS_JAVA_HEAP_ONLY(static void ensure_module_entry_table_exists(oop class_loader);) public: @@ -57,4 +60,4 @@ class ClassLoaderDataShared : AllStatic { static bool is_full_module_graph_loaded() { return _full_module_graph_loaded; } }; -#endif // SHARE_CLASSFILE_CLASSLOADERDATASHARED_HPP +#endif // SHARE_CDS_AOTMODULES_HPP diff --git a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp index a396c4d3af9..528c0f14979 100644 --- a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp @@ -23,12 +23,12 @@ */ #include "cds/aotMetaspace.hpp" +#include "cds/aotModules.hpp" #include "cds/aotStreamedHeapLoader.hpp" #include "cds/aotThread.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" #include "cds/heapShared.inline.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/stringTable.hpp" #include "classfile/vmClasses.hpp" diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 4199902b57f..57ab0b31663 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -28,6 +28,7 @@ #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" #include "cds/aotMetaspace.hpp" +#include "cds/aotModules.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" @@ -37,7 +38,6 @@ #include "cds/heapShared.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/classLoader.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 48ec7f98066..278c7397f12 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -25,12 +25,12 @@ #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" #include "cds/aotMetaspace.hpp" +#include "cds/aotModules.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/classListWriter.hpp" #include "cds/filemap.hpp" #include "cds/heapShared.inline.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/moduleEntry.hpp" #include "code/aotCodeCache.hpp" #include "compiler/compilerDefinitions.inline.hpp" @@ -1060,7 +1060,7 @@ void CDSConfig::disable_full_module_graph() { } bool CDSConfig::is_using_full_module_graph() { - if (ClassLoaderDataShared::is_full_module_graph_loaded()) { + if (AOTModules::is_full_module_graph_loaded()) { return true; } @@ -1089,7 +1089,7 @@ void CDSConfig::stop_dumping_full_module_graph(const char* reason) { } void CDSConfig::stop_using_full_module_graph(const char* reason) { - assert(!ClassLoaderDataShared::is_full_module_graph_loaded(), "you call this function too late!"); + assert(!AOTModules::is_full_module_graph_loaded(), "you call this function too late!"); if (_is_using_full_module_graph) { _is_using_full_module_graph = false; if (reason != nullptr) { diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index 456d6843207..e36348c5932 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -23,13 +23,13 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotModules.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" #include "cds/heapShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/systemDictionary.hpp" @@ -319,7 +319,7 @@ ModuleEntry* ModuleEntry::create_unnamed_module(ClassLoaderData* cld) { oop module = java_lang_ClassLoader::unnamedModule(cld->class_loader()); #if INCLUDE_CDS_JAVA_HEAP - ModuleEntry* archived_unnamed_module = ClassLoaderDataShared::archived_unnamed_module(cld); + ModuleEntry* archived_unnamed_module = AOTModules::archived_unnamed_module(cld); if (archived_unnamed_module != nullptr) { archived_unnamed_module->load_from_archive(cld); archived_unnamed_module->restore_archived_oops(cld); @@ -344,7 +344,7 @@ ModuleEntry* ModuleEntry::create_unnamed_module(ClassLoaderData* cld) { ModuleEntry* ModuleEntry::create_boot_unnamed_module(ClassLoaderData* cld) { #if INCLUDE_CDS_JAVA_HEAP - ModuleEntry* archived_unnamed_module = ClassLoaderDataShared::archived_boot_unnamed_module(); + ModuleEntry* archived_unnamed_module = AOTModules::archived_boot_unnamed_module(); if (archived_unnamed_module != nullptr) { archived_unnamed_module->load_from_archive(cld); // It's too early to call archived_unnamed_module->restore_archived_oops(cld). diff --git a/src/hotspot/share/classfile/modules.cpp b/src/hotspot/share/classfile/modules.cpp index 1e5f127815b..09ddc926556 100644 --- a/src/hotspot/share/classfile/modules.cpp +++ b/src/hotspot/share/classfile/modules.cpp @@ -24,12 +24,12 @@ #include "cds/aotLogging.hpp" #include "cds/aotMetaspace.hpp" +#include "cds/aotModules.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" #include "classfile/classFileParser.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaAssertions.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/moduleEntry.hpp" @@ -752,20 +752,20 @@ void Modules::init_archived_modules(JavaThread* current, Handle h_platform_loade if (CDSConfig::is_using_aot_linked_classes()) { ClassLoaderData* boot_loader_data = ClassLoaderData::the_null_class_loader_data(); - ClassLoaderDataShared::archived_boot_unnamed_module()->restore_archived_oops(boot_loader_data); + AOTModules::archived_boot_unnamed_module()->restore_archived_oops(boot_loader_data); } - Handle java_base_module(current, ClassLoaderDataShared::restore_archived_oops_for_null_class_loader_data()); + Handle java_base_module(current, AOTModules::restore_archived_oops_for_null_class_loader_data()); if (!CDSConfig::is_using_aot_linked_classes()) { // Patch any previously loaded class's module field with java.base's java.lang.Module. ModuleEntryTable::patch_javabase_entries(current, java_base_module); } - ClassLoaderDataShared::load_archived_platform_and_system_class_loaders(); + AOTModules::load_archived_platform_and_system_class_loaders(); ClassLoaderData* platform_loader_data = SystemDictionary::register_loader(h_platform_loader); SystemDictionary::set_platform_loader(platform_loader_data); - ClassLoaderDataShared::restore_java_platform_loader_from_archive(platform_loader_data); + AOTModules::restore_java_platform_loader_from_archive(platform_loader_data); ClassLoaderData* system_loader_data = SystemDictionary::register_loader(h_system_loader); SystemDictionary::set_system_loader(system_loader_data); @@ -774,7 +774,7 @@ void Modules::init_archived_modules(JavaThread* current, Handle h_platform_loade // be an instance of a user-defined class, so make sure this never happens. assert(Arguments::get_property("java.system.class.loader") == nullptr, "archived full module should have been disabled if -Djava.system.class.loader is specified"); - ClassLoaderDataShared::restore_java_system_loader_from_archive(system_loader_data); + AOTModules::restore_java_system_loader_from_archive(system_loader_data); } void Modules::check_cds_restrictions(TRAPS) { @@ -819,7 +819,7 @@ void Modules::set_bootloader_unnamed_module(Handle module, TRAPS) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_using_full_module_graph()) { - precond(unnamed_module == ClassLoaderDataShared::archived_boot_unnamed_module()); + precond(unnamed_module == AOTModules::archived_boot_unnamed_module()); if (!CDSConfig::is_using_aot_linked_classes()) { unnamed_module->restore_archived_oops(boot_loader_data); } diff --git a/src/hotspot/share/classfile/vmClasses.cpp b/src/hotspot/share/classfile/vmClasses.cpp index 1dde8b904a3..21a910486c8 100644 --- a/src/hotspot/share/classfile/vmClasses.cpp +++ b/src/hotspot/share/classfile/vmClasses.cpp @@ -24,11 +24,11 @@ #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotMappedHeapLoader.hpp" +#include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/dictionary.hpp" #include "classfile/javaClasses.hpp" #include "classfile/systemDictionary.hpp" @@ -157,7 +157,7 @@ void vmClasses::resolve_all(TRAPS) { } if (HeapShared::is_archived_heap_in_use() && !CDSConfig::is_using_full_module_graph()) { // Need to remove all the archived java.lang.Module objects from HeapShared::roots(). - ClassLoaderDataShared::clear_archived_oops(); + AOTModules::clear_archived_oops(); } #endif // INCLUDE_CDS_JAVA_HEAP // Initialize the constant pool for the Object_class diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index a4c46a4f0f4..b36f3d66c8f 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -23,12 +23,12 @@ */ #include "cds/aotMetaspace.hpp" +#include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/dynamicArchive.hpp" #include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" -#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaStackTraceClasses.hpp" #include "classfile/stringTable.hpp" @@ -939,7 +939,7 @@ jint universe_init() { #if INCLUDE_CDS #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_using_full_module_graph()) { - ClassLoaderDataShared::restore_archived_entries_for_null_class_loader_data(); + AOTModules::restore_archived_entries_for_null_class_loader_data(); } #endif // INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_archive()) { From f6ebed4f523dcb810eb3b830aa189c6c7ffac93f Mon Sep 17 00:00:00 2001 From: Frederic Parain Date: Wed, 14 Oct 2026 20:21:01 +0000 Subject: [PATCH 097/101] 8393281: JDK-8392214 is incomplete Reviewed-by: qamai, kvn --- src/hotspot/share/ci/ciObjArrayKlass.cpp | 23 +++-- src/hotspot/share/ci/ciObjArrayKlass.hpp | 2 + src/hotspot/share/opto/graphKit.cpp | 53 +++++----- src/hotspot/share/opto/graphKit.hpp | 6 +- src/hotspot/share/opto/library_call.cpp | 28 ++++-- src/hotspot/share/opto/parse2.cpp | 6 +- src/hotspot/share/opto/valuetypenode.cpp | 123 +++++++++++++---------- 7 files changed, 146 insertions(+), 95 deletions(-) diff --git a/src/hotspot/share/ci/ciObjArrayKlass.cpp b/src/hotspot/share/ci/ciObjArrayKlass.cpp index 09ab28e3319..458f4b57fdd 100644 --- a/src/hotspot/share/ci/ciObjArrayKlass.cpp +++ b/src/hotspot/share/ci/ciObjArrayKlass.cpp @@ -133,6 +133,17 @@ ciSymbol* ciObjArrayKlass::construct_array_name(ciSymbol* element_name, return ciSymbol::make(name); } +ciObjArrayKlass* ciObjArrayKlass::get_unloaded_named_klass(ciKlass* element_klass) { + ASSERT_IN_VM; + ciSymbol* array_name = construct_array_name(element_klass->name(), 1); + if (array_name == ciEnv::unloaded_cisymbol()) { + return ciEnv::unloaded_ciobjarrayklass(); + } + return + CURRENT_ENV->get_unloaded_klass(element_klass, array_name) + ->as_obj_array_klass(); +} + // ------------------------------------------------------------------ // ciObjArrayKlass::make_impl // @@ -146,7 +157,7 @@ ciObjArrayKlass* ciObjArrayKlass::make_impl(ciKlass* element_klass, bool refined if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; CURRENT_THREAD_ENV->record_out_of_memory_failure(); - return ciEnv::unloaded_ciobjarrayklass(); + return get_unloaded_named_klass(element_klass); } if (!refined_type) { return CURRENT_THREAD_ENV->get_obj_array_klass(array); @@ -168,7 +179,7 @@ ciObjArrayKlass* ciObjArrayKlass::make_impl(ciKlass* element_klass, bool refined if (HAS_PENDING_EXCEPTION) { CLEAR_PENDING_EXCEPTION; CURRENT_THREAD_ENV->record_out_of_memory_failure(); - return ciEnv::unloaded_ciobjarrayklass(); + return get_unloaded_named_klass(element_klass); } assert(array != nullptr, "klass_with_properties should return a klass or throw"); assert(!force_ref_layout || array->is_refArray_klass(), "must be a reference array klass"); @@ -180,13 +191,7 @@ ciObjArrayKlass* ciObjArrayKlass::make_impl(ciKlass* element_klass, bool refined } // The array klass was unable to be made or the element klass was not loaded. - ciSymbol* array_name = construct_array_name(element_klass->name(), 1); - if (array_name == ciEnv::unloaded_cisymbol()) { - return ciEnv::unloaded_ciobjarrayklass(); - } - return - CURRENT_ENV->get_unloaded_klass(element_klass, array_name) - ->as_obj_array_klass(); + return get_unloaded_named_klass(element_klass); } // ------------------------------------------------------------------ diff --git a/src/hotspot/share/ci/ciObjArrayKlass.hpp b/src/hotspot/share/ci/ciObjArrayKlass.hpp index 84dbe30d93a..fb9dc74b783 100644 --- a/src/hotspot/share/ci/ciObjArrayKlass.hpp +++ b/src/hotspot/share/ci/ciObjArrayKlass.hpp @@ -60,6 +60,8 @@ class ciObjArrayKlass : public ciArrayKlass { oop loader() { return _base_element_klass->loader(); } jobject loader_handle() { return _base_element_klass->loader_handle(); } + static ciObjArrayKlass* get_unloaded_named_klass(ciKlass* element_klass); + public: // The one-level type of the array elements. ciKlass* element_klass(); diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 8fbabc70930..b24801b42e2 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -1957,41 +1957,48 @@ Node* GraphKit::array_element_address(Node* ary, Node* idx, BasicType elembt, return basic_plus_adr(ary, base, scale); } -Node* GraphKit::cast_to_flat_array(Node* array, ciValueKlass* elem_vk) { +// The three methods below, get_flat_array_klass(), get_flat_array_klass_exact() and +// cast_to_flat_array() break down the processus to cast an array node into a flat array node. +// The first step involves obtaining the ciObjArrayKlass instance representing the flat array. +// The second step uses this ciObjArrayKlass instance to implement the cast on the array node. +// If retrieving the flat array klass fails, the returned ciObjArrayKlass instance is marked +// as being in an unloaded state, but it retains the array type name; this instance can be used +// to create an uncommon trap with Reason unloaded. +ciObjArrayKlass* GraphKit::get_flat_array_klass(ciValueKlass* elem_vk) { assert(elem_vk->maybe_flat_in_array(), "no flat array for %s", elem_vk->name()->as_utf8()); if (!elem_vk->has_null_free_atomic_layout() && !elem_vk->has_nullable_atomic_layout()) { - return cast_to_flat_array_exact(array, elem_vk, true, false); + return get_flat_array_klass_exact(elem_vk, true, false); } else if (!elem_vk->has_nullable_atomic_layout() && !elem_vk->has_null_free_non_atomic_layout()) { - return cast_to_flat_array_exact(array, elem_vk, true, true); + return get_flat_array_klass_exact(elem_vk, true, true); } else if (!elem_vk->has_null_free_atomic_layout() && !elem_vk->has_null_free_non_atomic_layout()) { - return cast_to_flat_array_exact(array, elem_vk, false, true); + return get_flat_array_klass_exact(elem_vk, false, true); } - - ciArrayKlass* array_klass = ciObjArrayKlass::make(elem_vk, false); - if (!array_klass->is_loaded()) { - return top(); - } - - const TypeAryPtr* arytype = TypeOopPtr::make_from_klass(array_klass)->isa_aryptr(); - arytype = arytype->cast_to_flat(); - if (!elem_vk->has_nullable_atomic_layout()) { - arytype = arytype->cast_to_null_free(); - } - - return _gvn.transform(new CheckCastPPNode(control(), array, arytype, ConstraintCastNode::DependencyType::NonFloatingNarrowing)); + return ciObjArrayKlass::make(elem_vk, false); } -Node* GraphKit::cast_to_flat_array_exact(Node* array, ciValueKlass* elem_vk, bool is_null_free, bool is_atomic) { +ciObjArrayKlass* GraphKit::get_flat_array_klass_exact(ciValueKlass* elem_vk, bool is_null_free, bool is_atomic) { assert(is_null_free || is_atomic, "nullable arrays must be atomic"); - ciArrayKlass* array_klass = ciObjArrayKlass::make(elem_vk, true, is_null_free, is_atomic); + return ciObjArrayKlass::make(elem_vk, true, is_null_free, is_atomic); +} + +Node* GraphKit::cast_to_flat_array(Node* array, ciObjArrayKlass* array_klass, bool exact) { if (!array_klass->is_loaded()) { return top(); } + assert(array_klass->element_klass()->is_value_klass(), "Must be"); + const TypeAryPtr* arytype = TypeOopPtr::make_from_klass(array_klass)->isa_aryptr(); - assert(arytype->klass_is_exact(), "inconsistency"); - assert(arytype->is_flat(), "inconsistency"); - assert(arytype->is_null_free() == is_null_free, "inconsistency"); - assert(arytype->is_not_null_free() == !is_null_free, "inconsistency"); + if (exact) { + assert(arytype->klass_is_exact(), "inconsistency"); + assert(arytype->is_flat(), "inconsistency"); + assert(arytype->is_null_free() == array_klass->is_elem_null_free(), "inconsistency"); + assert(arytype->is_not_null_free() == !array_klass->is_elem_null_free(), "inconsistency"); + } else { + arytype = arytype->cast_to_flat(); + if (!array_klass->element_klass()->as_value_klass()->has_nullable_atomic_layout()) { + arytype = arytype->cast_to_null_free(); + } + } return _gvn.transform(new CheckCastPPNode(control(), array, arytype, ConstraintCastNode::DependencyType::NonFloatingNarrowing)); } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index ae5aa75f5e0..a1796ea8125 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -663,8 +663,10 @@ class GraphKit : public Phase { const TypeInt* sizetype = nullptr, // Optional control dependency (for example, on range check) Node* ctrl = nullptr); - Node* cast_to_flat_array(Node* array, ciValueKlass* elem_vk); - Node* cast_to_flat_array_exact(Node* array, ciValueKlass* elem_vk, bool is_null_free, bool is_atomic); + + ciObjArrayKlass* get_flat_array_klass(ciValueKlass* elem_vk); + ciObjArrayKlass* get_flat_array_klass_exact(ciValueKlass* elem_vk, bool is_null_free, bool is_atomic); + Node* cast_to_flat_array(Node* array, ciObjArrayKlass* array_klass, bool exact); // Return a load of array element at idx. Node* load_array_element(Node* ary, Node* idx, const TypeAryPtr* arytype, bool set_ctrl); diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 9f30ff1c4f1..6b3f61342a9 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -2805,14 +2805,26 @@ bool LibraryCallKit::inline_unsafe_flat_access(bool is_store, AccessKind kind) { // Flat array must have an exact type bool is_null_free = !LayoutKindHelper::is_nullable_flat(layout); bool is_atomic = LayoutKindHelper::is_atomic_flat(layout); - Node* new_base = cast_to_flat_array_exact(base, value_klass, is_null_free, is_atomic); - replace_in_map(base, new_base); - base = new_base; - ptr = basic_plus_adr(base, ConvL2X(offset)); - const TypeAryPtr* ptr_type = _gvn.type(ptr)->is_aryptr(); - if (ptr_type->field_offset().get() != 0) { - // TODO 8388444 This should be a CheckCastPP, can we add a test? - ptr = _gvn.transform(new CastPPNode(control(), ptr, ptr_type->with_field_offset(0), ConstraintCastNode::DependencyType::NonFloatingNarrowing)); + ciObjArrayKlass* array_klass = get_flat_array_klass_exact(value_klass, is_null_free, is_atomic); + if (!array_klass->is_loaded()) { + uncommon_trap(Deoptimization::Reason_unloaded, + Deoptimization::Action_reinterpret, + array_klass, "Flat array class meta-data unavailable"); + return true; + } else { + Node* new_base = cast_to_flat_array(base, array_klass, true); + if (new_base->is_top()) { + uncommon_trap(Deoptimization::Reason_intrinsic, + Deoptimization::Action_none); + } + replace_in_map(base, new_base); + base = new_base; + ptr = basic_plus_adr(base, ConvL2X(offset)); + const TypeAryPtr* ptr_type = _gvn.type(ptr)->is_aryptr(); + if (ptr_type->field_offset().get() != 0) { + // TODO 8388444 This should be a CheckCastPP, can we add a test? + ptr = _gvn.transform(new CastPPNode(control(), ptr, ptr_type->with_field_offset(0), ConstraintCastNode::DependencyType::NonFloatingNarrowing)); + } } } else { uncommon_trap(Deoptimization::Reason_intrinsic, diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index ff2e7c0b0e6..0aae6e6bc44 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -124,7 +124,8 @@ void Parse::array_load(BasicType bt) { if (!array_type->is_not_flat()) { if (element_ptr->is_valueklassptr()) { ciValueKlass* vk = element_ptr->value_klass(); - Node* flat_array = cast_to_flat_array(array, vk); + ciObjArrayKlass* array_klass = get_flat_array_klass(vk); + Node* flat_array = cast_to_flat_array(array, array_klass, false); // It may be the case that array is only known to be not flat when we try to cast it to a // flat array. For example, array is a not-null-free array and vk does not have a @@ -296,7 +297,8 @@ void Parse::array_store(BasicType bt) { if (vk != nullptr) { // Element type is known, cast and store to flat array layout. - Node* flat_array = cast_to_flat_array(array, vk); + ciObjArrayKlass* array_klass = get_flat_array_klass(vk); + Node* flat_array = cast_to_flat_array(array, array_klass, false); // It may be the case that array is only known to be not flat when we try to cast it to // a flat array. For example, array is a not-null-free array and vk does not have a diff --git a/src/hotspot/share/opto/valuetypenode.cpp b/src/hotspot/share/opto/valuetypenode.cpp index f905367ff2f..15494a1cca0 100644 --- a/src/hotspot/share/opto/valuetypenode.cpp +++ b/src/hotspot/share/opto/valuetypenode.cpp @@ -502,6 +502,59 @@ void ValueTypeNode::store_flat(GraphKit* kit, Node* base, Node* ptr, bool atomic StoreFlatNode::store(kit, base, ptr, this, null_free, decorators); } +struct FlatArrayAccessContext { + GraphKit* _kit; + ValueTypeNode* _node; + RegionNode* _region; + PhiNode* _mem; + PhiNode* _io; + Node* _base; + Node* _idx; + ciValueKlass* _element_klass; + DecoratorSet _decorators; + int _stack_adj; + + FlatArrayAccessContext(GraphKit* kit, ValueTypeNode* node, RegionNode* region, PhiNode* mem, + PhiNode* io, Node* base, Node* idx, ciValueKlass* element_klass, + DecoratorSet decorators, int stack_adj) : + _kit(kit), _node(node), _region(region), _mem(mem), _io(io), _base(base), _idx(idx), + _element_klass(element_klass), _decorators(decorators), _stack_adj(stack_adj) {} +}; + +static ValueTypeNode* do_flat_array_access(const FlatArrayAccessContext& ctx, bool is_atomic, bool is_null_free, + int region_idx, bool is_read) { + ciObjArrayKlass* array_klass = ctx._kit->get_flat_array_klass_exact(ctx._element_klass, + is_null_free, + is_atomic); + if (!array_klass->is_loaded()) { + PreserveJVMState pjvms(ctx._kit); + ctx._kit->inc_sp(ctx._stack_adj); + ctx._kit->uncommon_trap(Deoptimization::Reason_unloaded, + Deoptimization::Action_reinterpret, + array_klass, "Flat array class meta-data unavailable"); + return nullptr; + } + ValueTypeNode* res = nullptr; + Node* cast = ctx._kit->cast_to_flat_array(ctx._base, array_klass, true); + if (!cast->is_top()) { + Node* ptr = ctx._kit->array_element_address(cast, ctx._idx, T_FLAT_ELEMENT); + if (is_read) { + res = ValueTypeNode::make_from_flat(ctx._kit, ctx._element_klass, cast, ptr, + is_atomic, false, is_null_free, + ctx._decorators); + } else { + assert(ctx._node != nullptr, "Store to a flat array needs a ValueTypeNode"); + ctx._node->store_flat(ctx._kit, cast, ptr, is_atomic, false, + is_null_free, ctx._decorators); + } + + ctx._region->init_req(region_idx, ctx._kit->control()); + ctx._mem->set_req(region_idx, ctx._kit->reset_memory()); + ctx._io->set_req(region_idx, ctx._kit->i_o()); + } + return res; +} + void ValueTypeNode::store_flat_array(GraphKit* kit, Node* base, Node* idx) { PhaseGVN& gvn = kit->gvn(); DecoratorSet decorators = IN_HEAP | IS_ARRAY | MO_UNORDERED; @@ -527,18 +580,14 @@ void ValueTypeNode::store_flat_array(GraphKit* kit, Node* base, Node* idx) { Node* bol_null_free = kit->null_free_array_test(base); // Argument evaluation order is undefined in C++ and since this sets control, it needs to come first IfNode* iff_null_free = kit->create_and_map_if(kit->control(), bol_null_free, PROB_FAIR, COUNT_UNKNOWN); + FlatArrayAccessContext faac(kit, this, region, mem, io, base, idx, vk, decorators, /* stack_adj */0); + // Nullable kit->set_control(kit->IfFalse(iff_null_free)); if (!kit->stopped()) { assert(vk->has_nullable_atomic_layout(), "element type %s does not have a nullable flat layout", vk->name()->as_utf8()); kit->set_all_memory(input_memory_state); - Node* cast = kit->cast_to_flat_array_exact(base, vk, false, true); - Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); - store_flat(kit, cast, ptr, true, false, false, decorators); - - region->init_req(1, kit->control()); - mem->set_req(1, kit->reset_memory()); - io->set_req(1, kit->i_o()); + do_flat_array_access(faac, true, false, 1, false); } // Null-free @@ -554,13 +603,7 @@ void ValueTypeNode::store_flat_array(GraphKit* kit, Node* base, Node* idx) { if (!kit->stopped()) { assert(vk->has_null_free_atomic_layout(), "element type %s does not have a null-free atomic flat layout", vk->name()->as_utf8()); kit->set_all_memory(input_memory_state); - Node* cast = kit->cast_to_flat_array_exact(base, vk, true, true); - Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); - store_flat(kit, cast, ptr, true, false, true, decorators); - - region->init_req(2, kit->control()); - mem->set_req(2, kit->reset_memory()); - io->set_req(2, kit->i_o()); + do_flat_array_access(faac, true, true, 2, false); } // Non-atomic @@ -568,19 +611,13 @@ void ValueTypeNode::store_flat_array(GraphKit* kit, Node* base, Node* idx) { if (!kit->stopped()) { assert(vk->has_null_free_non_atomic_layout(), "element type %s does not have a null-free non-atomic flat layout", vk->name()->as_utf8()); kit->set_all_memory(input_memory_state); - Node* cast = kit->cast_to_flat_array_exact(base, vk, true, false); - Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); - store_flat(kit, cast, ptr, false, false, true, decorators); - - region->init_req(3, kit->control()); - mem->set_req(3, kit->reset_memory()); - io->set_req(3, kit->i_o()); + do_flat_array_access(faac, false, true, 3, false); } } - kit->set_control(gvn.transform(region)); - kit->set_all_memory(gvn.transform(mem)); - kit->set_i_o(gvn.transform(io)); + kit->set_control(region); + kit->set_all_memory(mem); + kit->set_i_o(io); } void ValueTypeNode::store(GraphKit* kit, Node* base, Node* ptr, bool immutable_memory, DecoratorSet decorators) const { @@ -1618,6 +1655,8 @@ ValueTypeNode* ValueTypeNode::make_from_flat_array(GraphKit* kit, ciValueKlass* gvn.set_type(io, Type::ABIO); kit->record_for_igvn(io); + FlatArrayAccessContext faac(kit, nullptr, region, mem, io, base, idx, vk, decorators, /* stack_adj */ 2); + Node* bol_null_free = kit->null_free_array_test(base); // Argument evaluation order is undefined in C++ and since this sets control, it needs to come first IfNode* iff_null_free = kit->create_and_map_if(kit->control(), bol_null_free, PROB_FAIR, COUNT_UNKNOWN); @@ -1626,13 +1665,7 @@ ValueTypeNode* ValueTypeNode::make_from_flat_array(GraphKit* kit, ciValueKlass* if (!kit->stopped()) { assert(vk->has_nullable_atomic_layout(), "element type %s does not have a nullable flat layout", vk->name()->as_utf8()); kit->set_all_memory(input_memory_state); - Node* cast = kit->cast_to_flat_array_exact(base, vk, false, true); - Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); - vt_nullable = ValueTypeNode::make_from_flat(kit, vk, cast, ptr, true, false, false, decorators); - - region->init_req(1, kit->control()); - mem->set_req(1, kit->reset_memory()); - io->set_req(1, kit->i_o()); + vt_nullable = do_flat_array_access(faac, true, false, 1, true); } // Null-free @@ -1648,13 +1681,7 @@ ValueTypeNode* ValueTypeNode::make_from_flat_array(GraphKit* kit, ciValueKlass* if (!kit->stopped()) { assert(vk->has_null_free_atomic_layout(), "element type %s does not have a null-free atomic flat layout", vk->name()->as_utf8()); kit->set_all_memory(input_memory_state); - Node* cast = kit->cast_to_flat_array_exact(base, vk, true, true); - Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); - vt_null_free = ValueTypeNode::make_from_flat(kit, vk, cast, ptr, true, false, true, decorators); - - region->init_req(2, kit->control()); - mem->set_req(2, kit->reset_memory()); - io->set_req(2, kit->i_o()); + vt_null_free = do_flat_array_access(faac, true, true, 2, true); } // Non-Atomic @@ -1662,13 +1689,7 @@ ValueTypeNode* ValueTypeNode::make_from_flat_array(GraphKit* kit, ciValueKlass* if (!kit->stopped()) { assert(vk->has_null_free_non_atomic_layout(), "element type %s does not have a null-free non-atomic flat layout", vk->name()->as_utf8()); kit->set_all_memory(input_memory_state); - Node* cast = kit->cast_to_flat_array_exact(base, vk, true, false); - Node* ptr = kit->array_element_address(cast, idx, T_FLAT_ELEMENT); - vt_non_atomic = ValueTypeNode::make_from_flat(kit, vk, cast, ptr, false, false, true, decorators); - - region->init_req(3, kit->control()); - mem->set_req(3, kit->reset_memory()); - io->set_req(3, kit->i_o()); + vt_non_atomic = do_flat_array_access(faac, false, true, 3, true); } } @@ -1684,9 +1705,9 @@ ValueTypeNode* ValueTypeNode::make_from_flat_array(GraphKit* kit, ciValueKlass* vt = vt_nullable; } if (vt != nullptr) { - kit->set_control(kit->gvn().transform(region)); - kit->set_all_memory(kit->gvn().transform(mem)); - kit->set_i_o(kit->gvn().transform(io)); + kit->set_control(region); + kit->set_all_memory(mem); + kit->set_i_o(io); return vt; } @@ -1702,9 +1723,9 @@ ValueTypeNode* ValueTypeNode::make_from_flat_array(GraphKit* kit, ciValueKlass* vt = vt->merge_with(&gvn, vt_non_atomic, 3, false); } - kit->set_control(kit->gvn().transform(region)); - kit->set_all_memory(kit->gvn().transform(mem)); - kit->set_i_o(kit->gvn().transform(io)); + kit->set_control(region); + kit->set_all_memory(mem); + kit->set_i_o(io); return gvn.transform(vt)->as_ValueType(); } From 610607d7ad27e5ab59783fef6facf5f54a45ee55 Mon Sep 17 00:00:00 2001 From: Alexander Zuev Date: Wed, 14 Oct 2026 21:10:56 +0000 Subject: [PATCH 098/101] 8394583: ProblemList java/awt/dnd/Button2DragTest/Button2DragTest.java due to the JDK-8394582 Reviewed-by: azvegint, prr --- test/jdk/ProblemList.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 25fbbb628a0..d25cea8165c 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -129,6 +129,7 @@ java/awt/dnd/MissingDragExitEventTest/MissingDragExitEventTest.java 8288839 wind java/awt/dnd/DragExitBeforeDropTest.java 8242805 macosx-all java/awt/dnd/DragThresholdTest.java 8076299 macosx-all java/awt/dnd/CustomDragCursorTest.java 8242805 macosx-all +java/awt/dnd/Button2DragTest/Button2DragTest.java 8393735 macosx-aarch64 java/awt/Focus/ClearLwQueueBreakTest/ClearLwQueueBreakTest.java 8198618 macosx-all java/awt/Focus/ConsumeNextKeyTypedOnModalShowTest/ConsumeNextKeyTypedOnModalShowTest.java 6986252 macosx-all java/awt/Focus/MouseClickRequestFocusRaceTest/MouseClickRequestFocusRaceTest.java 8194753 linux-all,macosx-all From 4dde007d404445a141bdfdbc19de543032a8d15e Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Wed, 14 Oct 2026 23:56:59 +0000 Subject: [PATCH 099/101] 8394493: RISC-V: Enable RVV IR checks in compiler gallery tests Reviewed-by: gcao, fyang --- .../jtreg/compiler/gallery/TestNormalMapping.java | 4 ++-- .../jtreg/compiler/gallery/TestParticleLife.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/hotspot/jtreg/compiler/gallery/TestNormalMapping.java b/test/hotspot/jtreg/compiler/gallery/TestNormalMapping.java index 5c133822485..968e017cbc8 100644 --- a/test/hotspot/jtreg/compiler/gallery/TestNormalMapping.java +++ b/test/hotspot/jtreg/compiler/gallery/TestNormalMapping.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -107,7 +107,7 @@ public void run() { IRNode.STORE_VECTOR, "> 0"}, applyIf = {"AlignVector", "false"}, applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) private void testIR() { // This call should inline givne the CompileCommand above. state.update(); diff --git a/test/hotspot/jtreg/compiler/gallery/TestParticleLife.java b/test/hotspot/jtreg/compiler/gallery/TestParticleLife.java index bb1eea927c7..ce2645dff28 100644 --- a/test/hotspot/jtreg/compiler/gallery/TestParticleLife.java +++ b/test/hotspot/jtreg/compiler/gallery/TestParticleLife.java @@ -101,7 +101,7 @@ public void run() { IRNode.STORE_VECTOR, "> 0"}, applyIf = {"AlignVector", "false"}, applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) private void testIR_updatePositions() { // This call should inline given the CompileCommand above. // We expect auto vectorization of the relatively simple loop. @@ -129,7 +129,7 @@ private void testIR_updatePositions() { IRNode.STORE_VECTOR, "= 0", IRNode.ADD_REDUCTION_VF, "= 0"}, applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"}) private void testIR_updateForcesScalar() { // This call should inline given the CompileCommand above. // We expect no vectorization, though it may in principle be possible @@ -156,7 +156,7 @@ private void testIR_updateForcesScalar() { IRNode.VECTOR_BLEND_F, "> 0", IRNode.ADD_REDUCTION_VF, "> 0"}, // instead we reduce the vector to a scalar applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeature = {"avx2", "true"}) + applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}) private void testIR_updateForcesVectorAPI_Inner_Gather() { // This call should inline given the CompileCommand above. // We expect the VectorAPI calls to intrinsify. @@ -182,7 +182,7 @@ private void testIR_updateForcesVectorAPI_Inner_Gather() { IRNode.VECTOR_BLEND_F, "> 0", IRNode.ADD_REDUCTION_VF, "> 0"}, // instead we reduce the vector to a scalar applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"}) private void testIR_updateForcesVectorAPI_Inner_Rearranged() { // This call should inline given the CompileCommand above. // We expect the VectorAPI calls to intrinsify. @@ -210,7 +210,7 @@ private void testIR_updateForcesVectorAPI_Inner_Rearranged() { IRNode.STORE_VECTOR, "> 0", // store back a vector IRNode.ADD_REDUCTION_VF, "= 0"}, // and no reduction operation applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeature = {"avx2", "true"}) + applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}) private void testIR_updateForcesVectorAPI_Outer() { // This call should inline given the CompileCommand above. // We expect the VectorAPI calls to intrinsify. From 44647eb7568534c7f99231da5e296b54e128280a Mon Sep 17 00:00:00 2001 From: Vicente Romero Date: Thu, 15 Oct 2026 00:52:49 +0000 Subject: [PATCH 100/101] 8387279: javac error message refers to value_class modifier instead of value Reviewed-by: liach --- .../share/classes/com/sun/tools/javac/code/Flags.java | 1 + .../valhalla/value-objects/ValueObjectCompilationTests.java | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java index 9caa59d5bf7..be2bb631b84 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java @@ -180,6 +180,7 @@ public static EnumSet asFlagSet(long flags) { /** Marks a type as a value class */ @Use({FlagTarget.CLASS}) + @CustomToStringValue("value") public static final int VALUE_CLASS = 1<<20; /** A parameter of a lambda function. diff --git a/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java b/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java index 27472769df4..49208295ad3 100644 --- a/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java +++ b/test/langtools/tools/javac/valhalla/value-objects/ValueObjectCompilationTests.java @@ -26,7 +26,7 @@ * * @test * @bug 8287136 8292630 8279368 8287136 8287770 8279840 8279672 8292753 8287763 8279901 8287767 8293183 8293120 - * 8329345 8341061 8340984 8334484 + * 8329345 8341061 8340984 8334484 8387279 * @summary Negative compilation tests, and positive compilation (smoke) tests for Value Objects * @enablePreview * @library /lib/combo /tools/lib @@ -99,6 +99,7 @@ class Test { } """); assertFail("compiler.err.mod.not.allowed.here", + d -> Assert.check("modifier value not allowed here".equals(d.getMessage(Locale.ROOT))), """ value enum Enum {} """); From 84054dbf1760e3f17b4a48e0c4de731a4971ce52 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Thu, 15 Oct 2026 03:47:27 +0000 Subject: [PATCH 101/101] 8394585: Move AOT code from modules.cpp to aotModules.cpp Reviewed-by: matsaave, kvn --- src/hotspot/share/cds/aotMappedHeapWriter.cpp | 3 +- src/hotspot/share/cds/aotMetaspace.cpp | 4 +- src/hotspot/share/cds/aotModules.cpp | 314 ++++++++++++++++- src/hotspot/share/cds/aotModules.hpp | 22 +- .../share/cds/aotStreamedHeapWriter.cpp | 3 +- src/hotspot/share/classfile/modules.cpp | 323 +----------------- src/hotspot/share/classfile/modules.hpp | 22 +- src/hotspot/share/prims/jvm.cpp | 3 +- 8 files changed, 348 insertions(+), 346 deletions(-) diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp index 164cd2e7ee0..ed35c8d09d1 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp @@ -24,6 +24,7 @@ #include "cds/aotMappedHeap.hpp" #include "cds/aotMappedHeapWriter.hpp" +#include "cds/aotModules.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" @@ -419,7 +420,7 @@ void AOTMappedHeapWriter::copy_source_objs_to_buffer(GrowableArrayCHeapmaybe_grow(); if (java_lang_Module::is_instance(src_obj)) { - Modules::check_archived_module_oop(src_obj); + AOTModules::check_archived_module_oop(src_obj); } } diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index 415a5853ccf..cd102e4eee7 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -493,7 +493,7 @@ void AOTMetaspace::write_method_handle_intrinsics() { void AOTMetaspace::early_serialize(SerializeClosure* soc) { int tag = 0; soc->do_tag(--tag); - CDS_JAVA_HEAP_ONLY(Modules::serialize_archived_module_info(soc);) + AOTModules::serialize_archived_module_info(soc); soc->do_tag(666); } @@ -740,7 +740,7 @@ class StaticArchiveBuilder : public ArchiveBuilder { char* VM_PopulateDumpSharedSpace::dump_early_read_only_tables() { ArchiveBuilder::OtherROAllocMark mark; - CDS_JAVA_HEAP_ONLY(Modules::dump_archived_module_info()); + AOTModules::dump_archived_module_info(); DumpRegion* ro_region = ArchiveBuilder::current()->ro_region(); char* start = ro_region->top(); diff --git a/src/hotspot/share/cds/aotModules.cpp b/src/hotspot/share/cds/aotModules.cpp index ce179c03190..69eb41d343d 100644 --- a/src/hotspot/share/cds/aotModules.cpp +++ b/src/hotspot/share/cds/aotModules.cpp @@ -161,6 +161,318 @@ void AOTClassLoaderData::clear_archived_oops() { // ------------------------------ +void AOTModules::check_restrictions(TRAPS) { + if (CDSConfig::is_dumping_full_module_graph() && Universe::is_module_initialized()) { + // When dumping the full module graph, we don't execute any user Java code. We can + // come here only if there's a bug in the core library. + THROW_MSG(vmSymbols::java_lang_UnsupportedOperationException(), + "When dumping full module graph, module system cannot be modified after it's initialized"); + } +} + +static bool _seen_boot_unnamed_module = false; +static bool _seen_platform_unnamed_module = false; +static bool _seen_system_unnamed_module = false; + +// Validate the states of an java.lang.Module oop to be archived. +void AOTModules::check_archived_module_oop(oop orig_module_obj) { + assert(CDSConfig::is_dumping_full_module_graph(), "must be"); + assert(java_lang_Module::is_instance(orig_module_obj), "must be"); + + ModuleEntry* orig_module_ent = java_lang_Module::module_entry_raw(orig_module_obj); + if (orig_module_ent == nullptr) { + // These special java.lang.Module oops are created in Java code. They are not + // defined via Modules::define_module(), so they don't have a ModuleEntry: + // java.lang.Module::ALL_UNNAMED_MODULE + // java.lang.Module::EVERYONE_MODULE + // jdk.internal.loader.ClassLoaders$BootClassLoader::unnamedModule + log_info(aot, module)("Archived java.lang.Module oop " PTR_FORMAT " with no ModuleEntry*", p2i(orig_module_obj)); + assert(java_lang_Module::name(orig_module_obj) == nullptr, "must be unnamed"); + } else { + // This java.lang.Module oop has an ModuleEntry*. Check if the latter is archived. + if (log_is_enabled(Info, aot, module)) { + ResourceMark rm; + LogStream ls(Log(aot, module)::info()); + ls.print("Archived java.lang.Module oop " PTR_FORMAT " for ", p2i(orig_module_obj)); + orig_module_ent->print(&ls); + } + + // We only archive the default module graph, which should contain only java.lang.Module oops + // for the 3 built-in loaders (boot/platform/system) + ClassLoaderData* loader_data = orig_module_ent->loader_data(); + assert(loader_data->is_builtin_class_loader_data(), "must be"); + + precond(ArchiveBuilder::current()->has_been_archived(orig_module_ent)); + if (orig_module_ent->name() == nullptr) { + // We always archive unnamed module oop for boot, platform, and system loaders. + precond(orig_module_ent->should_be_archived()); + + if (loader_data->is_boot_class_loader_data()) { + assert(!_seen_boot_unnamed_module, "only once"); + _seen_boot_unnamed_module = true; + } else if (SystemDictionary::is_platform_class_loader(loader_data->class_loader())) { + assert(!_seen_platform_unnamed_module, "only once"); + _seen_platform_unnamed_module = true; + } else if (SystemDictionary::is_system_class_loader(loader_data->class_loader())) { + assert(!_seen_system_unnamed_module, "only once"); + _seen_system_unnamed_module = true; + } else { + ShouldNotReachHere(); + } + } + } +} + +class AOTModules::ArchivedProperty { + const char* _prop; + const bool _numbered; + const char* _archived_value; + + const char* get_flattened_value() const { + if (_numbered) { + return get_numbered_property_as_sorted_string(); + } else { + return Arguments::get_property(_prop); + } + } + + void runtime_check() const; + const char* get_numbered_property_as_sorted_string() const; + +public: + ArchivedProperty(const char* prop, bool numbered) + : _prop(prop), _numbered(numbered), _archived_value(nullptr) {} + + void dump() { + ResourceMark rm; + const char* str = get_flattened_value(); + if (str != nullptr) { + _archived_value = ArchiveBuilder::current()->ro_strdup(str); + } + } + + void serialize(SerializeClosure* soc) { + soc->do_ptr(&_archived_value); + if (soc->reading()) { + runtime_check(); + // Don't hold onto the pointer, in case we might decide to unmap the archive. + _archived_value = nullptr; + } + } +}; + +AOTModules::ArchivedProperty AOTModules::_archived_props[] = { + // non-numbered + {"jdk.module.main", false}, + + // numbered + {"jdk.module.addexports", true}, // --add-exports + {"jdk.module.addmods", true}, // --add-modules + {"jdk.module.enable.native.access", true}, // --enable-native-access + {"jdk.module.addopens", true}, // --add-opens + {"jdk.module.addreads", true}, // --add-reads +}; + +constexpr size_t AOTModules::num_archived_props() { + return sizeof(_archived_props) / sizeof(_archived_props[0]); +} + +AOTModules::ArchivedProperty& AOTModules::archived_prop(size_t i) { + assert(i < num_archived_props(), "oob"); + return _archived_props[i]; +} + +void AOTModules::ArchivedProperty::runtime_check() const { + ResourceMark rm; + const char* old_value = _archived_value; + const char* new_value = get_flattened_value(); + aot_log_info(aot)("archived module property %s: %s", _prop, + old_value != nullptr ? old_value : "(null)"); + + bool mismatch = false; + const char* old_label1; + const char* old_label2; + const char* new_label1; + const char* new_label2; + + if (CDSConfig::is_dumping_final_static_archive()) { + old_label1 = "in AOTConfiguration"; + old_label2 = ", AOTConfiguration ="; + new_label1 = "for current JVM"; + new_label2 = "current ="; + } else if (CDSConfig::new_aot_flags_used()) { + old_label1 = "in AOTCache"; + old_label2 = ", AOTCache ="; + new_label1 = "for current JVM"; + new_label2 = "current ="; + } else { + old_label1 = "during dump time"; + old_label2 = " dump time"; + new_label1 = "during runtime"; + new_label2 = "runtime"; + } + + if (new_value == nullptr) { + if (old_value != nullptr) { + AOTMetaspace::report_loading_error("Mismatched values for property %s: %s specified %s but not %s", + _prop, old_value, old_label1, new_label1); + mismatch = true; + } + } else { + if (old_value == nullptr) { + AOTMetaspace::report_loading_error("Mismatched values for property %s: %s specified %s but not %s", + _prop, new_value, new_label1, old_label1); + mismatch = true; + } else if (strcmp(new_value, old_value) != 0) { + AOTMetaspace::report_loading_error("Mismatched values for property %s: %s %s%s %s", + _prop, new_label2, new_value, old_label2, old_value); + mismatch = true; + } + } + + if (mismatch) { + AOTMetaspace::report_loading_error("Disabling full module graph"); + CDSConfig::disable_full_module_graph(); + + if (CDSConfig::is_dumping_final_static_archive()) { + AOTMetaspace::unrecoverable_writing_error("mismatched module options"); + } + } +} + +static int compare_module_names(const char** p1, const char** p2) { + return strcmp(*p1, *p2); +} + +// Caller needs ResourceMark +const char* AOTModules::ArchivedProperty::get_numbered_property_as_sorted_string() const { + assert(_numbered, "sanity"); + // theoretical string size limit for decimal int, but the following loop will end much sooner due to + // OS command-line size limit. + const int max_digits = 10; + const int extra_symbols_count = 2; // includes '.', '\0' + size_t prop_len = strlen(_prop) + max_digits + extra_symbols_count; + char* prop_name = resource_allocate_bytes(prop_len); + GrowableArray list; + for (unsigned int i = 0;; i++) { + jio_snprintf(prop_name, prop_len, "%s.%d", _prop, i); + const char* prop_value = Arguments::get_property(prop_name); + if (prop_value == nullptr) { + break; + } + char* p = ResourceArea::strdup(prop_value); + while (*p == ',') p++; // skip leading commas + while (*p) { + char* next = strchr(p, ','); + if (next == nullptr) { + // no more commas, p is the last element + list.append(p); + break; + } else { + *next = 0; + list.append(p); + p = next + 1; + } + } + } + + // Example: + // --add-modules=java.compiler --add-modules=java.base,java.base,, + // + // list[0] = "java.compiler" + // list[1] = "java.base" + // list[2] = "java.base" + // list[3] = "" + // list[4] = "" + list.sort(compare_module_names); + + const char* prefix = ""; + stringStream st; + const char* last_string = ""; // This also filters out all empty strings + for (int i = 0; i < list.length(); i++) { + const char* m = list.at(i); + if (strcmp(m, last_string) != 0) { // filter out duplicates + st.print("%s%s", prefix, m); + last_string = m; + prefix = ","; + } + } + + return (st.size() > 0) ? st.as_string() : nullptr; // Example: "java.base,java.compiler" +} + +void AOTModules::dump_archived_module_info() { + for (size_t i = 0; i < num_archived_props(); i++) { + archived_prop(i).dump(); + } +} + +void AOTModules::serialize_archived_module_info(SerializeClosure* soc) { + for (size_t i = 0; i < num_archived_props(); i++) { + archived_prop(i).serialize(soc); + } + if (soc->reading()) { + aot_log_info(aot)("full module graph: %s", CDSConfig::is_using_full_module_graph() ? "enabled" : "disabled"); + } +} + +void AOTModules::define_archived_modules(Handle h_platform_loader, Handle h_system_loader, TRAPS) { + assert(CDSConfig::is_using_full_module_graph(), "must be"); + if (h_platform_loader.is_null()) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null platform loader object"); + } + + if (h_system_loader.is_null()) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null system loader object"); + } + + if (CDSConfig::is_using_aot_linked_classes()) { + // Already initialized + precond(SystemDictionary::java_platform_loader() == h_platform_loader()); + precond(SystemDictionary::java_system_loader() == h_system_loader()); + } else { + init_archived_modules(THREAD, h_platform_loader, h_system_loader); + } +} + +void AOTModules::init_archived_modules(JavaThread* current, Handle h_platform_loader, Handle h_system_loader) { + assert(CDSConfig::is_using_full_module_graph(), "must be"); + ExceptionMark em(current); + + // We don't want the classes used by the archived full module graph to be redefined by JVMTI. + // Luckily, such classes are loaded in the JVMTI "early" phase, and CDS is disabled if a JVMTI + // agent wants to redefine classes in this phase. + JVMTI_ONLY(assert(JvmtiExport::is_early_phase(), "must be")); + assert(!(JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()), + "CDS should be disabled if early class hooks are enabled"); + + if (CDSConfig::is_using_aot_linked_classes()) { + ClassLoaderData* boot_loader_data = ClassLoaderData::the_null_class_loader_data(); + archived_boot_unnamed_module()->restore_archived_oops(boot_loader_data); + } + + Handle java_base_module(current, restore_archived_oops_for_null_class_loader_data()); + if (!CDSConfig::is_using_aot_linked_classes()) { + // Patch any previously loaded class's module field with java.base's java.lang.Module. + ModuleEntryTable::patch_javabase_entries(current, java_base_module); + } + + load_archived_platform_and_system_class_loaders(); + + ClassLoaderData* platform_loader_data = SystemDictionary::register_loader(h_platform_loader); + SystemDictionary::set_platform_loader(platform_loader_data); + restore_java_platform_loader_from_archive(platform_loader_data); + + ClassLoaderData* system_loader_data = SystemDictionary::register_loader(h_system_loader); + SystemDictionary::set_system_loader(system_loader_data); + // system_loader_data here is always an instance of jdk.internal.loader.ClassLoader$AppClassLoader. + // However, if -Djava.system.class.loader=xxx is specified, java_platform_loader() would + // be an instance of a user-defined class, so make sure this never happens. + assert(Arguments::get_property("java.system.class.loader") == nullptr, + "archived full module should have been disabled if -Djava.system.class.loader is specified"); + restore_java_system_loader_from_archive(system_loader_data); +} + void AOTModules::load_archived_platform_and_system_class_loaders() { // The streaming object loader prefers loading the class loader related objects before // the CLD constructor which has a NoSafepointVerifier. @@ -324,7 +636,7 @@ void AOTModules::restore_archived_modules_for_preloading_classes(JavaThread* cur Handle h_platform_loader(current, HeapShared::get_root(_platform_loader_root_index)); Handle h_system_loader(current, HeapShared::get_root(_system_loader_root_index)); - Modules::init_archived_modules(current, h_platform_loader, h_system_loader); + init_archived_modules(current, h_platform_loader, h_system_loader); } #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/aotModules.hpp b/src/hotspot/share/cds/aotModules.hpp index f81a54acb78..4a6496bf6ec 100644 --- a/src/hotspot/share/cds/aotModules.hpp +++ b/src/hotspot/share/cds/aotModules.hpp @@ -27,6 +27,7 @@ #include "memory/allStatic.hpp" #include "oops/oopsHierarchy.hpp" +#include "runtime/handles.hpp" #include "utilities/exceptions.hpp" #include "utilities/macros.hpp" @@ -35,12 +36,31 @@ class MetaspaceClosure; class ModuleEntry; class SerializeClosure; -// This class manages the information (such as module oops, ModuleEntry's, and ClassLoaderData's) +// This class manages the information (such as properties, module oops, ModuleEntry's, and ClassLoaderData's) // for the archived module system. class AOTModules : AllStatic { static bool _full_module_graph_loaded; CDS_JAVA_HEAP_ONLY(static void ensure_module_entry_table_exists(oop class_loader);) + +#if INCLUDE_CDS_JAVA_HEAP + class ArchivedProperty; + + static ArchivedProperty _archived_props[]; + static constexpr size_t num_archived_props(); + static ArchivedProperty& archived_prop(size_t i); +#endif + public: + + static void check_archived_module_oop(oop orig_module_obj) NOT_CDS_JAVA_HEAP_RETURN; + static void define_archived_modules(Handle h_platform_loader, Handle h_system_loader, + TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + static void init_archived_modules(JavaThread* current, Handle h_platform_loader, Handle h_system_loader) + NOT_CDS_JAVA_HEAP_RETURN; + static void dump_archived_module_info() NOT_CDS_JAVA_HEAP_RETURN; + static void serialize_archived_module_info(SerializeClosure* soc) NOT_CDS_JAVA_HEAP_RETURN; + + static void check_restrictions(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; static void load_archived_platform_and_system_class_loaders() NOT_CDS_JAVA_HEAP_RETURN; static void restore_archived_modules_for_preloading_classes(JavaThread* current) NOT_CDS_JAVA_HEAP_RETURN; static void build_tables(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; diff --git a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp index 30d976a0fe1..007a0ae4c6d 100644 --- a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp @@ -22,6 +22,7 @@ * */ +#include "cds/aotModules.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/aotStreamedHeapWriter.hpp" #include "cds/cdsConfig.hpp" @@ -322,7 +323,7 @@ size_t AOTStreamedHeapWriter::copy_one_source_obj_to_buffer(oop src_obj) { if (java_lang_Module::is_instance(src_obj)) { // These native pointers will be restored explicitly at run time. - Modules::check_archived_module_oop(src_obj); + AOTModules::check_archived_module_oop(src_obj); update_buffered_object_field(to, java_lang_Module::module_entry_offset(), nullptr); } else if (java_lang_ClassLoader::is_instance(src_obj)) { #ifdef ASSERT diff --git a/src/hotspot/share/classfile/modules.cpp b/src/hotspot/share/classfile/modules.cpp index 09ddc926556..13e41c5fb02 100644 --- a/src/hotspot/share/classfile/modules.cpp +++ b/src/hotspot/share/classfile/modules.cpp @@ -277,7 +277,7 @@ static void throw_dup_pkg_exception(const char* module_name, PackageEntry* packa void Modules::define_module(Handle module, jboolean is_open, jstring version, jstring location, jobjectArray packages, TRAPS) { - check_cds_restrictions(CHECK); + AOTModules::check_restrictions(CHECK); ResourceMark rm(THREAD); if (module.is_null()) { @@ -472,319 +472,6 @@ void Modules::define_module(Handle module, jboolean is_open, jstring version, #endif // COMPILER2 } -#if INCLUDE_CDS_JAVA_HEAP -static bool _seen_boot_unnamed_module = false; -static bool _seen_platform_unnamed_module = false; -static bool _seen_system_unnamed_module = false; - -// Validate the states of an java.lang.Module oop to be archived. -void Modules::check_archived_module_oop(oop orig_module_obj) { - assert(CDSConfig::is_dumping_full_module_graph(), "must be"); - assert(java_lang_Module::is_instance(orig_module_obj), "must be"); - - ModuleEntry* orig_module_ent = java_lang_Module::module_entry_raw(orig_module_obj); - if (orig_module_ent == nullptr) { - // These special java.lang.Module oops are created in Java code. They are not - // defined via Modules::define_module(), so they don't have a ModuleEntry: - // java.lang.Module::ALL_UNNAMED_MODULE - // java.lang.Module::EVERYONE_MODULE - // jdk.internal.loader.ClassLoaders$BootClassLoader::unnamedModule - log_info(aot, module)("Archived java.lang.Module oop " PTR_FORMAT " with no ModuleEntry*", p2i(orig_module_obj)); - assert(java_lang_Module::name(orig_module_obj) == nullptr, "must be unnamed"); - } else { - // This java.lang.Module oop has an ModuleEntry*. Check if the latter is archived. - if (log_is_enabled(Info, aot, module)) { - ResourceMark rm; - LogStream ls(Log(aot, module)::info()); - ls.print("Archived java.lang.Module oop " PTR_FORMAT " for ", p2i(orig_module_obj)); - orig_module_ent->print(&ls); - } - - // We only archive the default module graph, which should contain only java.lang.Module oops - // for the 3 built-in loaders (boot/platform/system) - ClassLoaderData* loader_data = orig_module_ent->loader_data(); - assert(loader_data->is_builtin_class_loader_data(), "must be"); - - precond(ArchiveBuilder::current()->has_been_archived(orig_module_ent)); - if (orig_module_ent->name() == nullptr) { - // We always archive unnamed module oop for boot, platform, and system loaders. - precond(orig_module_ent->should_be_archived()); - - if (loader_data->is_boot_class_loader_data()) { - assert(!_seen_boot_unnamed_module, "only once"); - _seen_boot_unnamed_module = true; - } else if (SystemDictionary::is_platform_class_loader(loader_data->class_loader())) { - assert(!_seen_platform_unnamed_module, "only once"); - _seen_platform_unnamed_module = true; - } else if (SystemDictionary::is_system_class_loader(loader_data->class_loader())) { - assert(!_seen_system_unnamed_module, "only once"); - _seen_system_unnamed_module = true; - } else { - ShouldNotReachHere(); - } - } - } -} - -class Modules::ArchivedProperty { - const char* _prop; - const bool _numbered; - const char* _archived_value; - - const char* get_flattened_value() const { - if (_numbered) { - return get_numbered_property_as_sorted_string(); - } else { - return Arguments::get_property(_prop); - } - } - - void runtime_check() const; - const char* get_numbered_property_as_sorted_string() const; - -public: - ArchivedProperty(const char* prop, bool numbered) - : _prop(prop), _numbered(numbered), _archived_value(nullptr) {} - - void dump() { - ResourceMark rm; - const char* str = get_flattened_value(); - if (str != nullptr) { - _archived_value = ArchiveBuilder::current()->ro_strdup(str); - } - } - - void serialize(SerializeClosure* soc) { - soc->do_ptr(&_archived_value); - if (soc->reading()) { - runtime_check(); - // Don't hold onto the pointer, in case we might decide to unmap the archive. - _archived_value = nullptr; - } - } -}; - -Modules::ArchivedProperty Modules::_archived_props[] = { - // non-numbered - {"jdk.module.main", false}, - - // numbered - {"jdk.module.addexports", true}, // --add-exports - {"jdk.module.addmods", true}, // --add-modules - {"jdk.module.enable.native.access", true}, // --enable-native-access - {"jdk.module.addopens", true}, // --add-opens - {"jdk.module.addreads", true}, // --add-reads -}; - -constexpr size_t Modules::num_archived_props() { - return sizeof(_archived_props) / sizeof(_archived_props[0]); -} - -Modules::ArchivedProperty& Modules::archived_prop(size_t i) { - assert(i < num_archived_props(), "oob"); - return _archived_props[i]; -} - -void Modules::ArchivedProperty::runtime_check() const { - ResourceMark rm; - const char* old_value = _archived_value; - const char* new_value = get_flattened_value(); - aot_log_info(aot)("archived module property %s: %s", _prop, - old_value != nullptr ? old_value : "(null)"); - - bool mismatch = false; - const char* old_label1; - const char* old_label2; - const char* new_label1; - const char* new_label2; - - if (CDSConfig::is_dumping_final_static_archive()) { - old_label1 = "in AOTConfiguration"; - old_label2 = ", AOTConfiguration ="; - new_label1 = "for current JVM"; - new_label2 = "current ="; - } else if (CDSConfig::new_aot_flags_used()) { - old_label1 = "in AOTCache"; - old_label2 = ", AOTCache ="; - new_label1 = "for current JVM"; - new_label2 = "current ="; - } else { - old_label1 = "during dump time"; - old_label2 = " dump time"; - new_label1 = "during runtime"; - new_label2 = "runtime"; - } - - if (new_value == nullptr) { - if (old_value != nullptr) { - AOTMetaspace::report_loading_error("Mismatched values for property %s: %s specified %s but not %s", - _prop, old_value, old_label1, new_label1); - mismatch = true; - } - } else { - if (old_value == nullptr) { - AOTMetaspace::report_loading_error("Mismatched values for property %s: %s specified %s but not %s", - _prop, new_value, new_label1, old_label1); - mismatch = true; - } else if (strcmp(new_value, old_value) != 0) { - AOTMetaspace::report_loading_error("Mismatched values for property %s: %s %s%s %s", - _prop, new_label2, new_value, old_label2, old_value); - mismatch = true; - } - } - - if (mismatch) { - AOTMetaspace::report_loading_error("Disabling full module graph"); - CDSConfig::disable_full_module_graph(); - - if (CDSConfig::is_dumping_final_static_archive()) { - AOTMetaspace::unrecoverable_writing_error("mismatched module options"); - } - } -} - - -static int compare_module_names(const char** p1, const char** p2) { - return strcmp(*p1, *p2); -} - -// Caller needs ResourceMark -const char* Modules::ArchivedProperty::get_numbered_property_as_sorted_string() const { - assert(_numbered, "sanity"); - // theoretical string size limit for decimal int, but the following loop will end much sooner due to - // OS command-line size limit. - const int max_digits = 10; - const int extra_symbols_count = 2; // includes '.', '\0' - size_t prop_len = strlen(_prop) + max_digits + extra_symbols_count; - char* prop_name = resource_allocate_bytes(prop_len); - GrowableArray list; - for (unsigned int i = 0;; i++) { - jio_snprintf(prop_name, prop_len, "%s.%d", _prop, i); - const char* prop_value = Arguments::get_property(prop_name); - if (prop_value == nullptr) { - break; - } - char* p = ResourceArea::strdup(prop_value); - while (*p == ',') p++; // skip leading commas - while (*p) { - char* next = strchr(p, ','); - if (next == nullptr) { - // no more commas, p is the last element - list.append(p); - break; - } else { - *next = 0; - list.append(p); - p = next + 1; - } - } - } - - // Example: - // --add-modules=java.compiler --add-modules=java.base,java.base,, - // - // list[0] = "java.compiler" - // list[1] = "java.base" - // list[2] = "java.base" - // list[3] = "" - // list[4] = "" - list.sort(compare_module_names); - - const char* prefix = ""; - stringStream st; - const char* last_string = ""; // This also filters out all empty strings - for (int i = 0; i < list.length(); i++) { - const char* m = list.at(i); - if (strcmp(m, last_string) != 0) { // filter out duplicates - st.print("%s%s", prefix, m); - last_string = m; - prefix = ","; - } - } - - return (st.size() > 0) ? st.as_string() : nullptr; // Example: "java.base,java.compiler" -} - -void Modules::dump_archived_module_info() { - for (size_t i = 0; i < num_archived_props(); i++) { - archived_prop(i).dump(); - } -} - -void Modules::serialize_archived_module_info(SerializeClosure* soc) { - for (size_t i = 0; i < num_archived_props(); i++) { - archived_prop(i).serialize(soc); - } - if (soc->reading()) { - aot_log_info(aot)("full module graph: %s", CDSConfig::is_using_full_module_graph() ? "enabled" : "disabled"); - } -} - -void Modules::define_archived_modules(Handle h_platform_loader, Handle h_system_loader, TRAPS) { - assert(CDSConfig::is_using_full_module_graph(), "must be"); - if (h_platform_loader.is_null()) { - THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null platform loader object"); - } - - if (h_system_loader.is_null()) { - THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null system loader object"); - } - - if (CDSConfig::is_using_aot_linked_classes()) { - // Already initialized - precond(SystemDictionary::java_platform_loader() == h_platform_loader()); - precond(SystemDictionary::java_system_loader() == h_system_loader()); - } else { - init_archived_modules(THREAD, h_platform_loader, h_system_loader); - } -} - -void Modules::init_archived_modules(JavaThread* current, Handle h_platform_loader, Handle h_system_loader) { - assert(CDSConfig::is_using_full_module_graph(), "must be"); - ExceptionMark em(current); - - // We don't want the classes used by the archived full module graph to be redefined by JVMTI. - // Luckily, such classes are loaded in the JVMTI "early" phase, and CDS is disabled if a JVMTI - // agent wants to redefine classes in this phase. - JVMTI_ONLY(assert(JvmtiExport::is_early_phase(), "must be")); - assert(!(JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()), - "CDS should be disabled if early class hooks are enabled"); - - if (CDSConfig::is_using_aot_linked_classes()) { - ClassLoaderData* boot_loader_data = ClassLoaderData::the_null_class_loader_data(); - AOTModules::archived_boot_unnamed_module()->restore_archived_oops(boot_loader_data); - } - - Handle java_base_module(current, AOTModules::restore_archived_oops_for_null_class_loader_data()); - if (!CDSConfig::is_using_aot_linked_classes()) { - // Patch any previously loaded class's module field with java.base's java.lang.Module. - ModuleEntryTable::patch_javabase_entries(current, java_base_module); - } - - AOTModules::load_archived_platform_and_system_class_loaders(); - - ClassLoaderData* platform_loader_data = SystemDictionary::register_loader(h_platform_loader); - SystemDictionary::set_platform_loader(platform_loader_data); - AOTModules::restore_java_platform_loader_from_archive(platform_loader_data); - - ClassLoaderData* system_loader_data = SystemDictionary::register_loader(h_system_loader); - SystemDictionary::set_system_loader(system_loader_data); - // system_loader_data here is always an instance of jdk.internal.loader.ClassLoader$AppClassLoader. - // However, if -Djava.system.class.loader=xxx is specified, java_platform_loader() would - // be an instance of a user-defined class, so make sure this never happens. - assert(Arguments::get_property("java.system.class.loader") == nullptr, - "archived full module should have been disabled if -Djava.system.class.loader is specified"); - AOTModules::restore_java_system_loader_from_archive(system_loader_data); -} - -void Modules::check_cds_restrictions(TRAPS) { - if (CDSConfig::is_dumping_full_module_graph() && Universe::is_module_initialized()) { - THROW_MSG(vmSymbols::java_lang_UnsupportedOperationException(), - "During -Xshare:dump, module system cannot be modified after it's initialized"); - } -} -#endif // INCLUDE_CDS_JAVA_HEAP - void Modules::set_bootloader_unnamed_module(Handle module, TRAPS) { ResourceMark rm(THREAD); @@ -833,7 +520,7 @@ void Modules::set_bootloader_unnamed_module(Handle module, TRAPS) { } void Modules::add_module_exports(Handle from_module, jstring package_name, Handle to_module, TRAPS) { - check_cds_restrictions(CHECK); + AOTModules::check_restrictions(CHECK); if (package_name == nullptr) { THROW_MSG(vmSymbols::java_lang_NullPointerException(), @@ -901,7 +588,7 @@ void Modules::add_module_exports(Handle from_module, jstring package_name, Handl void Modules::add_module_exports_qualified(Handle from_module, jstring package, Handle to_module, TRAPS) { - check_cds_restrictions(CHECK); + AOTModules::check_restrictions(CHECK); if (to_module.is_null()) { THROW_MSG(vmSymbols::java_lang_NullPointerException(), "to_module is null"); @@ -910,7 +597,7 @@ void Modules::add_module_exports_qualified(Handle from_module, jstring package, } void Modules::add_reads_module(Handle from_module, Handle to_module, TRAPS) { - check_cds_restrictions(CHECK); + AOTModules::check_restrictions(CHECK); if (from_module.is_null()) { THROW_MSG(vmSymbols::java_lang_NullPointerException(), "from_module is null"); @@ -1016,7 +703,7 @@ oop Modules::get_named_module(Handle h_loader, const char* package_name) { // Export package in module to all unnamed modules. void Modules::add_module_exports_to_all_unnamed(Handle module, jstring package_name, TRAPS) { - check_cds_restrictions(CHECK); + AOTModules::check_restrictions(CHECK); if (module.is_null()) { THROW_MSG(vmSymbols::java_lang_NullPointerException(), "module is null"); diff --git a/src/hotspot/share/classfile/modules.hpp b/src/hotspot/share/classfile/modules.hpp index 75857c8960c..bb7e710258c 100644 --- a/src/hotspot/share/classfile/modules.hpp +++ b/src/hotspot/share/classfile/modules.hpp @@ -26,14 +26,12 @@ #define SHARE_CLASSFILE_MODULES_HPP #include "memory/allStatic.hpp" -#include "runtime/handles.hpp" +class Handle; class ModuleEntryTable; -class SerializeClosure; class Symbol; class Modules : AllStatic { - static void check_cds_restrictions(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; public: // define_module defines a module containing the specified packages. It binds the @@ -54,24 +52,6 @@ class Modules : AllStatic { static void define_module(Handle module, jboolean is_open, jstring version, jstring location, jobjectArray packages, TRAPS); - static void check_archived_module_oop(oop orig_module_obj) NOT_CDS_JAVA_HEAP_RETURN; - static void define_archived_modules(Handle h_platform_loader, Handle h_system_loader, - TRAPS) NOT_CDS_JAVA_HEAP_RETURN; - static void init_archived_modules(JavaThread* current, Handle h_platform_loader, Handle h_system_loader) - NOT_CDS_JAVA_HEAP_RETURN; - static void dump_archived_module_info() NOT_CDS_JAVA_HEAP_RETURN; - static void serialize_archived_module_info(SerializeClosure* soc) NOT_CDS_JAVA_HEAP_RETURN; - -#if INCLUDE_CDS_JAVA_HEAP -private: - class ArchivedProperty; - - static ArchivedProperty _archived_props[]; - static constexpr size_t num_archived_props(); - static ArchivedProperty& archived_prop(size_t i); -public: -#endif - // Provides the java.lang.Module for the unnamed module defined // to the boot loader. // diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index b83fb90bb73..071c7922dc7 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -24,6 +24,7 @@ #include "cds/aotClassInitializer.hpp" #include "cds/aotMetaspace.hpp" +#include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/classListParser.hpp" #include "cds/classListWriter.hpp" @@ -1345,7 +1346,7 @@ JVM_END JVM_ENTRY(void, JVM_DefineArchivedModules(JNIEnv *env, jobject platform_loader, jobject system_loader)) Handle h_platform_loader (THREAD, JNIHandles::resolve(platform_loader)); Handle h_system_loader (THREAD, JNIHandles::resolve(system_loader)); - Modules::define_archived_modules(h_platform_loader, h_system_loader, CHECK); + AOTModules::define_archived_modules(h_platform_loader, h_system_loader, CHECK); JVM_END // Reflection support //////////////////////////////////////////////////////////////////////////////