diff --git a/doc/hotspot-style.html b/doc/hotspot-style.html
index f0e07630a21a..54a7e42949a3 100644
--- a/doc/hotspot-style.html
+++ b/doc/hotspot-style.html
@@ -268,8 +268,8 @@
Conventions for Lock-free
synchronization context, such as a held mutex or at a safepoint. In such
cases the variable should be declared volatile and it
should NOT be accessed as a normal C++ lvalue. Rather, access should be
-performed via functions from Atomic, such as
-Atomic::load, Atomic::store, etc.
+performed via functions from AtomicAccess, such as
+AtomicAccess::load, AtomicAccess::store, etc.
This special formulation makes it more clear to maintainers that the
variable is accessed concurrently in a lock-free manner.
Source Files
diff --git a/doc/hotspot-style.md b/doc/hotspot-style.md
index a1ee04036325..8cc18f329637 100644
--- a/doc/hotspot-style.md
+++ b/doc/hotspot-style.md
@@ -140,8 +140,8 @@ change should be done with a "setter" accessor matched to the simple
Sometimes variables are accessed concurrently without appropriate synchronization
context, such as a held mutex or at a safepoint. In such cases the variable should
be declared `volatile` and it should NOT be accessed as a normal C++ lvalue. Rather,
-access should be performed via functions from `Atomic`, such as `Atomic::load`,
-`Atomic::store`, etc.
+access should be performed via functions from `AtomicAccess`, such as `AtomicAccess::load`,
+`AtomicAccess::store`, etc.
This special formulation makes it more clear to maintainers that the variable is
accessed concurrently in a lock-free manner.
diff --git a/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java b/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java
index 1b2a3ebebaf0..f99635a9dee1 100644
--- a/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java
+++ b/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 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,11 +28,9 @@
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
-import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
-import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -40,6 +38,7 @@
import java.util.Set;
import java.util.TreeSet;
+import sun.security.util.CurveDB;
import sun.security.util.DerInputStream;
import sun.security.util.DerOutputStream;
import sun.security.util.DerValue;
@@ -140,11 +139,8 @@ private static List altEncodings(X509Certificate c)
List algIds = Arrays.asList(seq[1], altAlgId(seq[1]));
List sigs;
- PublicKey p = c.getPublicKey();
- if (p instanceof ECPublicKey) {
- ECPublicKey ep = (ECPublicKey) p;
- BigInteger mod = ep.getParams().getOrder();
- sigs = Arrays.asList(seq[2], altSig(mod, seq[2]));
+ if (c.getSigAlgName().contains("ECDSA")) {
+ sigs = Arrays.asList(seq[2], altSig(seq[2]));
} else {
sigs = Arrays.asList(seq[2]);
}
@@ -163,14 +159,27 @@ private static List altEncodings(X509Certificate c)
return result;
}
- private static DerValue altSig(BigInteger mod, DerValue sig)
+ private static DerValue altSig(DerValue sig)
throws IOException {
byte[] sigBits = sig.getBitString();
+ int sigLength = sigBits.length;
DerInputStream in =
- new DerInputStream(sigBits, 0, sigBits.length, false);
+ new DerInputStream(sigBits, 0, sigLength, false);
DerValue[] values = in.getSequence(2);
BigInteger r = values[0].getBigInteger();
BigInteger s = values[1].getBigInteger();
+ BigInteger mod;
+ // The following checks cover more than 99.99999% of signature lengths
+ // for the three NIST-P curves. Fail if the length lies outside.
+ if (sigLength >= 100 && sigLength <= 104) {
+ mod = CurveDB.P_384.getOrder();
+ } else if (sigLength >= 68 && sigLength <= 72) {
+ mod = CurveDB.P_256.getOrder();
+ } else if (sigLength >= 135 && sigLength <= 139) {
+ mod = CurveDB.P_521.getOrder();
+ } else {
+ throw new RuntimeException("Unrecognized signature length: " + sigLength);
+ }
BigInteger s2 = s.negate().mod(mod);
DerOutputStream out = new DerOutputStream();
out.putInteger(r);
diff --git a/make/modules/java.sql.rowset/Java.gmk b/make/modules/java.sql.rowset/Java.gmk
index ecfe3e6e6412..9326a59220d1 100644
--- a/make/modules/java.sql.rowset/Java.gmk
+++ b/make/modules/java.sql.rowset/Java.gmk
@@ -29,6 +29,7 @@ DISABLED_WARNINGS_java += dangling-doc-comments
DOCLINT += -Xdoclint:all/protected \
'-Xdoclint/package:java.*,javax.*'
+COPY += .xsd
CLEAN_FILES += $(wildcard \
$(TOPDIR)/src/java.sql.rowset/share/classes/com/sun/rowset/*.properties \
$(TOPDIR)/src/java.sql.rowset/share/classes/javax/sql/rowset/*.properties)
diff --git a/make/modules/jdk.jpackage/Lib.gmk b/make/modules/jdk.jpackage/Lib.gmk
index 86b11bdafeea..cee8309c679d 100644
--- a/make/modules/jdk.jpackage/Lib.gmk
+++ b/make/modules/jdk.jpackage/Lib.gmk
@@ -55,8 +55,8 @@ $(eval $(call SetupJdkExecutable, BUILD_JPACKAGEAPPLAUNCHER, \
EXTRA_SRC := common, \
INCLUDE_FILES := $(JPACKAGEAPPLAUNCHER_INCLUDE_FILES), \
OPTIMIZATION := SIZE, \
+ DISABLED_WARNINGS_clang_ExecCommand.c := format-nonliteral, \
DISABLED_WARNINGS_clang_JvmLauncherLib.c := format-nonliteral, \
- DISABLED_WARNINGS_clang_LinuxPackage.c := format-nonliteral, \
DISABLED_WARNINGS_clang_Log.cpp := unused-const-variable, \
CFLAGS_FILTER_OUT := -MD, \
CXXFLAGS_FILTER_OUT := -MD, \
@@ -93,6 +93,7 @@ ifeq ($(call isTargetOs, linux), true)
LINK_TYPE := C++, \
OPTIMIZATION := SIZE, \
DISABLED_WARNINGS_gcc_Log.cpp := unused-const-variable, \
+ DISABLED_WARNINGS_clang_ExecCommand.c := format-nonliteral, \
DISABLED_WARNINGS_clang_JvmLauncherLib.c := format-nonliteral, \
DISABLED_WARNINGS_clang_tstrings.cpp := format-nonliteral, \
LD_SET_ORIGIN := false, \
diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp
index 745a3f58a7fc..c9d5d9b4dedb 100644
--- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp
@@ -2379,13 +2379,14 @@ void LIR_Assembler::store_parameter(jobject o, int offset_from_rsp_in_words) {
}
void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) {
+ assert(Arguments::is_valhalla_enabled(), "Value types must be enabled");
if (null_check) {
__ cbz(obj, *slow_path->entry());
}
if (is_dest) {
__ test_null_free_array_oop(obj, tmp, *slow_path->entry());
- __ test_flat_array_oop(obj, tmp, *slow_path->entry());
- } else {
+ }
+ if (UseArrayFlattening) {
__ test_flat_array_oop(obj, tmp, *slow_path->entry());
}
}
@@ -2467,11 +2468,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) {
}
// Handle value type arrays
- if (flags & LIR_OpArrayCopy::src_valuetype_check) {
- arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
- }
- if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
- arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ if (Arguments::is_valhalla_enabled()) {
+ if (flags & LIR_OpArrayCopy::src_valuetype_check) {
+ arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
+ }
+ if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
+ arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ }
}
assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point");
@@ -2989,6 +2992,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) {
}
void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) {
+ assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled");
Register obj = op->obj()->as_register();
Register tmp = op->tmp()->as_pointer_register();
bool not_null = op->not_null();
diff --git a/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp b/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp
index 9c8c7393354d..e3140a7ba6ed 100644
--- a/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp
@@ -35,9 +35,6 @@ void CodeBuffer::share_trampoline_for(address dest, int caller_offset) {
bool created;
Offsets* offsets = _shared_trampoline_requests->put_if_absent(dest, &created);
- if (created) {
- _shared_trampoline_requests->maybe_grow();
- }
offsets->push(caller_offset);
_finalize_stubs = true;
}
diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
index e3af4f150f76..51d3959e4753 100644
--- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp
@@ -549,7 +549,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce,
Register obj = stub->obj()->as_register();
if (stub->do_load()) {
- ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, /* wide = */ false);
+ ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), /* wide = */ false);
}
__ cbz(obj, *stub->continuation());
ce->store_parameter(obj, 0);
diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp
index c2bf73092cc5..caa75144893c 100644
--- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp
@@ -44,6 +44,7 @@
#include "prims/jvmtiThreadState.hpp"
#include "runtime/basicLock.hpp"
#include "runtime/frame.inline.hpp"
+#include "runtime/globals.hpp"
#include "runtime/javaThread.hpp"
#include "runtime/safepointMechanism.hpp"
#include "runtime/sharedRuntime.hpp"
@@ -211,10 +212,12 @@ void InterpreterMacroAssembler::get_method_counters(Register method,
}
void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry);
}
void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
assert_different_registers(entry, tmp1, tmp2, obj);
Label slow_path, done;
@@ -1202,19 +1205,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re
mov(tmp, array);
profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset())));
- Label not_flat;
- test_non_flat_array_oop(array, tmp, not_flat);
+ if (Arguments::is_valhalla_enabled()) {
+ if (UseArrayFlattening) {
+ Label not_flat;
+ test_non_flat_array_oop(array, tmp, not_flat);
- set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant());
+ set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant());
- bind(not_flat);
+ bind(not_flat);
+ }
- Label not_null_free;
- test_non_null_free_array_oop(array, tmp, not_null_free);
+ Label not_null_free;
+ test_non_null_free_array_oop(array, tmp, not_null_free);
- set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant());
+ set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant());
- bind(not_null_free);
+ bind(not_null_free);
+ }
bind(profile_continue);
}
@@ -1278,6 +1285,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp,
Register left,
Register right,
Register tmp) {
+ assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types");
if (ProfileInterpreter) {
Label profile_continue;
diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
index dd9215c55bd6..0077a2daab5f 100644
--- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp
@@ -2404,6 +2404,7 @@ void MacroAssembler::null_check(Register reg, int offset) {
}
void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
assert_different_registers(markword, rscratch2);
mov(rscratch2, markWord::value_type_pattern_mask);
andr(markword, markword, rscratch2);
@@ -2413,6 +2414,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va
}
void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
assert_different_registers(tmp, rscratch1);
if (can_be_null) {
cbz(object, not_value_type);
@@ -2426,16 +2428,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L
}
void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86
tbnz(flags, ResolvedFieldEntry::is_null_free_value_type_shift, is_null_free_value_type);
}
void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86
tbz(flags, ResolvedFieldEntry::is_null_free_value_type_shift, not_null_free_value_type);
}
void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) {
+ assert(UseFieldFlattening, "should not be used without field flattening enabled");
assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86
tbnz(flags, ResolvedFieldEntry::is_flat_shift, is_flat);
}
@@ -2452,23 +2457,28 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int
}
void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array);
}
void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg,
Label&is_non_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array);
}
void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array);
}
void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array);
}
void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
tst(lh, Klass::_lh_array_tag_flat_value_bit_inplace);
br(Assembler::NE, is_flat_array);
}
@@ -5695,11 +5705,13 @@ void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators,
}
void MacroAssembler::payload_offset(Register value_klass, Register offset) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
ldr(offset, Address(value_klass, ValueKlass::adr_members_offset()));
ldrw(offset, Address(offset, ValueKlass::payload_offset_offset()));
}
void MacroAssembler::payload_address(Register oop, Register data, Register value_klass) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
// ((address) (void*) o) + vk->payload_offset();
Register offset = (data == oop) ? rscratch1 : data;
payload_offset(value_klass, offset);
@@ -5887,7 +5899,11 @@ void MacroAssembler::adrp(Register reg1, const Address &dest, uint64_t &byte_off
code_section()->relocate(inst_mark(), dest.rspec());
// 8143067: Ensure that the adrp can reach the dest from anywhere within
// the code cache so that if it is relocated we know it will still reach
- if (offset_high >= -(1<<20) && offset_low < (1<<20)) {
+ // For AOT code force far distance for external_word relocation.
+ const bool force_far = AOTCodeCache::is_on_for_dump() &&
+ (dest.rspec().type() == relocInfo::external_word_type) &&
+ !CodeCache::contains(dest.target());
+ if (!force_far && offset_high >= -(1<<20) && offset_low < (1<<20)) {
_adrp(reg1, dest.target());
} else {
uint64_t target = (uint64_t)dest.target();
@@ -7152,6 +7168,7 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i
// Move a value between registers/stack slots and update the reg_state
bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
assert(from->is_valid() && to->is_valid(), "source and destination must be valid");
if (reg_state[to->value()] == reg_written) {
return true; // Already written
@@ -7214,6 +7231,7 @@ bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState re
// Calculate the extra stack space required for packing or unpacking value
// args and adjust the stack pointer
int MacroAssembler::extend_stack_for_value_args(int args_on_stack) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
int sp_inc = args_on_stack * VMRegImpl::stack_slot_size;
sp_inc = align_up(sp_inc, StackAlignmentInBytes);
assert(sp_inc > 0, "sanity");
@@ -7245,6 +7263,7 @@ int MacroAssembler::extend_stack_for_value_args(int args_on_stack) {
bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index,
VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index,
RegState reg_state[]) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
assert(sig->at(sig_index)._bt == T_VOID, "should be at end delimiter");
assert(from->is_valid(), "source must be valid");
bool progress = false;
@@ -7391,6 +7410,7 @@ bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int
bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index,
VMRegPair* from, int from_count, int& from_index, VMReg to,
RegState reg_state[], Register val_array) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
assert(sig->at(sig_index)._bt == T_METADATA, "should be at delimiter");
assert(to->is_valid(), "destination must be valid");
diff --git a/src/hotspot/cpu/aarch64/matcher_aarch64.hpp b/src/hotspot/cpu/aarch64/matcher_aarch64.hpp
index 42bb61f6889a..ede5a4814950 100644
--- a/src/hotspot/cpu/aarch64/matcher_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/matcher_aarch64.hpp
@@ -130,11 +130,6 @@
return true;
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return false;
diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
index 92f604c507e5..b59c23489bea 100644
--- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
@@ -2621,10 +2621,12 @@ class StubGenerator: public StubCodeGenerator {
__ eor(rscratch2, rscratch2, scratch_src_klass);
__ cbnz(rscratch2, L_failed);
- if (Arguments::is_valhalla_enabled()) {
+ if (UseArrayFlattening) {
// Check for flat value type array -> return -1
__ test_flat_array_oop(src, rscratch2, L_failed);
+ }
+ if (Arguments::is_valhalla_enabled()) {
// Check for null-free (non-flat) value type array -> handle as object array
__ test_null_free_array_oop(src, rscratch2, L_objArray);
}
diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp
index f1ab668d211d..0f8b21f47927 100644
--- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp
@@ -29,9 +29,9 @@
#include "gc/shared/barrierSetAssembler.hpp"
#include "gc/shared/collectedHeap.hpp"
#include "gc/shared/tlab_globals.hpp"
+#include "interpreter/interp_masm.hpp"
#include "interpreter/interpreter.hpp"
#include "interpreter/interpreterRuntime.hpp"
-#include "interpreter/interp_masm.hpp"
#include "interpreter/templateTable.hpp"
#include "memory/universe.hpp"
#include "oops/methodData.hpp"
@@ -45,6 +45,7 @@
#include "prims/methodHandles.hpp"
#include "runtime/arguments.hpp"
#include "runtime/frame.inline.hpp"
+#include "runtime/globals.hpp"
#include "runtime/sharedRuntime.hpp"
#include "runtime/stubRoutines.hpp"
#include "runtime/synchronizer.hpp"
@@ -2020,12 +2021,14 @@ void TemplateTable::if_acmp(Condition cc) {
Label taken, not_taken;
__ pop_ptr(r1);
- __ profile_acmp(r2, r1, r0, r4);
- Register is_value_type_mask = rscratch1;
- __ mov(is_value_type_mask, markWord::value_type_pattern);
if (Arguments::is_valhalla_enabled()) {
+ __ profile_acmp(r2, r1, r0, r4);
+
+ Register is_value_type_mask = rscratch1;
+ __ mov(is_value_type_mask, markWord::value_type_pattern);
+
// The substitutability test is only necessary if r1 and r0 are not the same...
__ cmp(r1, r0);
__ br(Assembler::EQ, (cc == equal) ? taken : not_taken);
@@ -2785,7 +2788,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
__ b(Done);
} else {
Label is_flat;
- __ test_field_is_flat(flags, noreg /* temp */, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(flags, noreg /* temp */, is_flat);
+ }
__ load_heap_oop(r0, field, rscratch1, rscratch2);
__ push(atos);
if (rc == may_rewrite) {
@@ -2793,15 +2798,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
}
__ b(Done);
__ bind(is_flat);
- // field is flat (null-free or nullable with a null-marker)
- __ mov(r0, obj);
- __ read_flat_field(cache, r0);
- __ verify_oop(r0);
- __ push(atos);
- if (rc == may_rewrite) {
- patch_bytecode(Bytecodes::_fast_vgetfield, bc, r1);
+ if (UseFieldFlattening) {
+ // field is flat (null-free or nullable with a null-marker)
+ __ mov(r0, obj);
+ __ read_flat_field(cache, r0);
+ __ verify_oop(r0);
+ __ push(atos);
+ if (rc == may_rewrite) {
+ patch_bytecode(Bytecodes::_fast_vgetfield, bc, r1);
+ }
+ __ b(Done);
}
- __ b(Done);
}
}
@@ -3046,7 +3053,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr
__ b(Done);
} else {
Label null_free_reference, is_flat, rewrite_value;
- __ test_field_is_flat(flags, noreg /* temp */, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(flags, noreg /* temp */, is_flat);
+ }
__ test_field_is_null_free_value_type(flags, noreg /* temp */, null_free_reference);
pop_and_check_object(obj);
// Store into the field
@@ -3065,8 +3074,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr
do_oop_store(_masm, field, r0, IN_HEAP);
__ b(rewrite_value);
__ bind(is_flat);
- pop_and_check_object(r7);
- __ write_flat_field(cache, index, flags, r7);
+ if (UseFieldFlattening) {
+ pop_and_check_object(r7);
+ __ write_flat_field(cache, index, flags, r7);
+ }
__ bind(rewrite_value);
if (rc == may_rewrite) {
patch_bytecode(Bytecodes::_fast_vputfield, bc, r19, true, byte_no);
@@ -3287,17 +3298,25 @@ void TemplateTable::fast_storefield(TosState state)
switch (bytecode()) {
case Bytecodes::_fast_vputfield:
{
- Label is_flat, done;
- __ test_field_is_flat(r5, noreg /* temp */, is_flat);
- __ null_check(r0);
- do_oop_store(_masm, field, r0, IN_HEAP);
- __ b(done);
- __ bind(is_flat);
- __ load_field_entry(r4, r5);
- // Re-shuffle registers because of VM calls calling convention
- __ mov(r7, r2);
- __ write_flat_field(r4, r6, r8, r7);
- __ bind(done);
+ if (Arguments::is_valhalla_enabled()) {
+ Label is_flat, done;
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(r5, noreg /* temp */, is_flat);
+ }
+ __ null_check(r0);
+ do_oop_store(_masm, field, r0, IN_HEAP);
+ __ b(done);
+ __ bind(is_flat);
+ if (UseFieldFlattening) {
+ __ load_field_entry(r4, r5);
+ // Re-shuffle registers because of VM calls calling convention
+ __ mov(r7, r2);
+ __ write_flat_field(r4, r6, r8, r7);
+ }
+ __ bind(done);
+ } else {
+ __ should_not_reach_here();
+ }
}
break;
case Bytecodes::_fast_aputfield:
@@ -3397,9 +3416,13 @@ void TemplateTable::fast_accessfield(TosState state)
switch (bytecode()) {
case Bytecodes::_fast_vgetfield:
{
- // field is flat
- __ read_flat_field(r2, r0);
- __ verify_oop(r0);
+ if (UseFieldFlattening) {
+ // field is flat
+ __ read_flat_field(r2, r0);
+ __ verify_oop(r0);
+ } else {
+ __ should_not_reach_here();
+ }
}
break;
case Bytecodes::_fast_agetfield:
@@ -4104,8 +4127,10 @@ void TemplateTable::monitorenter()
__ null_check(r0);
Label is_value_type;
- __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes()));
- __ test_markword_is_value_type(rscratch1, is_value_type);
+ if (Arguments::is_valhalla_enabled()) {
+ __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes()));
+ __ test_markword_is_value_type(rscratch1, is_value_type);
+ }
const Address monitor_block_top(
rfp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
@@ -4210,9 +4235,11 @@ void TemplateTable::monitorenter()
__ dispatch_next(vtos);
__ bind(is_value_type);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_identity_exception), r0);
- __ should_not_reach_here();
+ if (Arguments::is_valhalla_enabled()) {
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_identity_exception), r0);
+ __ should_not_reach_here();
+ }
}
@@ -4223,17 +4250,19 @@ void TemplateTable::monitorexit()
// check for null object
__ null_check(r0);
- const int is_value_type_mask = markWord::value_type_pattern;
- Label has_identity;
- __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes()));
- __ mov(rscratch2, is_value_type_mask);
- __ andr(rscratch1, rscratch1, rscratch2);
- __ cmp(rscratch1, rscratch2);
- __ br(Assembler::NE, has_identity);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_illegal_monitor_state_exception));
- __ should_not_reach_here();
- __ bind(has_identity);
+ if (Arguments::is_valhalla_enabled()) {
+ const int is_value_type_mask = markWord::value_type_pattern;
+ Label has_identity;
+ __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes()));
+ __ mov(rscratch2, is_value_type_mask);
+ __ andr(rscratch1, rscratch1, rscratch2);
+ __ cmp(rscratch1, rscratch2);
+ __ br(Assembler::NE, has_identity);
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_illegal_monitor_state_exception));
+ __ should_not_reach_here();
+ __ bind(has_identity);
+ }
const Address monitor_block_top(
rfp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
diff --git a/src/hotspot/cpu/arm/matcher_arm.hpp b/src/hotspot/cpu/arm/matcher_arm.hpp
index a3139d91f302..7980838c5cca 100644
--- a/src/hotspot/cpu/arm/matcher_arm.hpp
+++ b/src/hotspot/cpu/arm/matcher_arm.hpp
@@ -122,11 +122,6 @@
return VM_Version::has_simd();
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return false; // not supported
diff --git a/src/hotspot/cpu/arm/vm_version_arm.hpp b/src/hotspot/cpu/arm/vm_version_arm.hpp
index c400cad24b45..4ade00a5d1fa 100644
--- a/src/hotspot/cpu/arm/vm_version_arm.hpp
+++ b/src/hotspot/cpu/arm/vm_version_arm.hpp
@@ -29,9 +29,6 @@
#include "runtime/globals_extension.hpp"
class VM_Version: public Abstract_VM_Version {
- static bool _has_simd;
- static bool _has_mp_ext;
-
protected:
// Are we done with vm version initialization
static bool _is_initialized;
diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
index ca84fa198462..6834cf1a6870 100644
--- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp
@@ -1778,14 +1778,15 @@ void LIR_Assembler::unwind_op(LIR_Opr exceptionOop) {
void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) {
+ assert(Arguments::is_valhalla_enabled(), "Value types must be enabled");
if (null_check) {
__ cmpdi(CR0, obj, 0);
__ bc_far_optimized(Assembler::bcondCRbiIs1, __ bi0(CR0, Assembler::equal), *slow_path->entry());
}
if (is_dest) {
__ test_null_free_array_oop(obj, tmp, *slow_path->entry(), true);
- __ test_flat_array_oop(obj, tmp, *slow_path->entry(), true);
- } else {
+ }
+ if (UseArrayFlattening) {
__ test_flat_array_oop(obj, tmp, *slow_path->entry(), true);
}
}
@@ -1853,12 +1854,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) {
return;
}
- // Handle value type arrays
- if (flags & LIR_OpArrayCopy::src_valuetype_check) {
- arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
- }
- if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
- arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ if (Arguments::is_valhalla_enabled()) {
+ // Handle value type arrays
+ if (flags & LIR_OpArrayCopy::src_valuetype_check) {
+ arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
+ }
+ if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
+ arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ }
}
assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point");
@@ -3075,6 +3078,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) {
}
void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) {
+ assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled");
Register obj = op->obj()->as_register();
//Register tmp = op->tmp()->as_pointer_register(); not needed!
LIR_Address* mdo_addr = op->mdp()->as_address_ptr();
diff --git a/src/hotspot/cpu/ppc/c2_init_ppc.cpp b/src/hotspot/cpu/ppc/c2_init_ppc.cpp
index 3c524f3b80e9..d4174192fe91 100644
--- a/src/hotspot/cpu/ppc/c2_init_ppc.cpp
+++ b/src/hotspot/cpu/ppc/c2_init_ppc.cpp
@@ -1,6 +1,6 @@
/*
- * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2012, 2024 SAP SE. All rights reserved.
+ * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2026 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,6 +23,7 @@
*
*/
+#include "asm/macroAssembler.hpp"
#include "opto/compile.hpp"
#include "opto/node.hpp"
#include "runtime/globals.hpp"
@@ -34,4 +35,7 @@
void Compile::pd_compiler2_init() {
guarantee(CodeEntryAlignment >= InteriorEntryAlignment, "");
+#ifdef _LP64
+ MacroAssembler::initialize_compressed_oops_fit_in_31_bits();
+#endif
}
diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp
index a393f6fbb118..cc45d60f3295 100644
--- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp
@@ -763,7 +763,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce,
// If 'do_load()' returns false, the to-be-stored value is already available in 'obj'
if (stub->do_load()) {
- ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, false);
+ ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), false);
}
// Fast path: reference is null.
diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
index 4226b3dbdbcd..57febf78fed2 100644
--- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
+++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp
@@ -1509,15 +1509,19 @@ template void InterpreterMacroAssembler::profile_array_type(Re
profile_obj_type(array, R28_mdx, in_bytes(ArrayData::array_offset()), tmp1, tmp2);
- Label not_flat;
- test_non_flat_array_oop(array, tmp1, not_flat);
- set_mdp_flag_at(ArrayData::flat_array_byte_constant(), tmp1);
- bind(not_flat);
+ if (Arguments::is_valhalla_enabled()) {
+ if (UseArrayFlattening) {
+ Label not_flat;
+ test_non_flat_array_oop(array, tmp1, not_flat);
+ set_mdp_flag_at(ArrayData::flat_array_byte_constant(), tmp1);
+ bind(not_flat);
+ }
- Label not_null_free;
- test_non_null_free_array_oop(array, tmp1, not_null_free);
- set_mdp_flag_at(ArrayData::null_free_array_byte_constant(), tmp1);
- bind(not_null_free);
+ Label not_null_free;
+ test_non_null_free_array_oop(array, tmp1, not_null_free);
+ set_mdp_flag_at(ArrayData::null_free_array_byte_constant(), tmp1);
+ bind(not_null_free);
+ }
bind(profile_continue);
}
@@ -1579,6 +1583,7 @@ void InterpreterMacroAssembler::profile_acmp(Register left,
Register right,
Register tmp1,
Register tmp2) {
+ assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types");
if (ProfileInterpreter) {
Label profile_continue;
assert_different_registers(left, right, tmp1, tmp2);
@@ -2431,11 +2436,13 @@ void InterpreterMacroAssembler::notify_method_exit(bool is_native_method, TosSta
}
void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry);
}
void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2,
Register obj, Register value) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
assert_different_registers(entry, tmp1, tmp2, obj, value);
Label slow_path, done;
diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
index e3294587f0d8..c58c1b248742 100644
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp
@@ -202,36 +202,27 @@ address MacroAssembler::get_address_of_calculate_address_from_global_toc_at(addr
}
#ifdef _LP64
-// Patch compressed oops or klass constants.
-// Assembler sequence is
-// 1) compressed oops:
-// lis rx = const.hi
-// ori rx = rx | const.lo
-// 2) compressed klass:
-// lis rx = const.hi
-// clrldi rx = rx & 0xFFFFffff // clearMS32b, optional
-// ori rx = rx | const.lo
-// Clrldi will be passed by.
-address MacroAssembler::patch_set_narrow_oop(address a, address bound, narrowOop data) {
+bool MacroAssembler::_compressed_oops_fit_in_31_bits = false;
+
+void MacroAssembler::initialize_compressed_oops_fit_in_31_bits() {
+ if (UseCompressedOops) {
+ address max_oop = align_down(CompressedOops::end() - 1, MinObjAlignmentInBytes);
+ narrowOop max_encoded_oop = CompressedOops::encode(cast_to_oop(max_oop));
+ _compressed_oops_fit_in_31_bits = Assembler::is_uimm((jlong)max_encoded_oop, 31);
+ }
+}
+
+// Patch compressed oop constants.
+// The relocation points to the ori of an adjacent lis/ori pair.
+//
+// lis rx = const.hi
+// ori rx = rx | const.lo
+address MacroAssembler::patch_set_narrow_oop(address a, address lower_bound, narrowOop data) {
assert(UseCompressedOops, "Should only patch compressed oops");
const address inst2_addr = a;
- const int inst2 = *(int *)inst2_addr;
-
- // The relocation points to the second instruction, the ori,
- // and the ori reads and writes the same register dst.
- const int dst = inv_rta_field(inst2);
- assert(is_ori(inst2) && inv_rs_field(inst2) == dst, "must be ori reading and writing dst");
- // Now, find the preceding addis which writes to dst.
- int inst1 = 0;
- address inst1_addr = inst2_addr - BytesPerInstWord;
- bool inst1_found = false;
- while (inst1_addr >= bound) {
- inst1 = *(int *)inst1_addr;
- if (is_lis(inst1) && inv_rs_field(inst1) == dst) { inst1_found = true; break; }
- inst1_addr -= BytesPerInstWord;
- }
- assert(inst1_found, "inst is not lis");
+ assert(is_set_narrow_oop(inst2_addr, lower_bound), "must be lis/ori narrow oop constant");
+ const address inst1_addr = inst2_addr - BytesPerInstWord;
uint32_t data_value = CompressedOops::narrow_oop_value(data);
int xc = (data_value >> 16) & 0xffff;
@@ -243,27 +234,12 @@ address MacroAssembler::patch_set_narrow_oop(address a, address bound, narrowOop
}
// Get compressed oop constant.
-narrowOop MacroAssembler::get_narrow_oop(address a, address bound) {
+narrowOop MacroAssembler::get_narrow_oop(address a, address lower_bound) {
assert(UseCompressedOops, "Should only patch compressed oops");
const address inst2_addr = a;
- const int inst2 = *(int *)inst2_addr;
-
- // The relocation points to the second instruction, the ori,
- // and the ori reads and writes the same register dst.
- const int dst = inv_rta_field(inst2);
- assert(is_ori(inst2) && inv_rs_field(inst2) == dst, "must be ori reading and writing dst");
- // Now, find the preceding lis which writes to dst.
- int inst1 = 0;
- address inst1_addr = inst2_addr - BytesPerInstWord;
- bool inst1_found = false;
-
- while (inst1_addr >= bound) {
- inst1 = *(int *) inst1_addr;
- if (is_lis(inst1) && inv_rs_field(inst1) == dst) { inst1_found = true; break;}
- inst1_addr -= BytesPerInstWord;
- }
- assert(inst1_found, "inst is not lis");
+ assert(is_set_narrow_oop(inst2_addr, lower_bound), "must be lis/ori narrow oop constant");
+ const address inst1_addr = inst2_addr - BytesPerInstWord;
uint xl = ((unsigned int) (get_imm(inst2_addr, 0) & 0xffff));
uint xh = (((get_imm(inst1_addr, 0)) & 0xffff) << 16);
@@ -1278,7 +1254,7 @@ void MacroAssembler::post_call_nop() {
relocate(post_call_nop_Relocation::spec());
InlineSkippedInstructionsCounter skipCounter(this);
Assembler::emit_int32(Assembler::CMPLI_OPCODE | Assembler::opp_u_field(1, 9, 9));
- assert(is_post_call_nop(*(int*)(pc() - 4)), "post call not not found");
+ assert(is_post_call_nop(*(int*)(pc() - 4)), "post call nop not found");
}
int MacroAssembler::ic_check_size() {
@@ -3286,6 +3262,7 @@ void MacroAssembler::load_method_holder(Register holder, Register method) {
}
void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
assert_different_registers(markword, R0);
andi(R0, markword, markWord::value_type_pattern_mask);
cmpwi(CR0, R0, markWord::value_type_pattern);
@@ -3293,6 +3270,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va
}
void MacroAssembler::test_oop_is_not_value_type(Register object, Label& not_value_type, bool can_be_null) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
if (can_be_null) {
cmpdi(CR0, object, 0);
beq(CR0, not_value_type);
@@ -3304,16 +3282,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Label& not_valu
}
void MacroAssembler::test_field_is_null_free_value_type(Register flags, Label& is_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_null_free_value_type_shift);
bne(CR0, is_null_free_value_type);
}
void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Label& not_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_null_free_value_type_shift);
beq(CR0, not_null_free_value_type);
}
void MacroAssembler::test_field_is_flat(Register flags, Label& is_flat) {
+ assert(UseFieldFlattening, "should not be used without field flattening enabled");
testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_flat_shift);
bne(CR0, is_flat);
}
@@ -3336,22 +3317,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int
}
void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array, bool maybe_far) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array, maybe_far);
}
void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array);
}
void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array, bool maybe_far) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array, maybe_far);
}
void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label& is_non_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array);
}
void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
testbitdi(CR0, R0, lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace));
bne(CR0, is_flat_array);
}
@@ -3365,11 +3351,13 @@ void MacroAssembler::load_metadata(Register dst, Register src) {
}
void MacroAssembler::payload_offset(Register value_klass, Register offset) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
ld(offset, in_bytes(ValueKlass::adr_members_offset()), value_klass);
lwz(offset, in_bytes(ValueKlass::payload_offset_offset()), offset);
}
void MacroAssembler::payload_address(Register oop, Register data, Register value_klass, Register t1) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
// ((address) (void*) o) + vk->payload_offset();
payload_offset(value_klass, t1);
add(data, oop, t1);
diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
index d336ef81dd70..1bb1290ff252 100644
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp
@@ -131,11 +131,19 @@ class MacroAssembler: public Assembler {
static address get_address_of_calculate_address_from_global_toc_at(address a, address addr);
#ifdef _LP64
+ private:
+ static bool _compressed_oops_fit_in_31_bits;
+
+ public:
+ static void initialize_compressed_oops_fit_in_31_bits();
+ inline static bool compressed_oops_fit_in_31_bits() {
+ return _compressed_oops_fit_in_31_bits;
+ }
// Patch narrow oop constant.
- inline static bool is_set_narrow_oop(address a, address bound);
+ inline static bool is_set_narrow_oop(address a, address lower_bound);
// Returns address of first instruction in sequence.
- static address patch_set_narrow_oop(address a, address bound, narrowOop data);
- static narrowOop get_narrow_oop(address a, address bound);
+ static address patch_set_narrow_oop(address a, address lower_bound, narrowOop data);
+ static narrowOop get_narrow_oop(address a, address lower_bound);
#endif
inline static bool is_load_const_at(address a);
diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp
index cdeb8527beaa..440b8e72dbec 100644
--- a/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp
+++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp
@@ -137,7 +137,7 @@ inline bool MacroAssembler::is_calculate_address_from_global_toc_at(address a, a
#ifdef _LP64
// Detect narrow oop constants.
-inline bool MacroAssembler::is_set_narrow_oop(address a, address bound) {
+inline bool MacroAssembler::is_set_narrow_oop(address a, address lower_bound) {
const address inst2_addr = a;
const int inst2 = *(int *)a;
// The relocation points to the second instruction, the ori.
@@ -147,15 +147,11 @@ inline bool MacroAssembler::is_set_narrow_oop(address a, address bound) {
const int dst = inv_rta_field(inst2);
if (inv_rs_field(inst2) != dst) return false;
- // Now, find the preceding addis which writes to dst.
- int inst1 = 0;
- address inst1_addr = inst2_addr - BytesPerInstWord;
- while (inst1_addr >= bound) {
- inst1 = *(int *) inst1_addr;
- if (is_lis(inst1) && inv_rs_field(inst1) == dst) return true;
- inst1_addr -= BytesPerInstWord;
- }
- return false;
+ // The preceding instruction must be the lis which writes dst.
+ const address inst1_addr = inst2_addr - BytesPerInstWord;
+ if (inst1_addr < lower_bound) return false;
+ const int inst1 = *(int *) inst1_addr;
+ return is_lis(inst1) && inv_rs_field(inst1) == dst;
}
#endif
diff --git a/src/hotspot/cpu/ppc/matcher_ppc.hpp b/src/hotspot/cpu/ppc/matcher_ppc.hpp
index 209d317a0335..356682bb760b 100644
--- a/src/hotspot/cpu/ppc/matcher_ppc.hpp
+++ b/src/hotspot/cpu/ppc/matcher_ppc.hpp
@@ -130,11 +130,6 @@
return false;
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return false;
diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad
index 1126d8884618..a3c36163e466 100644
--- a/src/hotspot/cpu/ppc/ppc.ad
+++ b/src/hotspot/cpu/ppc/ppc.ad
@@ -3776,6 +3776,15 @@ operand immN() %{
interface(CONST_INTER);
%}
+operand immN_31bit() %{
+ predicate(MacroAssembler::compressed_oops_fit_in_31_bits());
+ match(ConN);
+
+ op_cost(5);
+ format %{ %}
+ interface(CONST_INTER);
+%}
+
// nullptr Pointer Immediate
operand immN_0() %{
predicate(n->get_narrowcon() == 0);
@@ -5678,6 +5687,21 @@ instruct loadBase(iRegLdst dst) %{
ins_pipe(pipe_class_default);
%}
+instruct loadConN_31bit(iRegNdst dst, immN_31bit src) %{
+ match(Set dst src);
+ ins_cost(DEFAULT_COST*2);
+
+ format %{ "LIS/ORI $dst, $src" %}
+ size(8);
+ ins_encode %{
+ AddressLiteral addrlit = __ constant_oop_address((jobject)$src$$constant);
+ __ lis($dst$$Register, 0); // Will get patched.
+ __ relocate(addrlit.rspec(), /*compressed format*/ 1);
+ __ ori($dst$$Register, $dst$$Register, 0); // Will get patched.
+ %}
+ ins_pipe(pipe_class_default);
+%}
+
instruct loadConN(iRegNdst dst, immN src) %{
match(Set dst src);
ins_cost(DEFAULT_COST*3);
diff --git a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp
index cbb3af06c8a5..75c3a69c81f7 100644
--- a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp
+++ b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp
@@ -2594,10 +2594,14 @@ class StubGenerator: public StubCodeGenerator {
__ bne(CR5, L_failed);
// Check for flat value type array -> return -1
- __ test_flat_array_oop(src, temp, L_failed);
+ if (UseArrayFlattening) {
+ __ test_flat_array_oop(src, temp, L_failed);
+ }
// Check for null-free (non-flat) value type array -> handle as object array
- __ test_null_free_array_oop(src, temp, L_objArray);
+ if (Arguments::is_valhalla_enabled()) {
+ __ test_null_free_array_oop(src, temp, L_objArray);
+ }
__ cmpwi(CR6, lh, Klass::_lh_neutral_value); // if (!src->is_Array()) return -1;
__ bge(CR6, L_failed);
@@ -2695,6 +2699,17 @@ class StubGenerator: public StubCodeGenerator {
__ cmpw(CR0, lh, temp);
__ bne(CR0, L_failed);
+#ifdef ASSERT
+ if (Arguments::is_valhalla_enabled()) {
+ BLOCK_COMMENT("assert not null-free array {");
+ Label L;
+ __ test_non_null_free_array_oop(dst, temp, L);
+ __ stop("unexpected null-free array");
+ __ bind(L);
+ BLOCK_COMMENT("} assert not null-free array");
+ }
+#endif
+
// It is safe to examine both src.length and dst.length.
arraycopy_range_checks(src, src_pos, dst, dst_pos, length,
temp, lh, L_failed);
diff --git a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp
index 5ee4e94afe99..2040b2f5a4ec 100644
--- a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp
+++ b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp
@@ -1866,10 +1866,10 @@ void TemplateTable::if_acmp(Condition cc) {
__ pop_ptr(Rfirst);
- __ profile_acmp(Rfirst, Rsecond, R11_scratch1, R12_scratch2);
-
- const int is_value_type_mask = markWord::value_type_pattern;
if (Arguments::is_valhalla_enabled()) {
+ __ profile_acmp(Rfirst, Rsecond, R11_scratch1, R12_scratch2);
+
+ const int is_value_type_mask = markWord::value_type_pattern;
Label taken, not_taken;
__ cmpd(CR0, Rfirst, Rsecond);
__ beq(CR0, (cc == equal) ? taken : not_taken);
@@ -2852,7 +2852,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
__ push(atos);
} else {
Label is_flat;
- __ test_field_is_flat(Rflags, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(Rflags, is_flat);
+ }
do_oop_load(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, /* nv temp */ Rflags, IN_HEAP);
__ verify_oop(R17_tos);
__ push(atos);
@@ -2863,13 +2865,15 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
__ dispatch_epilog(vtos, Bytecodes::length_for(bytecode()));
__ bind(is_flat);
- // field is flat (null-free or nullable with a null-marker)
- __ mr(R17_tos, Rclass_or_obj);
- __ read_flat_field(Rcache, R17_tos);
- __ verify_oop(R17_tos);
- __ push(atos);
- if (rc == may_rewrite) {
- patch_bytecode(Bytecodes::_fast_vgetfield, Rbc, Rscratch);
+ if (UseFieldFlattening) {
+ // field is flat (null-free or nullable with a null-marker)
+ __ mr(R17_tos, Rclass_or_obj);
+ __ read_flat_field(Rcache, R17_tos);
+ __ verify_oop(R17_tos);
+ __ push(atos);
+ if (rc == may_rewrite) {
+ patch_bytecode(Bytecodes::_fast_vgetfield, Rbc, Rscratch);
+ }
}
}
}
@@ -3215,7 +3219,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr
do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP);
} else {
Label null_free_reference, is_flat, rewrite_value;
- __ test_field_is_flat(Rflags, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(Rflags, is_flat);
+ }
__ test_field_is_null_free_value_type(Rflags, null_free_reference);
pop_and_check_object(Rclass_or_obj);
// Store into the field
@@ -3237,8 +3243,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr
__ b(rewrite_value);
__ bind(is_flat);
- pop_and_check_object(Rclass_or_obj);
- __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos);
+ if (UseFieldFlattening) {
+ pop_and_check_object(Rclass_or_obj);
+ __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos);
+ }
__ bind(rewrite_value);
if (rc == may_rewrite) {
patch_bytecode(Bytecodes::_fast_vputfield, Rbc, Rscratch, true, byte_no);
@@ -3316,14 +3324,22 @@ void TemplateTable::fast_storefield(TosState state) {
switch(bytecode()) {
case Bytecodes::_fast_vputfield:
{
- Label is_flat, done;
- __ test_field_is_flat(Rflags, is_flat);
- __ null_check_throw(R17_tos, -1, Rscratch);
- do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP);
- __ b(done);
- __ bind(is_flat);
- __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos);
- __ bind(done);
+ if (Arguments::is_valhalla_enabled()) {
+ Label is_flat, done;
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(Rflags, is_flat);
+ }
+ __ null_check_throw(R17_tos, -1, Rscratch);
+ do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP);
+ __ b(done);
+ __ bind(is_flat);
+ if (UseFieldFlattening) {
+ __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos);
+ }
+ __ bind(done);
+ } else {
+ __ should_not_reach_here();
+ }
break;
}
@@ -3404,17 +3420,22 @@ void TemplateTable::fast_accessfield(TosState state) {
switch(bytecode()) {
case Bytecodes::_fast_vgetfield:
{
- // field is flat
- __ read_flat_field(Rcache, R17_tos);
- __ verify_oop(R17_tos);
- __ dispatch_epilog(state, Bytecodes::length_for(bytecode()));
-
- __ bind(LisVolatile);
- if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ fence(); }
- __ read_flat_field(Rcache, R17_tos);
- __ verify_oop(R17_tos);
- __ twi_0(R17_tos);
- __ isync();
+ if (UseFieldFlattening) {
+ // field is flat
+ __ read_flat_field(Rcache, R17_tos);
+ __ verify_oop(R17_tos);
+ __ dispatch_epilog(state, Bytecodes::length_for(bytecode()));
+
+ __ bind(LisVolatile);
+ if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ fence(); }
+ __ read_flat_field(Rcache, R17_tos);
+ __ verify_oop(R17_tos);
+ __ twi_0(R17_tos);
+ __ isync();
+ } else {
+ __ bind(LisVolatile);
+ __ should_not_reach_here();
+ }
break;
}
case Bytecodes::_fast_agetfield:
@@ -4317,8 +4338,10 @@ void TemplateTable::monitorenter() {
__ null_check_throw(Robj_to_lock, -1, Rscratch1);
Label is_value_type;
- __ ld(Rscratch1, oopDesc::mark_offset_in_bytes(), Robj_to_lock);
- __ test_markword_is_value_type(Rscratch1, is_value_type);
+ if (Arguments::is_valhalla_enabled()) {
+ __ ld(Rscratch1, oopDesc::mark_offset_in_bytes(), Robj_to_lock);
+ __ test_markword_is_value_type(Rscratch1, is_value_type);
+ }
// Check if any slot is present => short cut to allocation if not.
__ cmpld(CR0, Rcurrent_monitor, Rbot);
@@ -4378,9 +4401,11 @@ void TemplateTable::monitorenter() {
__ dispatch_next(vtos);
__ bind(is_value_type);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_identity_exception), Robj_to_lock);
- __ should_not_reach_here();
+ if (Arguments::is_valhalla_enabled()) {
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_identity_exception), Robj_to_lock);
+ __ should_not_reach_here();
+ }
}
void TemplateTable::monitorexit() {
@@ -4404,11 +4429,13 @@ void TemplateTable::monitorexit() {
// Null pointer check.
__ null_check_throw(Robj_to_lock, -1, Rscratch);
- const int is_value_type_mask = markWord::value_type_pattern;
- __ ld(Rscratch, oopDesc::mark_offset_in_bytes(), Robj_to_lock);
- __ andi(Rscratch, Rscratch, is_value_type_mask);
- __ cmpwi(CR0, Rscratch, is_value_type_mask);
- __ beq(CR0, Lillegal_monitor_state);
+ if (Arguments::is_valhalla_enabled()) {
+ const int is_value_type_mask = markWord::value_type_pattern;
+ __ ld(Rscratch, oopDesc::mark_offset_in_bytes(), Robj_to_lock);
+ __ andi(Rscratch, Rscratch, is_value_type_mask);
+ __ cmpwi(CR0, Rscratch, is_value_type_mask);
+ __ beq(CR0, Lillegal_monitor_state);
+ }
// Check corner case: unbalanced monitorEnter / Exit.
__ cmpld(CR0, Rcurrent_monitor, Rbot);
diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp
index 97d049c428b1..70cccefb2ea5 100644
--- a/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp
@@ -241,13 +241,14 @@ void LIR_Assembler::arraycopy_type_check(Register src, Register src_pos, Registe
}
void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) {
+ assert(Arguments::is_valhalla_enabled(), "Value types must be enabled");
if (null_check) {
__ beqz(obj, *slow_path->entry(), /* is_far */ true);
}
if (is_dest) {
__ test_null_free_array_oop(obj, tmp, *slow_path->entry());
- __ test_flat_array_oop(obj, tmp, *slow_path->entry());
- } else {
+ }
+ if (UseArrayFlattening) {
__ test_flat_array_oop(obj, tmp, *slow_path->entry());
}
}
@@ -278,11 +279,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) {
return;
}
- if (flags & LIR_OpArrayCopy::src_valuetype_check) {
- arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
- }
- if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
- arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ if (Arguments::is_valhalla_enabled()) {
+ if (flags & LIR_OpArrayCopy::src_valuetype_check) {
+ arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
+ }
+ if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
+ arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ }
}
assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(),
diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp
index a105faf9137a..f73e41bcb4ba 100644
--- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp
@@ -1322,6 +1322,7 @@ void LIR_Assembler::emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op
}
void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) {
+ assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled");
Register obj = op->obj()->as_register();
Register tmp = op->tmp()->as_pointer_register();
bool not_null = op->not_null();
@@ -1390,10 +1391,12 @@ void LIR_Assembler::emit_compare_and_swap(LIR_OpCompareAndSwap* op) {
}
}
-void LIR_Assembler::intrinsic_op(LIR_Code code, LIR_Opr value, LIR_Opr unused, LIR_Opr dest, LIR_Op* op) {
+void LIR_Assembler::intrinsic_op(LIR_Code code, LIR_Opr value, LIR_Opr tmp, LIR_Opr dest, LIR_Op* op) {
switch (code) {
case lir_abs: __ fabs_d(dest->as_double_reg(), value->as_double_reg()); break;
case lir_sqrt: __ fsqrt_d(dest->as_double_reg(), value->as_double_reg()); break;
+ case lir_f2hf: __ flt_to_flt16(dest->as_register(), value->as_float_reg(), tmp->as_float_reg()); break;
+ case lir_hf2f: __ flt16_to_flt(dest->as_float_reg(), value->as_register()); break;
default: ShouldNotReachHere();
}
}
@@ -1701,6 +1704,7 @@ void LIR_Assembler::emit_updatecrc32(LIR_OpUpdateCRC32* op) {
__ zext(crc, crc, 32);
__ update_byte_crc32(crc, val, res);
__ notr(res, crc); // ~crc
+ __ sext(res, res, 32);
}
void LIR_Assembler::check_conflict(ciKlass* exact_klass, intptr_t current_klass,
diff --git a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp
index a3594d15feea..e37726b6632a 100644
--- a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp
@@ -640,7 +640,9 @@ void LIRGenerator::do_MathIntrinsic(Intrinsic* x) {
break;
case vmIntrinsics::_dabs: // fall through
case vmIntrinsics::_dsqrt: // fall through
- case vmIntrinsics::_dsqrt_strict: {
+ case vmIntrinsics::_dsqrt_strict: // fall through
+ case vmIntrinsics::_floatToFloat16: // fall through
+ case vmIntrinsics::_float16ToFloat: {
assert(x->number_of_arguments() == 1, "wrong type");
LIRItem value(x->argument_at(0), this);
value.load_item();
@@ -656,6 +658,17 @@ void LIRGenerator::do_MathIntrinsic(Intrinsic* x) {
__ abs(value.result(), dst, LIR_OprFact::illegalOpr);
break;
}
+ case vmIntrinsics::_floatToFloat16: {
+ // flt_to_flt16 needs a float temporary to move the result through.
+ LIR_Opr tmp = new_register(T_FLOAT);
+ __ f2hf(value.result(), dst, tmp);
+ break;
+ }
+ case vmIntrinsics::_float16ToFloat: {
+ // flt16_to_flt only needs t0/t1, which are not visible to the allocator.
+ __ hf2f(value.result(), dst, LIR_OprFact::illegalOpr);
+ break;
+ }
default:
ShouldNotReachHere();
}
diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
index 72a20ca50012..7d412087753e 100644
--- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp
@@ -2108,6 +2108,40 @@ void C2_MacroAssembler::enc_cmove(int cmpFlag, Register op1, Register op2, Regis
}
}
+// Set dst to 0 if the condition specified by cmpFlag is true, or 1 otherwise.
+// cmpFlag may include unsigned_branch_mask for an unsigned comparison.
+void C2_MacroAssembler::enc_cmove_zero_one(int cmpFlag, Register op1, Register op2, Register dst) {
+ bool is_unsigned = (cmpFlag & unsigned_branch_mask) == unsigned_branch_mask;
+ int op_select = cmpFlag & (~unsigned_branch_mask);
+
+ switch (op_select) {
+ case BoolTest::eq:
+ xorr(dst, op1, op2);
+ snez(dst, dst);
+ break;
+ case BoolTest::ne:
+ xorr(dst, op1, op2);
+ seqz(dst, dst);
+ break;
+ case BoolTest::lt:
+ is_unsigned ? sltu(dst, op1, op2) : slt(dst, op1, op2);
+ seqz(dst, dst);
+ break;
+ case BoolTest::gt:
+ is_unsigned ? sltu(dst, op2, op1) : slt(dst, op2, op1);
+ seqz(dst, dst);
+ break;
+ case BoolTest::le:
+ is_unsigned ? sltu(dst, op2, op1) : slt(dst, op2, op1);
+ break;
+ case BoolTest::ge:
+ is_unsigned ? sltu(dst, op1, op2) : slt(dst, op1, op2);
+ break;
+ default:
+ ShouldNotReachHere();
+ }
+}
+
void C2_MacroAssembler::enc_cmove_cmp_fp(int cmpFlag, FloatRegister op1, FloatRegister op2, Register dst, Register src, bool is_single) {
int op_select = cmpFlag & (~unsigned_branch_mask);
@@ -2213,6 +2247,41 @@ void C2_MacroAssembler::enc_cmove_fp_cmp_fp(int cmpFlag,
}
}
+// Set dst to 0 if the floating-point condition is true, or 1 otherwise.
+// cmpFlag may include double_branch_mask for a double comparison.
+// CmpF/CmpD treat unordered as less: eq/gt/ge are false for NaN, while
+// ne/lt/le are true.
+void C2_MacroAssembler::enc_cmove_cmp_fp_zero_one(int cmpFlag, FloatRegister op1, FloatRegister op2, Register dst) {
+ bool is_single = (cmpFlag & double_branch_mask) == 0;
+ int op_select = cmpFlag & (~double_branch_mask);
+
+ switch (op_select) {
+ case BoolTest::eq:
+ is_single ? feq_s(dst, op1, op2) : feq_d(dst, op1, op2);
+ seqz(dst, dst);
+ break;
+ case BoolTest::ne:
+ is_single ? feq_s(dst, op1, op2) : feq_d(dst, op1, op2);
+ break;
+ case BoolTest::lt:
+ is_single ? fle_s(dst, op2, op1) : fle_d(dst, op2, op1);
+ break;
+ case BoolTest::le:
+ is_single ? flt_s(dst, op2, op1) : flt_d(dst, op2, op1);
+ break;
+ case BoolTest::gt:
+ is_single ? flt_s(dst, op2, op1) : flt_d(dst, op2, op1);
+ seqz(dst, dst);
+ break;
+ case BoolTest::ge:
+ is_single ? fle_s(dst, op2, op1) : fle_d(dst, op2, op1);
+ seqz(dst, dst);
+ break;
+ default:
+ ShouldNotReachHere();
+ }
+}
+
static void vshift_left(C2_MacroAssembler* masm, VectorRegister dst, VectorRegister src, int shift) {
assert(shift >= 0 && shift <= BitsPerInt, "unexpected shift amount");
if (shift < BitsPerInt) {
diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp
index 093f367b7903..1713b60ccc6a 100644
--- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp
+++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp
@@ -131,6 +131,9 @@
Register op1, Register op2,
Register dst, Register src);
+ void enc_cmove_zero_one(int cmpFlag,
+ Register op1, Register op2, Register dst);
+
void enc_cmove_cmp_fp(int cmpFlag,
FloatRegister op1, FloatRegister op2,
Register dst, Register src, bool is_single);
@@ -142,6 +145,10 @@
FloatRegister dst, FloatRegister src,
bool cmp_single, bool cmov_single);
+ void enc_cmove_cmp_fp_zero_one(int cmpFlag,
+ FloatRegister op1, FloatRegister op2,
+ Register dst);
+
void compress_bits_v(VectorRegister dst, VectorRegister src, VectorRegister bit_mask,
VectorRegister src_tmp, VectorRegister mask_tmp,
VectorRegister tmp1, VectorRegister tmp2,
diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
index 0d1f603b9cc8..40489bd32cb8 100644
--- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp
@@ -568,7 +568,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce,
Register obj = stub->obj()->as_register();
if (stub->do_load()) {
- ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, false /* wide */);
+ ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), false /* wide */);
}
__ beqz(obj, *stub->continuation(), /* is_far */ true);
diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
index 3204f081db6f..9c9e636b5b23 100644
--- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
+++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp
@@ -44,6 +44,7 @@
#include "prims/jvmtiThreadState.hpp"
#include "runtime/basicLock.hpp"
#include "runtime/frame.inline.hpp"
+#include "runtime/globals.hpp"
#include "runtime/javaThread.hpp"
#include "runtime/safepointMechanism.hpp"
#include "runtime/sharedRuntime.hpp"
@@ -1203,19 +1204,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re
mv(tmp, array);
profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset())), t1);
- Label not_flat;
- test_non_flat_array_oop(array, tmp, not_flat);
+ if (Arguments::is_valhalla_enabled()) {
+ if (UseArrayFlattening) {
+ Label not_flat;
+ test_non_flat_array_oop(array, tmp, not_flat);
- set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant());
+ set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant());
- bind(not_flat);
+ bind(not_flat);
+ }
- Label not_null_free;
- test_non_null_free_array_oop(array, tmp, not_null_free);
+ Label not_null_free;
+ test_non_null_free_array_oop(array, tmp, not_null_free);
- set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant());
+ set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant());
- bind(not_null_free);
+ bind(not_null_free);
+ }
bind(profile_continue);
}
@@ -1278,6 +1283,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp,
Register left,
Register right,
Register tmp) {
+ assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types");
if (ProfileInterpreter) {
Label profile_continue;
@@ -1867,11 +1873,13 @@ void InterpreterMacroAssembler::get_method_counters(Register method,
}
void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry);
}
void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1,
Register tmp2, Register obj) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
assert_different_registers(entry, tmp1, tmp2, obj);
Label slow_path, done;
diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
index 733464b8b269..8e383ea9e544 100644
--- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
+++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp
@@ -3792,7 +3792,7 @@ void MacroAssembler::cmp_klass_bne(Register obj, Register klass,
}
// Move an oop into a register.
-void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) {
+void MacroAssembler::movoop(Register dst, jobject obj) {
int oop_index;
if (obj == nullptr) {
oop_index = oop_recorder()->allocate_oop_index(obj);
@@ -3808,7 +3808,7 @@ void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) {
RelocationHolder rspec = oop_Relocation::spec(oop_index);
if (BarrierSet::barrier_set()->barrier_set_assembler()->supports_instruction_patching()) {
- movptr(dst, Address((address)obj, rspec), tmp);
+ movptr(dst, Address((address)obj, rspec));
} else {
address dummy = address(uintptr_t(pc()) & -wordSize); // A nearby aligned address
ld(dst, Address(dummy, rspec));
@@ -3816,7 +3816,7 @@ void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) {
}
// Move a metadata address into a register.
-void MacroAssembler::mov_metadata(Register dst, Metadata* obj, Register tmp) {
+void MacroAssembler::mov_metadata(Register dst, Metadata* obj) {
assert((uintptr_t)obj < (1ull << 48), "48-bit overflow in metadata");
int oop_index;
if (obj == nullptr) {
@@ -3825,15 +3825,17 @@ void MacroAssembler::mov_metadata(Register dst, Metadata* obj, Register tmp) {
oop_index = oop_recorder()->find_index(obj);
}
RelocationHolder rspec = metadata_Relocation::spec(oop_index);
- movptr(dst, Address((address)obj, rspec), tmp);
+ movptr(dst, Address((address)obj, rspec));
}
void MacroAssembler::payload_offset(Register value_klass, Register offset) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
ld(offset, Address(value_klass, ValueKlass::adr_members_offset()));
lwu(offset, Address(offset, ValueKlass::payload_offset_offset()));
}
void MacroAssembler::payload_address(Register oop, Register data, Register value_klass) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
assert_different_registers(data, t0);
// ((address) (void*) o) + vk->payload_offset();
Register offset = (data == oop) ? t0 : data;
@@ -3932,21 +3934,25 @@ void MacroAssembler::null_check(Register reg, int offset) {
}
void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_bit(temp_reg, flags, ResolvedFieldEntry::is_null_free_value_type_shift);
bnez(temp_reg, is_null_free_value_type);
}
void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_bit(temp_reg, flags, ResolvedFieldEntry::is_null_free_value_type_shift);
beqz(temp_reg, not_null_free_value_type);
}
void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) {
+ assert(UseFieldFlattening, "should not be used without field flattening enabled");
test_bit(temp_reg, flags, ResolvedFieldEntry::is_flat_shift);
bnez(temp_reg, is_flat);
}
void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
assert_different_registers(markword, t1);
mv(t1, markWord::value_type_pattern_mask);
andr(markword, markword, t1);
@@ -3955,6 +3961,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va
}
void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
assert_different_registers(tmp, t0);
if (can_be_null) {
beqz(object, not_value_type);
@@ -3979,22 +3986,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int
}
void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array);
}
void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array);
}
void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label&is_non_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array);
}
void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array);
}
void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_bit(t0, lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace));
bnez(t0, is_flat_array);
}
@@ -6873,6 +6885,66 @@ void MacroAssembler::float_to_float16_NaN(Register dst, FloatRegister src,
orr(dst, tmp2, tmp1);
}
+// j.l.Float.float16ToFloat
+void MacroAssembler::flt16_to_flt(FloatRegister dst, Register src) {
+ assert_different_registers(src, t0, t1);
+ Label L_NaN_Inf, L_done;
+
+ // On riscv, NaN needs a special process as fcvt does not work in that case.
+ // On riscv, Inf does not need a special process as fcvt can handle it correctly.
+ // but we consider to get the slow path to process NaN and Inf at the same time,
+ // as both of them are rare cases, and if we try to get the slow path to handle
+ // only NaN case it would sacrifise the performance for normal cases,
+ // i.e. non-NaN and non-Inf cases.
+
+ // check whether it's a NaN or +/- Inf.
+ mv(t0, 0x7c00);
+ andr(t1, src, t0);
+ beq(t0, t1, L_NaN_Inf);
+
+ // non-NaN or non-Inf cases, just use built-in instructions.
+ fmv_h_x(dst, src);
+ fcvt_s_h(dst, dst);
+ j(L_done);
+
+ bind(L_NaN_Inf);
+ // construct a NaN in 32 bits from the NaN in 16 bits,
+ // we need the payloads of non-canonical NaNs to be preserved.
+ mv(t1, 0x7f800000);
+ // The upper 16 bits of a short argument are unspecified. Sign-extend the
+ // low 16 bits before shifting so that the float sign bit comes from the
+ // float16 sign bit.
+ sext(t0, src, 16);
+ slli(t0, t0, 13);
+ orr(t1, t0, t1);
+ fmv_w_x(dst, t1);
+
+ bind(L_done);
+}
+
+// j.l.Float.floatToFloat16
+void MacroAssembler::flt_to_flt16(Register dst, FloatRegister src, FloatRegister ftmp) {
+ assert_different_registers(dst, t0, t1);
+ Label L_NaN, L_done;
+
+ // On riscv, NaN needs a special process as fcvt does not work in that case.
+
+ // check whether it's a NaN.
+ // replace fclass with feq as performance optimization.
+ feq_s(t0, src, src);
+ beqz(t0, L_NaN);
+
+ // non-NaN cases, just use built-in instructions.
+ fcvt_h_s(ftmp, src);
+ fmv_x_h(dst, ftmp);
+ j(L_done);
+
+ bind(L_NaN);
+ float_to_float16_NaN(dst, src, t0, t1);
+
+ bind(L_done);
+}
+
#define FCVT_SAFE(FLOATCVT, FLOATSIG) \
void MacroAssembler::FLOATCVT##_safe(Register dst, FloatRegister src, Register tmp) { \
Label done; \
diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
index 7a65ce5a27e5..de1352a1215a 100644
--- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
+++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp
@@ -191,8 +191,8 @@ class MacroAssembler: public Assembler {
void resolve_jobject(Register value, Register tmp1, Register tmp2);
void resolve_global_jobject(Register value, Register tmp1, Register tmp2);
- void movoop(Register dst, jobject obj, Register tmp = noreg);
- void mov_metadata(Register dst, Metadata* obj, Register tmp = noreg);
+ void movoop(Register dst, jobject obj);
+ void mov_metadata(Register dst, Metadata* obj);
void bang_stack_size(Register size, Register tmp);
void set_narrow_oop(Register dst, jobject obj);
void set_narrow_klass(Register dst, Klass* k);
@@ -1560,6 +1560,13 @@ INSN(sd_release, sd, sd_rl);
// Helper routine processing the slow path of NaN when converting float to float16
void float_to_float16_NaN(Register dst, FloatRegister src, Register tmp1, Register tmp2);
+ // j.l.Float.float16ToFloat and j.l.Float.floatToFloat16, with the NaN handling
+ // emitted inline. C2 uses C2_MacroAssembler::float16_to_float() and
+ // C2_MacroAssembler::float_to_float16() instead, which keep it out of line.
+ // Both clobber t0 and t1.
+ void flt16_to_flt(FloatRegister dst, Register src);
+ void flt_to_flt16(Register dst, FloatRegister src, FloatRegister ftmp);
+
// vector load/store unit-stride instructions
void vlex_v(VectorRegister vd, Register base, Assembler::SEW sew, VectorMask vm = unmasked) {
switch (sew) {
@@ -1598,11 +1605,15 @@ INSN(sd_release, sd, sd_rl);
}
// vector pseudo instructions
- // rotate vector register left with shift bits, 32-bit version
+ // Rotate 32-bit vector elements left. The caller must configure SEW=32.
inline void vrole32_vi(VectorRegister vd, uint32_t shift, VectorRegister tmp_vr) {
- vsrl_vi(tmp_vr, vd, 32 - shift);
- vsll_vi(vd, vd, shift);
- vor_vv(vd, vd, tmp_vr);
+ if (UseZvbb) {
+ vror_vi(vd, vd, 32 - shift);
+ } else {
+ vsrl_vi(tmp_vr, vd, 32 - shift);
+ vsll_vi(vd, vd, shift);
+ vor_vv(vd, vd, tmp_vr);
+ }
}
inline void vl1r_v(VectorRegister vd, Register rs) {
diff --git a/src/hotspot/cpu/riscv/matcher_riscv.hpp b/src/hotspot/cpu/riscv/matcher_riscv.hpp
index bb3d2d015f2d..570bfbf711ad 100644
--- a/src/hotspot/cpu/riscv/matcher_riscv.hpp
+++ b/src/hotspot/cpu/riscv/matcher_riscv.hpp
@@ -129,11 +129,6 @@
return UseRVV;
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static bool supports_vector_variable_rotates(void) {
return UseZvbb;
diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad
index 8dfc989846e5..5513eafa7762 100644
--- a/src/hotspot/cpu/riscv/riscv.ad
+++ b/src/hotspot/cpu/riscv/riscv.ad
@@ -2372,9 +2372,9 @@ encode %{
} else {
relocInfo::relocType rtype = $src->constant_reloc();
if (rtype == relocInfo::oop_type) {
- __ movoop(dst_reg, (jobject)con, t0);
+ __ movoop(dst_reg, (jobject)con);
} else if (rtype == relocInfo::metadata_type) {
- __ mov_metadata(dst_reg, (Metadata*)con, t0);
+ __ mov_metadata(dst_reg, (Metadata*)con);
} else {
assert(rtype == relocInfo::none || rtype == relocInfo::external_word_type, "unexpected reloc type");
// Load fake address constants as a plain immediate. They are not real
@@ -10870,6 +10870,23 @@ instruct far_cmpP_narrowOop_imm0_branch(cmpOpEqNe cmp, iRegN op1, immP0 zero, la
// --------- CMoveI ---------
+instruct cmovI_cmpI_cmpL_zero_one(iRegINoSp dst, iRegIorL op1, iRegIorL op2,
+ cmpOp cop, immI_1 one, immI0 zero) %{
+ match(Set dst (CMoveI (Binary cop (CmpI op1 op2)) (Binary one zero)));
+ match(Set dst (CMoveI (Binary cop (CmpL op1 op2)) (Binary one zero)));
+
+ ins_cost(ALU_COST * 2);
+ format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpI_cmpL_zero_one" %}
+
+ ins_encode %{
+ __ enc_cmove_zero_one($cop$$cmpcode,
+ as_Register($op1$$reg), as_Register($op2$$reg),
+ as_Register($dst$$reg));
+ %}
+
+ ins_pipe(pipe_class_compare);
+%}
+
instruct cmovI_cmpI(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOp cop) %{
match(Set dst (CMoveI (Binary cop (CmpI op1 op2)) (Binary dst src)));
ins_cost(ALU_COST + BRANCH_COST);
@@ -10887,6 +10904,23 @@ instruct cmovI_cmpI(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOp cop) %
ins_pipe(pipe_class_compare);
%}
+instruct cmovI_cmpU_cmpUL_zero_one(iRegINoSp dst, iRegIorL op1, iRegIorL op2,
+ cmpOpU cop, immI_1 one, immI0 zero) %{
+ match(Set dst (CMoveI (Binary cop (CmpU op1 op2)) (Binary one zero)));
+ match(Set dst (CMoveI (Binary cop (CmpUL op1 op2)) (Binary one zero)));
+
+ ins_cost(ALU_COST * 2);
+ format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpU_cmpUL_zero_one" %}
+
+ ins_encode %{
+ __ enc_cmove_zero_one($cop$$cmpcode | C2_MacroAssembler::unsigned_branch_mask,
+ as_Register($op1$$reg), as_Register($op2$$reg),
+ as_Register($dst$$reg));
+ %}
+
+ ins_pipe(pipe_class_compare);
+%}
+
instruct cmovI_cmpU(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOpU cop) %{
match(Set dst (CMoveI (Binary cop (CmpU op1 op2)) (Binary dst src)));
ins_cost(ALU_COST + BRANCH_COST);
@@ -10938,6 +10972,22 @@ instruct cmovI_cmpUL(iRegINoSp dst, iRegI src, iRegL op1, iRegL op2, cmpOpU cop)
ins_pipe(pipe_class_compare);
%}
+instruct cmovI_cmpF_zero_one(iRegINoSp dst, fRegF op1, fRegF op2,
+ cmpOp cop, immI_1 one, immI0 zero) %{
+ match(Set dst (CMoveI (Binary cop (CmpF op1 op2)) (Binary one zero)));
+
+ ins_cost(ALU_COST * 2);
+ format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpF_zero_one" %}
+
+ ins_encode %{
+ __ enc_cmove_cmp_fp_zero_one($cop$$cmpcode,
+ as_FloatRegister($op1$$reg), as_FloatRegister($op2$$reg),
+ as_Register($dst$$reg));
+ %}
+
+ ins_pipe(pipe_class_compare);
+%}
+
instruct cmovI_cmpF(iRegINoSp dst, iRegI src, fRegF op1, fRegF op2, cmpOp cop) %{
match(Set dst (CMoveI (Binary cop (CmpF op1 op2)) (Binary dst src)));
ins_cost(ALU_COST + BRANCH_COST);
@@ -10955,6 +11005,22 @@ instruct cmovI_cmpF(iRegINoSp dst, iRegI src, fRegF op1, fRegF op2, cmpOp cop) %
ins_pipe(pipe_class_compare);
%}
+instruct cmovI_cmpD_zero_one(iRegINoSp dst, fRegD op1, fRegD op2,
+ cmpOp cop, immI_1 one, immI0 zero) %{
+ match(Set dst (CMoveI (Binary cop (CmpD op1 op2)) (Binary one zero)));
+
+ ins_cost(ALU_COST * 2);
+ format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpD_zero_one" %}
+
+ ins_encode %{
+ __ enc_cmove_cmp_fp_zero_one($cop$$cmpcode | C2_MacroAssembler::double_branch_mask,
+ as_FloatRegister($op1$$reg), as_FloatRegister($op2$$reg),
+ as_Register($dst$$reg));
+ %}
+
+ ins_pipe(pipe_class_compare);
+%}
+
instruct cmovI_cmpD(iRegINoSp dst, iRegI src, fRegD op1, fRegD op2, cmpOp cop) %{
match(Set dst (CMoveI (Binary cop (CmpD op1 op2)) (Binary dst src)));
ins_cost(ALU_COST + BRANCH_COST);
diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad
index f7951a4a2d12..09c790f6dd10 100644
--- a/src/hotspot/cpu/riscv/riscv_v.ad
+++ b/src/hotspot/cpu/riscv/riscv_v.ad
@@ -47,6 +47,36 @@ source %{
}
}
+ // test_predicate selects the mask reduction; cmpcode selects the outer eq/ne condition.
+ static void vectorTestCMove(C2_MacroAssembler* masm, int cmpcode, Register dst, Register src,
+ VectorRegister mask, BasicType bt, uint vector_length, BoolTest::mask test_predicate) {
+ assert(test_predicate == BoolTest::ne || test_predicate == BoolTest::overflow, "unsupported predicate");
+ assert(cmpcode == BoolTest::eq || cmpcode == BoolTest::ne, "unsupported condition");
+ __ vsetvli_helper(bt, vector_length);
+ __ vcpop_m(t0, mask);
+ if (test_predicate == BoolTest::overflow) {
+ // Subtract vector_length so we can compare against zr without
+ // materializing vector_length in another register.
+ __ sub(t0, t0, vector_length);
+ }
+ __ enc_cmove(cmpcode, t0, zr, dst, src);
+ }
+
+ static void vectorTestCMove(C2_MacroAssembler* masm, int cmpcode, FloatRegister dst, FloatRegister src,
+ VectorRegister mask, BasicType bt, uint vector_length,
+ BoolTest::mask test_predicate, bool is_single) {
+ assert(test_predicate == BoolTest::ne || test_predicate == BoolTest::overflow, "unsupported predicate");
+ assert(cmpcode == BoolTest::eq || cmpcode == BoolTest::ne, "unsupported condition");
+ __ vsetvli_helper(bt, vector_length);
+ __ vcpop_m(t0, mask);
+ if (test_predicate == BoolTest::overflow) {
+ // Subtract vector_length so we can compare against zr without
+ // materializing vector_length in another register.
+ __ sub(t0, t0, vector_length);
+ }
+ __ enc_cmove_fp_cmp(cmpcode, t0, zr, dst, src, is_single);
+ }
+
bool Matcher::match_rule_supported_auto_vectorization(int opcode, int vlen, BasicType bt) {
if (opcode == Op_MulReductionVF || opcode == Op_MulReductionVD) {
return false;
@@ -101,15 +131,6 @@ source %{
return false;
}
break;
- case Op_VectorLoadShuffle:
- case Op_VectorRearrange:
- // vlen >= 4 is required, because min vector size for byte is 4 on riscv,
- // VectorLoadShuffle is from byte to X, so it requires vlen >= 4.
- // VectorRearrange depends on VectorLoadShuffle, so it also requires vlen >= 4.
- if (vlen < 4) {
- return false;
- }
- break;
case Op_MulReductionVI:
case Op_MulReductionVL:
// When vlen < 4, our log2(vlen) implementation does not help to gain performance improvement.
@@ -6155,6 +6176,115 @@ instruct cmovI_vtest_alltrue_negate(iRegINoSp dst, cmpOp cop, vRegMask op1, vReg
ins_pipe(pipe_slow);
%}
+// CMove with register values and a condition produced by VectorTest.
+//
+// The two rules above directly materialize a boolean (0 or 1) from anyTrue()/allTrue().
+// The rules below select between arbitrary register values, using the population
+// count to form the condition passed to the corresponding cmove encoder:
+//
+// anytrue: vcpop.m t0, op1 // t0 != 0 iff some element is set
+// alltrue: vcpop.m t0, op1; sub t0, t0, vl // t0 == 0 iff every element is set
+//
+// The outer Bool condition ($cop) is restricted to eq/ne by cmpOpEqNe.
+// Separate rules distinguish the VectorTest predicates: ne for anyTrue,
+// overflow for allTrue.
+
+instruct cmovI_vtest_anytrue(iRegINoSp dst, iRegI src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{
+ predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne);
+ match(Set dst (CMoveI (Binary cop (VectorTest op1 op2)) (Binary dst src)));
+ format %{ "CMoveI $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovI_vtest_anytrue" %}
+ ins_encode %{
+ vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register,
+ as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1),
+ Matcher::vector_length(this, $op1), BoolTest::ne);
+ %}
+ ins_pipe(pipe_slow);
+%}
+
+instruct cmovI_vtest_alltrue(iRegINoSp dst, iRegI src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{
+ predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow);
+ match(Set dst (CMoveI (Binary cop (VectorTest op1 op2)) (Binary dst src)));
+ format %{ "CMoveI $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovI_vtest_alltrue" %}
+ ins_encode %{
+ vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register,
+ as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1),
+ Matcher::vector_length(this, $op1), BoolTest::overflow);
+ %}
+ ins_pipe(pipe_slow);
+%}
+
+instruct cmovL_vtest_anytrue(iRegLNoSp dst, iRegL src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{
+ predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne);
+ match(Set dst (CMoveL (Binary cop (VectorTest op1 op2)) (Binary dst src)));
+ format %{ "CMoveL $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovL_vtest_anytrue" %}
+ ins_encode %{
+ vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register,
+ as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1),
+ Matcher::vector_length(this, $op1), BoolTest::ne);
+ %}
+ ins_pipe(pipe_slow);
+%}
+
+instruct cmovL_vtest_alltrue(iRegLNoSp dst, iRegL src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{
+ predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow);
+ match(Set dst (CMoveL (Binary cop (VectorTest op1 op2)) (Binary dst src)));
+ format %{ "CMoveL $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovL_vtest_alltrue" %}
+ ins_encode %{
+ vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register,
+ as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1),
+ Matcher::vector_length(this, $op1), BoolTest::overflow);
+ %}
+ ins_pipe(pipe_slow);
+%}
+
+instruct cmovF_vtest_anytrue(fRegF dst, fRegF src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{
+ predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne);
+ match(Set dst (CMoveF (Binary cop (VectorTest op1 op2)) (Binary dst src)));
+ format %{ "CMoveF $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovF_vtest_anytrue" %}
+ ins_encode %{
+ vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister,
+ as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1),
+ Matcher::vector_length(this, $op1), BoolTest::ne, /* is_single */ true);
+ %}
+ ins_pipe(pipe_slow);
+%}
+
+instruct cmovF_vtest_alltrue(fRegF dst, fRegF src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{
+ predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow);
+ match(Set dst (CMoveF (Binary cop (VectorTest op1 op2)) (Binary dst src)));
+ format %{ "CMoveF $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovF_vtest_alltrue" %}
+ ins_encode %{
+ vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister,
+ as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1),
+ Matcher::vector_length(this, $op1), BoolTest::overflow, /* is_single */ true);
+ %}
+ ins_pipe(pipe_slow);
+%}
+
+instruct cmovD_vtest_anytrue(fRegD dst, fRegD src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{
+ predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne);
+ match(Set dst (CMoveD (Binary cop (VectorTest op1 op2)) (Binary dst src)));
+ format %{ "CMoveD $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovD_vtest_anytrue" %}
+ ins_encode %{
+ vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister,
+ as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1),
+ Matcher::vector_length(this, $op1), BoolTest::ne, /* is_single */ false);
+ %}
+ ins_pipe(pipe_slow);
+%}
+
+instruct cmovD_vtest_alltrue(fRegD dst, fRegD src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{
+ predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow);
+ match(Set dst (CMoveD (Binary cop (VectorTest op1 op2)) (Binary dst src)));
+ format %{ "CMoveD $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovD_vtest_alltrue" %}
+ ins_encode %{
+ vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister,
+ as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1),
+ Matcher::vector_length(this, $op1), BoolTest::overflow, /* is_single */ false);
+ %}
+ ins_pipe(pipe_slow);
+%}
+
// anytrue
instruct vtest_anytrue_branch(cmpOpEqNe cop, vRegMask op1, vRegMask op2, label lbl) %{
diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp
index 074f80297aa1..9793dddb68be 100644
--- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp
+++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp
@@ -2247,10 +2247,12 @@ class StubGenerator: public StubCodeGenerator {
__ load_klass(t1, dst);
__ bne(t1, scratch_src_klass, L_failed);
- if (Arguments::is_valhalla_enabled()) {
+ if (UseArrayFlattening) {
// Check for flat value type array -> return -1
__ test_flat_array_oop(src, t1, L_failed);
+ }
+ if (Arguments::is_valhalla_enabled()) {
// Check for null-free (non-flat) value type array -> handle as object array
__ test_null_free_array_oop(src, t1, L_objArray);
}
@@ -5816,10 +5818,11 @@ class StubGenerator: public StubCodeGenerator {
}
}
- // generate load for the i'th register
- void gen_load(uint i, Register base) {
- assert(i < L, "invalid i: %u", i);
- __ ld(_regs[i], Address(base, 8 * i));
+ // Generate loads for all cached registers.
+ void gen_loads(Register base) {
+ for (uint i = 0; i < L; i++) {
+ __ ld(_regs[i], Address(base, 8 * i));
+ }
}
// add i'th 32-bit integer to dest
@@ -5840,72 +5843,84 @@ class StubGenerator: public StubCodeGenerator {
typedef RegCache<8> BufRegCache;
- // a += value + x + ac;
+ // a += (d ^ (b & (c ^ d))) + x + ac;
// a = Integer.rotateLeft(a, s) + b;
- void m5_FF_GG_HH_II_epilogue(BufRegCache& reg_cache,
- Register a, Register b, Register c, Register d,
- int k, int s, int t,
- Register value) {
+ void md5_FF(BufRegCache& reg_cache,
+ Register a, Register b, Register c, Register d,
+ int k, int s, int t,
+ Register rtmp1) {
+ // rtmp1 = c ^ d
+ __ xorr(rtmp1, c, d);
+
// a += ac
__ addw(a, a, t, t1);
- // a += x;
+ // rtmp1 = b & (c ^ d) = b & rtmp1
+ __ andr(rtmp1, b, rtmp1);
+
+ // a += x
reg_cache.add_u32(a, k);
- // a += value;
- __ addw(a, a, value);
- // a = Integer.rotateLeft(a, s) + b;
+ // rtmp1 = d ^ (b & (c ^ d)) = d ^ rtmp1
+ __ xorr(rtmp1, d, rtmp1);
+
+ // a += d ^ (b & (c ^ d))
+ __ addw(a, a, rtmp1);
+
+ // a = Integer.rotateLeft(a, s) + b
__ rolw(a, a, s);
__ addw(a, a, b);
}
- // a += ((b & c) | ((~b) & d)) + x + ac;
+ // a += ((c & (~d)) + (b & d)) + x + ac;
// a = Integer.rotateLeft(a, s) + b;
- void md5_FF(BufRegCache& reg_cache,
+ void md5_GG(BufRegCache& reg_cache,
Register a, Register b, Register c, Register d,
int k, int s, int t,
Register rtmp1, Register rtmp2) {
- // rtmp1 = b & c
- __ andr(rtmp1, b, c);
-
- // rtmp2 = (~b) & d
- __ andn(rtmp2, d, b);
-
- // rtmp1 = (b & c) | ((~b) & d)
- __ orr(rtmp1, rtmp1, rtmp2);
+ // a += x + ac
+ reg_cache.add_u32(a, k);
+ __ addw(a, a, t, t1);
- m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1);
- }
+ // rtmp1 = c & (~d)
+ __ andn(rtmp1, c, d);
- // a += ((b & d) | (c & (~d))) + x + ac;
- // a = Integer.rotateLeft(a, s) + b;
- void md5_GG(BufRegCache& reg_cache,
- Register a, Register b, Register c, Register d,
- int k, int s, int t,
- Register rtmp1, Register rtmp2) {
- // rtmp1 = b & d
- __ andr(rtmp1, b, d);
+ // rtmp2 = b & d
+ __ andr(rtmp2, b, d);
- // rtmp2 = c & (~d)
- __ andn(rtmp2, c, d);
+ // a += c & (~d)
+ __ addw(a, a, rtmp1);
- // rtmp1 = (b & d) | (c & (~d))
- __ orr(rtmp1, rtmp1, rtmp2);
+ // a += b & d
+ __ addw(a, a, rtmp2);
- m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1);
+ // a = Integer.rotateLeft(a, s) + b
+ __ rolw(a, a, s);
+ __ addw(a, a, b);
}
- // a += ((b ^ c) ^ d) + x + ac;
+ // a += (b ^ (c ^ d)) + x + ac;
// a = Integer.rotateLeft(a, s) + b;
void md5_HH(BufRegCache& reg_cache,
Register a, Register b, Register c, Register d,
int k, int s, int t,
- Register rtmp1, Register rtmp2) {
- // rtmp1 = (b ^ c) ^ d
- __ xorr(rtmp2, b, c);
- __ xorr(rtmp1, rtmp2, d);
+ Register rtmp1) {
+ // rtmp1 = c ^ d
+ __ xorr(rtmp1, c, d);
+
+ // a += x + ac
+ reg_cache.add_u32(a, k);
+ __ addw(a, a, t, t1);
+
+ // rtmp1 = b ^ (c ^ d)
+ __ xorr(rtmp1, b, rtmp1);
- m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1);
+ // a += b ^ (c ^ d)
+ __ addw(a, a, rtmp1);
+
+ // a = Integer.rotateLeft(a, s) + b
+ __ rolw(a, a, s);
+ __ addw(a, a, b);
}
// a += (c ^ (b | (~d))) + x + ac;
@@ -5914,11 +5929,20 @@ class StubGenerator: public StubCodeGenerator {
Register a, Register b, Register c, Register d,
int k, int s, int t,
Register rtmp1, Register rtmp2) {
+ // a += x + ac
+ reg_cache.add_u32(a, k);
+ __ addw(a, a, t, t1);
+
// rtmp1 = c ^ (b | (~d))
__ orn(rtmp2, b, d);
__ xorr(rtmp1, c, rtmp2);
- m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1);
+ // a += c ^ (b | (~d))
+ __ addw(a, a, rtmp1);
+
+ // a = Integer.rotateLeft(a, s) + b
+ __ rolw(a, a, s);
+ __ addw(a, a, b);
}
// Arguments:
@@ -6077,31 +6101,25 @@ class StubGenerator: public StubCodeGenerator {
__ mv(c, state2);
__ mv(d, state3);
+ reg_cache.gen_loads(buf);
+
// Round 1
- reg_cache.gen_load(0, buf);
- md5_FF(reg_cache, a, b, c, d, 0, S11, 0xd76aa478, rtmp1, rtmp2);
- md5_FF(reg_cache, d, a, b, c, 1, S12, 0xe8c7b756, rtmp1, rtmp2);
- reg_cache.gen_load(1, buf);
- md5_FF(reg_cache, c, d, a, b, 2, S13, 0x242070db, rtmp1, rtmp2);
- md5_FF(reg_cache, b, c, d, a, 3, S14, 0xc1bdceee, rtmp1, rtmp2);
- reg_cache.gen_load(2, buf);
- md5_FF(reg_cache, a, b, c, d, 4, S11, 0xf57c0faf, rtmp1, rtmp2);
- md5_FF(reg_cache, d, a, b, c, 5, S12, 0x4787c62a, rtmp1, rtmp2);
- reg_cache.gen_load(3, buf);
- md5_FF(reg_cache, c, d, a, b, 6, S13, 0xa8304613, rtmp1, rtmp2);
- md5_FF(reg_cache, b, c, d, a, 7, S14, 0xfd469501, rtmp1, rtmp2);
- reg_cache.gen_load(4, buf);
- md5_FF(reg_cache, a, b, c, d, 8, S11, 0x698098d8, rtmp1, rtmp2);
- md5_FF(reg_cache, d, a, b, c, 9, S12, 0x8b44f7af, rtmp1, rtmp2);
- reg_cache.gen_load(5, buf);
- md5_FF(reg_cache, c, d, a, b, 10, S13, 0xffff5bb1, rtmp1, rtmp2);
- md5_FF(reg_cache, b, c, d, a, 11, S14, 0x895cd7be, rtmp1, rtmp2);
- reg_cache.gen_load(6, buf);
- md5_FF(reg_cache, a, b, c, d, 12, S11, 0x6b901122, rtmp1, rtmp2);
- md5_FF(reg_cache, d, a, b, c, 13, S12, 0xfd987193, rtmp1, rtmp2);
- reg_cache.gen_load(7, buf);
- md5_FF(reg_cache, c, d, a, b, 14, S13, 0xa679438e, rtmp1, rtmp2);
- md5_FF(reg_cache, b, c, d, a, 15, S14, 0x49b40821, rtmp1, rtmp2);
+ md5_FF(reg_cache, a, b, c, d, 0, S11, 0xd76aa478, rtmp1);
+ md5_FF(reg_cache, d, a, b, c, 1, S12, 0xe8c7b756, rtmp1);
+ md5_FF(reg_cache, c, d, a, b, 2, S13, 0x242070db, rtmp1);
+ md5_FF(reg_cache, b, c, d, a, 3, S14, 0xc1bdceee, rtmp1);
+ md5_FF(reg_cache, a, b, c, d, 4, S11, 0xf57c0faf, rtmp1);
+ md5_FF(reg_cache, d, a, b, c, 5, S12, 0x4787c62a, rtmp1);
+ md5_FF(reg_cache, c, d, a, b, 6, S13, 0xa8304613, rtmp1);
+ md5_FF(reg_cache, b, c, d, a, 7, S14, 0xfd469501, rtmp1);
+ md5_FF(reg_cache, a, b, c, d, 8, S11, 0x698098d8, rtmp1);
+ md5_FF(reg_cache, d, a, b, c, 9, S12, 0x8b44f7af, rtmp1);
+ md5_FF(reg_cache, c, d, a, b, 10, S13, 0xffff5bb1, rtmp1);
+ md5_FF(reg_cache, b, c, d, a, 11, S14, 0x895cd7be, rtmp1);
+ md5_FF(reg_cache, a, b, c, d, 12, S11, 0x6b901122, rtmp1);
+ md5_FF(reg_cache, d, a, b, c, 13, S12, 0xfd987193, rtmp1);
+ md5_FF(reg_cache, c, d, a, b, 14, S13, 0xa679438e, rtmp1);
+ md5_FF(reg_cache, b, c, d, a, 15, S14, 0x49b40821, rtmp1);
// Round 2
md5_GG(reg_cache, a, b, c, d, 1, S21, 0xf61e2562, rtmp1, rtmp2);
@@ -6122,22 +6140,22 @@ class StubGenerator: public StubCodeGenerator {
md5_GG(reg_cache, b, c, d, a, 12, S24, 0x8d2a4c8a, rtmp1, rtmp2);
// Round 3
- md5_HH(reg_cache, a, b, c, d, 5, S31, 0xfffa3942, rtmp1, rtmp2);
- md5_HH(reg_cache, d, a, b, c, 8, S32, 0x8771f681, rtmp1, rtmp2);
- md5_HH(reg_cache, c, d, a, b, 11, S33, 0x6d9d6122, rtmp1, rtmp2);
- md5_HH(reg_cache, b, c, d, a, 14, S34, 0xfde5380c, rtmp1, rtmp2);
- md5_HH(reg_cache, a, b, c, d, 1, S31, 0xa4beea44, rtmp1, rtmp2);
- md5_HH(reg_cache, d, a, b, c, 4, S32, 0x4bdecfa9, rtmp1, rtmp2);
- md5_HH(reg_cache, c, d, a, b, 7, S33, 0xf6bb4b60, rtmp1, rtmp2);
- md5_HH(reg_cache, b, c, d, a, 10, S34, 0xbebfbc70, rtmp1, rtmp2);
- md5_HH(reg_cache, a, b, c, d, 13, S31, 0x289b7ec6, rtmp1, rtmp2);
- md5_HH(reg_cache, d, a, b, c, 0, S32, 0xeaa127fa, rtmp1, rtmp2);
- md5_HH(reg_cache, c, d, a, b, 3, S33, 0xd4ef3085, rtmp1, rtmp2);
- md5_HH(reg_cache, b, c, d, a, 6, S34, 0x04881d05, rtmp1, rtmp2);
- md5_HH(reg_cache, a, b, c, d, 9, S31, 0xd9d4d039, rtmp1, rtmp2);
- md5_HH(reg_cache, d, a, b, c, 12, S32, 0xe6db99e5, rtmp1, rtmp2);
- md5_HH(reg_cache, c, d, a, b, 15, S33, 0x1fa27cf8, rtmp1, rtmp2);
- md5_HH(reg_cache, b, c, d, a, 2, S34, 0xc4ac5665, rtmp1, rtmp2);
+ md5_HH(reg_cache, a, b, c, d, 5, S31, 0xfffa3942, rtmp1);
+ md5_HH(reg_cache, d, a, b, c, 8, S32, 0x8771f681, rtmp1);
+ md5_HH(reg_cache, c, d, a, b, 11, S33, 0x6d9d6122, rtmp1);
+ md5_HH(reg_cache, b, c, d, a, 14, S34, 0xfde5380c, rtmp1);
+ md5_HH(reg_cache, a, b, c, d, 1, S31, 0xa4beea44, rtmp1);
+ md5_HH(reg_cache, d, a, b, c, 4, S32, 0x4bdecfa9, rtmp1);
+ md5_HH(reg_cache, c, d, a, b, 7, S33, 0xf6bb4b60, rtmp1);
+ md5_HH(reg_cache, b, c, d, a, 10, S34, 0xbebfbc70, rtmp1);
+ md5_HH(reg_cache, a, b, c, d, 13, S31, 0x289b7ec6, rtmp1);
+ md5_HH(reg_cache, d, a, b, c, 0, S32, 0xeaa127fa, rtmp1);
+ md5_HH(reg_cache, c, d, a, b, 3, S33, 0xd4ef3085, rtmp1);
+ md5_HH(reg_cache, b, c, d, a, 6, S34, 0x04881d05, rtmp1);
+ md5_HH(reg_cache, a, b, c, d, 9, S31, 0xd9d4d039, rtmp1);
+ md5_HH(reg_cache, d, a, b, c, 12, S32, 0xe6db99e5, rtmp1);
+ md5_HH(reg_cache, c, d, a, b, 15, S33, 0x1fa27cf8, rtmp1);
+ md5_HH(reg_cache, b, c, d, a, 2, S34, 0xc4ac5665, rtmp1);
// Round 4
md5_II(reg_cache, a, b, c, d, 0, S41, 0xf4292244, rtmp1, rtmp2);
@@ -7820,6 +7838,7 @@ static const int64_t right_3_bits = right_n_bits(3);
__ kernel_crc32(crc, buf, len,
c_rarg3, c_rarg4, c_rarg5, c_rarg6, // tmp's for tables
c_rarg7, t2, t3, t4, t5, t6); // misc tmps
+ __ sext(crc, crc, 32);
__ leave(); // required for proper stackwalking of RuntimeStub frame
__ ret();
@@ -7857,6 +7876,7 @@ static const int64_t right_3_bits = right_n_bits(3);
c_rarg3, c_rarg4, // byte_table, clmul_table
c_rarg5, c_rarg6, c_rarg7, // accum_hi, k1, k2
t2, t3, t4); // scratch1, scratch2, fold_end
+ __ sext(crc, crc, 32);
__ leave(); // required for proper stackwalking of RuntimeStub frame
__ ret();
diff --git a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp
index a98b9e496671..2848ccf8a133 100644
--- a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp
+++ b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp
@@ -967,9 +967,55 @@ address TemplateInterpreterGenerator::generate_CRC32C_updateBytes_entry(Abstract
return entry;
}
-// Not supported
-address TemplateInterpreterGenerator::generate_Float_float16ToFloat_entry() { return nullptr; }
-address TemplateInterpreterGenerator::generate_Float_floatToFloat16_entry() { return nullptr; }
+/**
+ * Method entry for static method:
+ * java.lang.Float.float16ToFloat(short floatBinary16)
+ */
+address TemplateInterpreterGenerator::generate_Float_float16ToFloat_entry() {
+ assert(VM_Version::supports_float16_float_conversion(), "this intrinsic is not supported");
+ if (StubRoutines::hf2f_adr() == nullptr) {
+ return nullptr;
+ }
+
+ // x19_sender_sp: sender sp
+ // stack:
+ // [ arg ] <-- esp
+ // retaddr in ra
+ // result in f10
+
+ address entry_point = __ pc();
+ __ lh(c_rarg0, Address(esp)); // Load and sign-extend the low 16 bits of the argument
+ __ andi(sp, x19_sender_sp, -16); // Restore caller's SP
+ // The stub is a leaf which returns through 'ra', so it returns straight
+ // back to our caller: this is a tail call.
+ __ far_jump(RuntimeAddress(StubRoutines::hf2f_adr()));
+ return entry_point;
+}
+
+/**
+ * Method entry for static method:
+ * java.lang.Float.floatToFloat16(float value)
+ */
+address TemplateInterpreterGenerator::generate_Float_floatToFloat16_entry() {
+ assert(VM_Version::supports_float16_float_conversion(), "this intrinsic is not supported");
+ if (StubRoutines::f2hf_adr() == nullptr) {
+ return nullptr;
+ }
+
+ // x19_sender_sp: sender sp
+ // stack:
+ // [ arg ] <-- esp
+ // retaddr in ra
+ // result in x10
+
+ address entry_point = __ pc();
+ __ flw(f10, Address(esp));
+ __ andi(sp, x19_sender_sp, -16); // Restore caller's SP
+ // The stub is a leaf which returns through 'ra', so it returns straight
+ // back to our caller: this is a tail call.
+ __ far_jump(RuntimeAddress(StubRoutines::f2hf_adr()));
+ return entry_point;
+}
void TemplateInterpreterGenerator::bang_stack_shadow_pages(bool native_call) {
// See more discussion in stackOverflow.hpp.
diff --git a/src/hotspot/cpu/riscv/templateTable_riscv.cpp b/src/hotspot/cpu/riscv/templateTable_riscv.cpp
index 82b2f0eeb3d9..02ad154bf652 100644
--- a/src/hotspot/cpu/riscv/templateTable_riscv.cpp
+++ b/src/hotspot/cpu/riscv/templateTable_riscv.cpp
@@ -46,6 +46,7 @@
#include "prims/methodHandles.hpp"
#include "runtime/arguments.hpp"
#include "runtime/frame.inline.hpp"
+#include "runtime/globals.hpp"
#include "runtime/sharedRuntime.hpp"
#include "runtime/stubRoutines.hpp"
#include "runtime/synchronizer.hpp"
@@ -1906,12 +1907,12 @@ void TemplateTable::if_acmp(Condition cc) {
Label taken, not_taken;
__ pop_ptr(x11);
- __ profile_acmp(x12, x11, x10, x14);
+ if (Arguments::is_valhalla_enabled()) {
+ __ profile_acmp(x12, x11, x10, x14);
- Register is_value_type_mask = t1;
- __ mv(is_value_type_mask, markWord::value_type_pattern);
+ Register is_value_type_mask = t1;
+ __ mv(is_value_type_mask, markWord::value_type_pattern);
- if (Arguments::is_valhalla_enabled()) {
// The substitutability test is only necessary if x11 and x10 are not the same...
if (cc == equal) {
__ beq(x11, x10, taken);
@@ -2181,7 +2182,6 @@ void TemplateTable::fast_binaryswitch() {
__ load_unsigned_byte(t0, Address(temp, 0));
__ add(xbcp, xbcp, j);
- __ la(xbcp, Address(xbcp, 0));
__ dispatch_only(vtos, /*generate_poll*/true);
// default case -> j = default offset
@@ -2194,7 +2194,6 @@ void TemplateTable::fast_binaryswitch() {
__ load_unsigned_byte(t0, Address(temp, 0));
__ add(xbcp, xbcp, j);
- __ la(xbcp, Address(xbcp, 0));
__ dispatch_only(vtos, /*generate_poll*/true);
}
@@ -2721,7 +2720,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
__ j(Done);
} else {
Label is_flat;
- __ test_field_is_flat(flags, x28, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(flags, x28, is_flat);
+ }
__ load_heap_oop(x10, field, x28, x29);
__ push(atos);
if (rc == may_rewrite) {
@@ -2729,15 +2730,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
}
__ j(Done);
__ bind(is_flat);
- // field is flat (null-free or nullable with a null-marker)
- __ mv(x10, obj);
- __ read_flat_field(cache, x10);
- __ verify_oop(x10);
- __ push(atos);
- if (rc == may_rewrite) {
- patch_bytecode(Bytecodes::_fast_vgetfield, bc, x11);
+ if (UseFieldFlattening) {
+ // field is flat (null-free or nullable with a null-marker)
+ __ mv(x10, obj);
+ __ read_flat_field(cache, x10);
+ __ verify_oop(x10);
+ __ push(atos);
+ if (rc == may_rewrite) {
+ patch_bytecode(Bytecodes::_fast_vgetfield, bc, x11);
+ }
+ __ j(Done);
}
- __ j(Done);
}
}
@@ -2996,7 +2999,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr
__ j(Done);
} else {
Label null_free_reference, is_flat, rewrite_value;
- __ test_field_is_flat(flags, x28, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(flags, x28, is_flat);
+ }
__ test_field_is_null_free_value_type(flags, x28, null_free_reference);
pop_and_check_object(obj);
{
@@ -3022,8 +3027,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr
}
__ j(rewrite_value);
__ bind(is_flat);
- pop_and_check_object(x17);
- __ write_flat_field(cache, index, flags, x17);
+ if (UseFieldFlattening) {
+ pop_and_check_object(x17);
+ __ write_flat_field(cache, index, flags, x17);
+ }
__ bind(rewrite_value);
if (rc == may_rewrite) {
patch_bytecode(Bytecodes::_fast_vputfield, bc, x9, true, byte_no);
@@ -3275,17 +3282,25 @@ void TemplateTable::fast_storefield(TosState state) {
switch (bytecode()) {
case Bytecodes::_fast_vputfield:
{
- Label is_flat, done;
- __ test_field_is_flat(x13, x28, is_flat);
- __ null_check(x10);
- __ store_heap_oop(field, x10, x28, x29, x15, IN_HEAP);
- __ j(done);
- __ bind(is_flat);
- __ load_field_entry(x14, x13);
- // Re-shuffle registers because of VM calls calling convention
- __ mv(x17, x12);
- __ write_flat_field(x14, x16, x18, x17);
- __ bind(done);
+ if (Arguments::is_valhalla_enabled()) {
+ Label is_flat, done;
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(x13, x28, is_flat);
+ }
+ __ null_check(x10);
+ __ store_heap_oop(field, x10, x28, x29, x15, IN_HEAP);
+ __ j(done);
+ __ bind(is_flat);
+ if (UseFieldFlattening) {
+ __ load_field_entry(x14, x13);
+ // Re-shuffle registers because of VM calls calling convention
+ __ mv(x17, x12);
+ __ write_flat_field(x14, x16, x18, x17);
+ }
+ __ bind(done);
+ } else {
+ __ should_not_reach_here();
+ }
}
break;
case Bytecodes::_fast_aputfield:
@@ -3373,9 +3388,13 @@ void TemplateTable::fast_accessfield(TosState state) {
switch (bytecode()) {
case Bytecodes::_fast_vgetfield:
{
- // field is flat
- __ read_flat_field(x12, x10);
- __ verify_oop(x10);
+ if (UseFieldFlattening) {
+ // field is flat
+ __ read_flat_field(x12, x10);
+ __ verify_oop(x10);
+ } else {
+ __ should_not_reach_here();
+ }
}
break;
case Bytecodes::_fast_agetfield:
@@ -4070,8 +4089,10 @@ void TemplateTable::monitorenter() {
__ null_check(x10);
Label is_value_type;
- __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes()));
- __ test_markword_is_value_type(t0, is_value_type);
+ if (Arguments::is_valhalla_enabled()) {
+ __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes()));
+ __ test_markword_is_value_type(t0, is_value_type);
+ }
const Address monitor_block_top(
fp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
@@ -4174,9 +4195,11 @@ void TemplateTable::monitorenter() {
__ dispatch_next(vtos);
__ bind(is_value_type);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_identity_exception), x10);
- __ should_not_reach_here();
+ if (Arguments::is_valhalla_enabled()) {
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_identity_exception), x10);
+ __ should_not_reach_here();
+ }
}
void TemplateTable::monitorexit() {
@@ -4185,16 +4208,18 @@ void TemplateTable::monitorexit() {
// check for null object
__ null_check(x10);
- const int is_value_type_mask = markWord::value_type_pattern;
- Label has_identity;
- __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes()));
- __ mv(t1, is_value_type_mask);
- __ andr(t0, t0, t1);
- __ bne(t0, t1, has_identity);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_illegal_monitor_state_exception));
- __ should_not_reach_here();
- __ bind(has_identity);
+ if (Arguments::is_valhalla_enabled()) {
+ const int is_value_type_mask = markWord::value_type_pattern;
+ Label has_identity;
+ __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes()));
+ __ mv(t1, is_value_type_mask);
+ __ andr(t0, t0, t1);
+ __ bne(t0, t1, has_identity);
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_illegal_monitor_state_exception));
+ __ should_not_reach_here();
+ __ bind(has_identity);
+ }
const Address monitor_block_top(
fp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp
index 58901ae4d361..68e870fee2d0 100644
--- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp
+++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp
@@ -559,10 +559,20 @@ class VM_Version : public Abstract_VM_Version {
static bool supports_fast_class_init_checks() { return true; }
static bool supports_fencei_barrier() { return ext_Zifencei.enabled(); }
+ // Zfhmin only provides half-precision load/store/convert instructions,
+ // Zfh additionally provides half-precision arithmetic. Either one is
+ // enough to convert between float16 and float.
static bool supports_float16_float_conversion() {
return UseZfh || UseZfhmin;
}
+ // Shared code gates the Float.float16ToFloat/floatToFloat16 intrinsics on
+ // this, and those only need the conversion instructions. Note that it does
+ // not imply half-precision arithmetic is available, which needs UseZfh.
+ static bool supports_float16() {
+ return supports_float16_float_conversion();
+ }
+
// Check intrinsic support
static bool is_intrinsic_supported(vmIntrinsicID id);
diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp
index 11c65ec91bc2..f3407d3af1cf 100644
--- a/src/hotspot/cpu/s390/assembler_s390.hpp
+++ b/src/hotspot/cpu/s390/assembler_s390.hpp
@@ -3281,6 +3281,15 @@ class Assembler : public AbstractAssembler {
static bool is_z_nop(address x) {
return is_z_nop(* (short *) x);
}
+ // A post-call NOP is encoded as CLFI SP, imm32 (RIL-a format, 6 bytes).
+ // Byte 0 = 0xC2 (opcode1), byte 1 = 0xFF (R1=SP=15 in bits 39..36,
+ // opcode2=0xF in bits 35..32). The 4-byte imm32 carries the payload.
+ // Result goes only to the condition code, which is caller-scratch after a call.
+ static bool is_post_call_nop(address x) {
+ // Check the first 2 bytes for the CLFI SP opcode signature (0xC2 0xFF).
+ // Only 2 bytes are read to avoid a page-boundary out-of-bounds access.
+ return (*(unsigned short*) x) == (unsigned short)((0xc2 << 8) | 0xff);
+ }
static bool is_z_illtrap(address x) {
return *(uint8_t*)x == 0u;
}
diff --git a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp
index d7799769ff83..d410668306a0 100644
--- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp
+++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp
@@ -2063,11 +2063,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) {
}
// Handle value type arrays
- if (flags & LIR_OpArrayCopy::src_valuetype_check) {
- arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
- }
- if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
- arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ if (Arguments::is_valhalla_enabled()) {
+ if (flags & LIR_OpArrayCopy::src_valuetype_check) {
+ arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
+ }
+ if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
+ arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ }
}
assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point");
@@ -3065,6 +3067,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) {
}
void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) {
+ assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled");
Register obj = op->obj()->as_register();
Register tmp = op->tmp()->as_pointer_register();
LIR_Address* mdo_addr = op->mdp()->as_address_ptr();
@@ -3185,13 +3188,14 @@ void LIR_Assembler::emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op
}
void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) {
+ assert(Arguments::is_valhalla_enabled(), "Value types must be enabled");
if (null_check) {
__ compare64_and_branch(obj, (intptr_t)0, Assembler::bcondEqual, *slow_path->entry());
}
if (is_dest) {
__ test_null_free_array_oop(obj, tmp, *slow_path->entry());
- __ test_flat_array_oop(obj, tmp, *slow_path->entry());
- } else {
+ }
+ if (UseArrayFlattening) {
__ test_flat_array_oop(obj, tmp, *slow_path->entry());
}
}
diff --git a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
index 725d3623128a..a7d186c0517e 100644
--- a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
+++ b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp
@@ -331,7 +331,7 @@ inline void Thaw::patch_caller_links(intptr_t* sp, intptr_t* bottom) {
callers_sp = _cont.entryFP();
} else {
assert(!Interpreter::contains(pc), "sp:" PTR_FORMAT " pc:" PTR_FORMAT, p2i(sp), p2i(pc));
- CodeBlob* cb = CodeCache::find_blob(pc);
+ CodeBlob* cb = CodeCache::find_blob_fast(pc);
callers_sp = sp + cb->frame_size();
}
// set the back link
diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp
index 2946ed60d3e7..9256e9ae0d3b 100644
--- a/src/hotspot/cpu/s390/frame_s390.hpp
+++ b/src/hotspot/cpu/s390/frame_s390.hpp
@@ -489,10 +489,19 @@
// Mark a frame as not fully initialized. Must not be used for frames in the valid back chain.
void mark_not_fully_initialized() const { DEBUG_ONLY(own_abi()->callers_sp = NOT_FULLY_INITIALIZED;) }
+ enum class kind {
+ unknown, // The frame's pc is not necessarily in the CodeCache.
+ // CodeCache::find_blob_fast() can yield wrong results here and must not be used.
+ code_blob, // The frame's pc is known to be in the CodeCache but not likely in an nmethod.
+ // CodeCache::find_blob_fast() is correct but not faster.
+ nmethod // This frame is likely from an nmethod.
+ // CodeCache lookup is optimized via NativePostCallNop payload.
+ };
+
private:
// Initialize frame members (_pc and _sp must be given)
- inline void setup();
+ inline void setup(kind knd);
public:
@@ -500,6 +509,7 @@
inline frame(intptr_t* sp, intptr_t* fp, address pc);
// To be used, if sp was not extended to match callee's calling convention.
inline frame(intptr_t* sp, address pc, intptr_t* unextended_sp = nullptr, intptr_t* fp = nullptr, CodeBlob* cb = nullptr);
+ inline frame(intptr_t* sp, address pc, kind knd);
inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap);
inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map = nullptr);
diff --git a/src/hotspot/cpu/s390/frame_s390.inline.hpp b/src/hotspot/cpu/s390/frame_s390.inline.hpp
index 7acdd01a9c91..08f86de40e21 100644
--- a/src/hotspot/cpu/s390/frame_s390.inline.hpp
+++ b/src/hotspot/cpu/s390/frame_s390.inline.hpp
@@ -35,14 +35,14 @@
// Inline functions for z/Architecture frames:
// Initialize frame members (_sp must be given)
-inline void frame::setup() {
+inline void frame::setup(kind knd) {
if (_pc == nullptr) {
_pc = (address)own_abi()->return_pc;
assert(_pc != nullptr, "must have PC");
}
if (_cb == nullptr) {
- _cb = CodeCache::find_blob(_pc);
+ _cb = (knd == kind::nmethod) ? CodeCache::find_blob_fast(_pc) : CodeCache::find_blob(_pc);
}
if (_unextended_sp == nullptr) {
@@ -99,20 +99,26 @@ inline frame::frame() : _sp(nullptr), _pc(nullptr), _cb(nullptr), _oop_map(nullp
inline frame::frame(intptr_t* sp, address pc, intptr_t* unextended_sp, intptr_t* fp, CodeBlob* cb)
: _sp(sp), _pc(pc), _cb(cb), _oop_map(nullptr),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) {
- setup();
+ setup(kind::nmethod);
}
-inline frame::frame(intptr_t* sp) : frame(sp, nullptr) {}
+inline frame::frame(intptr_t* sp) : frame(sp, nullptr, kind::nmethod) {}
inline frame::frame(intptr_t* sp, intptr_t* fp, address pc)
: _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr), _deopt_state(unknown),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(nullptr), _fp(fp) {
- setup();
+ setup(kind::nmethod);
+}
+
+inline frame::frame(intptr_t* sp, address pc, kind knd)
+ : _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr),
+ _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(sp), _fp(nullptr) {
+ setup(knd);
}
inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map)
:_sp(sp), _pc(pc), _cb(cb), _oop_map(oop_map), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) {
- setup();
+ setup(kind::nmethod);
}
inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap)
@@ -120,7 +126,7 @@ inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address
// In thaw, non-heap frames use this constructor to pass oop_map. I don't know why.
assert(_on_heap || _cb != nullptr, "these frames are always heap frames");
if (cb != nullptr) {
- setup();
+ setup(kind::nmethod);
}
}
@@ -129,7 +135,7 @@ inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address
inline frame::frame(void* sp, void* pc, void* unextended_sp)
: _sp((intptr_t*)sp), _pc((address)pc), _cb(nullptr), _oop_map(nullptr),
_on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp((intptr_t*)unextended_sp) {
- setup();
+ setup(kind::unknown);
}
#endif
@@ -405,7 +411,7 @@ inline frame frame::sender_raw(RegisterMap* map) const {
// Must be native-compiled frame, i.e. the marshaling code for native
// methods that exists in the core system.
- return frame(sender_sp(), sender_pc());
+ return frame(sender_sp(), sender_pc(), kind::code_blob);
}
inline frame frame::sender_for_compiled_frame(RegisterMap *map) const {
@@ -452,7 +458,7 @@ inline frame frame::sender_for_compiled_frame(RegisterMap *map) const {
return Continuation::continuation_bottom_sender(map->thread(), *this, sender_sp);
}
}
- return frame(sender_sp, sender_pc);
+ return frame(sender_sp, sender_pc, kind::nmethod);
}
template
diff --git a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp
index c3775f8038b9..5f0db9eabaa1 100644
--- a/src/hotspot/cpu/s390/interp_masm_s390.cpp
+++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp
@@ -1367,6 +1367,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp,
Register left,
Register right,
Register tmp) {
+ assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types");
if (ProfileInterpreter) {
assert_different_registers(mdp, left, right, tmp);
Label profile_continue;
@@ -1638,15 +1639,19 @@ template void InterpreterMacroAssembler::profile_array_type(Re
profile_obj_type(array, Address(tmp1, in_bytes(ArrayData::array_offset())), tmp2);
- Label not_flat;
- test_non_flat_array_oop(array, tmp2, not_flat);
- set_mdp_flag_at(tmp1, ArrayData::flat_array_byte_constant());
- bind(not_flat);
+ if (Arguments::is_valhalla_enabled()) {
+ if (UseArrayFlattening) {
+ Label not_flat;
+ test_non_flat_array_oop(array, tmp2, not_flat);
+ set_mdp_flag_at(tmp1, ArrayData::flat_array_byte_constant());
+ bind(not_flat);
+ }
- Label not_null_free;
- test_non_null_free_array_oop(array, tmp2, not_null_free);
- set_mdp_flag_at(tmp1, ArrayData::null_free_array_byte_constant());
- bind(not_null_free);
+ Label not_null_free;
+ test_non_null_free_array_oop(array, tmp2, not_null_free);
+ set_mdp_flag_at(tmp1, ArrayData::null_free_array_byte_constant());
+ bind(not_null_free);
+ }
bind(profile_continue);
}
@@ -2216,6 +2221,7 @@ void InterpreterMacroAssembler::pop_interpreter_frame(Register return_pc, Regist
//-------------------------------------
void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
assert_different_registers(entry, obj);
assert_different_registers(entry, Z_ARG2);
call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry);
@@ -2223,6 +2229,7 @@ void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) {
void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1,
Register tmp2, Register obj) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
assert_different_registers(entry, tmp1, tmp2, obj);
Label slow_path, done;
diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp
index b3e1b223bf0e..dd65e52b70a5 100644
--- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp
+++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp
@@ -3939,6 +3939,7 @@ void MacroAssembler::null_check(Register reg, Register tmp, int64_t offset) {
//-------------------------------------
void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
static_assert(markWord::value_type_pattern <= 0x7FFF, "must fit in simm16 for z_chi");
z_nilf(markword, markWord::value_type_pattern_mask);
z_chi(markword, markWord::value_type_pattern);
@@ -3946,6 +3947,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va
}
void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
if (can_be_null) {
z_ltgr(object, object);
branch_optimized(bcondEqual, not_value_type);
@@ -3957,16 +3959,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L
}
void MacroAssembler::test_field_is_null_free_value_type(Register flags, Label& is_null_free) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
testbit(flags, ResolvedFieldEntry::is_null_free_value_type_shift);
z_brc(Assembler::bcondAllOne, is_null_free);
}
void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Label& not_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
testbit(flags, ResolvedFieldEntry::is_null_free_value_type_shift);
z_brc(Assembler::bcondAllZero, not_null_free_value_type);
}
void MacroAssembler::test_field_is_flat(Register flags, Label& is_flat) {
+ assert(UseFieldFlattening, "should not be used without field flattening enabled");
testbit(flags, ResolvedFieldEntry::is_flat_shift);
z_brc(Assembler::bcondAllOne, is_flat);
}
@@ -4280,22 +4285,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int
}
void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array);
}
void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array);
}
void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array);
}
void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label& is_non_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array);
}
void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
testbit(lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace));
z_brnaz(is_flat_array);
}
@@ -4544,11 +4554,13 @@ void MacroAssembler::store_heap_oop(Register Roop, const Address &a,
}
void MacroAssembler::payload_offset(Register value_klass, Register offset) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
z_lg(offset, Address(value_klass, ValueKlass::adr_members_offset()));
z_llgf(offset, Address(offset, ValueKlass::payload_offset_offset()));
}
void MacroAssembler::payload_addr(Register oop, Register data, Register value_klass) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
// ((address) (void*) o) + vk->payload_offset();
//
// oop must differ from value_klass: payload_offset() overwrites value_klass
@@ -6928,10 +6940,12 @@ void MacroAssembler::post_call_nop() {
if (!Continuations::enabled()) {
return;
}
- nop();
- // TODO:
- // 1. https://bugs.openjdk.org/browse/JDK-8300002
- // 2. https://bugs.openjdk.org/browse/JDK-8290965
+ // We use CLFI SP, imm32 to encode post call nops.
+ // Refer to NativePostCallNop for details.
+ relocate(post_call_nop_Relocation::spec());
+ InlineSkippedInstructionsCounter skipCounter(this);
+ z_clfi(Z_SP, 0);
+ assert(is_post_call_nop(pc() - 6), "post call nop not found");
}
void MacroAssembler::push_cont_fastpath() {
@@ -7142,22 +7156,26 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i
}
bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
Unimplemented();
}
bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index,
VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index,
RegState reg_state[]) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
Unimplemented();
}
bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index,
VMRegPair* from, int from_count, int& from_index, VMReg to,
RegState reg_state[], Register val_array) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
Unimplemented();
}
int MacroAssembler::extend_stack_for_value_args(int args_on_stack) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
Unimplemented();
}
diff --git a/src/hotspot/cpu/s390/matcher_s390.hpp b/src/hotspot/cpu/s390/matcher_s390.hpp
index 188d46438aca..7e006bdc6c7b 100644
--- a/src/hotspot/cpu/s390/matcher_s390.hpp
+++ b/src/hotspot/cpu/s390/matcher_s390.hpp
@@ -125,11 +125,6 @@
return false;
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- return false;
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return false;
diff --git a/src/hotspot/cpu/s390/nativeInst_s390.cpp b/src/hotspot/cpu/s390/nativeInst_s390.cpp
index 3520e9a34935..0a49826adf2f 100644
--- a/src/hotspot/cpu/s390/nativeInst_s390.cpp
+++ b/src/hotspot/cpu/s390/nativeInst_s390.cpp
@@ -634,6 +634,26 @@ void NativeGeneralJump::replace_mt_safe(address instr_addr, address code_buffer)
void NativeDeoptInstruction::verify() {
}
+bool NativePostCallNop::patch(int32_t oopmap_slot, int32_t cb_offset) {
+ if (((oopmap_slot & 0xff) != oopmap_slot) || ((cb_offset & 0xffffff) != cb_offset)) {
+ return false; // cannot encode
+ }
+ uint32_t data = ((uint32_t)oopmap_slot << 24) | (uint32_t)cb_offset;
+ assert(data != 0, "must be");
+ // Write the 4-byte payload into the imm32 field of the CLFI instruction
+ // (bytes 2..5). s390 is big-endian so a direct 32-bit write is correct.
+ *(uint32_t*) addr_at(displacement_offset) = data;
+ ICache::invalidate_range(addr_at(0), instruction_size);
+
+ int32_t oopmap_slot_dec, cb_offset_dec;
+ assert(check(), "pcn not recognized after patch");
+ assert(decode(oopmap_slot_dec, cb_offset_dec), "encoding failed");
+ assert(oopmap_slot == oopmap_slot_dec, "oopmap slot encoding is wrong");
+ assert(cb_offset == cb_offset_dec, "cb offset encoding is wrong");
+
+ return true; // successfully encoded
+}
+
void NativePostCallNop::make_deopt() {
NativeDeoptInstruction::insert(addr_at(0));
}
diff --git a/src/hotspot/cpu/s390/nativeInst_s390.hpp b/src/hotspot/cpu/s390/nativeInst_s390.hpp
index 0ba97830bb70..adc0395d4889 100644
--- a/src/hotspot/cpu/s390/nativeInst_s390.hpp
+++ b/src/hotspot/cpu/s390/nativeInst_s390.hpp
@@ -653,16 +653,50 @@ class NativeGeneralJump: public NativeInstruction {
};
class NativePostCallNop: public NativeInstruction {
+
+ // Post-call NOPs are encoded as CLFI R0, imm32 (RIL-a format, 6 bytes):
+ //
+ // Bit |0 7|8 11|12 15|16 47|
+ // +---------+------+------+--------------------------+
+ // | 0xC2 | R1 | 0xF | I2 (imm32) |
+ // +---------+------+------+--------------------------+
+ // | opcode1 | 0x0 |op2=F | oopmap_slot[31:24] |
+ // | |(R0) | | | cb_offset[23:0] |
+ //
+ // Byte 0 = 0xC2 (opcode1, from CLFI_ZOPC bits 47..40)
+ // Byte 1 = 0x0F (R1=R0=0x0 in bits 39..36; opcode2=0xF in bits 35..32)
+ // Bytes 2..5 = imm32 payload
+ //
+ // CLFI sets only the condition code — no GPR is written.
+ // The R1 field is fixed to R0 (encoding 0), allowing check() to
+ // recognize the instruction by testing only the first two bytes.
+
public:
- enum z_specific_constants {
- // The check reads a 2-byte nop instruction. Since s390 nop is 2 bytes (BCR instruction),
- // we can safely read it in a single stage without risk of out-of-bounds access.
- // The nop instruction is checked by is_nop() which reads a short (2 bytes).
- first_check_size = 2
+
+ enum z_pcn_constants {
+ // Only the first 2 bytes are needed to identify the instruction.
+ // This avoids an out-of-bounds read if the return address is at a
+ // page boundary (same rationale as x86 first_check_size).
+ first_check_size = 2,
+ instruction_size = 6, // CLFI is a 6-byte RIL-a instruction
+ displacement_offset = 2 // imm32 payload starts at byte 2
};
- bool check() const { return is_nop(); }
- bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { return false; }
- bool patch(int32_t oopmap_slot, int32_t cb_offset) { Unimplemented(); return false; }
+
+ bool check() const { return Assembler::is_post_call_nop(addr_at(0)); }
+
+ bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const {
+ // s390 is big-endian; read the 4-byte imm32 at byte offset 2.
+ // Use uint32_t to avoid signed integer arithmetic on the raw bit pattern.
+ uint32_t data = *(uint32_t*) addr_at(displacement_offset);
+ if (data == 0) {
+ return false; // no payload encoded yet
+ }
+ cb_offset = (int32_t)(data & 0xffffff);
+ oopmap_slot = (int32_t)(data >> 24); // top 8 bits, no masking needed
+ return true;
+ }
+
+ bool patch(int32_t oopmap_slot, int32_t cb_offset);
void make_deopt();
};
diff --git a/src/hotspot/cpu/s390/templateTable_s390.cpp b/src/hotspot/cpu/s390/templateTable_s390.cpp
index dcb3fa4fc3f1..7234db81cd19 100644
--- a/src/hotspot/cpu/s390/templateTable_s390.cpp
+++ b/src/hotspot/cpu/s390/templateTable_s390.cpp
@@ -2116,9 +2116,9 @@ void TemplateTable::if_acmp(Condition cc) {
Label taken, not_taken;
__ pop_ptr(Z_ARG5);
- __ profile_acmp(Z_tmp_1, Z_ARG5, Z_tos, Z_tmp_2);
-
if (Arguments::is_valhalla_enabled()) {
+ __ profile_acmp(Z_tmp_1, Z_ARG5, Z_tos, Z_tmp_2);
+
__ compareU64_and_branch(Z_tos, Z_ARG5, Assembler::bcondEqual, (cc == equal) ? taken : not_taken);
// Use z_ltgr for efficient null checks
@@ -2961,7 +2961,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
// still holds the ResolvedFieldEntry* for read_flat_field below.
__ load_sized_value(atos_flags, Address(atos_entry, in_bytes(ResolvedFieldEntry::flags_offset())), sizeof(u1), false);
Label is_flat;
- __ test_field_is_flat(atos_flags, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(atos_flags, is_flat);
+ }
__ load_heap_oop(Z_tos, field, oopLoad_tmp1, oopLoad_tmp2);
__ push(atos);
if (do_rewrite) {
@@ -2969,14 +2971,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
}
__ z_bru(Done);
__ bind(is_flat);
- // field is flat (null-free or nullable with a null-marker).
- // atos_entry (Z_ARG5) still holds the ResolvedFieldEntry* here.
- __ z_lgr(Z_tos, obj);
- __ read_flat_field(atos_entry, Z_tos);
- __ verify_oop(Z_tos);
- __ push(atos);
- if (do_rewrite) {
- patch_bytecode(Bytecodes::_fast_vgetfield, bc_reg, patch_tmp);
+ if (UseFieldFlattening) {
+ // field is flat (null-free or nullable with a null-marker).
+ // atos_entry (Z_ARG5) still holds the ResolvedFieldEntry* here.
+ __ z_lgr(Z_tos, obj);
+ __ read_flat_field(atos_entry, Z_tos);
+ __ verify_oop(Z_tos);
+ __ push(atos);
+ if (do_rewrite) {
+ patch_bytecode(Bytecodes::_fast_vgetfield, bc_reg, patch_tmp);
+ }
+ __ z_bru(Done);
}
}
}
@@ -3351,8 +3356,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr
oopStore_tmp1, oopStore_tmp2, oopStore_tmp3, IN_HEAP);
} else {
Label null_free_reference, is_flat, rewrite_value, done_valhalla;
- __ z_tmll(flags, 1 << ResolvedFieldEntry::is_flat_shift);
- __ branch_optimized(Assembler::bcondAllOne, is_flat);
+ if (UseFieldFlattening) {
+ __ z_tmll(flags, 1 << ResolvedFieldEntry::is_flat_shift);
+ __ branch_optimized(Assembler::bcondAllOne, is_flat);
+ }
__ z_tmll(flags, 1 << ResolvedFieldEntry::is_null_free_value_type_shift);
__ branch_optimized(Assembler::bcondAllOne, null_free_reference);
pop_and_check_object(obj);
@@ -3374,19 +3381,22 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr
oopStore_tmp1, oopStore_tmp2, oopStore_tmp3, IN_HEAP);
__ z_bru(rewrite_value);
__ bind(is_flat);
- pop_and_check_object(oopStore_tmp1);
- {
- // cache (Z_ARG5) is dead here — overwritten by load_resolved_field_entry.
- // tos_state (Z_ARG4) is dead here — its life ended after branch-table dispatch.
- // Reload the ResolvedFieldEntry pointer into tos_state (Z_ARG4); use
- // oopStore_tmp2 (Z_R1_scratch) as the index scratch.
- // Pass oopStore_tmp3 (Z_ARG2) as tmp2 so that flags (Z_tmp_1) is not
- // clobbered; flags must survive to the volatility check after Done.
- Register flat_entry = tos_state;
- Register flat_index = oopStore_tmp2; // Z_R1_scratch (index scratch, discarded after load)
- __ load_field_entry(flat_entry, flat_index);
- __ write_flat_field(flat_entry, flat_index, oopStore_tmp3, oopStore_tmp1);
- }__ bind(rewrite_value);
+ if (UseFieldFlattening) {
+ pop_and_check_object(oopStore_tmp1);
+ {
+ // cache (Z_ARG5) is dead here — overwritten by load_resolved_field_entry.
+ // tos_state (Z_ARG4) is dead here — its life ended after branch-table dispatch.
+ // Reload the ResolvedFieldEntry pointer into tos_state (Z_ARG4); use
+ // oopStore_tmp2 (Z_R1_scratch) as the index scratch.
+ // Pass oopStore_tmp3 (Z_ARG2) as tmp2 so that flags (Z_tmp_1) is not
+ // clobbered; flags must survive to the volatility check after Done.
+ Register flat_entry = tos_state;
+ Register flat_index = oopStore_tmp2; // Z_R1_scratch (index scratch, discarded after load)
+ __ load_field_entry(flat_entry, flat_index);
+ __ write_flat_field(flat_entry, flat_index, oopStore_tmp3, oopStore_tmp1);
+ }
+ }
+ __ bind(rewrite_value);
if (do_rewrite) {
patch_bytecode(Bytecodes::_fast_vputfield, bc_reg, patch_tmp, true, byte_no);
}
@@ -3550,34 +3560,40 @@ void TemplateTable::fast_storefield(TosState state) {
switch (bytecode()) {
case Bytecodes::_fast_vputfield:
{
- // Register state at this point:
- // obj/cache = Z_tmp_1 (R10) = target object (pop_and_check_object wrote obj here,
- // overwriting the entry pointer that cache held earlier)
- // off = Z_tmp_2 (R11) = field offset
- // flags = Z_ARG5 (R6) = field flags
- // Z_tos = Z_ARG1 (R2) = value oop being stored
- //
- // For write_flat_field we need the ResolvedFieldEntry pointer.
- // Re-load it into Z_ARG3 (R4) using Z_ARG4 (R5) as the index scratch;
- // both are volatile and not carrying live values at this point.
- Label is_flat, done;
- __ test_field_is_flat(flags, is_flat);
- __ null_check(Z_tos); // Value being stored must not be null for null-free flat field.
- do_oop_store(_masm, field, Z_tos,
- Z_ARG2, Z_ARG3, Z_ARG4, IN_HEAP);
- __ branch_optimized(Assembler::bcondAlways, done);
- __ bind(is_flat);
- {
- // Reload the ResolvedFieldEntry pointer. pop_and_check_object above
- // overwrote cache/Z_tmp_1 with the object.
- Register flat_entry = Z_ARG4; // R5 — safe for call_VM arg_3 slot
- Register flat_index = Z_ARG3; // R4 — discarded after load_field_entry
- __ load_field_entry(flat_entry, flat_index);
- // entry=R5, tmp1=Z_ARG2=R3, tmp2=flat_index=Z_ARG3=R4, obj=Z_tmp_1=R10
- // All four are distinct; obj(R10) != Z_tos(R2) so implementation is safe.
- __ write_flat_field(flat_entry, Z_ARG2, flat_index, obj);
+ if (Arguments::is_valhalla_enabled()) {
+ // Register state at this point:
+ // obj/cache = Z_tmp_1 (R10) = target object (pop_and_check_object wrote obj here,
+ // overwriting the entry pointer that cache held earlier)
+ // off = Z_tmp_2 (R11) = field offset
+ // flags = Z_ARG5 (R6) = field flags
+ // Z_tos = Z_ARG1 (R2) = value oop being stored
+ //
+ // For write_flat_field we need the ResolvedFieldEntry pointer.
+ // Re-load it into Z_ARG3 (R4) using Z_ARG4 (R5) as the index scratch;
+ // both are volatile and not carrying live values at this point.
+ Label is_flat, done;
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(flags, is_flat);
+ }
+ __ null_check(Z_tos); // Value being stored must not be null for null-free flat field.
+ do_oop_store(_masm, field, Z_tos,
+ Z_ARG2, Z_ARG3, Z_ARG4, IN_HEAP);
+ __ branch_optimized(Assembler::bcondAlways, done);
+ __ bind(is_flat);
+ if (UseFieldFlattening) {
+ // Reload the ResolvedFieldEntry pointer. pop_and_check_object above
+ // overwrote cache/Z_tmp_1 with the object.
+ Register flat_entry = Z_ARG4; // R5 — safe for call_VM arg_3 slot
+ Register flat_index = Z_ARG3; // R4 — discarded after load_field_entry
+ __ load_field_entry(flat_entry, flat_index);
+ // entry=R5, tmp1=Z_ARG2=R3, tmp2=flat_index=Z_ARG3=R4, obj=Z_tmp_1=R10
+ // All four are distinct; obj(R10) != Z_tos(R2) so implementation is safe.
+ __ write_flat_field(flat_entry, Z_ARG2, flat_index, obj);
+ }
+ __ bind(done);
+ } else {
+ __ should_not_reach_here();
}
- __ bind(done);
}
break;
case Bytecodes::_fast_aputfield:
@@ -3675,9 +3691,13 @@ void TemplateTable::fast_accessfield(TosState state) {
// access field
switch (bytecode()) {
case Bytecodes::_fast_vgetfield:
- // field is flat; cache (Z_tmp_1) holds ResolvedFieldEntry*, obj (Z_tos) is receiver/result
- __ read_flat_field(cache, obj);
- __ verify_oop(Z_tos);
+ if (UseFieldFlattening) {
+ // field is flat; cache (Z_tmp_1) holds ResolvedFieldEntry*, obj (Z_tos) is receiver/result
+ __ read_flat_field(cache, obj);
+ __ verify_oop(Z_tos);
+ } else {
+ __ stop("ShouldNotReachHere");
+ }
return;
case Bytecodes::_fast_agetfield:
do_oop_load(_masm, field, Z_tos, Z_tmp_1, Z_tmp_2, IN_HEAP);
@@ -4511,8 +4531,10 @@ void TemplateTable::monitorenter() {
// Check for value type (Valhalla feature)
NearLabel is_value_type;
- __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes()));
- __ test_markword_is_value_type(Z_R1_scratch, is_value_type);
+ if (Arguments::is_valhalla_enabled()) {
+ __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes()));
+ __ test_markword_is_value_type(Z_R1_scratch, is_value_type);
+ }
const int entry_size = frame::interpreter_frame_monitor_size_in_bytes();
NearLabel allocated;
// Initialize entry pointer.
@@ -4592,10 +4614,12 @@ void TemplateTable::monitorenter() {
__ dispatch_next(vtos);
// Handle value type exception (Valhalla feature)
- __ bind(is_value_type);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_identity_exception), Z_tos);
- __ should_not_reach_here();
+ if (Arguments::is_valhalla_enabled()) {
+ __ bind(is_value_type);
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_identity_exception), Z_tos);
+ __ should_not_reach_here();
+ }
BLOCK_COMMENT("} monitorenter");
}
@@ -4611,14 +4635,16 @@ void TemplateTable::monitorexit() {
// Check for value type (Valhalla feature)
NearLabel is_value_type, has_identity;
- __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes()));
- __ test_markword_is_value_type(Z_R1_scratch, is_value_type);
- __ z_bru(has_identity);
- __ bind(is_value_type);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_illegal_monitor_state_exception));
- __ should_not_reach_here();
- __ bind(has_identity);
+ if (Arguments::is_valhalla_enabled()) {
+ __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes()));
+ __ test_markword_is_value_type(Z_R1_scratch, is_value_type);
+ __ z_bru(has_identity);
+ __ bind(is_value_type);
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_illegal_monitor_state_exception));
+ __ should_not_reach_here();
+ __ bind(has_identity);
+ }
NearLabel found, not_found;
const Register Rcurr_monitor = Z_ARG2;
diff --git a/src/hotspot/cpu/x86/assembler_x86.cpp b/src/hotspot/cpu/x86/assembler_x86.cpp
index b020c817dd19..2191eb676a66 100644
--- a/src/hotspot/cpu/x86/assembler_x86.cpp
+++ b/src/hotspot/cpu/x86/assembler_x86.cpp
@@ -10790,14 +10790,14 @@ void Assembler::vpbroadcastb(XMMRegister dst, Address src, int vector_len) {
// duplicate 2-byte integer data from src into programmed locations in dest : requires AVX512BW and AVX512VL
void Assembler::vpbroadcastw(XMMRegister dst, XMMRegister src, int vector_len) {
- assert(VM_Version::supports_avx2(), "");
+ assert(vector_len == AVX_512bit ? VM_Version::supports_avx512bw() : VM_Version::supports_avx2(), "");
InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ _legacy_mode_bw, /* no_mask_reg */ true, /* uses_vl */ true);
int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes);
emit_int16(0x79, (0xC0 | encode));
}
void Assembler::vpbroadcastw(XMMRegister dst, Address src, int vector_len) {
- assert(VM_Version::supports_avx2(), "");
+ assert(vector_len == AVX_512bit ? VM_Version::supports_avx512bw() : VM_Version::supports_avx2(), "");
assert(dst != xnoreg, "sanity");
InstructionMark im(this);
InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ _legacy_mode_bw, /* no_mask_reg */ true, /* uses_vl */ true);
diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp
index 96300e612089..2f470ab44558 100644
--- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp
@@ -2493,14 +2493,15 @@ void LIR_Assembler::store_parameter(Metadata* m, int offset_from_rsp_in_words) {
void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) {
+ assert(Arguments::is_valhalla_enabled(), "Value types must be enabled");
if (null_check) {
__ testptr(obj, obj);
__ jcc(Assembler::zero, *slow_path->entry());
}
if (is_dest) {
__ test_null_free_array_oop(obj, tmp, *slow_path->entry());
- __ test_flat_array_oop(obj, tmp, *slow_path->entry());
- } else {
+ }
+ if (UseArrayFlattening) {
__ test_flat_array_oop(obj, tmp, *slow_path->entry());
}
}
@@ -2607,12 +2608,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) {
return;
}
- // Handle value type arrays
- if (flags & LIR_OpArrayCopy::src_valuetype_check) {
- arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
- }
- if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
- arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ if (Arguments::is_valhalla_enabled()) {
+ // Handle value type arrays
+ if (flags & LIR_OpArrayCopy::src_valuetype_check) {
+ arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check));
+ }
+ if (flags & LIR_OpArrayCopy::dst_valuetype_check) {
+ arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check));
+ }
}
assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point");
@@ -3152,6 +3155,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) {
}
void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) {
+ assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled");
Register obj = op->obj()->as_register();
Register tmp = op->tmp()->as_pointer_register();
Address mdo_addr = as_Address(op->mdp()->as_address_ptr());
diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
index dcce0aa9c00b..b4ca8648b326 100644
--- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp
@@ -1430,112 +1430,6 @@ void C2_MacroAssembler::vinsert(BasicType typ, XMMRegister dst, XMMRegister src,
}
}
-void C2_MacroAssembler::vgather8b_masked(BasicType elem_bt, XMMRegister dst,
- Register base, Register idx_base,
- Register mask, Register mask_idx,
- Register rtmp, int vlen_enc) {
- vpxor(dst, dst, dst, vlen_enc);
- if (elem_bt == T_SHORT) {
- for (int i = 0; i < 4; i++) {
- // dst[i] = mask[i] ? src[idx_base[i]] : 0
- Label skip_load;
- btq(mask, mask_idx);
- jccb(Assembler::carryClear, skip_load);
- movl(rtmp, Address(idx_base, i * 4));
- pinsrw(dst, Address(base, rtmp, Address::times_2), i);
- bind(skip_load);
- incq(mask_idx);
- }
- } else {
- assert(elem_bt == T_BYTE, "only handles short and byte");
- for (int i = 0; i < 8; i++) {
- // dst[i] = mask[i] ? src[idx_base[i]] : 0
- Label skip_load;
- btq(mask, mask_idx);
- jccb(Assembler::carryClear, skip_load);
- movl(rtmp, Address(idx_base, i * 4));
- pinsrb(dst, Address(base, rtmp), i);
- bind(skip_load);
- incq(mask_idx);
- }
- }
-}
-
-void C2_MacroAssembler::vgather8b(BasicType elem_bt, XMMRegister dst,
- Register base, Register idx_base,
- Register rtmp, int vlen_enc) {
- vpxor(dst, dst, dst, vlen_enc);
- if (elem_bt == T_SHORT) {
- for (int i = 0; i < 4; i++) {
- // dst[i] = src[idx_base[i]]
- movl(rtmp, Address(idx_base, i * 4));
- pinsrw(dst, Address(base, rtmp, Address::times_2), i);
- }
- } else {
- assert(elem_bt == T_BYTE, "only handles short and byte");
- for (int i = 0; i < 8; i++) {
- // dst[i] = src[idx_base[i]]
- movl(rtmp, Address(idx_base, i * 4));
- pinsrb(dst, Address(base, rtmp), i);
- }
- }
-}
-
-/*
- * Gather using hybrid algorithm, first partially unroll scalar loop
- * to accumulate values from gather indices into a quad-word(64bit) slice.
- * A slice may hold 8 bytes or 4 short values. This is followed by a vector
- * permutation to place the slice into appropriate vector lane
- * locations in destination vector. Following pseudo code describes the
- * algorithm in detail:
- *
- * DST_VEC = ZERO_VEC
- * PERM_INDEX = {0, 1, 2, 3, 4, 5, 6, 7, 8..}
- * TWO_VEC = {2, 2, 2, 2, 2, 2, 2, 2, 2..}
- * FOREACH_ITER:
- * TMP_VEC_64 = PICK_SUB_WORDS_FROM_GATHER_INDICES
- * TEMP_PERM_VEC = PERMUTE TMP_VEC_64 PERM_INDEX
- * DST_VEC = DST_VEC OR TEMP_PERM_VEC
- * PERM_INDEX = PERM_INDEX - TWO_VEC
- *
- * With each iteration, doubleword permute indices (0,1) corresponding
- * to gathered quadword gets right shifted by two lane positions.
- *
- */
-void C2_MacroAssembler::vgather_subword(BasicType elem_ty, XMMRegister dst,
- Register base, Register idx_base,
- Register mask, XMMRegister xtmp1,
- XMMRegister xtmp2, XMMRegister temp_dst,
- Register rtmp, Register mask_idx,
- Register length, int vector_len, int vlen_enc) {
- Label GATHER8_LOOP;
- assert(is_signed_subword_type(elem_ty), "only handles short and byte");
- movl(length, vector_len);
- vpxor(xtmp1, xtmp1, xtmp1, vlen_enc); // xtmp1 = {0, ...}
- vpxor(dst, dst, dst, vlen_enc); // dst = {0, ...}
- vallones(xtmp2, vlen_enc);
- vpsubd(xtmp2, xtmp1, xtmp2, vlen_enc);
- vpslld(xtmp2, xtmp2, 1, vlen_enc); // xtmp2 = {2, 2, ...}
- load_iota_indices(xtmp1, vector_len * type2aelembytes(elem_ty), T_INT); // xtmp1 = {0, 1, 2, ...}
-
- bind(GATHER8_LOOP);
- // TMP_VEC_64(temp_dst) = PICK_SUB_WORDS_FROM_GATHER_INDICES
- if (mask == noreg) {
- vgather8b(elem_ty, temp_dst, base, idx_base, rtmp, vlen_enc);
- } else {
- vgather8b_masked(elem_ty, temp_dst, base, idx_base, mask, mask_idx, rtmp, vlen_enc);
- }
- // TEMP_PERM_VEC(temp_dst) = PERMUTE TMP_VEC_64(temp_dst) PERM_INDEX(xtmp1)
- vpermd(temp_dst, xtmp1, temp_dst, vlen_enc == Assembler::AVX_512bit ? vlen_enc : Assembler::AVX_256bit);
- // PERM_INDEX(xtmp1) = PERM_INDEX(xtmp1) - TWO_VEC(xtmp2)
- vpsubd(xtmp1, xtmp1, xtmp2, vlen_enc);
- // DST_VEC = DST_VEC OR TEMP_PERM_VEC
- vpor(dst, dst, temp_dst, vlen_enc);
- addptr(idx_base, 32 >> (type2aelembytes(elem_ty) - 1));
- subl(length, 8 >> (type2aelembytes(elem_ty) - 1));
- jcc(Assembler::notEqual, GATHER8_LOOP);
-}
-
void C2_MacroAssembler::vgather(BasicType typ, XMMRegister dst, Register base, XMMRegister idx, XMMRegister mask, int vector_len) {
switch(typ) {
case T_INT:
diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
index a6471cbd3fdb..761879cbc9ca 100644
--- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
+++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp
@@ -509,15 +509,6 @@
void efp16sh(int opcode, XMMRegister dst, XMMRegister src1, XMMRegister src2);
- void vgather_subword(BasicType elem_ty, XMMRegister dst, Register base, Register idx_base, Register mask,
- XMMRegister xtmp1, XMMRegister xtmp2, XMMRegister xtmp3, Register rtmp,
- Register midx, Register length, int vector_len, int vlen_enc);
-
- void vgather8b_masked(BasicType elem_bt, XMMRegister dst, Register base, Register idx_base,
- Register mask, Register midx, Register rtmp, int vlen_enc);
- void vgather8b(BasicType elem_bt, XMMRegister dst, Register base, Register idx_base,
- Register rtmp, int vlen_enc);
-
void vector_saturating_op(int opc, BasicType elem_bt, XMMRegister dst, XMMRegister src1, XMMRegister src2, bool is_unsigned, int vlen_enc);
void vector_saturating_op(int opc, BasicType elem_bt, XMMRegister dst, XMMRegister src1, Address src2, bool is_unsigned, int vlen_enc);
diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86.hpp b/src/hotspot/cpu/x86/foreignGlobals_x86.hpp
index 7b3859ce6613..bc671ce4249a 100644
--- a/src/hotspot/cpu/x86/foreignGlobals_x86.hpp
+++ b/src/hotspot/cpu/x86/foreignGlobals_x86.hpp
@@ -32,7 +32,6 @@ struct ABIDescriptor {
GrowableArray _integer_return_registers;
GrowableArray _vector_argument_registers;
GrowableArray _vector_return_registers;
- size_t _X87_return_registers_noof;
GrowableArray _integer_additional_volatile_registers;
GrowableArray _vector_additional_volatile_registers;
diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp
index 55952bba063a..67462ba3cbee 100644
--- a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp
+++ b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp
@@ -54,8 +54,6 @@ const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) {
refArrayOop outputStorage = jdk_internal_foreign_abi_ABIDescriptor::outputStorage(abi_oop);
parse_register_array(outputStorage, StorageType::INTEGER, abi._integer_return_registers, as_Register);
parse_register_array(outputStorage, StorageType::VECTOR, abi._vector_return_registers, as_XMMRegister);
- refArrayOop subarray = oop_cast(outputStorage->obj_at((int) StorageType::X87));
- abi._X87_return_registers_noof = subarray->length();
refArrayOop volatileStorage = jdk_internal_foreign_abi_ABIDescriptor::volatileStorage(abi_oop);
parse_register_array(volatileStorage, StorageType::INTEGER, abi._integer_additional_volatile_registers, as_Register);
diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
index 0ef885fbffde..53bcd1300fcc 100644
--- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp
@@ -652,7 +652,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce,
Register obj = stub->obj()->as_register();
if (stub->do_load()) {
- ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, /* wide = */ false);
+ ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), /* wide = */ false);
}
__ cmpptr(obj, NULL_WORD);
__ jcc(Assembler::equal, *stub->continuation());
diff --git a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp
index c2f5eb68c2bc..efafe816e155 100644
--- a/src/hotspot/cpu/x86/interp_masm_x86.cpp
+++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp
@@ -1121,12 +1121,14 @@ void InterpreterMacroAssembler::get_method_counters(Register method,
}
void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field),
obj, entry);
}
void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2,
Register obj, Register value) {
+ assert(UseFieldFlattening, "Should be used only if field flattening is enabled");
assert_different_registers(entry, tmp1, tmp2, obj, value);
Label slow_path, done;
@@ -1610,19 +1612,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re
mov(tmp, array);
profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset())));
- Label not_flat;
- test_non_flat_array_oop(array, tmp, not_flat);
+ if (Arguments::is_valhalla_enabled()) {
+ if (UseArrayFlattening) {
+ Label not_flat;
+ test_non_flat_array_oop(array, tmp, not_flat);
- set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant());
+ set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant());
- bind(not_flat);
+ bind(not_flat);
+ }
- Label not_null_free;
- test_non_null_free_array_oop(array, tmp, not_null_free);
+ Label not_null_free;
+ test_non_null_free_array_oop(array, tmp, not_null_free);
- set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant());
+ set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant());
- bind(not_null_free);
+ bind(not_null_free);
+ }
bind(profile_continue);
}
@@ -1687,6 +1693,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp,
Register left,
Register right,
Register tmp) {
+ assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types");
if (ProfileInterpreter) {
Label profile_continue;
diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp
index ae7304b354b2..216ed9f92e75 100644
--- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp
+++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp
@@ -2406,12 +2406,14 @@ void MacroAssembler::null_check(Register reg, int offset) {
}
void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
andptr(markword, markWord::value_type_pattern_mask);
cmpptr(markword, markWord::value_type_pattern);
jcc(Assembler::equal, is_value_type);
}
void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
if (can_be_null) {
testptr(object, object);
jcc(Assembler::zero, not_value_type);
@@ -2424,18 +2426,21 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L
}
void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
movl(temp_reg, flags);
testl(temp_reg, 1 << ResolvedFieldEntry::is_null_free_value_type_shift);
jcc(Assembler::notEqual, is_null_free_value_type);
}
void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
movl(temp_reg, flags);
testl(temp_reg, 1 << ResolvedFieldEntry::is_null_free_value_type_shift);
jcc(Assembler::equal, not_null_free_value_type);
}
void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) {
+ assert(UseFieldFlattening, "should not be used without field flattening enabled");
movl(temp_reg, flags);
testl(temp_reg, 1 << ResolvedFieldEntry::is_flat_shift);
jcc(Assembler::notEqual, is_flat);
@@ -2450,23 +2455,28 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int
void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg,
Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array);
}
void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg,
Label& is_non_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array);
}
void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label&is_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array);
}
void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) {
+ assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled");
test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array);
}
void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) {
+ assert(UseArrayFlattening, "should not be used without array flattening enabled");
testl(lh, Klass::_lh_array_tag_flat_value_bit_inplace);
jcc(Assembler::notZero, is_flat_array);
}
@@ -5622,11 +5632,13 @@ void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators, Ad
}
void MacroAssembler::payload_offset(Register value_klass, Register offset) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
movptr(offset, Address(value_klass, ValueKlass::adr_members_offset()));
movl(offset, Address(offset, ValueKlass::payload_offset_offset()));
}
void MacroAssembler::payload_addr(Register oop, Register data, Register value_klass) {
+ assert(Arguments::is_valhalla_enabled(), "Must be");
// ((address) (void*) o) + vk->payload_offset();
Register offset = (data == oop) ? rscratch1 : data;
payload_offset(value_klass, offset);
@@ -6088,6 +6100,7 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i
// Move a value between registers/stack slots and update the reg_state
bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
assert(from->is_valid() && to->is_valid(), "source and destination must be valid");
if (reg_state[to->value()] == reg_written) {
return true; // Already written
@@ -6151,6 +6164,7 @@ bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState re
// Calculate the extra stack space required for packing or unpacking value
// args and adjust the stack pointer (see MacroAssembler::remove_frame).
int MacroAssembler::extend_stack_for_value_args(int args_on_stack) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
int sp_inc = args_on_stack * VMRegImpl::stack_slot_size;
sp_inc = align_up(sp_inc, StackAlignmentInBytes);
assert(sp_inc > 0, "sanity");
@@ -6170,6 +6184,7 @@ int MacroAssembler::extend_stack_for_value_args(int args_on_stack) {
bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index,
VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index,
RegState reg_state[]) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
assert(sig->at(sig_index)._bt == T_VOID, "should be at end delimiter");
assert(from->is_valid(), "source must be valid");
bool progress = false;
@@ -6307,6 +6322,7 @@ bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int
bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index,
VMRegPair* from, int from_count, int& from_index, VMReg to,
RegState reg_state[], Register val_array) {
+ assert(ValueTypePassFieldsAsArgs, "Must be");
assert(sig->at(sig_index)._bt == T_METADATA, "should be at delimiter");
assert(to->is_valid(), "destination must be valid");
diff --git a/src/hotspot/cpu/x86/matcher_x86.hpp b/src/hotspot/cpu/x86/matcher_x86.hpp
index eda8a05d974a..5b012768cb9d 100644
--- a/src/hotspot/cpu/x86/matcher_x86.hpp
+++ b/src/hotspot/cpu/x86/matcher_x86.hpp
@@ -115,16 +115,6 @@
return (UseAVX >= 2);
}
- // Does target support predicated operation emulation.
- static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) {
- switch(vopc) {
- case Op_LoadVectorGatherMasked:
- return is_subword_type(bt) && VM_Version::supports_avx2();
- default:
- return false;
- }
- }
-
// Does the CPU supports vector variable rotate instructions?
static constexpr bool supports_vector_variable_rotates(void) {
return true;
@@ -185,9 +175,6 @@
return 7;
case Op_MulVL:
return VM_Version::supports_avx512vldq() ? 0 : 6;
- case Op_LoadVectorGather:
- case Op_LoadVectorGatherMasked:
- return is_subword_type(ety) ? 50 : 0;
case Op_VectorCastF2X: // fall through
case Op_VectorCastD2X:
return is_floating_point_type(ety) ? 0 : (is_subword_type(ety) ? 35 : 30);
diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp
index 2575d0e5007b..9c95408ad8ff 100644
--- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp
+++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp
@@ -3601,10 +3601,12 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh
__ cmpq(r10_src_klass, rax);
__ jcc(Assembler::notEqual, L_failed);
- if (Arguments::is_valhalla_enabled()) {
+ if (UseArrayFlattening) {
// Check for flat value type array -> return -1
__ test_flat_array_oop(src, rax, L_failed);
+ }
+ if (Arguments::is_valhalla_enabled()) {
// Check for null-free (non-flat) value type array -> handle as object array
__ test_null_free_array_oop(src, rax, L_objArray);
}
@@ -3738,12 +3740,14 @@ __ BIND(L_checkcast_copy);
#ifdef ASSERT
{
- BLOCK_COMMENT("assert not null-free array {");
- Label L;
- __ test_non_null_free_array_oop(dst, rklass_tmp, L);
- __ stop("unexpected null-free array");
- __ bind(L);
- BLOCK_COMMENT("} assert not null-free array");
+ if (Arguments::is_valhalla_enabled()) {
+ BLOCK_COMMENT("assert not null-free array {");
+ Label L;
+ __ test_non_null_free_array_oop(dst, rklass_tmp, L);
+ __ stop("unexpected null-free array");
+ __ bind(L);
+ BLOCK_COMMENT("} assert not null-free array");
+ }
}
#endif
diff --git a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp
index 620498378e55..8961f621301c 100644
--- a/src/hotspot/cpu/x86/templateTable_x86.cpp
+++ b/src/hotspot/cpu/x86/templateTable_x86.cpp
@@ -1971,10 +1971,10 @@ void TemplateTable::if_acmp(Condition cc) {
Label taken, not_taken;
__ pop_ptr(rdx);
- __ profile_acmp(rbx, rdx, rax, rcx);
-
- const int is_value_type_mask = markWord::value_type_pattern;
if (Arguments::is_valhalla_enabled()) {
+ __ profile_acmp(rbx, rdx, rax, rcx);
+
+ const int is_value_type_mask = markWord::value_type_pattern;
__ cmpoop(rdx, rax);
__ jcc(Assembler::equal, (cc == equal) ? taken : not_taken);
@@ -2713,7 +2713,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
__ jmp(Done);
} else {
Label is_flat;
- __ test_field_is_flat(flags, rscratch1, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(flags, rscratch1, is_flat);
+ }
pop_and_check_object(obj);
__ load_heap_oop(rax, field);
__ push(atos);
@@ -2722,13 +2724,15 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr
}
__ jmp(Done);
__ bind(is_flat);
- // field is flat (null-free or nullable with a null-marker)
- pop_and_check_object(rax);
- __ read_flat_field(rcx, rax);
- __ verify_oop(rax);
- __ push(atos);
- if (rc == may_rewrite) {
- patch_bytecode(Bytecodes::_fast_vgetfield, bc, rbx);
+ if (UseFieldFlattening) {
+ // field is flat (null-free or nullable with a null-marker)
+ pop_and_check_object(rax);
+ __ read_flat_field(rcx, rax);
+ __ verify_oop(rax);
+ __ push(atos);
+ if (rc == may_rewrite) {
+ patch_bytecode(Bytecodes::_fast_vgetfield, bc, rbx);
+ }
}
__ jmp(Done);
}
@@ -2984,7 +2988,7 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri
patch_bytecode(Bytecodes::_fast_aputfield, bc, rbx, true, byte_no);
}
__ jmp(Done);
- } else {
+ } else { // Valhalla
__ pop(atos);
if (is_static) {
Label is_nullable;
@@ -2995,7 +2999,9 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri
__ jmp(Done);
} else {
Label is_flat, null_free_reference, rewrite_value;
- __ test_field_is_flat(flags, rscratch1, is_flat);
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(flags, rscratch1, is_flat);
+ }
__ test_field_is_null_free_value_type(flags, rscratch1, null_free_reference);
pop_and_check_object(obj);
// Store into the field
@@ -3011,8 +3017,10 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri
do_oop_store(_masm, field, rax);
__ jmp(rewrite_value);
__ bind(is_flat);
- pop_and_check_object(rscratch2);
- __ write_flat_field(rcx, r8, rscratch1, rscratch2, rax);
+ if (UseFieldFlattening) {
+ pop_and_check_object(rscratch2);
+ __ write_flat_field(rcx, r8, rscratch1, rscratch2, rax);
+ }
__ bind(rewrite_value);
if (rc == may_rewrite) {
patch_bytecode(Bytecodes::_fast_vputfield, bc, rbx, true, byte_no);
@@ -3240,17 +3248,25 @@ void TemplateTable::fast_storefield_helper(Address field, Register rax, Register
switch (bytecode()) {
case Bytecodes::_fast_vputfield:
{
- // Field is either flat (nullable or not) or non-flat and null-free
- Label is_flat, done;
- __ test_field_is_flat(flags, rscratch1, is_flat);
- __ null_check(rax); // FIXME JDK-8341120
- do_oop_store(_masm, field, rax);
- __ jmp(done);
- __ bind(is_flat);
- __ load_field_entry(r8, r9);
- __ movptr(rscratch2, rcx); // re-shuffle registers because of VM call calling convention
- __ write_flat_field(r8, rscratch1, r9, rscratch2, rax);
- __ bind(done);
+ if (Arguments::is_valhalla_enabled()) {
+ // Field is either flat (nullable or not) or non-flat and null-free
+ Label is_flat, done;
+ if (UseFieldFlattening) {
+ __ test_field_is_flat(flags, rscratch1, is_flat);
+ }
+ __ null_check(rax);
+ do_oop_store(_masm, field, rax);
+ __ jmp(done);
+ __ bind(is_flat);
+ if (UseFieldFlattening) {
+ __ load_field_entry(r8, r9);
+ __ movptr(rscratch2, rcx); // re-shuffle registers because of VM call calling convention
+ __ write_flat_field(r8, rscratch1, r9, rscratch2, rax);
+ }
+ __ bind(done);
+ } else {
+ __ should_not_reach_here();
+ }
}
break;
case Bytecodes::_fast_aputfield:
@@ -3322,8 +3338,12 @@ void TemplateTable::fast_accessfield(TosState state) {
// access field
switch (bytecode()) {
case Bytecodes::_fast_vgetfield:
- __ read_flat_field(rcx, rax);
- __ verify_oop(rax);
+ if (UseFieldFlattening) {
+ __ read_flat_field(rcx, rax);
+ __ verify_oop(rax);
+ } else {
+ __ should_not_reach_here();
+ }
break;
case Bytecodes::_fast_agetfield:
do_oop_load(_masm, field, rax);
@@ -4065,8 +4085,10 @@ void TemplateTable::monitorenter() {
__ null_check(rax);
Label is_value_type;
- __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes()));
- __ test_markword_is_value_type(rbx, is_value_type);
+ if (Arguments::is_valhalla_enabled()) {
+ __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes()));
+ __ test_markword_is_value_type(rbx, is_value_type);
+ }
const Address monitor_block_top(
rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
@@ -4162,9 +4184,11 @@ void TemplateTable::monitorenter() {
__ dispatch_next(vtos);
__ bind(is_value_type);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_identity_exception), rax);
- __ should_not_reach_here();
+ if (Arguments::is_valhalla_enabled()) {
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_identity_exception), rax);
+ __ should_not_reach_here();
+ }
}
void TemplateTable::monitorexit() {
@@ -4173,16 +4197,18 @@ void TemplateTable::monitorexit() {
// check for null object
__ null_check(rax);
- const int is_value_type_mask = markWord::value_type_pattern;
- Label has_identity;
- __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes()));
- __ andptr(rbx, is_value_type_mask);
- __ cmpl(rbx, is_value_type_mask);
- __ jcc(Assembler::notEqual, has_identity);
- __ call_VM(noreg, CAST_FROM_FN_PTR(address,
- InterpreterRuntime::throw_illegal_monitor_state_exception));
- __ should_not_reach_here();
- __ bind(has_identity);
+ if (Arguments::is_valhalla_enabled()) {
+ const int is_value_type_mask = markWord::value_type_pattern;
+ Label has_identity;
+ __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes()));
+ __ andptr(rbx, is_value_type_mask);
+ __ cmpl(rbx, is_value_type_mask);
+ __ jcc(Assembler::notEqual, has_identity);
+ __ call_VM(noreg, CAST_FROM_FN_PTR(address,
+ InterpreterRuntime::throw_illegal_monitor_state_exception));
+ __ should_not_reach_here();
+ __ bind(has_identity);
+ }
const Address monitor_block_top(
rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize);
diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad
index 62c30a668d7f..df62cf56c7c9 100644
--- a/src/hotspot/cpu/x86/x86.ad
+++ b/src/hotspot/cpu/x86/x86.ad
@@ -3507,16 +3507,6 @@ bool Matcher::match_rule_supported_vector(int opcode, int vlen, BasicType bt) {
}
break;
case Op_LoadVectorGatherMasked:
- if (!is_subword_type(bt) && size_in_bits < 512 && !VM_Version::supports_avx512vl()) {
- return false;
- }
- if (is_subword_type(bt) &&
- ((size_in_bits > 256 && !VM_Version::supports_avx512bw()) ||
- (size_in_bits < 64) ||
- (bt == T_SHORT && !VM_Version::supports_bmi2()))) {
- return false;
- }
- break;
case Op_StoreVectorScatterMasked:
case Op_StoreVectorScatter:
if (is_subword_type(bt)) {
@@ -3526,10 +3516,9 @@ bool Matcher::match_rule_supported_vector(int opcode, int vlen, BasicType bt) {
}
// fallthrough
case Op_LoadVectorGather:
- if (!is_subword_type(bt) && size_in_bits == 64) {
+ if (is_subword_type(bt)) {
return false;
- }
- if (is_subword_type(bt) && size_in_bits < 64) {
+ } else if (size_in_bits == 64) {
return false;
}
break;
@@ -10698,10 +10687,12 @@ instruct compareAndExchangeB(
effect(KILL cr);
format %{ "cmpxchgb $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax; rax -> byte\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgb($newval$$Register, $mem_ptr$$Address);
+ __ narrow_subword_type($oldval$$Register, T_BYTE);
%}
ins_pipe( pipe_cmpxchg );
%}
@@ -10715,10 +10706,12 @@ instruct compareAndExchangeS(
effect(KILL cr);
format %{ "cmpxchgw $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax; rax -> short\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgw($newval$$Register, $mem_ptr$$Address);
+ __ narrow_subword_type($oldval$$Register, T_SHORT);
%}
ins_pipe( pipe_cmpxchg );
%}
@@ -10732,7 +10725,8 @@ instruct compareAndExchangeI(
effect(KILL cr);
format %{ "cmpxchgl $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgl($newval$$Register, $mem_ptr$$Address);
@@ -10749,7 +10743,8 @@ instruct compareAndExchangeL(
effect(KILL cr);
format %{ "cmpxchgq $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgq($newval$$Register, $mem_ptr$$Address);
@@ -10766,7 +10761,8 @@ instruct compareAndExchangeN(
effect(KILL cr);
format %{ "cmpxchgl $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgl($newval$$Register, $mem_ptr$$Address);
@@ -10784,7 +10780,8 @@ instruct compareAndExchangeP(
effect(KILL cr);
format %{ "cmpxchgq $mem_ptr,$newval\t# "
- "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %}
+ "If rax == $mem_ptr then store $newval into $mem_ptr, "
+ "else store $mem_ptr into rax\n\t" %}
ins_encode %{
__ lock();
__ cmpxchgq($newval$$Register, $mem_ptr$$Address);
@@ -18379,7 +18376,7 @@ instruct storeV(memory mem, vec src) %{
// ---------------------------------------- Gather ------------------------------------
-// Gather BYTE, SHORT, INT, LONG, FLOAT, DOUBLE
+// Gather INT, LONG, FLOAT, DOUBLE
instruct gather(legVec dst, memory mem, legVec idx, rRegP tmp, legVec mask) %{
predicate(!VM_Version::supports_avx512vl() && !is_subword_type(Matcher::vector_element_basic_type(n)) &&
@@ -18436,118 +18433,6 @@ instruct evgather_masked(vec dst, memory mem, vec idx, kReg mask, kReg ktmp, rRe
ins_pipe( pipe_slow );
%}
-instruct vgather_subwordLE8B(vec dst, memory mem, rRegP idx_base, rRegP tmp, rRegI rtmp) %{
- predicate(is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8);
- match(Set dst (LoadVectorGather mem idx_base));
- effect(TEMP tmp, TEMP rtmp);
- format %{ "vector_gatherLE8 $dst, $mem, $idx_base\t! using $tmp and $rtmp as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ lea($tmp$$Register, $mem$$Address);
- __ vgather8b(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp$$Register, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_subwordGT8B(vec dst, memory mem, rRegP idx_base, rRegP tmp, rRegP idx_base_temp,
- vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegI length, rFlagsReg cr) %{
- predicate(is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8);
- match(Set dst (LoadVectorGather mem idx_base));
- effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP length, KILL cr);
- format %{ "vector_gatherGT8 $dst, $mem, $idx_base\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp and $length as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- int vector_len = Matcher::vector_length(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ lea($tmp$$Register, $mem$$Address);
- __ movptr($idx_base_temp$$Register, $idx_base$$Register);
- __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, noreg, $xtmp1$$XMMRegister,
- $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, noreg, $length$$Register, vector_len, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_masked_subwordLE8B_avx3(vec dst, memory mem, rRegP idx_base, kReg mask, rRegL mask_idx, rRegP tmp, rRegI rtmp, rRegL rtmp2, rFlagsReg cr) %{
- predicate(VM_Version::supports_avx512bw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8);
- match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask)));
- effect(TEMP mask_idx, TEMP tmp, TEMP rtmp, TEMP rtmp2, KILL cr);
- format %{ "vector_masked_gatherLE8 $dst, $mem, $idx_base, $mask\t! using $mask_idx, $tmp, $rtmp and $rtmp2 as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ xorq($mask_idx$$Register, $mask_idx$$Register);
- __ lea($tmp$$Register, $mem$$Address);
- __ kmovql($rtmp2$$Register, $mask$$KRegister);
- __ vgather8b_masked(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp2$$Register, $mask_idx$$Register, $rtmp$$Register, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_masked_subwordGT8B_avx3(vec dst, memory mem, rRegP idx_base, kReg mask, rRegP tmp, rRegP idx_base_temp,
- vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegL rtmp2, rRegL mask_idx, rRegI length, rFlagsReg cr) %{
- predicate(VM_Version::supports_avx512bw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8);
- match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask)));
- effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP rtmp2, TEMP mask_idx, TEMP length, KILL cr);
- format %{ "vector_gatherGT8_masked $dst, $mem, $idx_base, $mask\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp, $rtmp2, $mask_idx and $length as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- int vector_len = Matcher::vector_length(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ xorq($mask_idx$$Register, $mask_idx$$Register);
- __ lea($tmp$$Register, $mem$$Address);
- __ movptr($idx_base_temp$$Register, $idx_base$$Register);
- __ kmovql($rtmp2$$Register, $mask$$KRegister);
- __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, $rtmp2$$Register, $xtmp1$$XMMRegister,
- $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, $mask_idx$$Register, $length$$Register, vector_len, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_masked_subwordLE8B_avx2(vec dst, memory mem, rRegP idx_base, vec mask, rRegI mask_idx, rRegP tmp, rRegI rtmp, rRegI rtmp2, rFlagsReg cr) %{
- predicate(!VM_Version::supports_avx512vlbw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8);
- match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask)));
- effect(TEMP mask_idx, TEMP tmp, TEMP rtmp, TEMP rtmp2, KILL cr);
- format %{ "vector_masked_gatherLE8 $dst, $mem, $idx_base, $mask\t! using $mask_idx, $tmp, $rtmp and $rtmp2 as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ lea($tmp$$Register, $mem$$Address);
- __ vpmovmskb($rtmp2$$Register, $mask$$XMMRegister, vlen_enc);
- if (elem_bt == T_SHORT) {
- __ movl($mask_idx$$Register, 0x55555555);
- __ pextl($rtmp2$$Register, $rtmp2$$Register, $mask_idx$$Register);
- }
- __ xorl($mask_idx$$Register, $mask_idx$$Register);
- __ vgather8b_masked(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp2$$Register, $mask_idx$$Register, $rtmp$$Register, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
-instruct vgather_masked_subwordGT8B_avx2(vec dst, memory mem, rRegP idx_base, vec mask, rRegP tmp, rRegP idx_base_temp,
- vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegI rtmp2, rRegI mask_idx, rRegI length, rFlagsReg cr) %{
- predicate(!VM_Version::supports_avx512vlbw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8);
- match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask)));
- effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP rtmp2, TEMP mask_idx, TEMP length, KILL cr);
- format %{ "vector_gatherGT8_masked $dst, $mem, $idx_base, $mask\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp, $rtmp2, $mask_idx and $length as TEMP" %}
- ins_encode %{
- int vlen_enc = vector_length_encoding(this);
- int vector_len = Matcher::vector_length(this);
- BasicType elem_bt = Matcher::vector_element_basic_type(this);
- __ lea($tmp$$Register, $mem$$Address);
- __ movptr($idx_base_temp$$Register, $idx_base$$Register);
- __ vpmovmskb($rtmp2$$Register, $mask$$XMMRegister, vlen_enc);
- if (elem_bt == T_SHORT) {
- __ movl($mask_idx$$Register, 0x55555555);
- __ pextl($rtmp2$$Register, $rtmp2$$Register, $mask_idx$$Register);
- }
- __ xorl($mask_idx$$Register, $mask_idx$$Register);
- __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, $rtmp2$$Register, $xtmp1$$XMMRegister,
- $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, $mask_idx$$Register, $length$$Register, vector_len, vlen_enc);
- %}
- ins_pipe( pipe_slow );
-%}
-
// ====================Scatter=======================================
// Scatter INT, LONG, FLOAT, DOUBLE
@@ -18676,15 +18561,13 @@ instruct ReplHF_imm(vec dst, immH con, rRegI rtmp) %{
ins_pipe( pipe_slow );
%}
-instruct ReplHF_reg(vec dst, regF src, rRegI rtmp) %{
+instruct ReplHF_reg(vec dst, regF src) %{
predicate(VM_Version::supports_avx512_fp16() && Matcher::vector_element_basic_type(n) == T_SHORT);
match(Set dst (Replicate src));
- effect(TEMP rtmp);
- format %{ "replicateHF $dst, $src \t! using $rtmp as TEMP" %}
+ format %{ "replicateHF $dst, $src" %}
ins_encode %{
int vlen_enc = vector_length_encoding(this);
- __ evmovw($rtmp$$Register, $src$$XMMRegister);
- __ evpbroadcastw($dst$$XMMRegister, $rtmp$$Register, vlen_enc);
+ __ vpbroadcastw($dst$$XMMRegister, $src$$XMMRegister, vlen_enc);
%}
ins_pipe( pipe_slow );
%}
diff --git a/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp b/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp
new file mode 100644
index 000000000000..2ed9d8f558ad
--- /dev/null
+++ b/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * 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 "gc/z/zAdaptiveHeap.hpp"
+#include "runtime/os.inline.hpp"
+#include "utilities/debug.hpp"
+
+void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) {
+ info._physical_memory = os::Machine::physical_memory();
+ info._is_valid = os::Machine::available_memory(info._available_memory);
+}
+
+bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) {
+#ifdef __APPLE__
+ const int processor_count = os::processor_count();
+
+ if (processor_count <= 0) {
+ return false;
+ }
+
+ mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT;
+ host_cpu_load_info_data_t load_data;
+
+ kern_return_t ret = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&load_data, &count);
+ if (ret != KERN_SUCCESS) {
+ assert(false, "This should never happen");
+ return false;
+ }
+
+ // The CPU ticks counters are 32-bit and may experience wrapping behaviour.
+ natural_t ticks = load_data.cpu_ticks[CPU_STATE_USER] +
+ load_data.cpu_ticks[CPU_STATE_NICE] +
+ load_data.cpu_ticks[CPU_STATE_SYSTEM];
+
+ value._elapsed_time = double(ticks) / CLK_TCK;
+ value._processor_count = double(processor_count);
+ return true;
+#else
+ Unimplemented();
+#endif
+}
+
+bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) {
+ ShouldNotReachHere();
+}
+
+bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) {
+#ifdef __APPLE__
+ mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
+ vm_statistics64_data_t vmstat;
+ kern_return_t kerr = host_statistics64(mach_host_self(), HOST_VM_INFO64,
+ (host_info64_t)&vmstat, &count);
+ assert(kerr == KERN_SUCCESS,
+ "host_statistics64 failed - check mach_host_self() and count");
+ if (kerr == KERN_SUCCESS) {
+ value = vmstat.compressor_page_count * os::vm_page_size();
+ return true;
+ }
+#endif
+ return false;
+}
diff --git a/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp b/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp
index 0c42d7790886..11218d651839 100644
--- a/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp
+++ b/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp
@@ -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
@@ -23,6 +23,11 @@
#include "gc/z/zLargePages.hpp"
#include "runtime/globals.hpp"
+#include "utilities/debug.hpp"
+
+bool ZLargePages::pd_collapse(void* addr, size_t bytes) {
+ ShouldNotCallThis();
+}
void ZLargePages::pd_initialize() {
if (UseLargePages) {
diff --git a/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp b/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp
index 3acaa9ab8f94..c7046b806c20 100644
--- a/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp
+++ b/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp
@@ -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
@@ -28,6 +28,8 @@
void ZNUMA::pd_initialize() {
_enabled = false;
+ _node_count = 1;
+ _bound_node_count = 1;
_count = !FLAG_IS_DEFAULT(ZFakeNUMA)
? ZFakeNUMA
: 1;
diff --git a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp
index 861fda7a71d7..f80604cf764d 100644
--- a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp
+++ b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp
@@ -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
@@ -92,7 +92,7 @@ bool ZPhysicalMemoryBacking::is_initialized() const {
return _initialized;
}
-void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const {
+void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const {
// Does nothing
}
@@ -178,3 +178,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const {
fatal("Failed to map memory (%s)", err.to_string());
}
}
+
+void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const {
+ ShouldNotReachHere();
+}
diff --git a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp
index 9fa64fb51bd3..a8cbdfc9ad9e 100644
--- a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp
+++ b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp
@@ -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
@@ -38,13 +38,15 @@ class ZPhysicalMemoryBacking {
bool is_initialized() const;
- void warn_commit_limits(size_t max_capacity) const;
+ void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const;
size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id) const;
size_t uncommit(zbacking_offset offset, size_t length) const;
void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const;
void unmap(zaddress_unsafe addr, size_t size) const;
+
+ void collapse(zaddress_unsafe addr, size_t size) const;
};
#endif // OS_BSD_GC_Z_ZPHYSICALMEMORYBACKING_BSD_HPP
diff --git a/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp b/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp
new file mode 100644
index 000000000000..869b51f7b018
--- /dev/null
+++ b/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * 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 "gc/shared/gcLogPrecious.hpp"
+#include "gc/z/zAdaptiveHeap.hpp"
+#include "gc/z/zNUMA.inline.hpp"
+#include "jvm_io.h"
+#include "logging/log.hpp"
+#include "os_linux.hpp"
+#include "osContainer_linux.hpp"
+#include "runtime/os.inline.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) {
+ info._physical_memory = 0;
+ info._available_memory = 0;
+ info._is_valid = false;
+
+ if (!ZNUMA::is_bound()) {
+ info._physical_memory = os::Machine::physical_memory();
+ info._is_valid = os::Machine::available_memory(info._available_memory);
+ return;
+ }
+
+ // In the case where the user has bound the process and -UseNUMA is provided,
+ // we must still gather information from all bound nodes. Even though our memory
+ // management does not try to do any NUMA optimizations when managing with memory.
+ assert(!ZNUMA::is_enabled() || ZNUMA::bound_count() == ZNUMA::count(),
+ "UseNUMA implies that bound_count(%u) == count(%u)", ZNUMA::bound_count(), ZNUMA::count());
+
+ for (uint32_t numa_id = 0; numa_id < ZNUMA::bound_count(); numa_id++) {
+ const int node = ZNUMA::numa_id_to_node(numa_id);
+ char path[128];
+ jio_snprintf(path, sizeof(path), "/sys/devices/system/node/node%d/meminfo", node);
+
+ physical_memory_size_type node_physical_memory = 0;
+ physical_memory_size_type node_available_memory = 0;
+ bool found_mem_total = false;
+ bool found_mem_available = false;
+ bool found_mem_free = false;
+ bool found_active_file = false;
+ bool found_inactive_file = false;
+ bool found_sreclaimable = false;
+
+ FILE* f = os::fopen(path, "r");
+ if (f) {
+ char line[256];
+ while (fgets(line, sizeof(line), f) != nullptr) {
+ int n = -1;
+ physical_memory_size_type read_value = 0;
+ if (sscanf(line, "Node %d MemTotal: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) {
+ node_physical_memory = read_value * K;
+ found_mem_total = true;
+ } else if (sscanf(line, "Node %d MemAvailable: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) {
+ // If the Kernel has an approximation of MemAvailable, use it
+ node_available_memory = read_value * K;
+ found_mem_available = true;
+ } else if (!found_mem_available && sscanf(line, "Node %d MemFree: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) {
+ node_available_memory += read_value * K;
+ found_mem_free = true;
+ } else if (!found_mem_available && sscanf(line, "Node %d Active(file): " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) {
+ node_available_memory += read_value * K;
+ found_active_file = true;
+ } else if (!found_mem_available && sscanf(line, "Node %d Inactive(file): " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) {
+ node_available_memory += read_value * K;
+ found_inactive_file = true;
+ } else if (!found_mem_available && sscanf(line, "Node %d SReclaimable: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) {
+ node_available_memory += read_value * K;
+ found_sreclaimable = true;
+ }
+
+ if (found_mem_total && (found_mem_available || (found_mem_free && found_active_file && found_inactive_file && found_sreclaimable))) {
+ break;
+ }
+ }
+
+ fclose(f);
+
+ if (!found_mem_total) {
+ // Some Kernels might not have "MemTotal" available in the node-specific file.
+ // Fall back to numa_node_size64 if that's the case.
+ long long res = os::Linux::numa_node_size64(node, nullptr);
+ if (res != -1) {
+ node_physical_memory = (physical_memory_size_type)res;
+ found_mem_total = true;
+ }
+ }
+ }
+
+ if (!(found_mem_total && (found_mem_available || (found_mem_free && found_active_file && found_inactive_file && found_sreclaimable)))) {
+ static bool runs_once = [&]() {
+ log_warning_p(gc, heap)("Failed to read one of the NUMA-node specific values: "
+ "MemTotal: %d, MemAvailable: %d, MemFree: %d, Active(file): %d, Inactive(file): %d, SReclaimable: %d",
+ found_mem_total, found_mem_available, found_mem_free, found_active_file, found_inactive_file, found_sreclaimable);
+ return true;
+ }();
+ assert(false, "This should not happen");
+
+ info._physical_memory = os::Machine::physical_memory();
+ info._is_valid = os::Machine::available_memory(info._available_memory);
+ return;
+ }
+
+ info._physical_memory += node_physical_memory;
+ info._available_memory += node_available_memory;
+ }
+
+ info._is_valid = true;
+}
+
+bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) {
+ os::Linux::CPUPerfTicks ticks;
+
+ if (!os::Linux::get_tick_information(&ticks, -1)) {
+ return false;
+ }
+
+ const int online_cpus = checked_cast(::sysconf(_SC_NPROCESSORS_ONLN));
+
+ if (online_cpus == -1) {
+ assert(false, "This should never happen");
+ return false;
+ }
+ assert(online_cpus > 0, "Unespected online CPU count: %d", online_cpus);
+
+ const uint64_t sum = ticks.used + ticks.usedKernel;
+ value._elapsed_time = double(sum) / os::Posix::clock_tics_per_second();
+ value._processor_count = double(online_cpus);
+ return true;
+}
+
+bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) {
+ precond(os::is_containerized());
+ uint64_t result;
+ double processors;
+ // os::Container::processor_count does not account for the processors
+ // availiable in the container, but for the process. This can give a scew as
+ // cpu_usage_in_micros is the whole container.
+ if (OSContainer::cpu_usage_in_micros(result) &&
+ os::Container::processor_count(processors)) {
+ value._elapsed_time = double(result) / MICROUNITS;
+ value._processor_count = processors;
+ return true;
+ }
+
+ return false;
+}
+
+bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) {
+ return false;
+}
diff --git a/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp b/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp
index c9f99a4b6db4..76b5b38c5a34 100644
--- a/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp
+++ b/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp
@@ -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
@@ -21,13 +21,78 @@
* questions.
*/
+#include "gc/z/zAdaptiveHeap.inline.hpp"
#include "gc/z/zLargePages.hpp"
#include "hugepages.hpp"
#include "os_linux.hpp"
#include "runtime/globals.hpp"
+#include "runtime/globals_extension.hpp"
+
+#include
+
+// Define MADV_COLLAPSE here so we can build HotSpot on old systems.
+#define MADV_COLLAPSE_value 25
+#ifndef MADV_COLLAPSE
+#define MADV_COLLAPSE MADV_COLLAPSE_value
+#else
+ // Sanity-check our assumed default value if we build with a new enough libc.
+ static_assert(MADV_COLLAPSE == MADV_COLLAPSE_value);
+#endif
+
+bool ZLargePages::pd_collapse(void* addr, size_t bytes) {
+ // When MADV_COLLAPSE races with THP khugepaged, you sometimes get
+ // EAGAIN. We just do it again then.
+ // Note: mlock, mlockall, DMA / GUP with FOLL_PIN/FOLL_LONGTERM can
+ // can all cause EAGAIN to be persistent. We treat this as a
+ // user error, and allow a calling thread to spin forever here.
+ // We may need to evaluate potential live-locking here in the future.
+ for (;;) {
+ const int result = ::madvise(addr, bytes, MADV_COLLAPSE);
+ if (result == 0) {
+ return true;
+ }
+ if (result == -1 && errno == EAGAIN) {
+ continue;
+ }
+
+ return false;
+ }
+}
+
+static bool madv_collapse_available() {
+ const size_t size = 2 * M;
+ void* const res = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ if (res == MAP_FAILED) {
+ return false;
+ }
+
+ assert(size >= os::vm_page_size(), "Unexpected page size");
+ os::pretouch_memory(res, (void*)(((char*)res) + os::vm_page_size()));
+
+ const bool result = ZLargePages::pd_collapse(res, size);
+
+ munmap(res, size);
+
+ return result;
+}
void ZLargePages::pd_initialize() {
+ // Evaluate if we should move madv collapse logic to os::Linux / HugePages so
+ // that validate_thps_configured can log the warning correctly if shmem THP
+ // is disabled.
+ // Maybe combined with a general GC callback. Let os::Linux / HugePages contain
+ // the OS logic, let the GC do the extra log warnings as part of the
+ // validation callback.
+ const auto can_collapse = []() {
+ return ZMemoryHeating && madv_collapse_available();
+ };
+
if (os::Linux::thp_requested()) {
+ if (can_collapse()) {
+ _state = Collapse;
+ return;
+ }
// Check if the OS config turned off transparent huge pages for shmem.
_os_enforced_transparent_mode = HugePages::shmem_thp_info().is_disabled();
_state = _os_enforced_transparent_mode ? Disabled : Transparent;
@@ -35,7 +100,25 @@ void ZLargePages::pd_initialize() {
}
if (UseLargePages) {
- _state = Explicit;
+ if (!ZAdaptiveHeapSizing || ZAdaptiveHeap::explicit_max_capacity()) {
+ _state = Explicit;
+ return;
+ }
+
+ log_warning(gc, init)("UseLargePages requires a max heap size to be set (-Xmx) when running with Adaptive Heap Sizing. "
+ "Disabling the use of explicit large pages for the heap");
+ }
+
+ // Because os::Linux::large_page_init does not set the origin, we cannot tell
+ // whether the user explicitly disabled large pages or explicitly requested
+ // large pages which the OS configuration did not support.
+ // Look into adding a similar cached user request as os::Linux::thp_requested
+ // for UseLargePages.
+ const bool large_pages_explicitly_disabled = !FLAG_IS_DEFAULT(UseLargePages) && !UseLargePages;
+ const bool thp_explicitly_disabled = !FLAG_IS_DEFAULT(UseTransparentHugePages) && !UseTransparentHugePages;
+
+ if (!large_pages_explicitly_disabled && !thp_explicitly_disabled && can_collapse()) {
+ _state = Collapse;
return;
}
diff --git a/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp b/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp
index 3245d07caf9f..f44d4bac7f4f 100644
--- a/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp
+++ b/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp
@@ -102,7 +102,6 @@ class ZNUMAConverter {
public:
void initialize() {
precond(!_initialized);
- precond(UseNUMA);
populate_id_mappings();
populate_node_mappings();
@@ -140,15 +139,23 @@ static ZNUMAConverter z_numa_converter;
void ZNUMA::pd_initialize() {
_enabled = UseNUMA;
- if (UseNUMA) {
+ const int configured_nodes = os::Linux::numa_num_configured_nodes();
+ if (configured_nodes > 0) {
z_numa_converter.initialize();
- _count = z_numa_converter.count();
+
+ _node_count = integer_cast(configured_nodes);
+ _bound_node_count = z_numa_converter.count();
} else {
- // UseNUMA and is_faked() are mutually excluded in zArguments.cpp.
- _count = !FLAG_IS_DEFAULT(ZFakeNUMA)
- ? ZFakeNUMA
- : 1; // No NUMA nodes
+ _node_count = 1;
+ _bound_node_count = 1;
}
+
+ // UseNUMA and is_faked() are mutually excluded in zArguments.cpp.
+ _count = UseNUMA
+ ? _bound_node_count
+ : !FLAG_IS_DEFAULT(ZFakeNUMA)
+ ? ZFakeNUMA
+ : 1; // No NUMA nodes
}
uint32_t ZNUMA::id() {
diff --git a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp
index 28159ae48016..f6b92d746f3b 100644
--- a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp
+++ b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.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
@@ -22,6 +22,7 @@
*/
#include "gc/shared/gcLogPrecious.hpp"
+#include "gc/z/zAdaptiveHeap.inline.hpp"
#include "gc/z/zAddress.inline.hpp"
#include "gc/z/zArray.inline.hpp"
#include "gc/z/zErrno.hpp"
@@ -35,6 +36,8 @@
#include "hugepages.hpp"
#include "logging/log.hpp"
#include "os_linux.hpp"
+#include "runtime/globals.hpp"
+#include "runtime/globals_extension.hpp"
#include "runtime/init.hpp"
#include "runtime/os.hpp"
#include "runtime/safefetch.hpp"
@@ -116,6 +119,8 @@ static const char* ZPreferredHugetlbfsMountpoints[] = {
static int z_fallocate_hugetlbfs_attempts = 3;
static bool z_fallocate_supported = true;
+static size_t z_max_map_count = 0;
+
ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity)
: _fd(-1),
_filesystem(0),
@@ -151,6 +156,14 @@ ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity)
_block_size = buf.f_bsize;
_available = buf.f_bavail * _block_size;
+ // Note that the available space on a tmpfs or a hugetlbfs filesystem
+ // will be zero if no size limit was specified when it was mounted.
+ if (_available == 0) {
+ log_info_p(gc, init)("Available space on backing filesystem: N/A");
+ } else {
+ log_info_p(gc, init)("Available space on backing filesystem: %zuM", _available / M);
+ }
+
log_info_p(gc, init)("Heap Backing Filesystem: %s (" UINT64_FORMAT_X ")",
is_tmpfs() ? ZFILESYSTEM_TMPFS : is_hugetlbfs() ? ZFILESYSTEM_HUGETLBFS : "other", _filesystem);
@@ -192,6 +205,9 @@ ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity)
return;
}
+ // Should we try to use fallocate
+ z_fallocate_supported = ZLargePages::should_try_fallocate();
+
// Successfully initialized
_initialized = true;
}
@@ -308,12 +324,9 @@ void ZPhysicalMemoryBacking::warn_available_space(size_t max_capacity) const {
// will be zero if no size limit was specified when it was mounted.
if (_available == 0) {
// No size limit set, skip check
- log_info_p(gc, init)("Available space on backing filesystem: N/A");
return;
}
- log_info_p(gc, init)("Available space on backing filesystem: %zuM", _available / M);
-
// Warn if the filesystem doesn't currently have enough space available to hold
// the max heap size. The max heap size will be capped if we later hit this limit
// when trying to expand the heap.
@@ -328,7 +341,11 @@ void ZPhysicalMemoryBacking::warn_available_space(size_t max_capacity) const {
}
}
-void ZPhysicalMemoryBacking::warn_max_map_count(size_t max_capacity) const {
+void ZPhysicalMemoryBacking::compute_max_map_count() const {
+ if (z_max_map_count != 0) {
+ return;
+ }
+
const char* const filename = ZFILENAME_PROC_MAX_MAP_COUNT;
FILE* const file = os::fopen(filename, "r");
if (file == nullptr) {
@@ -337,38 +354,52 @@ void ZPhysicalMemoryBacking::warn_max_map_count(size_t max_capacity) const {
return;
}
- size_t actual_max_map_count = 0;
- const int result = fscanf(file, "%zu", &actual_max_map_count);
+ const int result = fscanf(file, "%zu", &z_max_map_count);
fclose(file);
if (result != 1) {
// Failed to read file, skip check
log_debug_p(gc, init)("Failed to read %s", filename);
return;
}
+}
+
+void ZPhysicalMemoryBacking::warn_max_map_count(size_t expected_capacity, size_t max_capacity) const {
+ // In the worst case, ZGC needs 2 mappings per granule. The reason is that if the
+ // fragmentation is at maximum, the mappings will be interleaved between being
+ // backed by memory vs not being backed by memory.
+ const size_t mappings_per_granule = 2;
+ const size_t expected_mappings = (expected_capacity / ZGranuleSize) * mappings_per_granule;
+ const size_t max_mappings = (max_capacity / ZGranuleSize) * mappings_per_granule;
// The required max map count is impossible to calculate exactly since subsystems
// other than ZGC are also creating memory mappings, and we have no control over that.
// However, ZGC tends to create the most mappings and dominate the total count.
- // In the worst cases, ZGC will map each granule three times, i.e. once per heap view.
- // We speculate that we need another 20% to allow for non-ZGC subsystems to map memory.
- const size_t required_max_map_count = (max_capacity / ZGranuleSize) * 3 * 1.2;
- if (actual_max_map_count < required_max_map_count) {
- log_warning_p(gc)("***** WARNING! INCORRECT SYSTEM CONFIGURATION DETECTED! *****");
- log_warning_p(gc)("The system limit on number of memory mappings per process might be too low for the given");
- log_warning_p(gc)("max Java heap size (%zuM). Please adjust %s to allow for at",
- max_capacity / M, filename);
- log_warning_p(gc)("least %zu mappings (current limit is %zu). Continuing execution "
- "with the current", required_max_map_count, actual_max_map_count);
- log_warning_p(gc)("limit could lead to a premature OutOfMemoryError being thrown, due to failure to map memory.");
+ const size_t required_max_map_count = expected_mappings + expected_mappings / 5;
+ if (z_max_map_count >= required_max_map_count) {
+ return;
}
+
+ const size_t recommended_max_map_count = max_mappings + max_mappings / 5;
+
+ const char* const filename = ZFILENAME_PROC_MAX_MAP_COUNT;
+ log_warning_p(gc)("***** WARNING! INCORRECT SYSTEM CONFIGURATION DETECTED! *****");
+ log_warning_p(gc)("The system limit on number of memory mappings per process might be too low for the");
+ log_warning_p(gc)("Java heap size (%zuM). Please adjust %s to allow for at",
+ expected_capacity / M, filename);
+ log_warning_p(gc)("least %zu mappings (current limit is %zu). Continuing execution "
+ "with the current", recommended_max_map_count, z_max_map_count);
+ log_warning_p(gc)("limit could lead to a premature OutOfMemoryError being thrown, due to failure to map memory.");
}
-void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const {
+void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const {
// Warn if available space is too low
- warn_available_space(max_capacity);
+ warn_available_space(expected_capacity);
+
+ // Compute max map count the first time (during bootstrapping)
+ compute_max_map_count();
// Warn if max map count is too low
- warn_max_map_count(max_capacity);
+ warn_max_map_count(expected_capacity, max_capacity);
}
bool ZPhysicalMemoryBacking::is_tmpfs() const {
@@ -513,21 +544,24 @@ ZErrno ZPhysicalMemoryBacking::fallocate_fill_hole(zbacking_offset offset, size_
// some point touch these segments, otherwise we can not punch hole in them.
// Also note that we need to use compat mode when using transparent huge pages,
// since we need to use madvise(2) on the mapping before the page is allocated.
- if (z_fallocate_supported && !ZLargePages::is_enabled()) {
- const ZErrno err = fallocate_fill_hole_syscall(offset, length);
- if (!err) {
- // Success
- return 0;
- }
+ if (z_fallocate_supported) {
+ precond(ZLargePages::should_try_fallocate());
+
+ const ZErrno err = fallocate_fill_hole_syscall(offset, length);
+
+ if (!err) {
+ // Success
+ return 0;
+ }
- if (err != ENOSYS && err != EOPNOTSUPP) {
- // Failed
- return err;
- }
+ if (err != ENOSYS && err != EOPNOTSUPP) {
+ // Failed
+ return err;
+ }
- // Not supported
- log_debug_p(gc)("Falling back to fallocate() compatibility mode");
- z_fallocate_supported = false;
+ // Not supported
+ log_debug_p(gc)("Falling back to fallocate() compatibility mode");
+ z_fallocate_supported = false;
}
return fallocate_fill_hole_compat(offset, length);
@@ -593,6 +627,8 @@ ZErrno ZPhysicalMemoryBacking::fallocate(bool punch_hole, zbacking_offset offset
return err;
}
+static Atomic warned_failed_commit{false};
+
bool ZPhysicalMemoryBacking::commit_inner(zbacking_offset offset, size_t length) const {
log_trace(gc, heap)("Committing memory: %zuM-%zuM (%zuM)",
untype(offset) / M, untype(to_zbacking_offset_end(offset, length)) / M, length / M);
@@ -615,8 +651,11 @@ bool ZPhysicalMemoryBacking::commit_inner(zbacking_offset offset, size_t length)
goto retry;
}
+ if (!warned_failed_commit.exchange(true, memory_order_relaxed)) {
+ log_error_p(gc)("Failed to commit memory (%s)", err.to_string());
+ }
+
// Failed
- log_error_p(gc)("Failed to commit memory (%s)", err.to_string());
return false;
}
@@ -705,3 +744,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const {
fatal("Failed to map memory (%s)", err.to_string());
}
}
+
+void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const {
+ ZLargePages::pd_collapse((void*)untype(addr), size);
+}
diff --git a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp
index 4b083d1c79c3..f192ba0dc286 100644
--- a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp
+++ b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.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
@@ -31,14 +31,14 @@ class ZErrno;
class ZPhysicalMemoryBacking {
private:
int _fd;
- size_t _size;
uint64_t _filesystem;
size_t _block_size;
size_t _available;
bool _initialized;
void warn_available_space(size_t max_capacity) const;
- void warn_max_map_count(size_t max_capacity) const;
+ void compute_max_map_count() const;
+ void warn_max_map_count(size_t expected_capacity, size_t max_capacity) const;
int create_mem_fd(const char* name) const;
int create_file_fd(const char* name) const;
@@ -67,13 +67,15 @@ class ZPhysicalMemoryBacking {
bool is_initialized() const;
- void warn_commit_limits(size_t max_capacity) const;
+ void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const;
size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id) const;
size_t uncommit(zbacking_offset offset, size_t length) const;
void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const;
void unmap(zaddress_unsafe addr, size_t size) const;
+
+ void collapse(zaddress_unsafe addr, size_t size) const;
};
#endif // OS_LINUX_GC_Z_ZPHYSICALMEMORYBACKING_LINUX_HPP
diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp
index b2db5a51d54c..ffe0b6f56310 100644
--- a/src/hotspot/os/linux/os_linux.cpp
+++ b/src/hotspot/os/linux/os_linux.cpp
@@ -3513,6 +3513,8 @@ bool os::Linux::libnuma_init() {
libnuma_dlsym(handle, "numa_num_configured_nodes")));
set_numa_available(CAST_TO_FN_PTR(numa_available_func_t,
libnuma_dlsym(handle, "numa_available")));
+ set_numa_node_size64(CAST_TO_FN_PTR(numa_node_size64_func_t,
+ libnuma_dlsym(handle, "numa_node_size64")));
set_numa_tonode_memory(CAST_TO_FN_PTR(numa_tonode_memory_func_t,
libnuma_dlsym(handle, "numa_tonode_memory")));
set_numa_interleave_memory(CAST_TO_FN_PTR(numa_interleave_memory_func_t,
@@ -3802,6 +3804,7 @@ os::Linux::numa_node_to_cpus_func_t os::Linux::_numa_node_to_cpus;
os::Linux::numa_node_to_cpus_v2_func_t os::Linux::_numa_node_to_cpus_v2;
os::Linux::numa_max_node_func_t os::Linux::_numa_max_node;
os::Linux::numa_num_configured_nodes_func_t os::Linux::_numa_num_configured_nodes;
+os::Linux::numa_node_size64_func_t os::Linux::_numa_node_size64;
os::Linux::numa_available_func_t os::Linux::_numa_available;
os::Linux::numa_tonode_memory_func_t os::Linux::_numa_tonode_memory;
os::Linux::numa_interleave_memory_func_t os::Linux::_numa_interleave_memory;
diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp
index 5b7259f2c24c..a2925f7207bf 100644
--- a/src/hotspot/os/linux/os_linux.hpp
+++ b/src/hotspot/os/linux/os_linux.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -215,6 +215,7 @@ class os::Linux {
typedef int (*numa_node_to_cpus_v2_func_t)(int node, void *mask);
typedef int (*numa_max_node_func_t)(void);
typedef int (*numa_num_configured_nodes_func_t)(void);
+ typedef long long (*numa_node_size64_func_t)(int node, long long* freep);
typedef int (*numa_available_func_t)(void);
typedef int (*numa_tonode_memory_func_t)(void *start, size_t size, int node);
typedef void (*numa_interleave_memory_func_t)(void *start, size_t size, unsigned long *nodemask);
@@ -237,6 +238,7 @@ class os::Linux {
static numa_node_to_cpus_v2_func_t _numa_node_to_cpus_v2;
static numa_max_node_func_t _numa_max_node;
static numa_num_configured_nodes_func_t _numa_num_configured_nodes;
+ static numa_node_size64_func_t _numa_node_size64;
static numa_available_func_t _numa_available;
static numa_tonode_memory_func_t _numa_tonode_memory;
static numa_interleave_memory_func_t _numa_interleave_memory;
@@ -266,6 +268,7 @@ class os::Linux {
static void set_numa_node_to_cpus_v2(numa_node_to_cpus_v2_func_t func) { _numa_node_to_cpus_v2 = func; }
static void set_numa_max_node(numa_max_node_func_t func) { _numa_max_node = func; }
static void set_numa_num_configured_nodes(numa_num_configured_nodes_func_t func) { _numa_num_configured_nodes = func; }
+ static void set_numa_node_size64(numa_node_size64_func_t func) { _numa_node_size64 = func; }
static void set_numa_available(numa_available_func_t func) { _numa_available = func; }
static void set_numa_tonode_memory(numa_tonode_memory_func_t func) { _numa_tonode_memory = func; }
static void set_numa_interleave_memory(numa_interleave_memory_func_t func) { _numa_interleave_memory = func; }
@@ -307,6 +310,9 @@ class os::Linux {
static int numa_num_configured_nodes() {
return _numa_num_configured_nodes != nullptr ? _numa_num_configured_nodes() : -1;
}
+ static long long numa_node_size64(int node, long long* freep) {
+ return _numa_node_size64 != nullptr ? _numa_node_size64(node, freep) : -1;
+ }
static int numa_available() { return _numa_available != nullptr ? _numa_available() : -1; }
static int numa_tonode_memory(void *start, size_t size, int node) {
return _numa_tonode_memory != nullptr ? _numa_tonode_memory(start, size, node) : -1;
diff --git a/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp b/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp
new file mode 100644
index 000000000000..eb706c323625
--- /dev/null
+++ b/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * 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 "gc/z/zAdaptiveHeap.hpp"
+#include "runtime/os.inline.hpp"
+
+#include
+#include
+
+void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) {
+ info._physical_memory = os::Machine::physical_memory();
+ info._is_valid = os::Machine::available_memory(info._available_memory);
+}
+
+bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) {
+ const int processor_count = os::processor_count();
+
+ if (processor_count <= 0 || processor_count > 64) {
+ // GetSystemTimes is not accurate on systems with more than 64 cores.
+ return false;
+ }
+
+ FILETIME idle, kernel, user;
+ if (GetSystemTimes(&idle, &kernel, &user) == 0) {
+ assert(false, "this should not fail");
+ return false;
+ }
+
+ // Kernel time includes idle time
+ jlong ticks = jlong_from(user.dwHighDateTime, user.dwLowDateTime) +
+ jlong_from(kernel.dwHighDateTime, kernel.dwLowDateTime) -
+ jlong_from(idle.dwHighDateTime, idle.dwLowDateTime);
+
+ // Ticks are 100 ns
+ value._elapsed_time = double(ticks) / 1e7;
+ value._processor_count = double(processor_count);
+ return true;
+}
+
+bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) {
+ ShouldNotReachHere();
+}
+
+bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) {
+ return false;
+}
diff --git a/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp b/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp
index dcd178da8212..70aaa3447a1b 100644
--- a/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp
+++ b/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp
@@ -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
@@ -25,6 +25,11 @@
#include "gc/z/zLargePages.hpp"
#include "gc/z/zSyscall_windows.hpp"
#include "runtime/globals.hpp"
+#include "utilities/debug.hpp"
+
+bool ZLargePages::pd_collapse(void* addr, size_t bytes) {
+ ShouldNotCallThis();
+}
void ZLargePages::pd_initialize() {
if (UseLargePages) {
diff --git a/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp b/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp
index e2bd68035844..2a49e58b656d 100644
--- a/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp
+++ b/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp
@@ -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
@@ -28,6 +28,8 @@
void ZNUMA::pd_initialize() {
_enabled = false;
+ _node_count = 1;
+ _bound_node_count = 1;
_count = !FLAG_IS_DEFAULT(ZFakeNUMA)
? ZFakeNUMA
: 1;
diff --git a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp
index b18abd7e9792..15f1a846de6d 100644
--- a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp
+++ b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp
@@ -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
@@ -226,7 +226,7 @@ bool ZPhysicalMemoryBacking::is_initialized() const {
return true;
}
-void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const {
+void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const {
// Does nothing
}
@@ -258,3 +258,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const {
_impl->unmap(addr, size);
}
+
+void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const {
+ ShouldNotReachHere();
+}
diff --git a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp
index 91d59f496098..3e20c655c785 100644
--- a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp
+++ b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp
@@ -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
@@ -40,13 +40,15 @@ class ZPhysicalMemoryBacking {
bool is_initialized() const;
- void warn_commit_limits(size_t max_capacity) const;
+ void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const;
size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id);
size_t uncommit(zbacking_offset offset, size_t length);
void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const;
void unmap(zaddress_unsafe addr, size_t size) const;
+
+ void collapse(zaddress_unsafe addr, size_t size) const;
};
#endif // OS_WINDOWS_GC_Z_ZPHYSICALMEMORYBACKING_WINDOWS_HPP
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 6643187170ab..f63bdb62dff5 100644
--- a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp
+++ b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp
@@ -47,7 +47,8 @@ frame JavaThread::pd_last_frame() {
}
#endif
- return frame(sp, pc);
+ // Likely the frame of a RuntimeStub (e.g. after a call to the VM).
+ return frame(sp, pc, frame::kind::code_blob);
}
bool JavaThread::pd_get_top_frame_for_profiling(frame* fr_addr, void* ucontext, bool isInJava) {
@@ -71,7 +72,8 @@ bool JavaThread::pd_get_top_frame_for_profiling(frame* fr_addr, void* ucontext,
return false;
}
- frame ret_frame((intptr_t*)uc->uc_mcontext.gregs[15/*Z_SP*/], pc);
+ // pc is from ucontext — could be anywhere (native, compiled, unknown).
+ frame ret_frame((intptr_t*)uc->uc_mcontext.gregs[15/*Z_SP*/], pc, frame::kind::unknown);
if (ret_frame.fp() == nullptr) {
// The found frame does not have a valid frame pointer.
diff --git a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp
index cbb06d2d07d8..17aa7fd84841 100644
--- a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp
+++ b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp
@@ -145,14 +145,14 @@ frame os::fetch_frame_from_context(const void* ucVoid) {
// compiled frame stack bang
return fetch_compiled_frame_from_context(ucVoid);
}
- return frame(sp, epc);
+ return frame(sp, epc, frame::kind::unknown);
}
frame os::fetch_compiled_frame_from_context(const void* ucVoid) {
const ucontext_t* uc = (const ucontext_t*)ucVoid;
intptr_t* sp = os::Linux::ucontext_get_sp(uc);
address lr = ucontext_get_lr(uc);
- return frame(sp, lr);
+ return frame(sp, lr, frame::kind::unknown);
}
intptr_t* os::fetch_bcp_from_context(const void* ucVoid) {
@@ -171,23 +171,23 @@ frame os::get_sender_for_C_frame(frame* fr) {
// of gpr10.
if ((Interpreter::code() != nullptr && Interpreter::contains(fr->pc())) ||
(CodeCache::contains(fr->pc()) && !StubRoutines::contains(fr->pc()))) {
- return frame(fr->sender_sp(), fr->sender_pc());
+ return frame(fr->sender_sp(), fr->sender_pc(), frame::kind::unknown);
} else {
if (StubRoutines::contains(fr->pc())) {
StubCodeDesc* desc = StubCodeDesc::desc_for(fr->pc());
if (desc && !strcmp(desc->name(),"call_stub")) {
- return frame(fr->sender_sp(), fr->callstub_sender_pc());
+ return frame(fr->sender_sp(), fr->callstub_sender_pc(), frame::kind::unknown);
} else {
- return frame(fr->sender_sp(), fr->sender_pc());
+ return frame(fr->sender_sp(), fr->sender_pc(), frame::kind::unknown);
}
} else {
intptr_t* sender_sp = fr->sender_sp();
address sender_fp = (address)*sender_sp;
ptrdiff_t entry_len = sender_fp - (address)sender_sp;
if (entry_len < frame::z_abi_160_size) {
- return frame(sender_sp, fr->sender_pc());
+ return frame(sender_sp, fr->sender_pc(), frame::kind::unknown);
} else {
- return frame(sender_sp, fr->native_sender_pc());
+ return frame(sender_sp, fr->native_sender_pc(), frame::kind::unknown);
}
}
}
@@ -200,7 +200,7 @@ frame os::current_frame() {
assert (csp != nullptr, "sp should not be null");
// Pass a dummy pc. This way we don't have to load it from the
// stack, since we don't know in which slot we can find it.
- frame topframe(csp, (address)0x8);
+ frame topframe(csp, (address)0x8, frame::kind::unknown);
if (os::is_first_C_frame(&topframe)) {
// Stack is not walkable.
return frame();
diff --git a/src/hotspot/share/adlc/output_c.cpp b/src/hotspot/share/adlc/output_c.cpp
index ec764a72846b..a21c6472ce9d 100644
--- a/src/hotspot/share/adlc/output_c.cpp
+++ b/src/hotspot/share/adlc/output_c.cpp
@@ -3279,7 +3279,6 @@ void ArchDesc::defineClasses(FILE *fp) {
if ( memory_operand != InstructForm::NO_MEMORY_OPERAND ) {
if( memory_operand == InstructForm::MANY_MEMORY_OPERANDS ) {
- fprintf(fp,"const TypePtr *%sNode::adr_type() const { return TypePtr::BOTTOM; }\n", instr->_ident);
fprintf(fp,"const MachOper* %sNode::memory_operand() const { return (MachOper*)-1; }\n", instr->_ident);
} else {
fprintf(fp,"const MachOper* %sNode::memory_operand() const { return _opnds[%d]; }\n", instr->_ident, memory_operand);
diff --git a/src/hotspot/share/adlc/output_h.cpp b/src/hotspot/share/adlc/output_h.cpp
index 616e457cdd3e..cd25acea664e 100644
--- a/src/hotspot/share/adlc/output_h.cpp
+++ b/src/hotspot/share/adlc/output_h.cpp
@@ -728,8 +728,8 @@ void gen_inst_format(FILE *fp, FormDict &globals, InstructForm &inst, bool for_c
}
else if( inst.is_ideal_mem() ) {
// Print out the field name if available to improve readability
- fprintf(fp, " if (ra->C->alias_type(adr_type())->field() != nullptr) {\n");
- fprintf(fp, " ciField* f = ra->C->alias_type(adr_type())->field();\n");
+ fprintf(fp, " if (ra->C->alias_type(in_adr_type())->field() != nullptr) {\n");
+ fprintf(fp, " ciField* f = ra->C->alias_type(in_adr_type())->field();\n");
fprintf(fp, " st->print(\" %s Field: \");\n", commentSeperator);
fprintf(fp, " if (f->is_volatile())\n");
fprintf(fp, " st->print(\"volatile \");\n");
@@ -740,7 +740,7 @@ void gen_inst_format(FILE *fp, FormDict &globals, InstructForm &inst, bool for_c
fprintf(fp, " st->print(\" (constant)\");\n");
fprintf(fp, " } else {\n");
// Make sure 'Volatile' gets printed out
- fprintf(fp, " if (ra->C->alias_type(adr_type())->is_volatile())\n");
+ fprintf(fp, " if (ra->C->alias_type(in_adr_type())->is_volatile())\n");
fprintf(fp, " st->print(\" volatile!\");\n");
fprintf(fp, " }\n");
}
@@ -1885,9 +1885,6 @@ void ArchDesc::declareClasses(FILE *fp) {
// Analyze machine instructions that either USE or DEF memory.
int memory_operand = instr->memory_operand(_globalNames);
if ( memory_operand != InstructForm::NO_MEMORY_OPERAND ) {
- if( memory_operand == InstructForm::MANY_MEMORY_OPERANDS ) {
- fprintf(fp," virtual const TypePtr *adr_type() const;\n");
- }
fprintf(fp," virtual const MachOper *memory_operand() const;\n");
}
diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp
index 50bad1daef21..c260b118ed91 100644
--- a/src/hotspot/share/c1/c1_GraphBuilder.cpp
+++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp
@@ -1389,7 +1389,7 @@ void GraphBuilder::if_node(Value x, If::Condition cond, Value y, ValueStack* sta
}
}
}
- if ((stream()->cur_bc() == Bytecodes::_if_acmpeq || stream()->cur_bc() == Bytecodes::_if_acmpne) &&
+ if (Arguments::is_valhalla_enabled() && (stream()->cur_bc() == Bytecodes::_if_acmpeq || stream()->cur_bc() == Bytecodes::_if_acmpne) &&
profile_acmp()) {
compilation()->set_would_profile(true);
append(new ProfileACmpTypes(method(), bci(), x, y));
@@ -1803,14 +1803,24 @@ Value GraphBuilder::make_constant(ciConstant field_value, ciField* field) {
}
}
-void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, ciField* enclosing_field) {
+void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before,
+ CopyNullChecks& null_checks, ciField* enclosing_field) {
for (int i = 0; i < vk->nof_declared_nonstatic_fields(); i++) {
ciField* field = vk->declared_nonstatic_field_at(i);
int offset = field->offset_in_bytes() - vk->payload_offset();
if (field->is_flat()) {
- copy_value_content(field->type()->as_value_klass(), src, src_off + offset, dest, dest_off + offset, state_before, enclosing_field);
+ copy_value_content(field->type()->as_value_klass(), src, src_off + offset, dest, dest_off + offset, state_before, null_checks, enclosing_field);
if (!field->is_null_free()) {
// Nullable, copy the null marker using Unsafe because null markers are not real fields
+ // Unsafe accesses do not perform implicit null checks. Add explicit ones if needed.
+ if (null_checks.needs_dst) {
+ null_check(dest);
+ null_checks.needs_dst = false;
+ }
+ if (null_checks.needs_src) {
+ null_check(src);
+ null_checks.needs_src = false;
+ }
int null_marker_offset = field->null_marker_offset() - vk->payload_offset();
Value offset = append(new Constant(new LongConstant(src_off + null_marker_offset)));
Value nm = append(new UnsafeGet(T_BOOLEAN, src, offset, false));
@@ -1819,9 +1829,11 @@ void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off,
}
} else {
Value value = append(new LoadField(src, src_off + offset, field, false, state_before, false));
+ null_checks.needs_src = false;
StoreField* store = new StoreField(dest, dest_off + offset, field, value, false, state_before, false);
store->set_enclosing_field(enclosing_field);
append(store);
+ null_checks.needs_dst = false;
}
}
}
@@ -2052,9 +2064,10 @@ void GraphBuilder::access_field(Bytecodes::Code code) {
NewInstance* buffer = new NewInstance(value_klass, state_before, false, true);
_memory->new_instance(buffer);
apush(append_split(buffer));
+ CopyNullChecks null_checks = { false, false };
copy_value_content(value_klass, pending_field_access()->obj(),
- pending_field_access()->offset() + field->offset_in_bytes() - field->holder()->as_value_klass()->payload_offset(),
- buffer, value_klass->payload_offset(), state_before);
+ pending_field_access()->offset() + field->offset_in_bytes() - field->holder()->as_value_klass()->payload_offset(),
+ buffer, value_klass->payload_offset(), state_before, null_checks);
set_pending_field_access(nullptr);
} else {
if (!field->is_null_free() && !value_klass->is_initialized()) {
@@ -2064,15 +2077,20 @@ void GraphBuilder::access_field(Bytecodes::Code code) {
return;
}
+ CopyNullChecks null_checks = { true, false };
+ if (value_klass->is_empty()) {
+ // The content copy emits no access that can implicitly null-check the holder.
+ null_check(obj);
+ null_checks.needs_src = false;
+ }
+
NewInstance* buffer = new NewInstance(value_klass, state_before, false, true);
_memory->new_instance(buffer);
append_split(buffer);
- if (value_klass->is_initialized() && value_klass->is_empty()) {
- // Needs an explicit null check because below code does not perform any actual load if there are no fields
- null_check(obj);
- }
- copy_value_content(value_klass, obj, field->offset_in_bytes(), buffer, value_klass->payload_offset(), state_before);
+ copy_value_content(value_klass, obj, field->offset_in_bytes(), buffer, value_klass->payload_offset(),
+ state_before, null_checks);
+ assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted");
Instruction* result = buffer;
if (!field->is_null_free()) {
@@ -2131,23 +2149,28 @@ void GraphBuilder::access_field(Bytecodes::Code code) {
append(new StoreField(obj, offset, field, val, false, state_before, needs_patching));
} else if (field->is_null_free()) {
assert(!value_klass->is_empty(), "should have been handled");
- copy_value_content(value_klass, val, value_klass->payload_offset(), obj, offset, state_before, field);
+ CopyNullChecks null_checks = { true, true };
+ copy_value_content(value_klass, val, value_klass->payload_offset(), obj, offset, state_before, null_checks, field);
+ assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted");
} else {
if (!value_klass->is_initialized()) {
// null_reset_value is not available, bailout for now
bailout("store to an uninitialized nullable non-atomic flat field");
return;
}
+ CopyNullChecks null_checks = { false, true };
if (value_klass->is_empty()) {
- // Needs an explicit null check because the code below does not emit a store that performs an implicit null check for empty classes.
+ // The content copy emits no access that can implicitly null-check the holder.
null_check(obj);
+ null_checks.needs_dst = false;
}
// Store the subfields when field is a nullable non-atomic field
Value object_null = append(new Constant(objectNull));
Value null_reset_value = append(new Constant(new ObjectConstant(value_klass->get_null_reset_value().as_object())));
Value src = append(new IfOp(val, Instruction::neq, object_null, val, null_reset_value, state_before, false));
- copy_value_content(value_klass, src, value_klass->payload_offset(), obj, offset, state_before);
+ copy_value_content(value_klass, src, value_klass->payload_offset(), obj, offset, state_before, null_checks);
+ assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted");
// Store the null marker
Value int_one = append(new Constant(new IntConstant(1)));
diff --git a/src/hotspot/share/c1/c1_GraphBuilder.hpp b/src/hotspot/share/c1/c1_GraphBuilder.hpp
index 0a4f31983508..e6cb358fc901 100644
--- a/src/hotspot/share/c1/c1_GraphBuilder.hpp
+++ b/src/hotspot/share/c1/c1_GraphBuilder.hpp
@@ -299,7 +299,13 @@ class GraphBuilder {
void throw_op(int bci);
// value types
- void copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, ciField* enclosing_field = nullptr);
+ struct CopyNullChecks {
+ bool needs_src;
+ bool needs_dst;
+ };
+
+ void copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before,
+ CopyNullChecks& null_checks, ciField* enclosing_field = nullptr);
// stack/code manipulation helpers
Instruction* append_with_bci(Instruction* instr, int bci);
diff --git a/src/hotspot/share/c1/c1_LIR.hpp b/src/hotspot/share/c1/c1_LIR.hpp
index 5f2a0bcbd843..55f7562c7e15 100644
--- a/src/hotspot/share/c1/c1_LIR.hpp
+++ b/src/hotspot/share/c1/c1_LIR.hpp
@@ -2133,7 +2133,9 @@ class LIR_OpProfileValueType : public LIR_Op {
, _obj(obj)
, _flag(flag)
, _tmp(tmp)
- , _not_null(not_null) { }
+ , _not_null(not_null) {
+ assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled");
+ }
LIR_Opr mdp() const { return _mdp; }
LIR_Opr obj() const { return _obj; }
diff --git a/src/hotspot/share/c1/c1_Optimizer.cpp b/src/hotspot/share/c1/c1_Optimizer.cpp
index 17ad26a311a2..58c9e5bf4c5e 100644
--- a/src/hotspot/share/c1/c1_Optimizer.cpp
+++ b/src/hotspot/share/c1/c1_Optimizer.cpp
@@ -767,9 +767,9 @@ void NullCheckVisitor::do_Throw (Throw* x) { nce()->clear_las
void NullCheckVisitor::do_Base (Base* x) {}
void NullCheckVisitor::do_OsrEntry (OsrEntry* x) {}
void NullCheckVisitor::do_ExceptionObject(ExceptionObject* x) { nce()->handle_ExceptionObject(x); }
-void NullCheckVisitor::do_UnsafeGet (UnsafeGet* x) {}
-void NullCheckVisitor::do_UnsafePut (UnsafePut* x) {}
-void NullCheckVisitor::do_UnsafeGetAndSet(UnsafeGetAndSet* x) {}
+void NullCheckVisitor::do_UnsafeGet (UnsafeGet* x) { nce()->clear_last_explicit_null_check(); }
+void NullCheckVisitor::do_UnsafePut (UnsafePut* x) { nce()->clear_last_explicit_null_check(); }
+void NullCheckVisitor::do_UnsafeGetAndSet(UnsafeGetAndSet* x) { nce()->clear_last_explicit_null_check(); }
void NullCheckVisitor::do_ProfileCall (ProfileCall* x) { nce()->clear_last_explicit_null_check();
nce()->handle_ProfileCall(x); }
void NullCheckVisitor::do_ProfileReturnType (ProfileReturnType* x) { nce()->handle_ProfileReturnType(x); }
@@ -1094,6 +1094,7 @@ void NullCheckEliminator::handle_Invoke(Invoke* x) {
void NullCheckEliminator::handle_NewInstance(NewInstance* x) {
+ clear_last_explicit_null_check();
set_put(x);
if (PrintNullCheckElimination) {
tty->print_cr("NewInstance %d is non-null", x->id());
diff --git a/src/hotspot/share/cds/aotArtifactFinder.cpp b/src/hotspot/share/cds/aotArtifactFinder.cpp
index 30476c96109c..90009271fb45 100644
--- a/src/hotspot/share/cds/aotArtifactFinder.cpp
+++ b/src/hotspot/share/cds/aotArtifactFinder.cpp
@@ -25,10 +25,10 @@
#include "cds/aotArtifactFinder.hpp"
#include "cds/aotClassInitializer.hpp"
#include "cds/aotClassLinker.hpp"
+#include "cds/aotHeap.hpp"
#include "cds/aotLogging.hpp"
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/dumpTimeClassInfo.inline.hpp"
-#include "cds/heapShared.hpp"
#include "cds/lambdaProxyClassDictionary.hpp"
#include "cds/regeneratedClasses.hpp"
#include "classfile/systemDictionaryShared.hpp"
@@ -44,7 +44,7 @@
static GrowableArrayCHeap* _all_cached_classes = nullptr;
// This is a stack that tracks all the AOT-inited classes that are waiting to be passed
-// to HeapShared::copy_and_rescan_aot_inited_mirror().
+// to AOTHeap::copy_and_rescan_aot_inited_mirror().
static GrowableArrayCHeap* _pending_aot_inited_classes = nullptr;
static const int TABLE_SIZE = 15889; // prime number
@@ -99,17 +99,17 @@ void AOTArtifactFinder::find_artifacts() {
BasicType bt = (BasicType)i;
if (!is_reference_type(bt)) {
oop orig_mirror = Universe::java_mirror(bt);
- oop scratch_mirror = HeapShared::scratch_java_mirror(bt);
- HeapShared::scan_java_mirror(orig_mirror);
+ oop scratch_mirror = AOTHeap::scratch_java_mirror(bt);
+ AOTHeap::scan_java_mirror(orig_mirror);
log_trace(aot, heap, mirror)(
"Archived %s mirror object from " PTR_FORMAT,
type2name(bt), p2i(scratch_mirror));
- Universe::set_archived_basic_type_mirror_index(bt, HeapShared::append_root(scratch_mirror));
+ Universe::set_archived_basic_type_mirror_index(bt, AOTHeap::append_root(scratch_mirror));
}
}
// Universe::fillerArrayKlass() isn't in the class hierarchy, so handle it specially.
- HeapShared::scan_java_mirror(Universe::fillerArrayKlass()->java_mirror());
+ AOTHeap::scan_java_mirror(Universe::fillerArrayKlass()->java_mirror());
}
#endif
@@ -121,7 +121,7 @@ void AOTArtifactFinder::find_artifacts() {
// This class is loading only by AOT tooling (not as part of the app's training run).
// Skip this class for now, but it might be added later if
// - One of its subtypes is included
- // - One of its instances is found by HeapShared.
+ // - One of its instances is found by AOTHeap.
skip = true;
}
}
@@ -157,7 +157,7 @@ void AOTArtifactFinder::find_artifacts() {
if (CDSConfig::is_dumping_aot_linked_classes()) {
while (_pending_aot_inited_classes->length() > 0) {
InstanceKlass* ik = _pending_aot_inited_classes->pop();
- HeapShared::copy_and_rescan_aot_inited_mirror(ik);
+ AOTHeap::copy_and_rescan_aot_inited_mirror(ik);
}
}
#endif
@@ -184,7 +184,7 @@ void AOTArtifactFinder::find_artifacts() {
void AOTArtifactFinder::start_scanning_for_oops() {
#if INCLUDE_CDS_JAVA_HEAP
if (CDSConfig::is_dumping_heap()) {
- HeapShared::start_scanning_for_oops();
+ AOTHeap::start_scanning_for_oops();
}
#endif
}
@@ -192,7 +192,7 @@ void AOTArtifactFinder::start_scanning_for_oops() {
void AOTArtifactFinder::end_scanning_for_oops() {
#if INCLUDE_CDS_JAVA_HEAP
if (CDSConfig::is_dumping_heap()) {
- HeapShared::end_scanning_for_oops();
+ AOTHeap::end_scanning_for_oops();
}
#endif
}
@@ -216,7 +216,9 @@ void AOTArtifactFinder::add_aot_inited_class(InstanceKlass* ik) {
add_aot_inited_class(s);
}
- Array* interfaces = ik->local_interfaces();
+ // An uninitialized direct interface may have initialized superinterfaces
+ // that declare default methods. Preserve their initialization state too.
+ Array* interfaces = ik->transitive_interfaces();
int len = interfaces->length();
for (int i = 0; i < len; i++) {
InstanceKlass* intf = interfaces->at(i);
@@ -306,7 +308,7 @@ void AOTArtifactFinder::add_cached_class(Klass* k) {
void AOTArtifactFinder::scan_oops_in_instance_class(InstanceKlass* ik) {
#if INCLUDE_CDS_JAVA_HEAP
if (CDSConfig::is_dumping_heap()) {
- HeapShared::scan_java_class(ik);
+ AOTHeap::scan_java_class(ik);
scan_oops_in_array_class(ik->array_klasses());
}
#endif
@@ -316,7 +318,7 @@ void AOTArtifactFinder::scan_oops_in_array_class(ArrayKlass* ak) {
#if INCLUDE_CDS_JAVA_HEAP
if (CDSConfig::is_dumping_heap()) {
while (ak != nullptr) {
- HeapShared::scan_java_class(ak);
+ AOTHeap::scan_java_class(ak);
ak = ak->array_klass_or_null();
}
}
diff --git a/src/hotspot/share/cds/aotArtifactFinder.hpp b/src/hotspot/share/cds/aotArtifactFinder.hpp
index 50057b6caee6..25aa64f3639b 100644
--- a/src/hotspot/share/cds/aotArtifactFinder.hpp
+++ b/src/hotspot/share/cds/aotArtifactFinder.hpp
@@ -39,10 +39,10 @@ class TypeArrayKlass;
// It also decides what Klasses must be cached in aot-initialized state.
//
// ArchiveBuilder uses [1] as roots to scan for all MetaspaceObjs that need to be cached.
-// HeapShared uses [2] to create an image of the archived heap.
+// AOTHeap uses [2] to create an image of the archived heap.
//
// [1] is stored in _all_cached_classes in aotArtifactFinder.cpp.
-// [2] is stored in HeapShared::archived_object_cache().
+// [2] is stored in AOTHeap::archived_object_cache().
//
// Although many Klasses and heap objects are created in the assembly phase, we only store a subset of them into
// the AOT cache. For example:
@@ -68,9 +68,9 @@ class TypeArrayKlass;
// turn discover more Klasses and java heap objects.
// - The scanning continues until we reach a steady state.
//
-// Note1: See TODO comments in HeapShared::archive_object() for exceptions to this rule.
+// Note1: See TODO comments in AOTHeap::archive_object() for exceptions to this rule.
//
-// Note2: The scanning of Java objects is done in heapShared.cpp. Please see calls into the HeapShared class
+// Note2: The scanning of Java objects is done in heapShared.cpp. Please see calls into the AOTHeap class
// from AOTArtifactFinder.
class AOTArtifactFinder : AllStatic {
diff --git a/src/hotspot/share/cds/aotCacheAccess.cpp b/src/hotspot/share/cds/aotCacheAccess.cpp
index b8d853792dc7..6ae52d9d03e4 100644
--- a/src/hotspot/share/cds/aotCacheAccess.cpp
+++ b/src/hotspot/share/cds/aotCacheAccess.cpp
@@ -23,10 +23,10 @@
*/
#include "cds/aotCacheAccess.hpp"
+#include "cds/aotHeap.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"
@@ -99,11 +99,11 @@ Metadata* AOTCacheAccess::try_narrow_ptr_to_metadata(narrowPtr narrowp, size_t s
#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.
+ return AOTHeap::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);
+ oop o = AOTHeap::get_root(permanent_index);
assert(oopDesc::is_oop_or_null(o), "sanity");
return o;
}
diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp
index 7f39d35bd8d6..62076931e69f 100644
--- a/src/hotspot/share/cds/aotClassInitializer.cpp
+++ b/src/hotspot/share/cds/aotClassInitializer.cpp
@@ -26,7 +26,6 @@
#include "cds/aotLinkedClassBulkLoader.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/cdsConfig.hpp"
-#include "cds/heapShared.hpp"
#include "cds/regeneratedClasses.hpp"
#include "classfile/symbolTable.hpp"
#include "classfile/systemDictionaryShared.hpp"
@@ -99,7 +98,7 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
//
// When CDSConfig::is_dumping_aot_linked_classes is enabled,
// AOTArtifactFinder::find_artifacts() finds the classes of all
- // heap objects that are reachable from HeapShared::_run_time_special_subgraph,
+ // heap objects that are reachable from AOTHeap::_run_time_special_subgraph,
// and mark these classes as aot-inited. This preserves the initialized
// mirrors of these classes, and their methods are NOT executed
// at runtime. See aotArtifactFinder.hpp for more info.
diff --git a/src/hotspot/share/cds/aotClassLinker.cpp b/src/hotspot/share/cds/aotClassLinker.cpp
index 0ed17dd67461..94e97820e58c 100644
--- a/src/hotspot/share/cds/aotClassLinker.cpp
+++ b/src/hotspot/share/cds/aotClassLinker.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,11 +24,11 @@
#include "cds/aotClassLinker.hpp"
#include "cds/aotConstantPoolResolver.hpp"
+#include "cds/aotHeap.hpp"
#include "cds/aotLinkedClassTable.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/archiveUtils.inline.hpp"
#include "cds/cdsConfig.hpp"
-#include "cds/heapShared.hpp"
#include "cds/lambdaFormInvokers.inline.hpp"
#include "classfile/classLoader.hpp"
#include "classfile/dictionary.hpp"
@@ -145,7 +145,7 @@ bool AOTClassLinker::try_add_candidate(InstanceKlass* ik) {
if (!CDSConfig::is_dumping_method_handles()) {
return false;
}
- if (HeapShared::is_lambda_proxy_klass(ik)) {
+ if (AOTHeap::is_lambda_proxy_klass(ik)) {
InstanceKlass* nest_host = ik->nest_host_not_null();
if (!try_add_candidate(nest_host)) {
ResourceMark rm;
diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp
index 4add165e1434..a13785057a4e 100644
--- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp
+++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp
@@ -24,9 +24,9 @@
#include "cds/aotClassLinker.hpp"
#include "cds/aotConstantPoolResolver.hpp"
+#include "cds/aotHeap.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"
@@ -165,7 +165,7 @@ void AOTConstantPoolResolver::preresolve_string_cp_entries(InstanceKlass* ik, TR
if (LambdaFormInvokers::may_be_regenerated_class(ik->name())) {
eager_resolve = true;
}
- if (ik->is_hidden() && HeapShared::is_archivable_hidden_klass(ik)) {
+ if (ik->is_hidden() && AOTHeap::is_archivable_hidden_klass(ik)) {
eager_resolve = true;
}
diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/aotHeap.cpp
similarity index 89%
rename from src/hotspot/share/cds/heapShared.cpp
rename to src/hotspot/share/cds/aotHeap.cpp
index b486b2f17cb2..2737e0f2165a 100644
--- a/src/hotspot/share/cds/heapShared.cpp
+++ b/src/hotspot/share/cds/aotHeap.cpp
@@ -26,6 +26,7 @@
#include "cds/aotClassInitializer.hpp"
#include "cds/aotClassLocation.hpp"
#include "cds/aotCompressedPointers.hpp"
+#include "cds/aotHeap.inline.hpp"
#include "cds/aotLogging.hpp"
#include "cds/aotMappedHeapLoader.hpp"
#include "cds/aotMappedHeapWriter.hpp"
@@ -40,7 +41,6 @@
#include "cds/cdsConfig.hpp"
#include "cds/cdsEnumKlass.hpp"
#include "cds/cdsHeapVerifier.hpp"
-#include "cds/heapShared.inline.hpp"
#include "cds/regeneratedClasses.hpp"
#include "classfile/classLoaderData.hpp"
#include "classfile/javaClasses.inline.hpp"
@@ -100,13 +100,13 @@ struct ArchivableStaticFieldInfo {
}
};
-HeapArchiveMode HeapShared::_heap_load_mode = HeapArchiveMode::_uninitialized;
-HeapArchiveMode HeapShared::_heap_write_mode = HeapArchiveMode::_uninitialized;
+HeapArchiveMode AOTHeap::_heap_load_mode = HeapArchiveMode::_uninitialized;
+HeapArchiveMode AOTHeap::_heap_write_mode = HeapArchiveMode::_uninitialized;
-size_t HeapShared::_alloc_count[HeapShared::ALLOC_STAT_SLOTS];
-size_t HeapShared::_alloc_size[HeapShared::ALLOC_STAT_SLOTS];
-size_t HeapShared::_total_obj_count;
-size_t HeapShared::_total_obj_size;
+size_t AOTHeap::_alloc_count[AOTHeap::ALLOC_STAT_SLOTS];
+size_t AOTHeap::_alloc_size[AOTHeap::ALLOC_STAT_SLOTS];
+size_t AOTHeap::_total_obj_count;
+size_t AOTHeap::_total_obj_size;
#ifdef ASSERT
// All classes that have at least one instance in the cached heap.
@@ -140,11 +140,11 @@ static ArchivableStaticFieldInfo archive_subgraph_entry_fields[] = {
{nullptr, nullptr},
};
-KlassSubGraphInfo* HeapShared::_dump_time_special_subgraph;
-ArchivedKlassSubGraphInfoRecord* HeapShared::_run_time_special_subgraph;
-GrowableArrayCHeap* HeapShared::_pending_roots = nullptr;
-OopHandle HeapShared::_scratch_basic_type_mirrors[T_VOID+1];
-MetaspaceObjToOopHandleTable* HeapShared::_scratch_objects_table = nullptr;
+KlassSubGraphInfo* AOTHeap::_dump_time_special_subgraph;
+ArchivedKlassSubGraphInfoRecord* AOTHeap::_run_time_special_subgraph;
+GrowableArrayCHeap* AOTHeap::_pending_roots = nullptr;
+OopHandle AOTHeap::_scratch_basic_type_mirrors[T_VOID+1];
+MetaspaceObjToOopHandleTable* AOTHeap::_scratch_objects_table = nullptr;
static GrowableArray* _dumptime_resolved_methods = nullptr;
static Array* _runtime_resolved_methods = nullptr;
@@ -157,7 +157,7 @@ static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], Instan
return false;
}
-bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) {
+bool AOTHeap::is_subgraph_root_class(InstanceKlass* ik) {
assert(CDSConfig::is_dumping_heap(), "dump-time only");
if (CDSConfig::is_dumping_klass_subgraphs()) {
// Legacy CDS archive support (to be deprecated)
@@ -167,7 +167,7 @@ bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) {
}
}
-oop HeapShared::CachedOopInfo::orig_referrer() const {
+oop AOTHeap::CachedOopInfo::orig_referrer() const {
return _orig_referrer.resolve();
}
@@ -175,7 +175,7 @@ oop HeapShared::CachedOopInfo::orig_referrer() const {
// while copying the oops into the AOT heap region. We don't want to
// have any side effects during the copying, so we avoid calling
// p->identity_hash() which can update the object header.
-unsigned HeapShared::oop_address_hash(oop const& p) {
+unsigned AOTHeap::oop_address_hash(oop const& p) {
assert(SafepointSynchronize::is_at_safepoint() ||
JavaThread::current()->is_in_no_safepoint_scope(), "sanity");
return primitive_hash(cast_from_oop(p));
@@ -188,7 +188,7 @@ unsigned HeapShared::oop_address_hash(oop const& p) {
// copying the objects into the cache. This will allow the hashcode to be
// dynamically and randomly computed in each production, which generally
// desirable to make the hashcodes more random between runs.
-unsigned HeapShared::archived_object_cache_hash(OopHandle const& oh) {
+unsigned AOTHeap::archived_object_cache_hash(OopHandle const& oh) {
oop o = oh.resolve();
if (o == nullptr) {
return 0;
@@ -208,7 +208,7 @@ unsigned HeapShared::archived_object_cache_hash(OopHandle const& oh) {
}
}
-bool HeapShared::oop_handle_equals(const OopHandle& a, const OopHandle& b) {
+bool AOTHeap::oop_handle_equals(const OopHandle& a, const OopHandle& b) {
return a.resolve() == b.resolve();
}
@@ -234,7 +234,7 @@ static void reset_states(oop obj, TRAPS) {
}
}
-void HeapShared::reset_archived_object_states(TRAPS) {
+void AOTHeap::reset_archived_object_states(TRAPS) {
assert(CDSConfig::is_dumping_heap(), "dump-time only");
log_debug(aot)("Resetting platform loader");
reset_states(SystemDictionary::java_platform_loader(), CHECK);
@@ -259,7 +259,7 @@ void HeapShared::reset_archived_object_states(TRAPS) {
reset_states(boot_loader(), CHECK);
}
-void HeapShared::ensure_determinism(TRAPS) {
+void AOTHeap::ensure_determinism(TRAPS) {
TempNewSymbol class_name = SymbolTable::new_symbol("jdk/internal/util/WeakReferenceKey");
TempNewSymbol method_name = SymbolTable::new_symbol("ensureDeterministicAOTCache");
@@ -276,22 +276,22 @@ void HeapShared::ensure_determinism(TRAPS) {
assert(result.get_jboolean() == false, "sanity");
}
-void HeapShared::prepare_for_archiving(TRAPS) {
+void AOTHeap::prepare_for_archiving(TRAPS) {
reset_archived_object_states(CHECK);
ensure_determinism(CHECK);
}
-HeapShared::ArchivedObjectCache* HeapShared::_archived_object_cache = nullptr;
+AOTHeap::ArchivedObjectCache* AOTHeap::_archived_object_cache = nullptr;
// Controls the hashing method for the _archived_object_cache.
// Changes from false to true once, after all objects are copied,
// inside make_archived_object_cache_gc_safe().
// See archived_object_cache_hash() for more details.
-bool HeapShared::_use_identity_hash_for_archived_object_cache = false;
+bool AOTHeap::_use_identity_hash_for_archived_object_cache = false;
-bool HeapShared::is_archived_heap_in_use() {
- if (HeapShared::is_loading()) {
- if (HeapShared::is_loading_streaming_mode()) {
+bool AOTHeap::is_archived_heap_in_use() {
+ if (AOTHeap::is_loading()) {
+ if (AOTHeap::is_loading_streaming_mode()) {
return AOTStreamedHeapLoader::is_in_use();
} else {
return AOTMappedHeapLoader::is_in_use();
@@ -301,7 +301,7 @@ bool HeapShared::is_archived_heap_in_use() {
return false;
}
-bool HeapShared::can_use_archived_heap() {
+bool AOTHeap::can_use_archived_heap() {
FileMapInfo* static_mapinfo = FileMapInfo::current_info();
if (static_mapinfo == nullptr) {
return false;
@@ -319,34 +319,34 @@ bool HeapShared::can_use_archived_heap() {
return true;
}
-bool HeapShared::is_too_large_to_archive(size_t size) {
- if (HeapShared::is_writing_streaming_mode()) {
+bool AOTHeap::is_too_large_to_archive(size_t size) {
+ if (AOTHeap::is_writing_streaming_mode()) {
return false;
} else {
return AOTMappedHeapWriter::is_too_large_to_archive(size);
}
}
-bool HeapShared::is_too_large_to_archive(oop obj) {
- if (HeapShared::is_writing_streaming_mode()) {
+bool AOTHeap::is_too_large_to_archive(oop obj) {
+ if (AOTHeap::is_writing_streaming_mode()) {
return false;
} else {
return AOTMappedHeapWriter::is_too_large_to_archive(obj);
}
}
-bool HeapShared::is_string_too_large_to_archive(oop string) {
+bool AOTHeap::is_string_too_large_to_archive(oop string) {
typeArrayOop value = java_lang_String::value_no_keepalive(string);
return is_too_large_to_archive(value);
}
-void HeapShared::initialize_loading_mode(HeapArchiveMode mode) {
+void AOTHeap::initialize_loading_mode(HeapArchiveMode mode) {
assert(_heap_load_mode == HeapArchiveMode::_uninitialized, "already set?");
assert(mode != HeapArchiveMode::_uninitialized, "sanity");
_heap_load_mode = mode;
};
-void HeapShared::initialize_writing_mode() {
+void AOTHeap::initialize_writing_mode() {
assert(!FLAG_IS_ERGO(AOTStreamableObjects), "Should not have been ergonomically set yet");
if (!CDSConfig::is_dumping_archive()) {
@@ -383,14 +383,14 @@ void HeapShared::initialize_writing_mode() {
}
}
-void HeapShared::initialize_streaming() {
+void AOTHeap::initialize_streaming() {
assert(is_loading_streaming_mode(), "shouldn't call this");
if (can_use_archived_heap()) {
AOTStreamedHeapLoader::initialize();
}
}
-void HeapShared::enable_gc() {
+void AOTHeap::enable_gc() {
#ifdef ASSERT
// At this point, a GC may start and will be able to see some or all
// of the cached oops. The class of each oop seen by the GC must have
@@ -411,26 +411,26 @@ void HeapShared::enable_gc() {
}
}
-void HeapShared::materialize_thread_object() {
+void AOTHeap::materialize_thread_object() {
if (AOTStreamedHeapLoader::is_in_use()) {
AOTStreamedHeapLoader::materialize_thread_object();
}
}
-void HeapShared::archive_interned_string(oop string) {
- assert(HeapShared::is_writing_mapping_mode(), "Only used by this mode");
+void AOTHeap::archive_interned_string(oop string) {
+ assert(AOTHeap::is_writing_mapping_mode(), "Only used by this mode");
bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, string);
assert(success, "shared strings array must not point to arrays or strings that are too large to archive");
}
-void HeapShared::update_scratch_mirror_field(oop scratch_m, int field_offset, oop new_field_obj) {
+void AOTHeap::update_scratch_mirror_field(oop scratch_m, int field_offset, oop new_field_obj) {
archive_updated_mirror_field(scratch_m->obj_field(field_offset), new_field_obj);
scratch_m->obj_field_put(field_offset, new_field_obj);
}
// It's possible for a scratch mirror to be scanned before one of its oop fields (such as acmp_maps) is
// updated. When this happens, we must ensure that the new oop is also archived.
-void HeapShared::archive_updated_mirror_field(oop old_field_obj, oop new_field_obj) {
+void AOTHeap::archive_updated_mirror_field(oop old_field_obj, oop new_field_obj) {
if (old_field_obj != new_field_obj) {
assert(old_field_obj == nullptr, "cannot update a non-null oop field that might have already been scanned");
assert(new_field_obj != nullptr, "must be");
@@ -439,9 +439,9 @@ void HeapShared::archive_updated_mirror_field(oop old_field_obj, oop new_field_o
}
}
-void HeapShared::finalize_initialization(FileMapInfo* static_mapinfo) {
- if (HeapShared::is_loading()) {
- if (HeapShared::is_loading_streaming_mode()) {
+void AOTHeap::finalize_initialization(FileMapInfo* static_mapinfo) {
+ if (AOTHeap::is_loading()) {
+ if (AOTHeap::is_loading_streaming_mode()) {
// Heap initialization can be done only after vtables are initialized by ReadClosure.
AOTStreamedHeapLoader::finish_initialization(static_mapinfo);
} else {
@@ -452,7 +452,7 @@ void HeapShared::finalize_initialization(FileMapInfo* static_mapinfo) {
}
}
-void HeapShared::make_archived_object_cache_gc_safe() {
+void AOTHeap::make_archived_object_cache_gc_safe() {
ArchivedObjectCache* new_cache = new (mtClass)ArchivedObjectCache(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE);
// It's safe to change the behavior of the hash function now, because iterate_all()
@@ -487,19 +487,19 @@ void HeapShared::make_archived_object_cache_gc_safe() {
_archived_object_cache = new_cache;
}
-HeapShared::CachedOopInfo* HeapShared::get_cached_oop_info(oop obj) {
+AOTHeap::CachedOopInfo* AOTHeap::get_cached_oop_info(oop obj) {
OopHandle oh(Universe::vm_global(), obj);
CachedOopInfo* result = _archived_object_cache->get(oh);
oh.release(Universe::vm_global());
return result;
}
-bool HeapShared::has_been_archived(oop obj) {
+bool AOTHeap::has_been_archived(oop obj) {
assert(CDSConfig::is_dumping_heap(), "dump-time only");
return get_cached_oop_info(obj) != nullptr;
}
-int HeapShared::append_root(oop obj) {
+int AOTHeap::append_root(oop obj) {
assert(SafepointSynchronize::is_at_safepoint(), "sanity");
assert(CDSConfig::is_dumping_heap(), "dump-time only");
assert(_pending_roots != nullptr, "sanity");
@@ -527,7 +527,7 @@ int HeapShared::append_root(oop obj) {
}
}
-int HeapShared::add_to_pending_roots(oop obj) {
+int AOTHeap::add_to_pending_roots(oop obj) {
if (_pending_roots->length() == (int)max_jint) {
// This should never happen, but just in case we have HUGE caches ...
AOTMetaspace::unrecoverable_writing_error("too many pending roots");
@@ -536,7 +536,7 @@ int HeapShared::add_to_pending_roots(oop obj) {
return _pending_roots->append(obj);
}
-int HeapShared::get_root_index(oop obj) {
+int AOTHeap::get_root_index(oop obj) {
if (java_lang_Class::is_instance(obj)) {
obj = scratch_java_mirror(obj);
}
@@ -558,15 +558,15 @@ int HeapShared::get_root_index(oop obj) {
return -1;
}
-oop HeapShared::get_root(int index, bool clear) {
+oop AOTHeap::get_root(int index, bool clear) {
assert(index >= 0, "sanity");
assert(is_archived_heap_in_use(), "getting roots into heap that is not used");
oop result;
- if (HeapShared::is_loading_streaming_mode()) {
+ if (AOTHeap::is_loading_streaming_mode()) {
result = AOTStreamedHeapLoader::get_root(index);
} else {
- assert(HeapShared::is_loading_mapping_mode(), "must be");
+ assert(AOTHeap::is_loading_mapping_mode(), "must be");
result = AOTMappedHeapLoader::get_root(index);
}
@@ -577,13 +577,13 @@ oop HeapShared::get_root(int index, bool clear) {
return result;
}
-void HeapShared::finish_materialize_objects() {
+void AOTHeap::finish_materialize_objects() {
if (AOTStreamedHeapLoader::is_in_use()) {
AOTStreamedHeapLoader::finish_materialize_objects();
}
}
-void HeapShared::clear_root(int index) {
+void AOTHeap::clear_root(int index) {
if (CDSConfig::is_using_aot_linked_classes()) {
// When AOT linked classes are in use, all roots will be in use all
// the time, there's no benefit for clearing the roots. Also, we
@@ -597,16 +597,16 @@ void HeapShared::clear_root(int index) {
if (log_is_enabled(Debug, aot, heap)) {
log_debug(aot, heap)("Clearing root %d: was %zu", index, p2i(get_root(index, false /* clear */)));
}
- if (HeapShared::is_loading_streaming_mode()) {
+ if (AOTHeap::is_loading_streaming_mode()) {
AOTStreamedHeapLoader::clear_root(index);
} else {
- assert(HeapShared::is_loading_mapping_mode(), "must be");
+ assert(AOTHeap::is_loading_mapping_mode(), "must be");
AOTMappedHeapLoader::clear_root(index);
}
}
}
-bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgraph_info) {
+bool AOTHeap::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgraph_info) {
assert(CDSConfig::is_dumping_heap(), "dump-time only");
assert(!obj->is_stackChunk(), "do not archive stack chunks");
@@ -626,7 +626,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra
count_allocation(obj->size());
DEBUG_ONLY(_dumptime_classes_with_cached_oops->add(obj->klass()));
- if (HeapShared::is_writing_streaming_mode()) {
+ if (AOTHeap::is_writing_streaming_mode()) {
AOTStreamedHeapWriter::add_source_obj(obj);
} else {
AOTMappedHeapWriter::add_source_obj(obj);
@@ -635,7 +635,6 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra
OopHandle oh(Universe::vm_global(), obj);
CachedOopInfo info = make_cached_oop_info(obj, referrer);
archived_object_cache()->put_when_absent(oh, info);
- archived_object_cache()->maybe_grow();
Klass* k = obj->klass();
if (k->is_instance_klass()) {
@@ -654,7 +653,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra
// For the other subgraphs, we have a weaker mechanism to ensure that
// all classes in a subgraph are initialized before the subgraph is programmatically
// returned from jdk.internal.misc.CDS::initializeFromArchive().
- // See HeapShared::initialize_from_archived_subgraph().
+ // See AOTHeap::initialize_from_archived_subgraph().
{
AOTArtifactFinder::add_aot_inited_class(InstanceKlass::cast(k));
}
@@ -670,7 +669,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra
m = RegeneratedClasses::maybe_get_regenerated_object(m);
InstanceKlass* method_holder = m->method_holder();
AOTArtifactFinder::add_cached_class(method_holder);
- _dumptime_resolved_methods->append(HeapShared::append_root(obj));
+ _dumptime_resolved_methods->append(AOTHeap::append_root(obj));
}
} else if (AOTCodeCache::is_dumping_code() &&
(java_lang_invoke_MethodHandle::is_instance(obj) || is_interned_string(obj))) {
@@ -728,24 +727,24 @@ class MetaspaceObjToOopHandleTable: public HashTablepool_holder()->class_loader_data())) {
_scratch_objects_table->set_oop(src, dest);
}
}
-refArrayOop HeapShared::scratch_resolved_references(ConstantPool* src) {
+refArrayOop AOTHeap::scratch_resolved_references(ConstantPool* src) {
oop rr = _scratch_objects_table->get_oop(src);
return rr == nullptr ? nullptr : oop_cast(rr);
}
-void HeapShared::remove_scratch_resolved_references(ConstantPool* src) {
+void AOTHeap::remove_scratch_resolved_references(ConstantPool* src) {
if (CDSConfig::is_dumping_heap()) {
_scratch_objects_table->remove_oop(src);
}
}
-void HeapShared::init_dumping() {
+void AOTHeap::init_dumping() {
_scratch_objects_table = new (mtClass)MetaspaceObjToOopHandleTable();
_dumptime_resolved_methods = new (mtClassShared) GrowableArray(100, mtClassShared);
_pending_roots = new GrowableArrayCHeap(500);
@@ -753,7 +752,7 @@ void HeapShared::init_dumping() {
DEBUG_ONLY(_dumptime_classes_with_cached_oops = new (mtClassShared)ArchivableKlassTable());
}
-void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) {
+void AOTHeap::init_scratch_objects_for_basic_type_mirrors(TRAPS) {
for (int i = T_BOOLEAN; i < T_VOID+1; i++) {
BasicType bt = (BasicType)i;
if (!is_reference_type(bt)) {
@@ -768,7 +767,7 @@ void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) {
// that java_mirror will be returned if the mirror is already a scratch mirror.
//
// See java_lang_Class::create_scratch_mirror() for more info.
-oop HeapShared::scratch_java_mirror(oop java_mirror) {
+oop AOTHeap::scratch_java_mirror(oop java_mirror) {
assert(java_lang_Class::is_instance(java_mirror), "must be");
for (int i = T_BOOLEAN; i < T_VOID+1; i++) {
@@ -787,21 +786,21 @@ oop HeapShared::scratch_java_mirror(oop java_mirror) {
}
}
-oop HeapShared::scratch_java_mirror(BasicType t) {
+oop AOTHeap::scratch_java_mirror(BasicType t) {
assert((uint)t < T_VOID+1, "range check");
assert(!is_reference_type(t), "sanity");
return _scratch_basic_type_mirrors[t].resolve();
}
-oop HeapShared::scratch_java_mirror(Klass* k) {
+oop AOTHeap::scratch_java_mirror(Klass* k) {
return _scratch_objects_table->get_oop(k);
}
-void HeapShared::set_scratch_java_mirror(Klass* k, oop mirror) {
+void AOTHeap::set_scratch_java_mirror(Klass* k, oop mirror) {
_scratch_objects_table->set_oop(k, mirror);
}
-void HeapShared::remove_scratch_objects(Klass* k) {
+void AOTHeap::remove_scratch_objects(Klass* k) {
// Klass is being deallocated. Java mirror can still be alive, and it should not
// point to dead klass. We need to break the link from mirror to the Klass.
// See how InstanceKlass::deallocate_contents does it for normal mirrors.
@@ -821,24 +820,24 @@ void HeapShared::remove_scratch_objects(Klass* k) {
//string compares on names is fragile and will break as soon as somebody
//changes the names in the JDK code. See discussion in JDK-8342481 for
//related ideas about marking AOT-related classes.
-bool HeapShared::is_lambda_form_klass(InstanceKlass* ik) {
+bool AOTHeap::is_lambda_form_klass(InstanceKlass* ik) {
return ik->is_hidden() && ik->name()->starts_with("java/lang/invoke/LambdaForm$");
}
-bool HeapShared::is_lambda_proxy_klass(InstanceKlass* ik) {
+bool AOTHeap::is_lambda_proxy_klass(InstanceKlass* ik) {
return ik->is_hidden() && (ik->name()->index_of_at(0, "$$Lambda+", 9) > 0);
}
-bool HeapShared::is_string_concat_klass(InstanceKlass* ik) {
+bool AOTHeap::is_string_concat_klass(InstanceKlass* ik) {
return ik->is_hidden() && ik->name()->starts_with("java/lang/String$$StringConcat");
}
-bool HeapShared::is_archivable_hidden_klass(InstanceKlass* ik) {
+bool AOTHeap::is_archivable_hidden_klass(InstanceKlass* ik) {
return CDSConfig::is_dumping_method_handles() &&
(is_lambda_form_klass(ik) || is_lambda_proxy_klass(ik) || is_string_concat_klass(ik));
}
-void HeapShared::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) {
+void AOTHeap::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) {
ik->set_has_aot_initialized_mirror();
oop orig_mirror;
@@ -922,7 +921,7 @@ void HeapShared::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) {
}
}
-void HeapShared::copy_java_mirror(oop orig_mirror, oop scratch_m) {
+void AOTHeap::copy_java_mirror(oop orig_mirror, oop scratch_m) {
// We need to retain the identity_hash, because it may have been used by some hashtables
// in the shared heap.
if (orig_mirror->has_identity_hash()) {
@@ -964,20 +963,20 @@ void HeapShared::copy_java_mirror(oop orig_mirror, oop scratch_m) {
static objArrayOop get_archived_resolved_references(InstanceKlass* src_ik) {
if (SystemDictionaryShared::is_builtin_loader(src_ik->class_loader_data())) {
objArrayOop rr = src_ik->constants()->resolved_references_or_null();
- if (rr != nullptr && !HeapShared::is_too_large_to_archive(rr)) {
- return HeapShared::scratch_resolved_references(src_ik->constants());
+ if (rr != nullptr && !AOTHeap::is_too_large_to_archive(rr)) {
+ return AOTHeap::scratch_resolved_references(src_ik->constants());
}
}
return nullptr;
}
-int HeapShared::archive_exception_instance(oop exception) {
+int AOTHeap::archive_exception_instance(oop exception) {
bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, exception);
assert(success, "sanity");
return append_root(exception);
}
-void HeapShared::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has_native_pointers) {
+void AOTHeap::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has_native_pointers) {
OopHandle oh(&src_obj);
CachedOopInfo* info = archived_object_cache()->get(oh);
assert(info != nullptr, "must be");
@@ -985,7 +984,7 @@ void HeapShared::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has
has_native_pointers = info->has_native_pointers();
}
-void HeapShared::set_has_native_pointers(oop src_obj) {
+void AOTHeap::set_has_native_pointers(oop src_obj) {
OopHandle oh(&src_obj);
CachedOopInfo* info = archived_object_cache()->get(oh);
assert(info != nullptr, "must be");
@@ -994,7 +993,7 @@ void HeapShared::set_has_native_pointers(oop src_obj) {
// Between start_scanning_for_oops() and end_scanning_for_oops(), we discover all Java heap objects that
// should be stored in the AOT cache. The scanning is coordinated by AOTArtifactFinder.
-void HeapShared::start_scanning_for_oops() {
+void AOTHeap::start_scanning_for_oops() {
{
NoSafepointVerifier nsv;
@@ -1005,7 +1004,7 @@ void HeapShared::start_scanning_for_oops() {
// Cache for recording where the archived objects are copied to
create_archived_object_cache();
- if (HeapShared::is_writing_mapping_mode() && (UseG1GC || UseCompressedOops)) {
+ if (AOTHeap::is_writing_mapping_mode() && (UseG1GC || UseCompressedOops)) {
aot_log_info(aot)("Heap range = [" PTR_FORMAT " - " PTR_FORMAT "]",
UseCompressedOops ? p2i(CompressedOops::begin()) :
G1GC_ONLY(UseG1GC ? p2i((address)G1CollectedHeap::heap()->reserved().start()) :) 0L,
@@ -1020,25 +1019,25 @@ void HeapShared::start_scanning_for_oops() {
Universe::archive_exception_instances();
}
-void HeapShared::end_scanning_for_oops() {
+void AOTHeap::end_scanning_for_oops() {
if (is_writing_mapping_mode()) {
StringTable::init_shared_table();
}
delete_seen_objects_table();
}
-void HeapShared::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeapInfo* streamed_heap_info) {
+void AOTHeap::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeapInfo* streamed_heap_info) {
{
NoSafepointVerifier nsv;
CDSHeapVerifier::verify();
check_special_subgraph_classes();
}
- if (HeapShared::is_writing_mapping_mode()) {
+ if (AOTHeap::is_writing_mapping_mode()) {
StringTable::write_shared_table();
AOTMappedHeapWriter::write(_pending_roots, mapped_heap_info);
} else {
- assert(HeapShared::is_writing_streaming_mode(), "are there more modes?");
+ assert(AOTHeap::is_writing_streaming_mode(), "are there more modes?");
AOTStreamedHeapWriter::write(_pending_roots, streamed_heap_info);
}
@@ -1057,7 +1056,7 @@ void HeapShared::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeap
make_archived_object_cache_gc_safe();
}
-void HeapShared::scan_java_mirror(oop orig_mirror) {
+void AOTHeap::scan_java_mirror(oop orig_mirror) {
oop m = scratch_java_mirror(orig_mirror);
if (m != nullptr) { // nullptr if for custom class loader
copy_java_mirror(orig_mirror, m);
@@ -1066,7 +1065,7 @@ void HeapShared::scan_java_mirror(oop orig_mirror) {
}
}
-void HeapShared::scan_java_class(Klass* orig_k) {
+void AOTHeap::scan_java_class(Klass* orig_k) {
scan_java_mirror(orig_k->java_mirror());
if (orig_k->is_instance_klass()) {
@@ -1074,13 +1073,13 @@ void HeapShared::scan_java_class(Klass* orig_k) {
orig_ik->constants()->prepare_resolved_references_for_archiving();
objArrayOop rr = get_archived_resolved_references(orig_ik);
if (rr != nullptr) {
- bool success = HeapShared::archive_reachable_objects_from(1, _dump_time_special_subgraph, rr);
+ bool success = AOTHeap::archive_reachable_objects_from(1, _dump_time_special_subgraph, rr);
assert(success, "must be");
}
}
}
-void HeapShared::archive_subgraphs() {
+void AOTHeap::archive_subgraphs() {
assert(CDSConfig::is_dumping_heap(), "must be");
if (CDSConfig::is_dumping_klass_subgraphs()) {
@@ -1091,13 +1090,13 @@ void HeapShared::archive_subgraphs() {
//
// Subgraph archiving support
//
-HeapShared::DumpTimeKlassSubGraphInfoTable* HeapShared::_dump_time_subgraph_info_table = nullptr;
-HeapShared::RunTimeKlassSubGraphInfoTable HeapShared::_run_time_subgraph_info_table;
+AOTHeap::DumpTimeKlassSubGraphInfoTable* AOTHeap::_dump_time_subgraph_info_table = nullptr;
+AOTHeap::RunTimeKlassSubGraphInfoTable AOTHeap::_run_time_subgraph_info_table;
// Get the subgraph_info for Klass k. A new subgraph_info is created if
// there is no existing one for k. The subgraph_info records the "buffered"
// address of the class.
-KlassSubGraphInfo* HeapShared::init_subgraph_info(Klass* k) {
+KlassSubGraphInfo* AOTHeap::init_subgraph_info(Klass* k) {
assert(CDSConfig::is_dumping_heap(), "dump time only");
bool created;
KlassSubGraphInfo* info =
@@ -1106,7 +1105,7 @@ KlassSubGraphInfo* HeapShared::init_subgraph_info(Klass* k) {
return info;
}
-KlassSubGraphInfo* HeapShared::get_subgraph_info(Klass* k) {
+KlassSubGraphInfo* AOTHeap::get_subgraph_info(Klass* k) {
assert(CDSConfig::is_dumping_heap(), "dump time only");
KlassSubGraphInfo* info = _dump_time_subgraph_info_table->get(k);
assert(info != nullptr, "must have been initialized");
@@ -1124,7 +1123,7 @@ void KlassSubGraphInfo::add_subgraph_entry_field(int static_field_offset, oop v)
if (v == nullptr) {
_subgraph_entry_fields->append(-1);
} else {
- _subgraph_entry_fields->append(HeapShared::append_root(v));
+ _subgraph_entry_fields->append(AOTHeap::append_root(v));
}
}
@@ -1151,7 +1150,7 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) {
// -XX:AOTInitTestClass must be used carefully in regression tests to
// include only classes that are safe to aot-initialize.
assert(ik->class_loader() == nullptr ||
- HeapShared::is_lambda_proxy_klass(ik) ||
+ AOTHeap::is_lambda_proxy_klass(ik) ||
AOTClassInitializer::has_test_class(),
"we can archive only instances of boot classes or lambda proxy classes");
} else {
@@ -1219,7 +1218,7 @@ void KlassSubGraphInfo::check_allowed_klass(InstanceKlass* ik) {
const char* lambda_msg = "";
if (CDSConfig::is_dumping_method_handles()) {
lambda_msg = ", or a lambda proxy class";
- if (HeapShared::is_lambda_proxy_klass(ik) &&
+ if (AOTHeap::is_lambda_proxy_klass(ik) &&
(ik->class_loader() == nullptr ||
ik->class_loader() == SystemDictionary::java_platform_loader() ||
ik->class_loader() == SystemDictionary::java_system_loader())) {
@@ -1294,14 +1293,14 @@ void ArchivedKlassSubGraphInfoRecord::init(KlassSubGraphInfo* info) {
ArchivePtrMarker::mark_pointer(&_subgraph_object_klasses);
}
-class HeapShared::CopyKlassSubGraphInfoToArchive : StackObj {
+class AOTHeap::CopyKlassSubGraphInfoToArchive : StackObj {
CompactHashtableWriter* _writer;
public:
CopyKlassSubGraphInfoToArchive(CompactHashtableWriter* writer) : _writer(writer) {}
bool do_entry(Klass* klass, KlassSubGraphInfo& info) {
if (info.subgraph_object_klasses() != nullptr || info.subgraph_entry_fields() != nullptr) {
- ArchivedKlassSubGraphInfoRecord* record = HeapShared::archive_subgraph_info(&info);
+ ArchivedKlassSubGraphInfoRecord* record = AOTHeap::archive_subgraph_info(&info);
Klass* buffered_k = ArchiveBuilder::get_buffered_klass(klass);
unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary((address)buffered_k);
_writer->add(hash, AOTCompressedPointers::encode_not_null(record));
@@ -1310,7 +1309,7 @@ class HeapShared::CopyKlassSubGraphInfoToArchive : StackObj {
}
};
-ArchivedKlassSubGraphInfoRecord* HeapShared::archive_subgraph_info(KlassSubGraphInfo* info) {
+ArchivedKlassSubGraphInfoRecord* AOTHeap::archive_subgraph_info(KlassSubGraphInfo* info) {
ArchivedKlassSubGraphInfoRecord* record =
(ArchivedKlassSubGraphInfoRecord*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedKlassSubGraphInfoRecord));
record->init(info);
@@ -1327,7 +1326,7 @@ ArchivedKlassSubGraphInfoRecord* HeapShared::archive_subgraph_info(KlassSubGraph
// info. The value is stored back to the corresponding field at runtime.
// - A list of klasses that need to be loaded/initialized before archived
// java object sub-graph can be accessed at runtime.
-void HeapShared::write_subgraph_info_table() {
+void AOTHeap::write_subgraph_info_table() {
// Allocate the contents of the hashtable(s) inside the RO region of the CDS archive.
DumpTimeKlassSubGraphInfoTable* d_table = _dump_time_subgraph_info_table;
CompactHashtableStats stats;
@@ -1344,14 +1343,14 @@ void HeapShared::write_subgraph_info_table() {
}
}
-void HeapShared::serialize_tables(SerializeClosure* soc) {
+void AOTHeap::serialize_tables(SerializeClosure* soc) {
_run_time_subgraph_info_table.serialize_header(soc);
soc->do_ptr(&_run_time_special_subgraph);
soc->do_ptr(&_runtime_resolved_methods);
DEBUG_ONLY(soc->do_ptr(&_runtime_classes_with_cached_oops));
}
-void HeapShared::load_cached_resolved_methods() {
+void AOTHeap::load_cached_resolved_methods() {
precond(CDSConfig::is_using_aot_linked_classes());
if (_runtime_resolved_methods != nullptr) {
JavaThread* current = JavaThread::current();
@@ -1395,16 +1394,16 @@ static void verify_the_heap(Klass* k, const char* which) {
}
}
-// Before GC can execute, we must ensure that all oops reachable from HeapShared::roots()
+// Before GC can execute, we must ensure that all oops reachable from AOTHeap::roots()
// have a valid klass. I.e., oopDesc::klass() must have already been resolved.
-void HeapShared::resolve_classes(JavaThread* current) {
+void AOTHeap::resolve_classes(JavaThread* current) {
assert(CDSConfig::is_using_archive(), "runtime only!");
if (CDSConfig::is_using_klass_subgraphs()) {
resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields);
}
}
-void HeapShared::resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]) {
+void AOTHeap::resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]) {
for (int i = 0; fields[i].valid(); i++) {
ArchivableStaticFieldInfo* info = &fields[i];
TempNewSymbol klass_name = SymbolTable::new_symbol(info->klass_name);
@@ -1414,7 +1413,7 @@ void HeapShared::resolve_classes_for_subgraphs(JavaThread* current, ArchivableSt
}
}
-void HeapShared::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) {
+void AOTHeap::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) {
JavaThread* THREAD = current;
ExceptionMark em(THREAD);
const ArchivedKlassSubGraphInfoRecord* record =
@@ -1437,7 +1436,7 @@ static const char* java_lang_invoke_core_klasses[] = {
"java/lang/invoke/BoundMethodHandle$Species_L",
};
-void HeapShared::initialize_java_lang_invoke(TRAPS) {
+void AOTHeap::initialize_java_lang_invoke(TRAPS) {
if (CDSConfig::is_using_aot_linked_classes() || CDSConfig::is_dumping_method_handles()) {
int len = sizeof(java_lang_invoke_core_klasses)/sizeof(char*);
for (int i = 0; i < len; i++) {
@@ -1446,7 +1445,7 @@ void HeapShared::initialize_java_lang_invoke(TRAPS) {
}
}
-bool HeapShared::is_core_java_lang_invoke_klass(InstanceKlass* klass) {
+bool AOTHeap::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*);
@@ -1474,7 +1473,7 @@ bool HeapShared::is_core_java_lang_invoke_klass(InstanceKlass* klass) {
// the aot-initialized mirror of Fruit has a static field that references HashSet, which
// should be initialized before any Java code can access the Fruit class. Note that
// HashSet itself doesn't necessary need to be an aot-initialized class.
-void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) {
+void AOTHeap::init_classes_for_special_subgraph(Handle class_loader, TRAPS) {
if (!is_archived_heap_in_use()) {
return;
}
@@ -1505,7 +1504,7 @@ void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) {
}
}
-void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k) {
+void AOTHeap::initialize_from_archived_subgraph(JavaThread* current, Klass* k) {
JavaThread* THREAD = current;
if (!CDSConfig::is_using_klass_subgraphs()) {
return; // nothing to do
@@ -1541,7 +1540,7 @@ void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k
}
const ArchivedKlassSubGraphInfoRecord*
-HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS) {
+AOTHeap::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS) {
assert(!CDSConfig::is_dumping_heap(), "Should not be called when dumping heap");
if (!k->in_aot_cache()) {
@@ -1597,7 +1596,7 @@ HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAP
return record;
}
-void HeapShared::resolve_or_init(const char* klass_name, bool do_init, TRAPS) {
+void AOTHeap::resolve_or_init(const char* klass_name, bool do_init, TRAPS) {
TempNewSymbol klass_name_sym = SymbolTable::new_symbol(klass_name);
InstanceKlass* k = SystemDictionaryShared::find_builtin_class(klass_name_sym);
if (k == nullptr) {
@@ -1610,7 +1609,7 @@ void HeapShared::resolve_or_init(const char* klass_name, bool do_init, TRAPS) {
}
}
-void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) {
+void AOTHeap::resolve_or_init(Klass* k, bool do_init, TRAPS) {
if (!do_init) {
if (k->class_loader_data() == nullptr) {
Klass* resolved_k = SystemDictionary::resolve_or_null(k->name(), CHECK);
@@ -1621,7 +1620,7 @@ void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) {
}
}
} else {
- assert(k->class_loader_data() != nullptr, "must have been resolved by HeapShared::resolve_classes");
+ assert(k->class_loader_data() != nullptr, "must have been resolved by AOTHeap::resolve_classes");
if (k->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(k);
ik->initialize(CHECK);
@@ -1632,7 +1631,7 @@ void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) {
}
}
-void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphInfoRecord* record) {
+void AOTHeap::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphInfoRecord* record) {
verify_the_heap(k, "before");
Array* entry_field_records = record->entry_field_records();
@@ -1672,7 +1671,7 @@ void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphI
verify_the_heap(k, "after ");
}
-void HeapShared::clear_archived_roots_of(Klass* k) {
+void AOTHeap::clear_archived_roots_of(Klass* k) {
unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary_quick(k);
const ArchivedKlassSubGraphInfoRecord* record = _run_time_subgraph_info_table.lookup(k, hash, 0);
if (record != nullptr) {
@@ -1690,7 +1689,7 @@ void HeapShared::clear_archived_roots_of(Klass* k) {
// Push all oop fields (or oop array elemenets in case of an objArray) in
// _referencing_obj onto the _stack.
-class HeapShared::OopFieldPusher: public BasicOopIterateClosure {
+class AOTHeap::OopFieldPusher: public BasicOopIterateClosure {
PendingOopStack* _stack;
GrowableArray _found_oop_fields;
int _level;
@@ -1771,13 +1770,13 @@ class PointsToOopsChecker : public BasicOopIterateClosure {
bool result() { return _result; }
};
-HeapShared::CachedOopInfo HeapShared::make_cached_oop_info(oop obj, oop referrer) {
+AOTHeap::CachedOopInfo AOTHeap::make_cached_oop_info(oop obj, oop referrer) {
PointsToOopsChecker points_to_oops_checker;
obj->oop_iterate(&points_to_oops_checker);
return CachedOopInfo(OopHandle(Universe::vm_global(), referrer), points_to_oops_checker.result());
}
-void HeapShared::init_box_classes(TRAPS) {
+void AOTHeap::init_box_classes(TRAPS) {
if (is_archived_heap_in_use()) {
vmClasses::Boolean_klass()->initialize(CHECK);
vmClasses::Character_klass()->initialize(CHECK);
@@ -1791,8 +1790,8 @@ void HeapShared::init_box_classes(TRAPS) {
}
}
-// Used by HeapShared::find_flat_field_klasses().
-class HeapShared::FlatFieldKlassFinder : public FieldClosure {
+// Used by AOTHeap::find_flat_field_klasses().
+class AOTHeap::FlatFieldKlassFinder : public FieldClosure {
KlassSubGraphInfo* _subgraph_info;
oop _obj;
const ValuePayloadContext* _vpc;
@@ -1822,7 +1821,7 @@ class HeapShared::FlatFieldKlassFinder : public FieldClosure {
}
};
-void HeapShared::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKlass* k) {
+void AOTHeap::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKlass* k) {
subgraph_info->add_subgraph_object_klass(k);
if (InstanceKlass::cast(k)->is_enum_subclass()
|| (subgraph_info == _dump_time_special_subgraph)) {
@@ -1840,12 +1839,12 @@ void HeapShared::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKla
// @NullRestricted Point p1;
// @NullRestricted Point p2; ... }
//
-// Klasses of non-flat fields are already recorded by HeapShared::archive_object().
+// Klasses of non-flat fields are already recorded by AOTHeap::archive_object().
//
-// If only a single instance of Line is archived, HeapShared::archive_object() would
+// If only a single instance of Line is archived, AOTHeap::archive_object() would
// have never visited a heap oop instance of Point, but we must store Point in
// AOT-initialized state. This function finds Point.
-void HeapShared::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop orig_obj) {
+void AOTHeap::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop orig_obj) {
Klass* klass = orig_obj->klass();
if (klass->is_flatArray_klass()) {
@@ -1885,7 +1884,7 @@ void HeapShared::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop o
// trace all objects that are reachable from it, and make sure these objects are archived.
// (3) Record the klasses of all objects that are reachable from orig_obj (including those that
// were already archived when this function is called)
-bool HeapShared::archive_reachable_objects_from(int level,
+bool AOTHeap::archive_reachable_objects_from(int level,
KlassSubGraphInfo* subgraph_info,
oop orig_obj) {
assert(orig_obj != nullptr, "must be");
@@ -1909,7 +1908,7 @@ bool HeapShared::archive_reachable_objects_from(int level,
return true;
}
-bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGraphInfo* subgraph_info,
+bool AOTHeap::walk_one_object(PendingOopStack* stack, int level, KlassSubGraphInfo* subgraph_info,
oop orig_obj, oop referrer) {
assert(orig_obj != nullptr, "must be");
if (!JavaClasses::is_supported_for_archiving(orig_obj)) {
@@ -1949,7 +1948,7 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap
} else if (java_lang_Class::is_instance(orig_obj) && subgraph_info != _dump_time_special_subgraph) {
// Without CDSConfig::is_dumping_aot_linked_classes(), we only allow archived objects to
// point to the mirrors of (1) j.l.Object, (2) primitive classes, and (3) box classes. These are initialized
- // very early by HeapShared::init_box_classes().
+ // very early by AOTHeap::init_box_classes().
if (orig_obj == vmClasses::Object_klass()->java_mirror()
|| java_lang_Class::is_primitive(orig_obj)
|| orig_obj == vmClasses::Boolean_klass()->java_mirror()
@@ -2064,7 +2063,7 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap
// for loading and initializing before any object in the archived graph can
// be accessed at runtime.
//
-void HeapShared::archive_reachable_objects_from_static_field(InstanceKlass *k,
+void AOTHeap::archive_reachable_objects_from_static_field(InstanceKlass *k,
const char* klass_name,
int field_offset,
const char* field_name) {
@@ -2113,12 +2112,12 @@ class VerifySharedOopClosure: public BasicOopIterateClosure {
template void do_oop_work(T *p) {
oop obj = HeapAccess<>::oop_load(p);
if (obj != nullptr) {
- HeapShared::verify_reachable_objects_from(obj);
+ AOTHeap::verify_reachable_objects_from(obj);
}
}
};
-void HeapShared::verify_subgraph_from_static_field(InstanceKlass* k, int field_offset) {
+void AOTHeap::verify_subgraph_from_static_field(InstanceKlass* k, int field_offset) {
precond(CDSConfig::is_dumping_klass_subgraphs());
assert(k->defined_by_boot_loader(), "must be boot class");
@@ -2129,7 +2128,7 @@ void HeapShared::verify_subgraph_from_static_field(InstanceKlass* k, int field_o
}
}
-void HeapShared::verify_subgraph_from(oop orig_obj) {
+void AOTHeap::verify_subgraph_from(oop orig_obj) {
if (!has_been_archived(orig_obj)) {
// It's OK for the root of a subgraph to be not archived. See comments in
// archive_reachable_objects_from().
@@ -2142,7 +2141,7 @@ void HeapShared::verify_subgraph_from(oop orig_obj) {
delete_seen_objects_table();
}
-void HeapShared::verify_reachable_objects_from(oop obj) {
+void AOTHeap::verify_reachable_objects_from(oop obj) {
_num_total_verifications ++;
if (java_lang_Class::is_instance(obj)) {
obj = RegeneratedClasses::maybe_get_regenerated_mirror(obj);
@@ -2158,7 +2157,7 @@ void HeapShared::verify_reachable_objects_from(oop obj) {
}
#endif
-void HeapShared::check_special_subgraph_classes() {
+void AOTHeap::check_special_subgraph_classes() {
if (CDSConfig::is_dumping_aot_linked_classes()) {
// We can have aot-initialized classes (such as Enums) that can reference objects
// of arbitrary types. Currently, we trust the JEP 483 implementation to only
@@ -2192,30 +2191,29 @@ void HeapShared::check_special_subgraph_classes() {
}
}
-HeapShared::SeenObjectsTable* HeapShared::_seen_objects_table = nullptr;
-HeapShared::PendingOop HeapShared::_object_being_archived;
-size_t HeapShared::_num_new_walked_objs;
-size_t HeapShared::_num_new_archived_objs;
-size_t HeapShared::_num_old_recorded_klasses;
+AOTHeap::SeenObjectsTable* AOTHeap::_seen_objects_table = nullptr;
+AOTHeap::PendingOop AOTHeap::_object_being_archived;
+size_t AOTHeap::_num_new_walked_objs;
+size_t AOTHeap::_num_new_archived_objs;
+size_t AOTHeap::_num_old_recorded_klasses;
-size_t HeapShared::_num_total_subgraph_recordings = 0;
-size_t HeapShared::_num_total_walked_objs = 0;
-size_t HeapShared::_num_total_archived_objs = 0;
-size_t HeapShared::_num_total_recorded_klasses = 0;
-size_t HeapShared::_num_total_verifications = 0;
+size_t AOTHeap::_num_total_subgraph_recordings = 0;
+size_t AOTHeap::_num_total_walked_objs = 0;
+size_t AOTHeap::_num_total_archived_objs = 0;
+size_t AOTHeap::_num_total_recorded_klasses = 0;
+size_t AOTHeap::_num_total_verifications = 0;
-bool HeapShared::has_been_seen_during_subgraph_recording(oop obj) {
+bool AOTHeap::has_been_seen_during_subgraph_recording(oop obj) {
return _seen_objects_table->get(obj) != nullptr;
}
-void HeapShared::set_has_been_seen_during_subgraph_recording(oop obj) {
+void AOTHeap::set_has_been_seen_during_subgraph_recording(oop obj) {
assert(!has_been_seen_during_subgraph_recording(obj), "sanity");
_seen_objects_table->put_when_absent(obj, true);
- _seen_objects_table->maybe_grow();
++ _num_new_walked_objs;
}
-void HeapShared::start_recording_subgraph(InstanceKlass *k, const char* class_name) {
+void AOTHeap::start_recording_subgraph(InstanceKlass *k, const char* class_name) {
log_info(aot, heap)("Start recording subgraph(s) for archived fields in %s", class_name);
init_subgraph_info(k);
init_seen_objects_table();
@@ -2224,7 +2222,7 @@ void HeapShared::start_recording_subgraph(InstanceKlass *k, const char* class_na
_num_old_recorded_klasses = get_subgraph_info(k)->num_subgraph_object_klasses();
}
-void HeapShared::done_recording_subgraph(InstanceKlass *k, const char* class_name) {
+void AOTHeap::done_recording_subgraph(InstanceKlass *k, const char* class_name) {
size_t num_new_recorded_klasses = get_subgraph_info(k)->num_subgraph_object_klasses() -
_num_old_recorded_klasses;
log_info(aot, heap)("Done recording subgraph(s) for archived fields in %s: "
@@ -2262,7 +2260,7 @@ class ArchivableStaticFieldFinder: public FieldClosure {
int offset() { return _offset; }
};
-void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
+void AOTHeap::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
TRAPS) {
for (int i = 0; fields[i].valid(); i++) {
ArchivableStaticFieldInfo* info = &fields[i];
@@ -2312,7 +2310,7 @@ void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[],
}
}
-void HeapShared::init_subgraph_entry_fields(TRAPS) {
+void AOTHeap::init_subgraph_entry_fields(TRAPS) {
assert(CDSConfig::is_dumping_heap(), "must be");
_dump_time_subgraph_info_table = new (mtClass)DumpTimeKlassSubGraphInfoTable();
if (CDSConfig::is_dumping_klass_subgraphs()) {
@@ -2320,21 +2318,21 @@ void HeapShared::init_subgraph_entry_fields(TRAPS) {
}
}
-void HeapShared::init_for_dumping(TRAPS) {
+void AOTHeap::init_for_dumping(TRAPS) {
if (CDSConfig::is_dumping_heap()) {
init_subgraph_entry_fields(CHECK);
}
}
-void HeapShared::init_heap_writer() {
- if (HeapShared::is_writing_streaming_mode()) {
+void AOTHeap::init_heap_writer() {
+ if (AOTHeap::is_writing_streaming_mode()) {
AOTStreamedHeapWriter::init();
} else {
AOTMappedHeapWriter::init();
}
}
-void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) {
+void AOTHeap::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) {
_num_total_subgraph_recordings = 0;
_num_total_walked_objs = 0;
_num_total_archived_objs = 0;
@@ -2346,7 +2344,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) {
// [2] Create a list of all the class of the objects that can be reached
// by any of these static fields.
// At runtime, these classes are initialized before X's archived fields
- // are restored by HeapShared::initialize_from_archived_subgraph().
+ // are restored by AOTHeap::initialize_from_archived_subgraph().
for (int i = 0; fields[i].valid(); ) {
ArchivableStaticFieldInfo* info = &fields[i];
const char* klass_name = info->klass_name;
@@ -2383,7 +2381,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) {
#endif
}
-bool HeapShared::is_interned_string(oop obj) {
+bool AOTHeap::is_interned_string(oop obj) {
if (!java_lang_String::is_instance(obj)) {
return false;
}
@@ -2397,14 +2395,14 @@ bool HeapShared::is_interned_string(oop obj) {
return StringTable::lookup(name, len) == obj;
}
-bool HeapShared::is_dumped_interned_string(oop o) {
+bool AOTHeap::is_dumped_interned_string(oop o) {
return is_interned_string(o) && has_been_archived(o);
}
// These tables should be used only within the CDS safepoint, so
// delete them before we exit the safepoint. Otherwise the table will
// contain bad oops after a GC.
-void HeapShared::delete_tables_with_raw_oops() {
+void AOTHeap::delete_tables_with_raw_oops() {
assert(_seen_objects_table == nullptr, "should have been deleted");
if (is_writing_mapping_mode()) {
@@ -2415,7 +2413,7 @@ void HeapShared::delete_tables_with_raw_oops() {
}
}
-void HeapShared::debug_trace() {
+void AOTHeap::debug_trace() {
ResourceMark rm;
oop referrer = _object_being_archived.referrer();
if (referrer != nullptr) {
@@ -2464,7 +2462,7 @@ class FindEmbeddedNonNullPointers: public BasicOopIterateClosure {
};
#endif
-void HeapShared::count_allocation(size_t size) {
+void AOTHeap::count_allocation(size_t size) {
_total_obj_count ++;
_total_obj_size += size;
for (int i = 0; i < ALLOC_STAT_SLOTS; i++) {
@@ -2484,7 +2482,7 @@ static double avg_size(size_t size, size_t count) {
return avg;
}
-void HeapShared::print_stats() {
+void AOTHeap::print_stats() {
size_t huge_count = _total_obj_count;
size_t huge_size = _total_obj_size;
@@ -2509,7 +2507,7 @@ void HeapShared::print_stats() {
avg_size(_total_obj_size, _total_obj_count));
}
-bool HeapShared::is_metadata_field(oop src_obj, int offset) {
+bool AOTHeap::is_metadata_field(oop src_obj, int offset) {
bool result = false;
do_metadata_offsets(src_obj, [&](int metadata_offset) {
if (metadata_offset == offset) {
@@ -2519,7 +2517,7 @@ bool HeapShared::is_metadata_field(oop src_obj, int offset) {
return result;
}
-void HeapShared::remap_dumped_metadata(oop src_obj, address archived_object) {
+void AOTHeap::remap_dumped_metadata(oop src_obj, address archived_object) {
do_metadata_offsets(src_obj, [&](int offset) {
Metadata** buffered_field_addr = (Metadata**)(archived_object + offset);
Metadata* native_ptr = *buffered_field_addr;
@@ -2536,7 +2534,7 @@ void HeapShared::remap_dumped_metadata(oop src_obj, address archived_object) {
});
}
-bool HeapShared::is_archived_boot_layer_available(JavaThread* current) {
+bool AOTHeap::is_archived_boot_layer_available(JavaThread* current) {
TempNewSymbol klass_name = SymbolTable::new_symbol(ARCHIVED_BOOT_LAYER_CLASS);
InstanceKlass* k = SystemDictionary::find_instance_klass(current, klass_name, Handle());
if (k == nullptr) {
diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/aotHeap.hpp
similarity index 97%
rename from src/hotspot/share/cds/heapShared.hpp
rename to src/hotspot/share/cds/aotHeap.hpp
index 78f493d17c8e..a6379a815852 100644
--- a/src/hotspot/share/cds/heapShared.hpp
+++ b/src/hotspot/share/cds/aotHeap.hpp
@@ -22,8 +22,8 @@
*
*/
-#ifndef SHARE_CDS_HEAPSHARED_HPP
-#define SHARE_CDS_HEAPSHARED_HPP
+#ifndef SHARE_CDS_AOTHEAP_HPP
+#define SHARE_CDS_AOTHEAP_HPP
#include "cds/aotMetaspace.hpp"
#include "cds/dumpTimeClassInfo.hpp"
@@ -128,7 +128,7 @@ enum class HeapArchiveMode {
_streaming
};
-class HeapShared: AllStatic {
+class AOTHeap: AllStatic {
friend class VerifySharedOopClosure;
public:
@@ -230,8 +230,8 @@ class HeapShared: AllStatic {
typedef ResizeableHashTable ArchivedObjectCache;
+ AOTHeap::archived_object_cache_hash,
+ AOTHeap::oop_handle_equals> ArchivedObjectCache;
static ArchivedObjectCache* _archived_object_cache;
class DumpTimeKlassSubGraphInfoTable
@@ -289,7 +289,7 @@ class HeapShared: AllStatic {
typedef ResizeableHashTable SeenObjectsTable;
+ AOTHeap::oop_address_hash> SeenObjectsTable;
static SeenObjectsTable *_seen_objects_table;
@@ -416,7 +416,7 @@ class HeapShared: AllStatic {
static void set_has_native_pointers(oop src_obj);
static uintptr_t archive_location(oop src_obj);
- // We use the HeapShared::roots() array to make sure that objects stored in the
+ // We use the AOTHeap::roots() array to make sure that objects stored in the
// archived heap region are not prematurely collected. These roots include:
//
// - mirrors of classes that have not yet been loaded.
@@ -425,7 +425,7 @@ class HeapShared: AllStatic {
// - java.lang.Module objects that have not yet been added to the module graph
//
// When a mirror M becomes referenced by a newly loaded class K, M will be removed
- // from HeapShared::roots() via clear_root(), and K will be responsible for
+ // from AOTHeap::roots() via clear_root(), and K will be responsible for
// keeping M alive.
//
// Other types of roots are also cleared similarly when they become referenced.
@@ -498,4 +498,4 @@ class HeapShared: AllStatic {
static void log_oop_details(oop source_oop, address buffered_addr);
};
-#endif // SHARE_CDS_HEAPSHARED_HPP
+#endif // SHARE_CDS_AOTHEAP_HPP
diff --git a/src/hotspot/share/cds/heapShared.inline.hpp b/src/hotspot/share/cds/aotHeap.inline.hpp
similarity index 82%
rename from src/hotspot/share/cds/heapShared.inline.hpp
rename to src/hotspot/share/cds/aotHeap.inline.hpp
index 52ec5182ec45..33e9a866121c 100644
--- a/src/hotspot/share/cds/heapShared.inline.hpp
+++ b/src/hotspot/share/cds/aotHeap.inline.hpp
@@ -22,10 +22,10 @@
*
*/
-#ifndef SHARE_CDS_HEAPSHARED_INLINE_HPP
-#define SHARE_CDS_HEAPSHARED_INLINE_HPP
+#ifndef SHARE_CDS_AOTHEAP_INLINE_HPP
+#define SHARE_CDS_AOTHEAP_INLINE_HPP
-#include "cds/heapShared.hpp"
+#include "cds/aotHeap.hpp"
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/regeneratedClasses.hpp"
@@ -34,37 +34,37 @@
#if INCLUDE_CDS_JAVA_HEAP
-inline bool HeapShared::is_loading() {
+inline bool AOTHeap::is_loading() {
return _heap_load_mode != HeapArchiveMode::_uninitialized;
}
-inline bool HeapShared::is_loading_streaming_mode() {
+inline bool AOTHeap::is_loading_streaming_mode() {
assert(_heap_load_mode != HeapArchiveMode::_uninitialized, "not initialized yet");
return _heap_load_mode == HeapArchiveMode::_streaming;
}
-inline bool HeapShared::is_loading_mapping_mode() {
+inline bool AOTHeap::is_loading_mapping_mode() {
assert(_heap_load_mode != HeapArchiveMode::_uninitialized, "not initialized yet");
return _heap_load_mode == HeapArchiveMode::_mapping;
}
-inline bool HeapShared::is_writing() {
+inline bool AOTHeap::is_writing() {
return _heap_write_mode != HeapArchiveMode::_uninitialized;
}
-inline bool HeapShared::is_writing_streaming_mode() {
+inline bool AOTHeap::is_writing_streaming_mode() {
assert(_heap_write_mode != HeapArchiveMode::_uninitialized, "not initialized yet");
return _heap_write_mode == HeapArchiveMode::_streaming;
}
-inline bool HeapShared::is_writing_mapping_mode() {
+inline bool AOTHeap::is_writing_mapping_mode() {
assert(_heap_write_mode != HeapArchiveMode::_uninitialized, "not initialized yet");
return _heap_write_mode == HeapArchiveMode::_mapping;
}
// Keep the knowledge about which objects have what metadata in one single place
template
-void HeapShared::do_metadata_offsets(oop src_obj, T callback) {
+void AOTHeap::do_metadata_offsets(oop src_obj, T callback) {
if (java_lang_Class::is_instance(src_obj)) {
assert(java_lang_Class::klass_offset() < java_lang_Class::array_klass_offset(),
"metadata offsets must be sorted");
@@ -75,7 +75,7 @@ void HeapShared::do_metadata_offsets(oop src_obj, T callback) {
}
}
-inline void HeapShared::remap_loaded_metadata(oop src_obj) {
+inline void AOTHeap::remap_loaded_metadata(oop src_obj) {
do_metadata_offsets(src_obj, [&](int offset) {
Metadata* metadata = src_obj->metadata_field(offset);
if (metadata != nullptr) {
@@ -85,7 +85,7 @@ inline void HeapShared::remap_loaded_metadata(oop src_obj) {
});
}
-inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_offset, oop referent) {
+inline oop AOTHeap::maybe_remap_referent(bool is_java_lang_ref, size_t field_offset, oop referent) {
if (referent == nullptr) {
return nullptr;
}
@@ -99,7 +99,7 @@ inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_
// When the source object points to a "real" mirror, the buffered object should point
// to the "scratch" mirror, which has all unarchivable fields scrubbed (to be reinstated
// at run time).
- referent = HeapShared::scratch_java_mirror(referent);
+ referent = AOTHeap::scratch_java_mirror(referent);
assert(referent != nullptr, "must be");
}
@@ -108,4 +108,4 @@ inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_
#endif // INCLUDE_CDS_JAVA_HEAP
-#endif // SHARE_CDS_HEAPSHARED_INLINE_HPP
+#endif // SHARE_CDS_AOTHEAP_INLINE_HPP
diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp
index c7911294e992..438587281815 100644
--- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp
+++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp
@@ -24,11 +24,11 @@
#include "cds/aotClassInitializer.hpp"
#include "cds/aotClassLinker.hpp"
+#include "cds/aotHeap.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/javaClasses.hpp"
#include "classfile/javaStackTraceClasses.hpp"
@@ -87,7 +87,7 @@ void AOTLinkedClassBulkLoader::preload_classes_impl(TRAPS) {
preload_classes_in_table(table->app(), "app", h_system_loader, CHECK);
// Do this after all boot/platform/app classes are loaded, but before bytecode execution.
- HeapShared::load_cached_resolved_methods();
+ AOTHeap::load_cached_resolved_methods();
}
void AOTLinkedClassBulkLoader::preload_classes_in_table(Array* classes,
@@ -424,7 +424,7 @@ void AOTLinkedClassBulkLoader::init_classes_for_loader(Handle class_loader, Arra
}
if (!early_only) {
- HeapShared::init_classes_for_special_subgraph(class_loader, CHECK);
+ AOTHeap::init_classes_for_special_subgraph(class_loader, CHECK);
}
}
diff --git a/src/hotspot/share/cds/aotMappedHeapLoader.cpp b/src/hotspot/share/cds/aotMappedHeapLoader.cpp
index b2552bea744c..ab2fc7dc474d 100644
--- a/src/hotspot/share/cds/aotMappedHeapLoader.cpp
+++ b/src/hotspot/share/cds/aotMappedHeapLoader.cpp
@@ -29,7 +29,6 @@
#include "cds/aotMetaspace.hpp"
#include "cds/aotModules.hpp"
#include "cds/cdsConfig.hpp"
-#include "cds/heapShared.inline.hpp"
#include "classfile/stringTable.hpp"
#include "classfile/systemDictionaryShared.hpp"
#include "gc/shared/collectedHeap.hpp"
diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp
index ed35c8d09d18..e40ea832baf6 100644
--- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp
+++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp
@@ -22,13 +22,13 @@
*
*/
+#include "cds/aotHeap.inline.hpp"
#include "cds/aotMappedHeap.hpp"
#include "cds/aotMappedHeapWriter.hpp"
#include "cds/aotModules.hpp"
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/filemap.hpp"
-#include "cds/heapShared.inline.hpp"
#include "cds/regeneratedClasses.hpp"
#include "classfile/javaClasses.hpp"
#include "classfile/modules.hpp"
@@ -197,7 +197,7 @@ oop AOTMappedHeapWriter::requested_obj_from_buffer_offset(size_t offset) {
oop AOTMappedHeapWriter::source_obj_to_requested_obj(oop src_obj) {
assert(CDSConfig::is_dumping_heap(), "dump-time only");
- HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj);
+ AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj);
if (p != nullptr) {
return requested_obj_from_buffer_offset(p->buffer_offset());
} else {
@@ -355,7 +355,7 @@ void AOTMappedHeapWriter::copy_roots_to_buffer(GrowableArrayCHeaplength(); i++) {
int src_obj_index = _source_objs_order->at(i)._index;
oop src_obj = _source_objs->at(src_obj_index);
- HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj);
+ AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj);
assert(info != nullptr, "must be");
size_t buffer_offset = copy_one_source_obj_to_buffer(src_obj);
info->set_buffer_offset(buffer_offset);
@@ -417,7 +417,6 @@ void AOTMappedHeapWriter::copy_source_objs_to_buffer(GrowableArrayCHeapput_when_absent(buffer_offset, handle);
- _buffer_offset_to_source_obj_table->maybe_grow();
if (java_lang_Module::is_instance(src_obj)) {
AOTModules::check_archived_module_oop(src_obj);
@@ -770,7 +769,7 @@ class AOTMappedHeapWriter::EmbeddedOopRelocator: public BasicOopIterateClosure {
int field_offset = pointer_delta_as_int((char*)p, cast_from_oop(_src_obj));
T* field_addr = (T*)(_buffered_obj + field_offset);
oop referent = load_source_oop_from_buffer(field_addr);
- referent = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, referent);
+ referent = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, referent);
AOTMappedHeapWriter::relocate_field_in_buffer(field_addr, referent, _oopmap);
}
};
@@ -796,7 +795,7 @@ void AOTMappedHeapWriter::relocate_embedded_oops(GrowableArrayCHeaplength(); i++) {
int src_obj_index = _source_objs_order->at(i)._index;
oop src_obj = _source_objs->at(src_obj_index);
- HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj);
+ AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj);
assert(info != nullptr, "must be");
oop requested_obj = requested_obj_from_buffer_offset(info->buffer_offset());
update_header_for_requested_obj(requested_obj, src_obj, src_obj->klass());
@@ -806,7 +805,7 @@ void AOTMappedHeapWriter::relocate_embedded_oops(GrowableArrayCHeap::oop_load(elem_addr);
- obj = HeapShared::maybe_remap_referent(false /* is_reference_field */, elem_offset, obj);
+ obj = AOTHeap::maybe_remap_referent(false /* is_reference_field */, elem_offset, obj);
if (UseCompressedOops) {
relocate_field_in_buffer((narrowOop*)elem_addr, obj, heap_info->oopmap());
} else {
@@ -847,13 +846,13 @@ void AOTMappedHeapWriter::mark_native_pointer(oop src_obj, int field_offset) {
info._src_obj = src_obj;
info._field_offset = field_offset;
_native_pointers->append(info);
- HeapShared::set_has_native_pointers(src_obj);
+ AOTHeap::set_has_native_pointers(src_obj);
_num_native_ptrs ++;
}
}
void AOTMappedHeapWriter::mark_native_pointers(oop orig_obj) {
- HeapShared::do_metadata_offsets(orig_obj, [&](int offset) {
+ AOTHeap::do_metadata_offsets(orig_obj, [&](int offset) {
mark_native_pointer(orig_obj, offset);
});
}
@@ -869,7 +868,7 @@ void AOTMappedHeapWriter::compute_ptrmap(AOTMappedHeapInfo* heap_info) {
NativePointerInfo info = _native_pointers->at(i);
oop src_obj = info._src_obj;
int field_offset = info._field_offset;
- HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj);
+ AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj);
// requested_field_addr = the address of this field in the requested space
oop requested_obj = requested_obj_from_buffer_offset(p->buffer_offset());
Metadata** requested_field_addr = (Metadata**)(cast_from_oop(requested_obj) + field_offset);
diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.hpp b/src/hotspot/share/cds/aotMappedHeapWriter.hpp
index 2420e68d9fea..5dc37ae350f3 100644
--- a/src/hotspot/share/cds/aotMappedHeapWriter.hpp
+++ b/src/hotspot/share/cds/aotMappedHeapWriter.hpp
@@ -25,8 +25,8 @@
#ifndef SHARE_CDS_AOTMAPPEDHEAPWRITER_HPP
#define SHARE_CDS_AOTMAPPEDHEAPWRITER_HPP
+#include "cds/aotHeap.hpp"
#include "cds/aotMapLogger.hpp"
-#include "cds/heapShared.hpp"
#include "memory/allocation.hpp"
#include "memory/allStatic.hpp"
#include "oops/compressedOops.hpp"
@@ -41,7 +41,7 @@ class MemRegion;
#if INCLUDE_CDS_JAVA_HEAP
class AOTMappedHeapWriter : AllStatic {
- friend class HeapShared;
+ friend class AOTHeap;
friend class AOTMappedHeapLoader;
// AOTMappedHeapWriter manipulates three types of addresses:
//
@@ -53,9 +53,9 @@ class AOTMappedHeapWriter : AllStatic {
// - "source objects" are regular Java objects allocated during the execution
// of "java -Xshare:dump". They can be used as regular oops.
//
- // Between HeapShared::start_scanning_for_oops() and HeapShared::end_scanning_for_oops(),
+ // Between AOTHeap::start_scanning_for_oops() and AOTHeap::end_scanning_for_oops(),
// we recursively search for the oops that need to be stored into the CDS archive.
- // These are entered into HeapShared::archived_object_cache().
+ // These are entered into AOTHeap::archived_object_cache().
//
// - "buffered objects" are copies of the "source objects", and are stored in into
// AOTMappedHeapWriter::_buffer, which is a GrowableArray that sits outside of
diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp
index cd102e4eee73..d43c9c297467 100644
--- a/src/hotspot/share/cds/aotMetaspace.cpp
+++ b/src/hotspot/share/cds/aotMetaspace.cpp
@@ -28,6 +28,7 @@
#include "cds/aotClassLinker.hpp"
#include "cds/aotClassLocation.hpp"
#include "cds/aotConstantPoolResolver.hpp"
+#include "cds/aotHeap.inline.hpp"
#include "cds/aotLinkedClassBulkLoader.hpp"
#include "cds/aotLogging.hpp"
#include "cds/aotMapLogger.hpp"
@@ -46,7 +47,6 @@
#include "cds/dynamicArchive.hpp"
#include "cds/filemap.hpp"
#include "cds/finalImageRecipes.hpp"
-#include "cds/heapShared.inline.hpp"
#include "cds/lambdaFormInvokers.hpp"
#include "cds/lambdaProxyClassDictionary.hpp"
#include "classfile/classLoaderDataGraph.hpp"
@@ -142,7 +142,7 @@ FileMapInfo* AOTMetaspace::_output_mapinfo = nullptr;
// [5] SymbolTable, StringTable, SystemDictionary, and a few other read-only data
// are copied into the ro region as read-only tables.
//
-// The heap region is written by HeapShared::write_heap().
+// The heap region is written by AOTHeap::write_heap().
//
// The bitmap region is used to relocate the ro/rw/hp regions.
@@ -195,9 +195,6 @@ class DumpClassListCLDClosure : public CLDClosure {
if (!created) {
return;
}
- if (_dumped_classes.maybe_grow()) {
- log_info(aot, hashtables)("Expanded _dumped_classes table to %d", _dumped_classes.table_size());
- }
if (ik->super()) {
dump(ik->super());
}
@@ -363,7 +360,7 @@ 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_streaming_mode())) {
+ if (!(AOTHeap::is_loading() && AOTHeap::is_loading_streaming_mode())) {
// Streaming mode will unmap the bm region later in AOTStreamedHeapLoader::cleanup()
static_mapinfo->unmap_region(AOTMetaspace::bm);
}
@@ -422,7 +419,7 @@ void AOTMetaspace::read_extra_data(JavaThread* current, const char* filename) {
CLEAR_PENDING_EXCEPTION;
} else {
#if INCLUDE_CDS_JAVA_HEAP
- if (HeapShared::is_string_too_large_to_archive(str)) {
+ if (AOTHeap::is_string_too_large_to_archive(str)) {
log_warning(aot, heap)("[line %d] extra interned string ignored; size too large: %d",
reader.last_line_no(), utf8_length);
continue;
@@ -528,7 +525,7 @@ void AOTMetaspace::serialize(SerializeClosure* soc) {
// Dump/restore the symbol/string/subgraph_info tables
SymbolTable::serialize_shared_table_header(soc);
StringTable::serialize_shared_table_header(soc);
- HeapShared::serialize_tables(soc);
+ AOTHeap::serialize_tables(soc);
SystemDictionaryShared::serialize_dictionary_headers(soc);
AOTLinkedClassBulkLoader::serialize(soc);
FinalImageRecipes::serialize(soc);
@@ -846,7 +843,7 @@ void VM_PopulateDumpSharedSpace::doit() {
_map_info->set_cloned_vtables(CppVtables::vtables_serialized_base());
_map_info->header()->set_class_location_config(cl_config);
- HeapShared::delete_tables_with_raw_oops();
+ AOTHeap::delete_tables_with_raw_oops();
CDSConfig::set_is_at_aot_safepoint(false);
}
@@ -1145,12 +1142,12 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS
// determinism is not needed.
assert(CDSConfig::allow_only_single_java_thread(), "Required");
}
- if (!HeapShared::is_archived_boot_layer_available(THREAD)) {
+ if (!AOTHeap::is_archived_boot_layer_available(THREAD)) {
report_loading_error("archivedBootLayer not available, disabling full module graph");
CDSConfig::stop_dumping_full_module_graph();
}
// Do this before link_shared_classes(), as the following line may load new classes.
- HeapShared::init_for_dumping(CHECK);
+ AOTHeap::init_for_dumping(CHECK);
}
#endif
@@ -1179,11 +1176,11 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS
#if INCLUDE_CDS_JAVA_HEAP
if (CDSConfig::is_dumping_heap()) {
- HeapShared::init_heap_writer();
+ AOTHeap::init_heap_writer();
if (CDSConfig::is_dumping_full_module_graph()) {
AOTModules::ensure_module_entry_tables_exist();
AOTModules::build_tables(CHECK);
- HeapShared::prepare_for_archiving(CHECK);
+ AOTHeap::prepare_for_archiving(CHECK);
}
AOTReferenceObjSupport::initialize(CHECK);
@@ -1437,7 +1434,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);
+ AOTHeap::write_heap(&_mapped_heap_info, &_streamed_heap_info);
} else if (!CDSConfig::is_dumping_preimage_static_archive()) {
CDSConfig::log_reasons_for_not_dumping_heap();
}
@@ -1880,12 +1877,12 @@ MapArchiveResult AOTMetaspace::map_archives(FileMapInfo* static_mapinfo, FileMap
if (static_mapinfo->can_use_heap_region()) {
if (static_mapinfo->object_streaming_mode()) {
- HeapShared::initialize_loading_mode(HeapArchiveMode::_streaming);
+ AOTHeap::initialize_loading_mode(HeapArchiveMode::_streaming);
} else {
// map_or_load_heap_region() compares the current narrow oop and klass encodings
// with the archived ones, so it must be done after all encodings are determined.
static_mapinfo->map_or_load_heap_region();
- HeapShared::initialize_loading_mode(HeapArchiveMode::_mapping);
+ AOTHeap::initialize_loading_mode(HeapArchiveMode::_mapping);
}
} else {
FileMapRegion* r = static_mapinfo->region_at(AOTMetaspace::hp);
@@ -2218,7 +2215,7 @@ void AOTMetaspace::initialize_shared_spaces() {
// Finish initializing the heap dump mode used in the archive
// Heap initialization can be done only after vtables are initialized by ReadClosure.
- HeapShared::finalize_initialization(static_mapinfo);
+ AOTHeap::finalize_initialization(static_mapinfo);
Universe::load_archived_object_instances();
AOTCodeCache::initialize();
@@ -2272,7 +2269,7 @@ void AOTMetaspace::initialize_shared_spaces() {
CountSharedSymbols cl;
SymbolTable::shared_symbols_do(&cl);
tty->print_cr("Number of shared symbols: %zu", cl.total());
- if (HeapShared::is_loading() && HeapShared::is_loading_mapping_mode()) {
+ if (AOTHeap::is_loading() && AOTHeap::is_loading_mapping_mode()) {
tty->print_cr("Number of shared strings: %zu", StringTable::shared_entry_count());
}
tty->print_cr("VM version: %s\r\n", static_mapinfo->vm_version());
diff --git a/src/hotspot/share/cds/aotModules.cpp b/src/hotspot/share/cds/aotModules.cpp
index 69eb41d343dd..3bf43d0f5176 100644
--- a/src/hotspot/share/cds/aotModules.cpp
+++ b/src/hotspot/share/cds/aotModules.cpp
@@ -22,10 +22,10 @@
*
*/
+#include "cds/aotHeap.inline.hpp"
#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/moduleEntry.hpp"
@@ -476,13 +476,13 @@ void AOTModules::init_archived_modules(JavaThread* current, Handle h_platform_lo
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()) {
+ if (!AOTHeap::is_loading_streaming_mode()) {
return;
}
// Ensure these class loaders are eagerly materialized before their CLDs are created.
- HeapShared::get_root(_platform_loader_root_index, false /* clear */);
- HeapShared::get_root(_system_loader_root_index, false /* clear */);
+ AOTHeap::get_root(_platform_loader_root_index, false /* clear */);
+ AOTHeap::get_root(_system_loader_root_index, false /* clear */);
if (Universe::is_module_initialized() || !CDSConfig::is_using_full_module_graph()) {
return;
@@ -551,8 +551,8 @@ void AOTModules::remove_unshareable_info() {
_archived_javabase_moduleEntry = ArchiveBuilder::current()->get_buffered_addr(ModuleEntryTable::javabase_moduleEntry());
- _platform_loader_root_index = HeapShared::append_root(SystemDictionary::java_platform_loader());
- _system_loader_root_index = HeapShared::append_root(SystemDictionary::java_system_loader());
+ _platform_loader_root_index = AOTHeap::append_root(SystemDictionary::java_platform_loader());
+ _system_loader_root_index = AOTHeap::append_root(SystemDictionary::java_system_loader());
}
void AOTModules::serialize(SerializeClosure* f) {
@@ -579,9 +579,9 @@ ModuleEntry* AOTModules::archived_unnamed_module(ClassLoaderData* loader_data) {
precond(_platform_loader_root_index >= 0);
precond(_system_loader_root_index >= 0);
- if (loader_data->class_loader() == HeapShared::get_root(_platform_loader_root_index)) {
+ if (loader_data->class_loader() == AOTHeap::get_root(_platform_loader_root_index)) {
archived_module = _archived_platform_loader_data.unnamed_module();
- } else if (loader_data->class_loader() == HeapShared::get_root(_system_loader_root_index)) {
+ } else if (loader_data->class_loader() == AOTHeap::get_root(_system_loader_root_index)) {
archived_module = _archived_system_loader_data.unnamed_module();
}
}
@@ -595,8 +595,8 @@ void AOTModules::clear_archived_oops() {
_archived_platform_loader_data.clear_archived_oops();
_archived_system_loader_data.clear_archived_oops();
if (_platform_loader_root_index >= 0) {
- HeapShared::clear_root(_platform_loader_root_index);
- HeapShared::clear_root(_system_loader_root_index);
+ AOTHeap::clear_root(_platform_loader_root_index);
+ AOTHeap::clear_root(_system_loader_root_index);
}
}
@@ -634,8 +634,8 @@ void AOTModules::restore_archived_modules_for_preloading_classes(JavaThread* cur
precond(_platform_loader_root_index >= 0);
precond(_system_loader_root_index >= 0);
- Handle h_platform_loader(current, HeapShared::get_root(_platform_loader_root_index));
- Handle h_system_loader(current, HeapShared::get_root(_system_loader_root_index));
+ Handle h_platform_loader(current, AOTHeap::get_root(_platform_loader_root_index));
+ Handle h_system_loader(current, AOTHeap::get_root(_system_loader_root_index));
init_archived_modules(current, h_platform_loader, h_system_loader);
}
diff --git a/src/hotspot/share/cds/aotOopChecker.cpp b/src/hotspot/share/cds/aotOopChecker.cpp
index 3c0a142a059f..befc341f7f7e 100644
--- a/src/hotspot/share/cds/aotOopChecker.cpp
+++ b/src/hotspot/share/cds/aotOopChecker.cpp
@@ -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
@@ -22,9 +22,9 @@
*
*/
+#include "cds/aotHeap.hpp"
#include "cds/aotMetaspace.hpp"
#include "cds/aotOopChecker.hpp"
-#include "cds/heapShared.hpp"
#include "classfile/javaClasses.hpp"
#include "classfile/symbolTable.hpp"
#include "classfile/vmClasses.hpp"
@@ -71,7 +71,7 @@ void AOTOopChecker::check(oop obj) {
ResourceMark rm;
log_error(aot)("Must cache only URLs with jrt/file protocols but got: %s",
java_lang_String::as_quoted_ascii(protocol));
- HeapShared::debug_trace();
+ AOTHeap::debug_trace();
AOTMetaspace::unrecoverable_writing_error();
}
}
diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp
index 9af1e836391f..082c116edb91 100644
--- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp
+++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp
@@ -22,8 +22,8 @@
*
*/
+#include "cds/aotHeap.hpp"
#include "cds/aotReferenceObjSupport.hpp"
-#include "cds/heapShared.hpp"
#include "classfile/javaClasses.hpp"
#include "classfile/symbolTable.hpp"
#include "classfile/systemDictionary.hpp"
@@ -96,7 +96,7 @@ class KeepAliveObjectsTable : public HashTable {};
+ AOTHeap::oop_address_hash> {};
static KeepAliveObjectsTable* _keep_alive_objs_table;
static OopHandle _keep_alive_objs_array;
@@ -226,7 +226,7 @@ bool AOTReferenceObjSupport::check_if_ref_obj(oop obj) {
PTR_FORMAT ")", p2i(_null_queue.resolve()));
log_error(aot, heap)("%s", (referent == nullptr) ?
"referent cannot be null" : "referent is not registered with CDS.keepAlive()");
- HeapShared::debug_trace();
+ AOTHeap::debug_trace();
AOTMetaspace::unrecoverable_writing_error();
}
diff --git a/src/hotspot/share/cds/aotStreamedHeap.hpp b/src/hotspot/share/cds/aotStreamedHeap.hpp
index f06b1bcb4c62..1f6cb7c16863 100644
--- a/src/hotspot/share/cds/aotStreamedHeap.hpp
+++ b/src/hotspot/share/cds/aotStreamedHeap.hpp
@@ -58,7 +58,7 @@ class AOTStreamedHeapHeader {
class AOTStreamedHeapInfo {
MemRegion _buffer_region; // Contains the archived objects to be written into the CDS archive.
CHeapBitMap _oopmap;
- size_t _roots_offset; // Offset of the HeapShared::roots() object, from the bottom
+ size_t _roots_offset; // Offset of the AOTHeap::roots() object, from the bottom
// of the archived heap objects, in bytes.
size_t _num_roots;
diff --git a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp
index 528c0f149793..012dc9eccdab 100644
--- a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp
+++ b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp
@@ -22,13 +22,13 @@
*
*/
+#include "cds/aotHeap.inline.hpp"
#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/javaClasses.inline.hpp"
#include "classfile/stringTable.hpp"
#include "classfile/vmClasses.hpp"
@@ -329,7 +329,7 @@ void AOTStreamedHeapLoader::copy_object_impl(oopDesc* archive_object,
// A concurrent GC thread might racingly read the object payload after GC is enabled.
InPlaceLinkingOopClosure cl(heap_object, linker);
heap_object->oop_iterate(&cl);
- HeapShared::remap_loaded_metadata(heap_object);
+ AOTHeap::remap_loaded_metadata(heap_object);
return;
}
@@ -353,7 +353,7 @@ void AOTStreamedHeapLoader::copy_object_impl(oopDesc* archive_object,
// shady metadata fields from the archive that do not point at real metadata.
// We deal with this by explicitly reading the requested address from the
// archive and fixing it to real Metadata before writing it into the heap object.
- HeapShared::do_metadata_offsets(heap_object, [&](int metadata_offset) {
+ AOTHeap::do_metadata_offsets(heap_object, [&](int metadata_offset) {
BitMap::idx_t metadata_field_idx = header_bit + (size_t)metadata_offset / sizeof(RawElementT);
BitMap::idx_t skip = word_scale;
assert(metadata_field_idx >= start_bit && metadata_field_idx + skip <= end_bit,
diff --git a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp
index 007a0ae4c6dc..3781aded1458 100644
--- a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp
+++ b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp
@@ -22,12 +22,12 @@
*
*/
+#include "cds/aotHeap.inline.hpp"
#include "cds/aotModules.hpp"
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/aotStreamedHeapWriter.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/filemap.hpp"
-#include "cds/heapShared.inline.hpp"
#include "cds/regeneratedClasses.hpp"
#include "classfile/modules.hpp"
#include "classfile/stringTable.hpp"
@@ -105,7 +105,7 @@ class FollowOopIterateClosure: public BasicOopIterateClosure {
private:
template void do_oop_work(T *p) {
size_t field_offset = pointer_delta(p, _src_obj, sizeof(char));
- oop obj = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p));
+ oop obj = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p));
if (obj != nullptr) {
_dfs_stack->push(obj);
}
@@ -127,7 +127,6 @@ void AOTStreamedHeapWriter::order_source_objs(GrowableArrayCHeaplength(); ++i) {
oop obj = _source_objs->at(i);
_dfs_order_table->put(cast_from_oop(obj), -1);
- _dfs_order_table->maybe_grow();
}
int dfs_order = 0;
@@ -152,7 +151,6 @@ void AOTStreamedHeapWriter::order_source_objs(GrowableArrayCHeapput(cast_from_oop(obj), ++dfs_order);
- _dfs_order_table->maybe_grow();
FollowOopIterateClosure cl(&dfs_stack, obj, AOTReferenceObjSupport::check_if_ref_obj(obj));
obj->oop_iterate(&cl);
@@ -255,14 +253,13 @@ static BitMap::idx_t bit_idx_for_buffer_offset(size_t buffer_offset) {
void AOTStreamedHeapWriter::copy_source_objs_to_buffer(GrowableArrayCHeap* roots) {
for (int i = 0; i < _source_objs->length(); i++) {
oop src_obj = _source_objs->at(i);
- HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj);
+ AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj);
assert(info != nullptr, "must be");
size_t buffer_offset = copy_one_source_obj_to_buffer(src_obj);
info->set_buffer_offset(buffer_offset);
OopHandle handle(Universe::vm_global(), src_obj);
_buffer_offset_to_source_obj_table->put_when_absent(buffer_offset, handle);
- _buffer_offset_to_source_obj_table->maybe_grow();
int dfs_order = i + 1;
_dfs_to_archive_object_table[dfs_order] = buffer_offset;
@@ -309,7 +306,7 @@ size_t AOTStreamedHeapWriter::copy_one_source_obj_to_buffer(oop src_obj) {
ensure_buffer_space(new_used);
- if (HeapShared::is_interned_string(src_obj)) {
+ if (AOTHeap::is_interned_string(src_obj)) {
java_lang_String::hash_code(src_obj); // Sets the hash code field(s)
java_lang_String::set_deduplication_forbidden(src_obj); // Allows faster interning at runtime
assert(java_lang_String::hash_is_set(src_obj), "hash must be set");
@@ -385,7 +382,7 @@ void AOTStreamedHeapWriter::update_header_for_buffered_addr(address buffered_add
mw = mw.copy_set_hash(src_hash);
}
- if (HeapShared::is_interned_string(src_obj)) {
+ if (AOTHeap::is_interned_string(src_obj)) {
// Mark the mark word of interned string so the loader knows to link these to
// the string table at runtime.
mw = mw.set_marked();
@@ -419,7 +416,7 @@ class AOTStreamedHeapWriter::EmbeddedOopMapper: public BasicOopIterateClosure {
template
void do_oop_work(T *p) {
size_t field_offset = pointer_delta(p, _src_obj, sizeof(char));
- oop obj = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p));
+ oop obj = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p));
AOTStreamedHeapWriter::map_oop_field_in_buffer(obj, (T*)(_buffered_obj + field_offset), _oopmap);
}
};
@@ -443,7 +440,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) {
for (int i = 0; i < _source_objs->length(); i++) {
oop src_obj = _source_objs->at(i);
- HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj);
+ AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj);
assert(info != nullptr, "must be");
address buffered_obj = offset_to_buffered_address(info->buffer_offset());
@@ -451,7 +448,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) {
EmbeddedOopMapper mapper(src_obj, buffered_obj, heap_info->oopmap());
src_obj->oop_iterate(&mapper);
- HeapShared::remap_dumped_metadata(src_obj, buffered_obj);
+ AOTHeap::remap_dumped_metadata(src_obj, buffered_obj);
};
size_t total_bytes = (size_t)_buffer->length();
@@ -459,7 +456,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) {
}
size_t AOTStreamedHeapWriter::source_obj_to_buffered_offset(oop src_obj) {
- HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj);
+ AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj);
return p->buffer_offset();
}
@@ -489,7 +486,7 @@ void AOTStreamedHeapWriter::populate_archive_heap_info(AOTStreamedHeapInfo* info
info->set_buffer_region(MemRegion(offset_to_buffered_address(0),
offset_to_buffered_address(_buffer_used)));
info->set_roots_offset(_roots_offset);
- info->set_num_roots((size_t)HeapShared::pending_roots()->length());
+ info->set_num_roots((size_t)AOTHeap::pending_roots()->length());
info->set_forwarding_offset(_forwarding_offset);
info->set_root_highest_object_index_table_offset(_root_highest_object_index_table_offset);
info->set_num_archived_objects((size_t)_source_objs->length());
diff --git a/src/hotspot/share/cds/aotStreamedHeapWriter.hpp b/src/hotspot/share/cds/aotStreamedHeapWriter.hpp
index c3cc9f2c0921..9690d9d1d283 100644
--- a/src/hotspot/share/cds/aotStreamedHeapWriter.hpp
+++ b/src/hotspot/share/cds/aotStreamedHeapWriter.hpp
@@ -26,7 +26,6 @@
#define SHARE_CDS_AOTSTREAMEDHEAPWRITER_HPP
#include "cds/aotMapLogger.hpp"
-#include "cds/heapShared.hpp"
#include "memory/allocation.hpp"
#include "memory/allStatic.hpp"
#include "oops/oopHandle.hpp"
diff --git a/src/hotspot/share/cds/aotThread.cpp b/src/hotspot/share/cds/aotThread.cpp
index f3382a6f7e4c..7f9304dd5554 100644
--- a/src/hotspot/share/cds/aotThread.cpp
+++ b/src/hotspot/share/cds/aotThread.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,7 +24,6 @@
#include "cds/aotStreamedHeapLoader.hpp"
#include "cds/aotThread.hpp"
-#include "cds/heapShared.hpp"
#include "classfile/javaClasses.hpp"
#include "classfile/javaThreadStatus.hpp"
#include "classfile/vmClasses.hpp"
diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp
index 57ab0b316639..0cbc08319312 100644
--- a/src/hotspot/share/cds/archiveBuilder.cpp
+++ b/src/hotspot/share/cds/archiveBuilder.cpp
@@ -25,6 +25,7 @@
#include "cds/aotArtifactFinder.hpp"
#include "cds/aotClassLinker.hpp"
#include "cds/aotCompressedPointers.hpp"
+#include "cds/aotHeap.hpp"
#include "cds/aotLogging.hpp"
#include "cds/aotMapLogger.hpp"
#include "cds/aotMetaspace.hpp"
@@ -35,7 +36,6 @@
#include "cds/cppVtables.hpp"
#include "cds/dumpAllocStats.hpp"
#include "cds/dynamicArchive.hpp"
-#include "cds/heapShared.hpp"
#include "cds/regeneratedClasses.hpp"
#include "classfile/classLoader.hpp"
#include "classfile/javaClasses.hpp"
@@ -437,11 +437,6 @@ bool ArchiveBuilder::gather_one_source_obj(MetaspaceClosure::Ref* ref, bool read
SourceObjInfo src_info(ref, read_only, follow_mode);
bool created;
SourceObjInfo* p = _src_obj_table.put_if_absent(src_obj, src_info, &created);
- if (created) {
- if (_src_obj_table.maybe_grow()) {
- log_info(aot, hashtables)("Expanded _src_obj_table table to %d", _src_obj_table.table_size());
- }
- }
#ifdef ASSERT
if (ref->type() == MetaspaceClosureType::MethodType) {
@@ -659,9 +654,6 @@ void ArchiveBuilder::make_shallow_copy(DumpRegion *dump_region, SourceObjInfo* s
bool created;
_buffered_to_src_table.put_if_absent((address)dest, src, &created);
assert(created, "must be");
- if (_buffered_to_src_table.maybe_grow()) {
- log_info(aot, hashtables)("Expanded _buffered_to_src_table table to %d", _buffered_to_src_table.table_size());
- }
}
intptr_t* archived_vtable = CppVtables::get_archived_vtable(src_info->type(), (address)dest);
@@ -852,10 +844,10 @@ void ArchiveBuilder::make_klasses_shareable() {
assert(0, "shouldn't happen");
}
if (CDSConfig::is_dumping_method_handles()) {
- assert(HeapShared::is_archivable_hidden_klass(ik), "sanity");
+ assert(AOTHeap::is_archivable_hidden_klass(ik), "sanity");
} else {
// Legacy CDS support for lambda proxies
- CDS_JAVA_HEAP_ONLY(assert(HeapShared::is_lambda_proxy_klass(ik), "sanity");)
+ CDS_JAVA_HEAP_ONLY(assert(AOTHeap::is_lambda_proxy_klass(ik), "sanity");)
}
} else if (ik->defined_by_boot_loader()) {
type = "boot";
@@ -1178,7 +1170,7 @@ void ArchiveBuilder::write_archive(FileMapInfo* mapinfo, AOTMappedHeapInfo* mapp
if (log_is_enabled(Info, aot, map)) {
AOTMapLogger::dumptime_log(this, mapinfo, mapped_heap_info, streamed_heap_info, bitmap, bitmap_size_in_bytes);
}
- CDS_JAVA_HEAP_ONLY(HeapShared::destroy_archived_object_cache());
+ CDS_JAVA_HEAP_ONLY(AOTHeap::destroy_archived_object_cache());
FREE_C_HEAP_ARRAY(bitmap);
}
diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp
index 3d4d36ab486b..9dbd8ce1cfa7 100644
--- a/src/hotspot/share/cds/archiveUtils.cpp
+++ b/src/hotspot/share/cds/archiveUtils.cpp
@@ -33,7 +33,6 @@
#include "cds/dumpAllocStats.hpp"
#include "cds/dynamicArchive.hpp"
#include "cds/filemap.hpp"
-#include "cds/heapShared.hpp"
#include "cds/lambdaProxyClassDictionary.hpp"
#include "classfile/systemDictionaryShared.hpp"
#include "classfile/vmClasses.hpp"
diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp
index 278c7397f12b..4faaa6329874 100644
--- a/src/hotspot/share/cds/cdsConfig.cpp
+++ b/src/hotspot/share/cds/cdsConfig.cpp
@@ -22,6 +22,7 @@
*
*/
+#include "cds/aotHeap.hpp"
#include "cds/aotLogging.hpp"
#include "cds/aotMapLogger.hpp"
#include "cds/aotMetaspace.hpp"
@@ -30,7 +31,6 @@
#include "cds/cdsConfig.hpp"
#include "cds/classListWriter.hpp"
#include "cds/filemap.hpp"
-#include "cds/heapShared.inline.hpp"
#include "classfile/moduleEntry.hpp"
#include "code/aotCodeCache.hpp"
#include "compiler/compilerDefinitions.inline.hpp"
@@ -88,8 +88,8 @@ void CDSConfig::ergo_initialize() {
if (is_dumping_static_archive() && !is_dumping_final_static_archive()) {
// Note: -Xshare and -XX:AOTMode flags are mutually exclusive.
- // - Classic workflow: -Xshare:on and -Xshare:dump cannot take effect at the same time.
- // - JEP 483 workflow: -XX:AOTMode:record and -XX:AOTMode=on cannot take effect at the same time.
+ // - Classic CDS workflow: -Xshare:on and -Xshare:dump cannot take effect at the same time.
+ // - AOT workflow: -XX:AOTMode:record and -XX:AOTMode=required cannot take effect at the same time.
// So we can never come to here with RequireSharedSpaces==true.
assert(!RequireSharedSpaces, "sanity");
@@ -122,7 +122,7 @@ void CDSConfig::ergo_initialize() {
precond(allow_only_single_java_thread());
// The AttachListenerThread may execute Java code or load new classes. It might see
- // unexpected results after HeapShared::prepare_for_archiving().
+ // unexpected results after AOTHeap::prepare_for_archiving().
//
// We disable all new incoming attach requests, so you can't use jcmd, etc, on this JVM.
// Since we are not running any application code in this JVM and only executed a very
@@ -515,6 +515,11 @@ void CDSConfig::check_aotmode_off() {
}
void CDSConfig::check_aotmode_auto_or_on() {
+ if (AOTMode != nullptr && strcmp(AOTMode, "on") == 0) {
+ warning("Option -XX:AOTMode-on was deprecated in version 28 and will likely be "
+ "removed in a future release. Use option -XX:AOTMode=required instead.");
+ }
+
if (!FLAG_IS_DEFAULT(AOTConfiguration)) {
vm_exit_during_initialization(err_msg("AOTConfiguration can only be used with when AOTMode is record or create (selected AOTMode = %s)",
FLAG_IS_DEFAULT(AOTMode) ? "auto" : AOTMode));
@@ -1020,7 +1025,7 @@ bool CDSConfig::is_dumping_heap() {
}
bool CDSConfig::is_loading_heap() {
- return HeapShared::is_archived_heap_in_use();
+ return AOTHeap::is_archived_heap_in_use();
}
bool CDSConfig::is_dumping_klass_subgraphs() {
@@ -1068,7 +1073,7 @@ bool CDSConfig::is_using_full_module_graph() {
return false;
}
- if (is_using_archive() && HeapShared::can_use_archived_heap()) {
+ if (is_using_archive() && AOTHeap::can_use_archived_heap()) {
// Classes used by the archived full module graph are loaded in JVMTI early phase.
assert(!(JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()),
"CDS should be disabled if early class hooks are enabled");
diff --git a/src/hotspot/share/cds/cdsEnumKlass.cpp b/src/hotspot/share/cds/cdsEnumKlass.cpp
index 7684e33e7b8c..4fb82b67bc40 100644
--- a/src/hotspot/share/cds/cdsEnumKlass.cpp
+++ b/src/hotspot/share/cds/cdsEnumKlass.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, 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
@@ -22,8 +22,8 @@
*
*/
+#include "cds/aotHeap.hpp"
#include "cds/cdsEnumKlass.hpp"
-#include "cds/heapShared.inline.hpp"
#include "classfile/systemDictionaryShared.hpp"
#include "classfile/vmClasses.hpp"
#include "memory/resourceArea.hpp"
@@ -58,7 +58,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) {
// ModuleLayer::EMPTY_LAYER, for example).
//
// Instead, we archive all static field of such Enum classes. At runtime,
-// HeapShared::initialize_enum_klass() skips the method and instead pulls
+// AOTHeap::initialize_enum_klass() skips the method and instead pulls
// the static fields out of the archived heap.
void CDSEnumKlass::handle_enum_obj(int level,
KlassSubGraphInfo* subgraph_info,
@@ -101,9 +101,9 @@ void CDSEnumKlass::archive_static_field(int level, KlassSubGraphInfo* subgraph_i
guarantee(false, "static field %s::%s is of the wrong type",
ik->external_name(), fd.name()->as_C_string());
}
- bool success = HeapShared::archive_reachable_objects_from(level, subgraph_info, oop_field);
+ bool success = AOTHeap::archive_reachable_objects_from(level, subgraph_info, oop_field);
assert(success, "VM should have exited with unarchivable objects for _level > 1");
- int root_index = HeapShared::append_root(oop_field);
+ int root_index = AOTHeap::append_root(oop_field);
log_info(aot, heap)("Archived enum obj @%d %s::%s (" INTPTR_FORMAT ")",
root_index, ik->external_name(), fd.name()->as_C_string(),
p2i((oopDesc*)oop_field));
@@ -111,7 +111,7 @@ void CDSEnumKlass::archive_static_field(int level, KlassSubGraphInfo* subgraph_i
}
bool CDSEnumKlass::initialize_enum_klass(InstanceKlass* k, TRAPS) {
- if (!HeapShared::is_archived_heap_in_use()) {
+ if (!AOTHeap::is_archived_heap_in_use()) {
return false;
}
@@ -129,7 +129,7 @@ bool CDSEnumKlass::initialize_enum_klass(InstanceKlass* k, TRAPS) {
int root_index = info->enum_klass_static_field_root_index_at(i++);
fieldDescriptor& fd = fs.field_descriptor();
assert(fd.field_type() == T_OBJECT || fd.field_type() == T_ARRAY, "must be");
- oop root_object = HeapShared::get_root(root_index, /*clear=*/true);
+ oop root_object = AOTHeap::get_root(root_index, /*clear=*/true);
k->java_mirror()->obj_field_put(fd.offset(), root_object);
}
}
diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp
index b0aaede89c1f..ee4ccea84a9c 100644
--- a/src/hotspot/share/cds/cdsHeapVerifier.cpp
+++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp
@@ -375,7 +375,7 @@ void CDSHeapVerifier::do_klass(Klass* k) {
if (k->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(k);
- if (HeapShared::is_subgraph_root_class(ik)) {
+ if (AOTHeap::is_subgraph_root_class(ik)) {
// ik is inside one of the ArchivableStaticFieldInfo tables
// in heapShared.cpp. We assume such classes are programmed to
// update their static fields correctly at runtime.
@@ -400,11 +400,11 @@ void CDSHeapVerifier::add_static_obj_field(InstanceKlass* ik, oop field, Symbol*
// This function is called once for every archived heap object. Warn if this object is referenced by
// a static field of a class that's not aot-initialized.
-inline bool CDSHeapVerifier::do_entry(OopHandle& orig_obj_handle, HeapShared::CachedOopInfo& value) {
+inline bool CDSHeapVerifier::do_entry(OopHandle& orig_obj_handle, AOTHeap::CachedOopInfo& value) {
oop orig_obj = orig_obj_handle.resolve();
_archived_objs++;
- if (java_lang_String::is_instance(orig_obj) && HeapShared::is_dumped_interned_string(orig_obj)) {
+ if (java_lang_String::is_instance(orig_obj) && AOTHeap::is_dumped_interned_string(orig_obj)) {
// It's quite often for static fields to have interned strings. These are most likely not
// problematic (and are hard to filter). So we will ignore them.
return true;
@@ -451,7 +451,7 @@ class CDSHeapVerifier::TraceFields : public FieldClosure {
// Call this function (from gdb, etc) if you want to know why an object is archived.
void CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj) {
- HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(orig_obj);
+ AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(orig_obj);
if (info != nullptr) {
trace_to_root(st, orig_obj, nullptr, info);
} else {
@@ -482,10 +482,10 @@ const char* static_field_name(oop mirror, oop field) {
return "";
}
-int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* info) {
+int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_field, AOTHeap::CachedOopInfo* info) {
int level = 0;
if (info->orig_referrer() != nullptr) {
- HeapShared::CachedOopInfo* ref = HeapShared::get_cached_oop_info(info->orig_referrer());
+ AOTHeap::CachedOopInfo* ref = AOTHeap::get_cached_oop_info(info->orig_referrer());
assert(ref != nullptr, "sanity");
level = trace_to_root(st, info->orig_referrer(), orig_obj, ref) + 1;
} else if (java_lang_String::is_instance(orig_obj)) {
@@ -521,7 +521,7 @@ int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_fiel
void CDSHeapVerifier::verify() {
CDSHeapVerifier verf;
- HeapShared::archived_object_cache()->iterate(&verf);
+ AOTHeap::archived_object_cache()->iterate(&verf);
}
#endif // INCLUDE_CDS_JAVA_HEAP
diff --git a/src/hotspot/share/cds/cdsHeapVerifier.hpp b/src/hotspot/share/cds/cdsHeapVerifier.hpp
index f8e090801bbc..33de9e8dfbaa 100644
--- a/src/hotspot/share/cds/cdsHeapVerifier.hpp
+++ b/src/hotspot/share/cds/cdsHeapVerifier.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -25,7 +25,7 @@
#ifndef SHARE_CDS_CDSHEAPVERIFIER_HPP
#define SHARE_CDS_CDSHEAPVERIFIER_HPP
-#include "cds/heapShared.hpp"
+#include "cds/aotHeap.hpp"
#include "memory/iterator.hpp"
#include "oops/oopHandle.hpp"
#include "utilities/growableArray.hpp"
@@ -53,7 +53,7 @@ class CDSHeapVerifier : public KlassClosure {
15889, // prime number
AnyObj::C_HEAP,
mtClassShared,
- HeapShared::oop_address_hash> _table;
+ AOTHeap::oop_address_hash> _table;
GrowableArray _exclusions;
GrowableArray _shared_secret_accessors;
@@ -88,7 +88,7 @@ class CDSHeapVerifier : public KlassClosure {
return false;
}
- static int trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* p);
+ static int trace_to_root(outputStream* st, oop orig_obj, oop orig_field, AOTHeap::CachedOopInfo* p);
CDSHeapVerifier();
~CDSHeapVerifier();
@@ -99,7 +99,7 @@ class CDSHeapVerifier : public KlassClosure {
virtual void do_klass(Klass* k);
// For HashTable::iterate()
- inline bool do_entry(OopHandle& orig_obj, HeapShared::CachedOopInfo& value);
+ inline bool do_entry(OopHandle& orig_obj, AOTHeap::CachedOopInfo& value);
static void verify();
diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp
index 17405b6c0796..d0898a39b301 100644
--- a/src/hotspot/share/cds/cds_globals.hpp
+++ b/src/hotspot/share/cds/cds_globals.hpp
@@ -105,7 +105,8 @@
\
product(ccstr, AOTMode, nullptr, \
"Specifies how AOTCache should be created or used. Valid values " \
- "are: off, record, create, auto, on; the default is auto") \
+ "are: off, record, create, auto, required, and (deprecated) on; " \
+ "the default is auto") \
constraint(AOTModeConstraintFunc, AtParse) \
\
product(ccstr, AOTConfiguration, nullptr, \
diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp
index 836d018a714c..24cd9454e088 100644
--- a/src/hotspot/share/cds/classListParser.cpp
+++ b/src/hotspot/share/cds/classListParser.cpp
@@ -774,9 +774,6 @@ Klass* ClassListParser::load_current_class(Symbol* class_name_symbol, TRAPS) {
if (!created) {
error("Duplicated ID %d for class %s", id, _class_name);
}
- if (id2klass_table()->maybe_grow()) {
- log_info(aot, hashtables)("Expanded id2klass_table() to %d", id2klass_table()->table_size());
- }
}
return klass;
diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp
index b8adbba22f1f..30afc2c2a5a1 100644
--- a/src/hotspot/share/cds/dynamicArchive.cpp
+++ b/src/hotspot/share/cds/dynamicArchive.cpp
@@ -33,7 +33,6 @@
#include "cds/cds_globals.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/dynamicArchive.hpp"
-#include "cds/heapShared.hpp"
#include "cds/lambdaFormInvokers.hpp"
#include "cds/lambdaProxyClassDictionary.hpp"
#include "cds/regeneratedClasses.hpp"
diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp
index d0788871221d..d0feb197ef28 100644
--- a/src/hotspot/share/cds/filemap.cpp
+++ b/src/hotspot/share/cds/filemap.cpp
@@ -23,6 +23,7 @@
*/
#include "cds/aotClassLocation.hpp"
+#include "cds/aotHeap.inline.hpp"
#include "cds/aotLogging.hpp"
#include "cds/aotMappedHeapLoader.hpp"
#include "cds/aotMappedHeapWriter.hpp"
@@ -33,7 +34,6 @@
#include "cds/cdsConfig.hpp"
#include "cds/dynamicArchive.hpp"
#include "cds/filemap.hpp"
-#include "cds/heapShared.inline.hpp"
#include "classfile/altHashing.hpp"
#include "classfile/classFileStream.hpp"
#include "classfile/classLoader.hpp"
@@ -293,7 +293,7 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment,
_compact_headers = UseCompactObjectHeaders;
#if INCLUDE_CDS_JAVA_HEAP
if (CDSConfig::is_dumping_heap()) {
- _object_streaming_mode = HeapShared::is_writing_streaming_mode();
+ _object_streaming_mode = AOTHeap::is_writing_streaming_mode();
_narrow_oop_mode = AOTMappedHeapWriter::narrow_oop_mode();
_narrow_oop_base = AOTMappedHeapWriter::narrow_oop_base();
_narrow_oop_shift = AOTMappedHeapWriter::narrow_oop_shift();
@@ -875,7 +875,7 @@ size_t FileMapRegion::used_aligned() const {
void FileMapRegion::init(int region_index, size_t mapping_offset, size_t size, bool read_only,
bool allow_exec, int crc) {
- _is_heap_region = HeapShared::is_heap_region(region_index);
+ _is_heap_region = AOTHeap::is_heap_region(region_index);
_is_bitmap_region = (region_index == AOTMetaspace::bm);
_mapping_offset = mapping_offset;
_used = size;
@@ -977,11 +977,11 @@ void FileMapInfo::write_region(int region, char* base, size_t size,
} else if (size == 0) {
// This is an unused region (e.g., a heap region when !INCLUDE_CDS_JAVA_HEAP)
requested_base = nullptr;
- } else if (HeapShared::is_heap_region(region)) {
+ } else if (AOTHeap::is_heap_region(region)) {
assert(CDSConfig::is_dumping_heap(), "sanity");
#if INCLUDE_CDS_JAVA_HEAP
assert(!CDSConfig::is_dumping_dynamic_archive(), "must be");
- if (HeapShared::is_writing_mapping_mode()) {
+ if (AOTHeap::is_writing_mapping_mode()) {
requested_base = (char*)AOTMappedHeapWriter::requested_address();
if (UseCompressedOops) {
mapping_offset = (size_t)((address)requested_base - AOTMappedHeapWriter::narrow_oop_base());
@@ -1055,7 +1055,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap,
if (mapped_heap_info != nullptr && mapped_heap_info->is_used()) {
// Remove leading and trailing zeros
- assert(HeapShared::is_writing_mapping_mode(), "unexpected dumping mode");
+ assert(AOTHeap::is_writing_mapping_mode(), "unexpected dumping mode");
size_t removed_oop_leading_zeros = remove_bitmap_zeros(mapped_heap_info->oopmap());
size_t removed_ptr_leading_zeros = remove_bitmap_zeros(mapped_heap_info->ptrmap());
mapped_heap_info->set_oopmap_start_pos(removed_oop_leading_zeros);
@@ -1064,7 +1064,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap,
size_in_bytes += mapped_heap_info->oopmap()->size_in_bytes();
size_in_bytes += mapped_heap_info->ptrmap()->size_in_bytes();
} else if (streamed_heap_info != nullptr && streamed_heap_info->is_used()) {
- assert(HeapShared::is_writing_streaming_mode(), "unexpected dumping mode");
+ assert(AOTHeap::is_writing_streaming_mode(), "unexpected dumping mode");
size_in_bytes += streamed_heap_info->oopmap()->size_in_bytes();
}
@@ -1090,7 +1090,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap,
}
if (mapped_heap_info != nullptr && mapped_heap_info->is_used()) {
- assert(HeapShared::is_writing_mapping_mode(), "unexpected dumping mode");
+ assert(AOTHeap::is_writing_mapping_mode(), "unexpected dumping mode");
FileMapRegion* r = region_at(AOTMetaspace::hp);
r->init_oopmap(written, mapped_heap_info->oopmap()->size());
@@ -1099,7 +1099,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap,
r->init_ptrmap(written, mapped_heap_info->ptrmap()->size());
written = write_bitmap(mapped_heap_info->ptrmap(), buffer, written);
} else if (streamed_heap_info != nullptr && streamed_heap_info->is_used()) {
- assert(HeapShared::is_writing_streaming_mode(), "unexpected dumping mode");
+ assert(AOTHeap::is_writing_streaming_mode(), "unexpected dumping mode");
FileMapRegion* r = region_at(AOTMetaspace::hp);
r->init_oopmap(written, streamed_heap_info->oopmap()->size());
@@ -1320,7 +1320,7 @@ bool FileMapInfo::read_region(int i, char* base, size_t size, bool do_commit) {
}
MapArchiveResult FileMapInfo::map_region(int i, intx addr_delta, char* mapped_base_address, ReservedSpace rs) {
- assert(!HeapShared::is_heap_region(i), "sanity");
+ assert(!AOTHeap::is_heap_region(i), "sanity");
FileMapRegion* r = region_at(i);
size_t size = r->used_aligned();
char *requested_addr = mapped_base_address + r->mapping_offset();
@@ -1660,7 +1660,7 @@ void FileMapInfo::stream_heap_region() {
assert(object_streaming_mode(), "This should only be done for the streaming approach");
if (map_auxiliary_region(AOTMetaspace::hp, /*readonly=*/true) != nullptr) {
- HeapShared::initialize_streaming();
+ AOTHeap::initialize_streaming();
} else {
on_heap_region_loading_error();
}
@@ -1723,11 +1723,11 @@ bool FileMapInfo::can_use_heap_region() {
// If JvmtiExport::has_early_class_hook_env() is false, the classes of some objects
// in the archived subgraphs may be replaced by the ClassFileLoadHook. But that's OK
// because we won't install an archived object subgraph if the klass of any of the
- // referenced objects are replaced. See HeapShared::initialize_from_archived_subgraph().
+ // referenced objects are replaced. See AOTHeap::initialize_from_archived_subgraph().
}
// We pre-compute narrow Klass IDs with the runtime mapping start intended to be the base, and a shift of
- // HeapShared::precomputed_narrow_klass_shift. We enforce this encoding at runtime (see
+ // AOTHeap::precomputed_narrow_klass_shift. We enforce this encoding at runtime (see
// CompressedKlassPointers::initialize_for_given_encoding()). Therefore, the following assertions must
// hold:
address archive_narrow_klass_base = (address)header()->mapped_base_address();
diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp
index 5987e6d52d3c..a92397db85f3 100644
--- a/src/hotspot/share/cds/filemap.hpp
+++ b/src/hotspot/share/cds/filemap.hpp
@@ -30,7 +30,6 @@
#include "cds/aotMetaspace.hpp"
#include "cds/aotStreamedHeap.hpp"
#include "cds/archiveUtils.hpp"
-#include "cds/heapShared.hpp"
#include "include/cds.h"
#include "logging/logLevel.hpp"
#include "memory/allocation.hpp"
diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp
index bd0e723c0c27..4b649c5dd482 100644
--- a/src/hotspot/share/ci/ciEnv.cpp
+++ b/src/hotspot/share/ci/ciEnv.cpp
@@ -24,7 +24,7 @@
#include "cds/aotCacheAccess.hpp"
#include "cds/aotConstantPoolResolver.hpp"
-#include "cds/heapShared.hpp"
+#include "cds/aotHeap.hpp"
#include "ci/ciConstant.hpp"
#include "ci/ciEnv.hpp"
#include "ci/ciField.hpp"
@@ -1275,7 +1275,7 @@ void ciEnv::register_method(ciMethod* target,
assert(!method->is_synchronized() || nm->has_monitors(), "");
#if INCLUDE_CDS
- if (task()->is_aot_compile()) {
+ if (is_aot_compile()) {
#ifdef ASSERT
LogStreamHandle(Trace, aot, codecache, nmethod) log;
if (log.is_enabled()) {
@@ -2009,7 +2009,7 @@ InstanceKlass::ClassState ciEnv::compute_init_state_for_aot_compile(InstanceKlas
// 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)) {
+ if (AOTHeap::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;
}
diff --git a/src/hotspot/share/ci/ciValueKlass.cpp b/src/hotspot/share/ci/ciValueKlass.cpp
index 0655e18e21e4..df27b679b1a6 100644
--- a/src/hotspot/share/ci/ciValueKlass.cpp
+++ b/src/hotspot/share/ci/ciValueKlass.cpp
@@ -51,10 +51,9 @@ bool ciValueKlass::can_be_returned_as_fields() const {
}
bool ciValueKlass::is_empty() {
- // Do not use ValueKlass::is_empty_value_type here because it does
- // consider the container empty even if fields of empty value types
- // are not flat
- return nof_declared_nonstatic_fields() == 0;
+ // Flat fields are recursively expanded, nullable flat fields include
+ // their null marker and non-flat fields remain as oops.
+ return nof_nonstatic_fields() == 0;
}
bool ciValueKlass::is_cloneable() const {
diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp
index 00a6442d13c1..fea2a8f9a26f 100644
--- a/src/hotspot/share/classfile/classLoader.cpp
+++ b/src/hotspot/share/classfile/classLoader.cpp
@@ -23,11 +23,11 @@
*/
#include "cds/aotClassLocation.hpp"
+#include "cds/aotHeap.hpp"
#include "cds/cds_globals.hpp"
#include "cds/cdsConfig.hpp"
#include "cds/cdsProtectionDomain.hpp"
#include "cds/dynamicArchive.hpp"
-#include "cds/heapShared.hpp"
#include "classfile/classFileStream.hpp"
#include "classfile/classLoader.inline.hpp"
#include "classfile/classLoaderData.inline.hpp"
@@ -1440,10 +1440,10 @@ void ClassLoader::record_hidden_class(InstanceKlass* ik) {
return;
}
- if (HeapShared::is_lambda_proxy_klass(ik)) {
+ if (AOTHeap::is_lambda_proxy_klass(ik)) {
InstanceKlass* nest_host = ik->nest_host_not_null();
ik->set_shared_classpath_index(nest_host->shared_classpath_index());
- } else if (HeapShared::is_lambda_form_klass(ik)) {
+ } else if (AOTHeap::is_lambda_form_klass(ik)) {
ik->set_shared_classpath_index(0);
} else {
// Generated invoker classes.
diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp
index e901e3142be9..51f02de3e755 100644
--- a/src/hotspot/share/classfile/classLoaderData.cpp
+++ b/src/hotspot/share/classfile/classLoaderData.cpp
@@ -46,7 +46,7 @@
// The bootstrap loader (represented by null) also has a ClassLoaderData,
// the singleton class the_null_class_loader_data().
-#include "cds/heapShared.hpp"
+#include "cds/aotHeap.hpp"
#include "classfile/classLoaderData.inline.hpp"
#include "classfile/classLoaderDataGraph.inline.hpp"
#include "classfile/dictionary.hpp"
@@ -904,7 +904,7 @@ void ClassLoaderData::free_deallocate_list() {
if (m->is_method()) {
MetadataFactory::free_metadata(this, (Method*)m);
} else if (m->is_constantPool()) {
- HeapShared::remove_scratch_resolved_references((ConstantPool*)m);
+ AOTHeap::remove_scratch_resolved_references((ConstantPool*)m);
MetadataFactory::free_metadata(this, (ConstantPool*)m);
} else if (m->is_klass()) {
JFR_ONLY(Jfr::on_deallocation(static_cast(m));)
diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp
index d55adf8093f3..643f6f5767f5 100644
--- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp
+++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp
@@ -187,19 +187,14 @@ bool LayoutRawBlock::fit(int size, int alignment) {
FieldGroup::FieldGroup(int contended_group) :
_next(nullptr),
- _small_primitive_fields(nullptr),
- _big_primitive_fields(nullptr),
+ _non_oop_fields(nullptr),
_oop_fields(nullptr),
_contended_group(contended_group) {} // -1 means no contended group, 0 means default contended group
void FieldGroup::add_primitive_field(int idx, BasicType type) {
int size = type2aelembytes(type);
LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::REGULAR, size, size /* alignment == size for primitive types */);
- if (size >= heapOopSize) {
- add_to_big_primitive_list(block);
- } else {
- add_to_small_primitive_list(block);
- }
+ add_to_non_oop_list(block);
}
void FieldGroup::add_oop_field(int idx) {
@@ -218,35 +213,20 @@ void FieldGroup::add_flat_field(int idx, ValueKlass* vk, LayoutKind lk) {
LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::FLAT, size, alignment);
block->set_value_klass(vk);
block->set_layout_kind(lk);
- if (block->size() >= heapOopSize) {
- add_to_big_primitive_list(block);
- } else {
- assert(!vk->contains_oops(), "Size of Value klass with oops should be >= heapOopSize");
- add_to_small_primitive_list(block);
- }
+ add_to_non_oop_list(block);
}
void FieldGroup::sort_by_size() {
- if (_small_primitive_fields != nullptr) {
- _small_primitive_fields->sort(LayoutRawBlock::compare_size_inverted);
- }
- if (_big_primitive_fields != nullptr) {
- _big_primitive_fields->sort(LayoutRawBlock::compare_size_inverted);
+ if (_non_oop_fields != nullptr) {
+ _non_oop_fields->sort(LayoutRawBlock::compare_size_inverted);
}
}
-void FieldGroup::add_to_small_primitive_list(LayoutRawBlock* block) {
- if (_small_primitive_fields == nullptr) {
- _small_primitive_fields = new GrowableArray(INITIAL_LIST_SIZE);
+void FieldGroup::add_to_non_oop_list(LayoutRawBlock* block) {
+ if (_non_oop_fields == nullptr) {
+ _non_oop_fields = new GrowableArray(INITIAL_LIST_SIZE);
}
- _small_primitive_fields->append(block);
-}
-
-void FieldGroup::add_to_big_primitive_list(LayoutRawBlock* block) {
- if (_big_primitive_fields == nullptr) {
- _big_primitive_fields = new GrowableArray(INITIAL_LIST_SIZE);
- }
- _big_primitive_fields->append(block);
+ _non_oop_fields->append(block);
}
FieldLayout::FieldLayout(GrowableArray* field_info, Array* value_field_info_array, ConstantPool* cp) :
@@ -310,18 +290,41 @@ LayoutRawBlock* FieldLayout::first_field_block() {
return block;
}
-// Insert a set of fields into a layout.
-// For each field, search for an empty slot able to fit the field
+// Return the first non-oop field smaller than a heap oop.
+int FieldGroup::first_small_field_index() const {
+ if (_non_oop_fields == nullptr) {
+ return 0;
+ }
+
+ int index = 0;
+ while (index < _non_oop_fields->length() && _non_oop_fields->at(index)->size() >= heapOopSize) {
+ index++;
+ }
+ return index;
+}
+
+// Insert fields from a list into a layout. add() inserts the whole list,
+// while add_range() inserts a half-open range of the list.
+// For each selected field, search for an empty slot able to fit the field
// (satisfying both size and alignment requirements), if none is found,
// add the field at the end of the layout.
// Fields cannot be inserted before the block specified in the "start" argument
void FieldLayout::add(GrowableArray* list, LayoutRawBlock* start) {
- if (list == nullptr) return;
- if (start == nullptr) start = this->_start;
+ if (list == nullptr) {
+ return;
+ }
+ add_range(list, 0, list->length(), start);
+}
+
+void FieldLayout::add_range(GrowableArray* list, int from, int to, LayoutRawBlock* start) {
+ assert(list != nullptr, "sanity");
+ assert(0 <= from && from <= to && to <= list->length(), "sanity");
+
+ if (start == nullptr) start = _start;
bool last_search_success = false;
int last_size = 0;
int last_alignment = 0;
- for (int i = 0; i < list->length(); i ++) {
+ for (int i = from; i < to; i ++) {
LayoutRawBlock* b = list->at(i);
LayoutRawBlock* cursor = nullptr;
LayoutRawBlock* candidate = nullptr;
@@ -983,14 +986,14 @@ LayoutRawBlock* FieldLayoutBuilder::insert_contended_padding(LayoutRawBlock* slo
// Computation of regular classes layout is an evolution of the previous default layout
// (FieldAllocationStyle 1):
-// - primitive fields (both primitive types and flat value types) are allocated
+// - non-oop fields (both primitive types and flat value types) are allocated
// first (from the biggest to the smallest)
// - oop fields are allocated, either in existing gaps or at the end of
// the layout. We allocate oops in a single block to have a single oop map entry.
// - if the super class ended with an oop, we lead with oops. That will cause the
// trailing oop map entry of the super class and the oop map entry of this class
// to be folded into a single entry later. Correspondingly, if the super class
-// ends with a primitive field, we gain nothing by leading with oops; therefore
+// ends with a non-oop field, we gain nothing by leading with oops; therefore
// we let oop fields trail, thus giving future derived classes the chance to apply
// the same trick.
void FieldLayoutBuilder::compute_regular_layout() {
@@ -1011,11 +1014,9 @@ void FieldLayoutBuilder::compute_regular_layout() {
if (_super_ends_with_oop) {
_layout->add(_root_group->oop_fields());
- _layout->add(_root_group->big_primitive_fields());
- _layout->add(_root_group->small_primitive_fields());
+ _layout->add(_root_group->non_oop_fields());
} else {
- _layout->add(_root_group->big_primitive_fields());
- _layout->add(_root_group->small_primitive_fields());
+ _layout->add(_root_group->non_oop_fields());
_layout->add(_root_group->oop_fields());
}
@@ -1030,8 +1031,7 @@ void FieldLayoutBuilder::compute_regular_layout() {
start = padding;
}
- _layout->add(cg->big_primitive_fields(), start);
- _layout->add(cg->small_primitive_fields(), start);
+ _layout->add(cg->non_oop_fields(), start);
_layout->add(cg->oop_fields(), start);
need_tail_padding = true;
}
@@ -1041,10 +1041,9 @@ void FieldLayoutBuilder::compute_regular_layout() {
insert_contended_padding(_layout->last_block());
}
- // Warning: IntanceMirrorKlass expects static oops to be allocated first
+ // InstanceMirrorKlass expects static oop fields to be allocated first.
_static_layout->add_contiguously(_static_fields->oop_fields());
- _static_layout->add(_static_fields->big_primitive_fields());
- _static_layout->add(_static_fields->small_primitive_fields());
+ _static_layout->add(_static_fields->non_oop_fields());
epilogue();
}
@@ -1056,10 +1055,10 @@ void FieldLayoutBuilder::compute_regular_layout() {
// of value classes is to be embedded into other containers, it is critical
// to keep their size as small as possible. For this reason, the allocation
// strategy is:
-// - big primitive fields (primitive types and flat value types larger
-// than an oop) are allocated first (from the biggest to the smallest)
+// - non-oop fields at least as large as a heap oop are allocated first
+// (from the biggest to the smallest)
// - then oop fields
-// - then small primitive fields (from the biggest to the smallest)
+// - then non-oop fields smaller than a heap oop (from the biggest to the smallest)
//
void FieldLayoutBuilder::compute_value_class_layout() {
@@ -1149,9 +1148,21 @@ void FieldLayoutBuilder::compute_value_class_layout() {
}
}
- _layout->add(_root_group->big_primitive_fields());
+ // The non-oop fields are sorted in decreasing size order. Split the list around
+ // heapOopSize so oop fields can be placed between large and small fields.
+ GrowableArray* non_oop_fields = _root_group->non_oop_fields();
+ int split_index = 0;
+
+ if (non_oop_fields != nullptr) {
+ split_index = _root_group->first_small_field_index();
+ _layout->add_range(non_oop_fields, 0, split_index);
+ }
+
_layout->add(_root_group->oop_fields());
- _layout->add(_root_group->small_primitive_fields());
+
+ if (non_oop_fields != nullptr) {
+ _layout->add_range(non_oop_fields, split_index, non_oop_fields->length());
+ }
LayoutRawBlock* first_field = _layout->first_field_block();
if (first_field != nullptr) {
@@ -1309,10 +1320,9 @@ void FieldLayoutBuilder::compute_value_class_layout() {
_payload_size_in_bytes = null_free_atomic_layout_size_in_bytes();
}
}
- // Warning:: InstanceMirrorKlass expects static oops to be allocated first
+ // InstanceMirrorKlass expects static oop fields to be allocated first.
_static_layout->add_contiguously(_static_fields->oop_fields());
- _static_layout->add(_static_fields->big_primitive_fields());
- _static_layout->add(_static_fields->small_primitive_fields());
+ _static_layout->add(_static_fields->non_oop_fields());
generate_acmp_maps();
epilogue();
@@ -1352,7 +1362,8 @@ void FieldLayoutBuilder::register_embedded_oops(OopMapBlocksBuilder* nonstatic_o
nonstatic_oop_maps->add(b->offset(), 1);
}
}
- register_embedded_oops_from_list(nonstatic_oop_maps, group->big_primitive_fields());
+ // A non-oop block can be a flat field that contains embedded oops.
+ register_embedded_oops_from_list(nonstatic_oop_maps, group->non_oop_fields());
}
static int insert_segment(GrowableArray* map, int offset, int size, int last_idx) {
diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp
index e1594255515d..3f4c868da8be 100644
--- a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp
+++ b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp
@@ -132,16 +132,16 @@ class LayoutRawBlock : public ResourceObj {
// A Field group represents a set of fields that have to be allocated together,
// this is the way the @Contended annotation is supported.
-// Inside a FieldGroup, fields are sorted based on their kind: primitive,
-// oop, or flat.
+// Fields are separated into oop fields and non-oop field blocks.
+// Non-oop blocks include primitive fields and flat fields.
+// Flat fields can themselves contain embedded oops.
//
class FieldGroup : public ResourceObj {
private:
FieldGroup* _next;
- GrowableArray* _small_primitive_fields;
- GrowableArray* _big_primitive_fields;
+ GrowableArray* _non_oop_fields;
GrowableArray* _oop_fields;
int _contended_group;
static const int INITIAL_LIST_SIZE = 16;
@@ -151,10 +151,11 @@ class FieldGroup : public ResourceObj {
FieldGroup* next() const { return _next; }
void set_next(FieldGroup* next) { _next = next; }
- GrowableArray* small_primitive_fields() const { return _small_primitive_fields; }
- GrowableArray* big_primitive_fields() const { return _big_primitive_fields; }
+ GrowableArray* non_oop_fields() const { return _non_oop_fields; }
GrowableArray* oop_fields() const { return _oop_fields; }
int contended_group() const { return _contended_group; }
+ // Requires the non-oop fields to be sorted in decreasing size order.
+ int first_small_field_index() const;
void add_primitive_field(int idx, BasicType type);
void add_oop_field(int idx);
@@ -162,8 +163,7 @@ class FieldGroup : public ResourceObj {
void add_block(LayoutRawBlock** list, LayoutRawBlock* block);
void sort_by_size();
private:
- void add_to_small_primitive_list(LayoutRawBlock* block);
- void add_to_big_primitive_list(LayoutRawBlock* block);
+ void add_to_non_oop_list(LayoutRawBlock* block);
};
// The FieldLayout class represents a set of fields organized
@@ -232,6 +232,8 @@ class FieldLayout : public ResourceObj {
LayoutRawBlock* first_field_block();
void add(GrowableArray* list, LayoutRawBlock* start = nullptr);
+ // Add the fields in the half-open range [from, to).
+ void add_range(GrowableArray* list, int from, int to, LayoutRawBlock* start = nullptr);
void add_field_at_offset(LayoutRawBlock* blocks, int offset, LayoutRawBlock* start = nullptr);
void add_contiguously(GrowableArray* list, LayoutRawBlock* start = nullptr);
LayoutRawBlock* insert_field_block(LayoutRawBlock* slot, LayoutRawBlock* block);
diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp
index c399d9e0a9b8..6f73e4a794aa 100644
--- a/src/hotspot/share/classfile/javaClasses.cpp
+++ b/src/hotspot/share/classfile/javaClasses.cpp
@@ -22,10 +22,10 @@
*
*/
+#include "cds/aotHeap.inline.hpp"
#include "cds/aotMetaspace.hpp"
#include "cds/aotReferenceObjSupport.hpp"
#include "cds/cdsConfig.hpp"
-#include "cds/heapShared.inline.hpp"
#include "classfile/classLoaderData.inline.hpp"
#include "classfile/javaClasses.inline.hpp"
#include "classfile/javaClassesImpl.hpp"
@@ -963,7 +963,7 @@ void java_lang_Class::fixup_mirror(Klass* k, TRAPS) {
}
if (k->in_aot_cache() && k->has_archived_mirror_index()) {
- if (HeapShared::is_archived_heap_in_use()) {
+ if (AOTHeap::is_archived_heap_in_use()) {
bool present = restore_archived_mirror(k, Handle(), Handle(), Handle(), CHECK);
assert(present, "Missing archived mirror for %s", k->external_name());
return;
@@ -1092,7 +1092,7 @@ void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protecti
if (k->is_typeArray_klass()) {
BasicType type = TypeArrayKlass::cast(k)->element_type();
if (is_scratch) {
- comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(type));
+ comp_mirror = Handle(THREAD, AOTHeap::scratch_java_mirror(type));
} else {
comp_mirror = Handle(THREAD, Universe::java_mirror(type));
}
@@ -1101,7 +1101,7 @@ void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protecti
Klass* element_klass = ObjArrayKlass::cast(k)->element_klass();
assert(element_klass != nullptr, "Must have an element klass");
if (is_scratch) {
- comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(element_klass));
+ comp_mirror = Handle(THREAD, AOTHeap::scratch_java_mirror(element_klass));
} else {
comp_mirror = Handle(THREAD, element_klass->java_mirror());
}
@@ -1208,7 +1208,7 @@ void java_lang_Class::create_scratch_mirror(Klass* k, TRAPS) {
release_set_array_klass(comp_mirror(), k);
}
- HeapShared::set_scratch_java_mirror(k, mirror());
+ AOTHeap::set_scratch_java_mirror(k, mirror());
}
// Returns true if the mirror is updated, false if no archived mirror
@@ -1499,7 +1499,7 @@ BasicType java_lang_Class::primitive_type(oop java_class) {
#ifdef ASSERT
if (CDSConfig::is_dumping_heap()) {
oop mirror = Universe::java_mirror(type);
- oop scratch_mirror = HeapShared::scratch_java_mirror(type);
+ oop scratch_mirror = AOTHeap::scratch_java_mirror(type);
assert(java_class == mirror || java_class == scratch_mirror, "must be consistent");
} else {
assert(Universe::java_mirror(type) == java_class, "must be consistent");
diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp
index 5b6b923bc7ad..56862016db49 100644
--- a/src/hotspot/share/classfile/javaClasses.hpp
+++ b/src/hotspot/share/classfile/javaClasses.hpp
@@ -234,7 +234,7 @@ class java_lang_String : AllStatic {
class java_lang_Class : AllStatic {
friend class VMStructs;
- friend class HeapShared;
+ friend class AOTHeap;
private:
diff --git a/src/hotspot/share/classfile/loaderConstraints.cpp b/src/hotspot/share/classfile/loaderConstraints.cpp
index 13c1ff7319dd..bebe7c53e551 100644
--- a/src/hotspot/share/classfile/loaderConstraints.cpp
+++ b/src/hotspot/share/classfile/loaderConstraints.cpp
@@ -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
@@ -442,22 +442,16 @@ InstanceKlass* LoaderConstraintTable::find_constrained_klass(Symbol* name,
return nullptr;
}
-// Removes a class that was added to the table then class loading subsequently failed for this class,
-// so we don't have a dangling pointer to InstanceKlass in the LoaderConstraintTable.
-void LoaderConstraintTable::remove_failed_loaded_klass(InstanceKlass* klass,
- ClassLoaderData* loader) {
+// Checks that a class that failed to load wasn't added to the table.
+void LoaderConstraintTable::check_failed_loaded_klass(InstanceKlass* klass,
+ ClassLoaderData* loader) {
+#ifdef ASSERT
MutexLocker ml(SystemDictionary_lock);
Symbol* name = klass->name();
- LoaderConstraint *p = find_loader_constraint(name, loader);
- if (p != nullptr && p->klass() != nullptr && p->klass() == klass) {
- // If this is the klass in the constraint, the error was OOM from the ClassLoader.addClass() call.
- // Other errors during loading (eg. constraint violations) will not have added this klass.
- log_info(class, loader, constraints)("removing klass %s: failed to load", name->as_C_string());
- // We only null out the class, since the constraint for the class name for this loader is still valid as
- // it was added when checking signature loaders for a method or field resolution.
- p->set_klass(nullptr);
- }
+ LoaderConstraint* p = find_loader_constraint(name, loader);
+ assert (p == nullptr || p->klass() != klass, "pointer to class that failed to load");
+#endif
}
void LoaderConstraintTable::merge_loader_constraints(Symbol* class_name,
diff --git a/src/hotspot/share/classfile/loaderConstraints.hpp b/src/hotspot/share/classfile/loaderConstraints.hpp
index 8153d9c28fd7..0e40781f7764 100644
--- a/src/hotspot/share/classfile/loaderConstraints.hpp
+++ b/src/hotspot/share/classfile/loaderConstraints.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2003, 2023, 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
@@ -59,7 +59,7 @@ class LoaderConstraintTable : public AllStatic {
// Class loader constraints
static bool check_or_update(InstanceKlass* k, ClassLoaderData* loader, Symbol* name);
- static void remove_failed_loaded_klass(InstanceKlass* k, ClassLoaderData* loader);
+ static void check_failed_loaded_klass(InstanceKlass* k, ClassLoaderData* loader);
static void purge_loader_constraints();
static void print_table_statistics(outputStream* st);
diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp
index e36348c59321..8a644f027aee 100644
--- a/src/hotspot/share/classfile/moduleEntry.cpp
+++ b/src/hotspot/share/classfile/moduleEntry.cpp
@@ -23,11 +23,11 @@
*/
#include "cds/aotClassLocation.hpp"
+#include "cds/aotHeap.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/javaClasses.inline.hpp"
@@ -413,7 +413,7 @@ bool ModuleEntry::should_be_archived() const {
}
void ModuleEntry::remove_unshareable_info() {
- _archived_module_index = HeapShared::append_root(module_oop());
+ _archived_module_index = AOTHeap::append_root(module_oop());
if (_reads != nullptr) {
_reads->set_in_aot_cache();
@@ -449,12 +449,12 @@ void ModuleEntry::load_from_archive(ClassLoaderData* loader_data) {
}
void ModuleEntry::preload_archived_oops() {
- (void)HeapShared::get_root(_archived_module_index, false /* clear */);
+ (void)AOTHeap::get_root(_archived_module_index, false /* clear */);
}
void ModuleEntry::restore_archived_oops(ClassLoaderData* loader_data) {
assert(CDSConfig::is_using_full_module_graph(), "runtime only");
- Handle module_handle(Thread::current(), HeapShared::get_root(_archived_module_index, /*clear=*/true));
+ Handle module_handle(Thread::current(), AOTHeap::get_root(_archived_module_index, /*clear=*/true));
assert(module_handle.not_null(), "huh");
set_module_handle(loader_data->add_handle(module_handle));
@@ -475,7 +475,7 @@ void ModuleEntry::restore_archived_oops(ClassLoaderData* loader_data) {
void ModuleEntry::clear_archived_oops() {
assert(CDSConfig::is_using_archive() && !CDSConfig::is_using_full_module_graph(), "runtime only");
- HeapShared::clear_root(_archived_module_index);
+ AOTHeap::clear_root(_archived_module_index);
}
static int compare_module_by_name(ModuleEntry* a, ModuleEntry* b) {
diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp
index a1e55229bb50..91c2384ee7fe 100644
--- a/src/hotspot/share/classfile/stringTable.cpp
+++ b/src/hotspot/share/classfile/stringTable.cpp
@@ -22,10 +22,10 @@
*
*/
+#include "cds/aotHeap.inline.hpp"
#include "cds/aotMappedHeapLoader.hpp"
#include "cds/archiveBuilder.hpp"
#include "cds/cdsConfig.hpp"
-#include "cds/heapShared.inline.hpp"
#include "classfile/altHashing.hpp"
#include "classfile/compactHashtable.hpp"
#include "classfile/javaClasses.inline.hpp"
@@ -76,7 +76,7 @@ const double CLEAN_DEAD_HIGH_WATER_MARK = 0.5;
#if INCLUDE_CDS_JAVA_HEAP
inline oop StringTable::read_string_from_compact_hashtable(address base_address, u4 index) {
assert(AOTMappedHeapLoader::is_in_use(), "sanity");
- oop s = HeapShared::get_root((int)index, false);
+ oop s = AOTHeap::get_root((int)index, false);
assert(java_lang_String::is_instance(s), "must be");
return s;
}
@@ -909,7 +909,7 @@ void StringtableDCmd::execute(DCmdSource source, TRAPS) {
// Sharing
#if INCLUDE_CDS_JAVA_HEAP
size_t StringTable::shared_entry_count() {
- assert(HeapShared::is_loading_mapping_mode(), "should not reach here");
+ assert(AOTHeap::is_loading_mapping_mode(), "should not reach here");
return _shared_table.entry_count();
}
@@ -935,18 +935,18 @@ oop StringTable::lookup_shared(const jchar* name, int len) {
void StringTable::init_shared_table() {
assert(SafepointSynchronize::is_at_safepoint(), "inside AOT safepoint");
precond(CDSConfig::is_dumping_heap());
- assert(HeapShared::is_writing_mapping_mode(), "not used for streamed oops");
+ assert(AOTHeap::is_writing_mapping_mode(), "not used for streamed oops");
int n = 0;
auto copy_into_aot_heap = [&] (WeakHandle* val) {
oop string = val->peek();
- if (string != nullptr && !HeapShared::is_string_too_large_to_archive(string)) {
+ if (string != nullptr && !AOTHeap::is_string_too_large_to_archive(string)) {
// If string is too large, don't put it into the string table.
// - If there are no other references to it, it won't be stored into the archive,
// so we are all good.
- // - If there's a reference to it, we will report an error inside HeapShared.cpp and
+ // - If there's a reference to it, we will report an error inside AOTHeap.cpp and
// dumping will fail.
- HeapShared::archive_interned_string(string);
+ AOTHeap::archive_interned_string(string);
}
n++;
return true;
@@ -959,16 +959,16 @@ void StringTable::init_shared_table() {
void StringTable::write_shared_table() {
assert(SafepointSynchronize::is_at_safepoint(), "inside AOT safepoint");
precond(CDSConfig::is_dumping_heap());
- assert(HeapShared::is_writing_mapping_mode(), "not used for streamed oops");
+ assert(AOTHeap::is_writing_mapping_mode(), "not used for streamed oops");
_shared_table_for_dumping.reset();
CompactHashtableWriter writer((int)items_count_acquire(), ArchiveBuilder::string_stats());
auto copy_into_shared_table = [&] (WeakHandle* val) {
oop string = val->peek();
- if (string != nullptr && !HeapShared::is_string_too_large_to_archive(string)) {
+ if (string != nullptr && !AOTHeap::is_string_too_large_to_archive(string)) {
unsigned int hash = java_lang_String::hash_code(string);
- int root_id = HeapShared::append_root(string);
+ int root_id = AOTHeap::append_root(string);
writer.add(hash, root_id);
}
return true;
diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp
index d5fc89f9cd0a..65ff8ec57bf1 100644
--- a/src/hotspot/share/classfile/systemDictionary.cpp
+++ b/src/hotspot/share/classfile/systemDictionary.cpp
@@ -23,8 +23,8 @@
*/
#include "cds/aotClassLocation.hpp"
+#include "cds/aotHeap.hpp"
#include "cds/cdsConfig.hpp"
-#include "cds/heapShared.hpp"
#include "classfile/classFileParser.hpp"
#include "classfile/classFileStream.hpp"
#include "classfile/classLoader.hpp"
@@ -1005,7 +1005,7 @@ bool SystemDictionary::is_shared_class_visible_impl(Symbol* class_name,
// At this point, no modules have been defined yet. KlassSubGraphInfo::check_allowed_klass()
// has restricted the classes can be loaded at this step to be only:
// [1] cs->is_modules_image(): classes in java.base, or,
- // [2] HeapShared::is_a_test_class_in_unnamed_module(ik): classes in bootstrap/unnamed module
+ // [2] AOTHeap::is_a_test_class_in_unnamed_module(ik): classes in bootstrap/unnamed module
assert(cl->is_modules_image(),
"only these classes can be loaded before the module system is initialized");
assert(class_loader.is_null(), "sanity");
@@ -1564,19 +1564,6 @@ void SystemDictionary::define_instance_class(InstanceKlass* k, Handle class_load
// which will require a token to perform the define class
check_constraints(k, loader_data, true, CHECK);
- // Register class just loaded with class loader (placed in ArrayList)
- // Note we do this before updating the dictionary, as this can
- // fail with an OutOfMemoryError (if it does, we will *not* put this
- // class in the dictionary and will not update the class hierarchy).
- // JVMTI FollowReferences needs to find the classes this way.
- if (k->class_loader() != nullptr) {
- methodHandle m(THREAD, Universe::loader_addClass_method());
- JavaValue result(T_VOID);
- JavaCallArguments args(class_loader);
- args.push_oop(Handle(THREAD, k->java_mirror()));
- JavaCalls::call(&result, m, &args, CHECK);
- }
-
JFR_ONLY(Jfr::on_definition(k, THREAD);)
// Add to class hierarchy, and do possible deoptimizations.
@@ -1688,8 +1675,8 @@ InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_nam
assert(defined_k != nullptr, "Should have a klass if there's no exception");
k->class_loader_data()->add_to_deallocate_list(k);
} else if (HAS_PENDING_EXCEPTION) {
- // Remove this InstanceKlass from the LoaderConstraintTable if added.
- LoaderConstraintTable::remove_failed_loaded_klass(k, class_loader_data(class_loader));
+ // Validate that this InstanceKlass is not present in the LoaderConstraintTable.
+ LoaderConstraintTable::check_failed_loaded_klass(k, class_loader_data(class_loader));
assert(defined_k == nullptr, "Should not have a klass if there's an exception");
k->class_loader_data()->add_to_deallocate_list(k);
}
@@ -1773,7 +1760,7 @@ void SystemDictionary::initialize(TRAPS) {
vmClasses::resolve_all(CHECK);
// Resolve classes used by archived heap objects
if (CDSConfig::is_using_archive()) {
- HeapShared::resolve_classes(THREAD);
+ AOTHeap::resolve_classes(THREAD);
}
}
diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp
index c7e16fce69d7..0d6c345762ce 100644
--- a/src/hotspot/share/classfile/systemDictionaryShared.cpp
+++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp
@@ -26,6 +26,7 @@
#include "cds/aotClassFilter.hpp"
#include "cds/aotClassLocation.hpp"
#include "cds/aotCompressedPointers.hpp"
+#include "cds/aotHeap.hpp"
#include "cds/aotLogging.hpp"
#include "cds/aotMetaspace.hpp"
#include "cds/archiveBuilder.hpp"
@@ -37,7 +38,6 @@
#include "cds/dumpTimeClassInfo.inline.hpp"
#include "cds/dynamicArchive.hpp"
#include "cds/filemap.hpp"
-#include "cds/heapShared.hpp"
#include "cds/lambdaFormInvokers.inline.hpp"
#include "cds/lambdaProxyClassDictionary.hpp"
#include "cds/runTimeClassInfo.hpp"
@@ -752,7 +752,7 @@ void SystemDictionaryShared::initialize() {
_dumptime_table = new (mtClass) DumpTimeSharedClassTable;
LambdaProxyClassDictionary::dumptime_init();
if (CDSConfig::is_dumping_heap()) {
- HeapShared::init_dumping();
+ AOTHeap::init_dumping();
}
}
}
diff --git a/src/hotspot/share/classfile/vmClasses.cpp b/src/hotspot/share/classfile/vmClasses.cpp
index 21a910486c88..396d7e5b61e4 100644
--- a/src/hotspot/share/classfile/vmClasses.cpp
+++ b/src/hotspot/share/classfile/vmClasses.cpp
@@ -22,11 +22,11 @@
*
*/
+#include "cds/aotHeap.inline.hpp"
#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/dictionary.hpp"
@@ -139,7 +139,7 @@ void vmClasses::resolve_all(TRAPS) {
#if INCLUDE_CDS
if (CDSConfig::is_using_archive()) {
#if INCLUDE_CDS_JAVA_HEAP
- if (HeapShared::is_loading() && HeapShared::is_loading_mapping_mode()) {
+ if (AOTHeap::is_loading() && AOTHeap::is_loading_mapping_mode()) {
// It's unsafe to access the archived heap regions before they
// are fixed up, so we must do the fixup as early as possible
// before the archived java objects are accessed by functions
@@ -155,8 +155,8 @@ void vmClasses::resolve_all(TRAPS) {
// Mirrors are restored after java.lang.Class is loaded.
AOTMappedHeapLoader::fixup_region();
}
- 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().
+ if (AOTHeap::is_archived_heap_in_use() && !CDSConfig::is_using_full_module_graph()) {
+ // Need to remove all the archived java.lang.Module objects from AOTHeap::roots().
AOTModules::clear_archived_oops();
}
#endif // INCLUDE_CDS_JAVA_HEAP
diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp
index 4d1bb8c83095..bec24305db2a 100644
--- a/src/hotspot/share/code/aotCodeCache.cpp
+++ b/src/hotspot/share/code/aotCodeCache.cpp
@@ -29,7 +29,6 @@
#include "cds/aotMetaspace.hpp"
#include "cds/cds_globals.hpp"
#include "cds/cdsConfig.hpp"
-#include "cds/heapShared.hpp"
#include "ci/ciEnv.hpp"
#include "ci/ciMethod.hpp"
#include "ci/ciUtilities.inline.hpp"
diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp
index 47ad8e650100..38e0df7fa468 100644
--- a/src/hotspot/share/compiler/compilationPolicy.cpp
+++ b/src/hotspot/share/compiler/compilationPolicy.cpp
@@ -1582,9 +1582,16 @@ void CompilationPolicy::method_back_branch_event(const methodHandle& mh, const m
if (is_compilation_enabled()) {
CompLevel next_osr_level = loop_event(imh, level, THREAD);
CompLevel max_osr_level = (CompLevel)imh->highest_osr_comp_level();
- // At the very least compile the OSR version
+
+ // Try to compile the OSR version, so that it can be switched at this back branch.
+ // If OSR compilation is not possible, compile normal version instead, and hope
+ // the back branch bearing method would be re-entered soon.
if (!CompileBroker::compilation_is_in_queue(imh) && (next_osr_level != level)) {
- compile(imh, bci, next_osr_level, CHECK);
+ if (can_be_osr_compiled(imh, CompLevel_any)) {
+ compile(imh, bci, next_osr_level, CHECK);
+ } else if (comp_level(imh()) < next_osr_level) {
+ compile(imh, InvocationEntryBci, next_osr_level, CHECK);
+ }
}
// Use loop event as an opportunity to also check if there's been
diff --git a/src/hotspot/share/compiler/compilerDirectives.hpp b/src/hotspot/share/compiler/compilerDirectives.hpp
index ea709c6897ce..5137b31ce76f 100644
--- a/src/hotspot/share/compiler/compilerDirectives.hpp
+++ b/src/hotspot/share/compiler/compilerDirectives.hpp
@@ -90,7 +90,9 @@ NOT_PRODUCT(cflags(PrintIdeal, bool, PrintIdeal, PrintIdeal)) \
NOT_PRODUCT(cflags(PhasePrintLevel, intx, PrintPhaseLevel, PhasePrintLevel)) \
NOT_PRODUCT(cflags(IGVPrintLevel, intx, PrintIdealGraphLevel, IGVPrintLevel)) \
cflags(IncrementalInlineForceCleanup, bool, IncrementalInlineForceCleanup, IncrementalInlineForceCleanup) \
- cflags(MaxNodeLimit, intx, MaxNodeLimit, MaxNodeLimit)
+ cflags(MaxNodeLimit, intx, MaxNodeLimit, MaxNodeLimit) \
+NOT_PRODUCT(cflags(PrintIdealNodeCount, bool, PrintIdealNodeCount, PrintIdealNodeCount)) \
+
#define compilerdirectives_c2_string_flags(cflags) \
NOT_PRODUCT(cflags(TraceAutoVectorization, ccstrlist, "", TraceAutoVectorization)) \
NOT_PRODUCT(cflags(TraceMergeStores, ccstrlist, "", TraceMergeStores)) \
diff --git a/src/hotspot/share/compiler/compilerOracle.hpp b/src/hotspot/share/compiler/compilerOracle.hpp
index 35cceea32cd3..b5b571e58eaa 100644
--- a/src/hotspot/share/compiler/compilerOracle.hpp
+++ b/src/hotspot/share/compiler/compilerOracle.hpp
@@ -95,6 +95,7 @@ NOT_PRODUCT(option(TraceMergeStores, "TraceMergeStores", Ccstrlist)) \
option(CloneMapDebug, "CloneMapDebug", Bool) \
option(IncrementalInlineForceCleanup, "IncrementalInlineForceCleanup", Bool) \
option(MaxNodeLimit, "MaxNodeLimit", Intx) \
+NOT_PRODUCT(option(PrintIdealNodeCount, "PrintIdealNodeCount", Bool)) \
option(DontAOTCompile, "DontAOTCompile", Bool) \
option(DontPreload, "DontPreload", Bool) \
option(IgnoreAOTCompiled, "IgnoreAOTCompiled", Bool) \
diff --git a/src/hotspot/share/gc/g1/g1CardSet.cpp b/src/hotspot/share/gc/g1/g1CardSet.cpp
index 8f84bf59387e..5da3c8096757 100644
--- a/src/hotspot/share/gc/g1/g1CardSet.cpp
+++ b/src/hotspot/share/gc/g1/g1CardSet.cpp
@@ -686,7 +686,10 @@ void G1CardSet::transfer_cards(G1CardSetHashTableValue* table_entry, ContainerPt
assert(container_type(source_container) == ContainerHowl, "must be");
// Need to correct for that the Full remembered set occupies more cards than the
// AoCS before.
- _num_occupied.add_then_fetch(_config->max_cards_in_region() - table_entry->_num_occupied.load_relaxed(), memory_order_relaxed);
+ // Use int64_t to represent the difference because due to races this difference can be negative.
+ const int64_t diff = (int64_t)_config->max_cards_in_region() - table_entry->_num_occupied.load_relaxed();
+
+ _num_occupied.add_then_fetch((size_t)diff, memory_order_relaxed);
}
}
@@ -702,7 +705,8 @@ void G1CardSet::transfer_cards_in_howl(ContainerPtr parent_container,
G1TransferCard iter(this, card_region);
iterate_cards_during_transfer(source_container, iter);
} else {
- uint diff = _config->max_cards_in_howl_bitmap() - container_ptr(source_container)->num_bits_set();
+ // Use int64_t to represent the difference because due to races this difference can be negative.
+ int64_t diff = (int64_t)_config->max_cards_in_howl_bitmap() - container_ptr(source_container)->num_bits_set();
// Need to correct for that the Full remembered set occupies more cards than the
// bitmap before.
@@ -712,14 +716,14 @@ void G1CardSet::transfer_cards_in_howl(ContainerPtr parent_container,
diff -= 1;
G1CardSetHowl* howling_array = container_ptr(parent_container);
- howling_array->_num_entries.add_then_fetch(diff, memory_order_relaxed);
+ howling_array->_num_entries.add_then_fetch((uint)diff, memory_order_relaxed);
G1CardSetHashTableValue* table_entry = get_container(card_region);
assert(table_entry != nullptr, "Table entry not found for transferred cards");
- table_entry->_num_occupied.add_then_fetch(diff, memory_order_relaxed);
+ table_entry->_num_occupied.add_then_fetch((uint)diff, memory_order_relaxed);
- _num_occupied.add_then_fetch(diff, memory_order_relaxed);
+ _num_occupied.add_then_fetch((size_t)diff, memory_order_relaxed);
}
}
diff --git a/src/hotspot/share/gc/g1/g1CardSetContainers.hpp b/src/hotspot/share/gc/g1/g1CardSetContainers.hpp
index 880c02a71ed9..2a5d332f55ce 100644
--- a/src/hotspot/share/gc/g1/g1CardSetContainers.hpp
+++ b/src/hotspot/share/gc/g1/g1CardSetContainers.hpp
@@ -252,6 +252,8 @@ class G1CardSetBitMap : public G1CardSetContainer {
};
class G1CardSetHowl : public G1CardSetContainer {
+ DEBUG_ONLY(uint _num_buckets;)
+
public:
typedef uint EntryCountType;
using ContainerPtr = G1CardSet::ContainerPtr;
diff --git a/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp b/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp
index c9e7a046ea7a..d45ee59bccc0 100644
--- a/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp
+++ b/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp
@@ -276,7 +276,7 @@ inline size_t G1CardSetBitMap::header_size_in_bytes() {
}
inline Atomic const* G1CardSetHowl::container_addr(EntryCountType index) const {
- assert(index < _num_entries.load_relaxed(), "precondition");
+ precond(index < _num_buckets);
return buckets() + index;
}
@@ -295,6 +295,7 @@ inline Atomic const* G1CardSetHowl::buckets() const
inline G1CardSetHowl::G1CardSetHowl(EntryCountType card_in_region, G1CardSetConfiguration* config) :
G1CardSetContainer(),
+ DEBUG_ONLY(_num_buckets(config->num_buckets_in_howl()) COMMA)
_num_entries((config->max_cards_in_array() + 1)) /* Card Transfer will not increment _num_entries */ {
EntryCountType num_buckets = config->num_buckets_in_howl();
EntryCountType bucket = config->howl_bucket_index(card_in_region);
diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp
index 448a40c1d2ec..20d8bb8ad9a2 100644
--- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp
+++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp
@@ -1311,7 +1311,7 @@ class G1CollectedHeap : public CollectedHeap {
// WhiteBox testing support.
bool supports_concurrent_gc_breakpoints() const override;
- WorkerThreads* safepoint_workers() override { return _workers; }
+ bool supports_parallel_object_iteration() const override { return true; }
// The methods below are here for convenience and dispatch the
// appropriate method depending on value of the given VerifyOption
diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp
index 9961c49802ab..4b27dce4dd3c 100644
--- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp
+++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp
@@ -1960,7 +1960,7 @@ class G1VerifyNoCollectionSetOops {
void operator()(G1TaskQueueEntry task_entry) const {
oop obj = task_entry.is_partial_array_state()
- ? task_entry.to_partial_array_state()->source()
+ ? task_entry.to_partial_array_state()->array()
: task_entry.to_oop();
guarantee(oopDesc::is_oop(obj),
"Non-oop " PTR_FORMAT ", location: %s, index: %d",
@@ -2450,26 +2450,25 @@ size_t G1CMTask::start_partial_array_processing(objArrayOop obj) {
process_klass(obj->klass());
size_t array_length = obj->length();
- size_t initial_chunk_size = _partial_array_splitter.start(_task_queue, obj, nullptr, array_length, ObjArrayMarkingStride);
+ PartialArraySplitter::Claim chunk = _partial_array_splitter.start(_task_queue, obj, array_length, ObjArrayMarkingStride);
- process_array_chunk(obj, 0, initial_chunk_size);
+ process_array_chunk(chunk._array, chunk._start, chunk._end);
+ const size_t chunk_len = chunk._end - chunk._start;
// Include object header size
if (obj->is_refArray()) {
- return refArrayOopDesc::object_size(checked_cast(initial_chunk_size));
+ return refArrayOopDesc::object_size(checked_cast(chunk_len));
} else {
FlatArrayKlass* fak = FlatArrayKlass::cast(obj->klass());
- return flatArrayOopDesc::object_size(fak->layout_helper(), checked_cast(initial_chunk_size));
+ return flatArrayOopDesc::object_size(fak->layout_helper(), checked_cast(chunk_len));
}
}
size_t G1CMTask::process_partial_array(const G1TaskQueueEntry& task, bool stolen) {
PartialArrayState* state = task.to_partial_array_state();
- // Access state before release by claim().
- objArrayOop obj = oop_cast(state->source());
-
PartialArraySplitter::Claim claim =
_partial_array_splitter.claim(state, _task_queue, stolen);
+ objArrayOop obj = claim._array;
process_array_chunk(obj, claim._start, claim._end);
diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp
index 6e0ef06da7dd..aeab92625a41 100644
--- a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp
+++ b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp
@@ -53,11 +53,9 @@ G1FullGCMarker::~G1FullGCMarker() {
}
void G1FullGCMarker::process_partial_array(PartialArrayState* state, bool stolen) {
- // Access state before release by claim().
- objArrayOop obj_array = objArrayOop(state->source());
PartialArraySplitter::Claim claim =
_partial_array_splitter.claim(state, task_queue(), stolen);
- process_array_chunk(obj_array, claim._start, claim._end);
+ process_array_chunk(claim._array, claim._start, claim._end);
}
static uintx calc_array_stride(uint array_len, uint num_threads) {
@@ -79,9 +77,9 @@ void G1FullGCMarker::start_partial_array_processing(objArrayOop obj) {
}
const uintx stride = calc_array_stride(array_length, _collector->workers());
- const size_t initial_chunk_size = _partial_array_splitter.start(task_queue(), obj, nullptr, array_length, stride);
+ PartialArraySplitter::Claim chunk = _partial_array_splitter.start(task_queue(), obj, array_length, stride);
- process_array_chunk(obj, 0, initial_chunk_size);
+ process_array_chunk(chunk._array, chunk._start, chunk._end);
}
void G1FullGCMarker::complete_marking(G1ScannerTasksQueueSet* task_queues,
diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp
index ff0cfc8b27a0..3762faa2ba78 100644
--- a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp
+++ b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp
@@ -91,7 +91,7 @@ inline void G1FullGCMarker::process_array_chunk(objArrayOop obj, size_t start, s
inline void G1FullGCMarker::dispatch_task(const ScannerTask& task, bool stolen) {
if (task.is_partial_array_state()) {
- assert(_bitmap->is_marked(task.to_partial_array_state()->source()), "should be marked");
+ assert(_bitmap->is_marked(task.to_partial_array_state()->array()), "should be marked");
process_partial_array(task.to_partial_array_state(), stolen);
} else {
oop obj = task.to_oop();
diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp
index 83f62fd7bab3..160c93cdbc21 100644
--- a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp
+++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp
@@ -92,21 +92,10 @@ void G1HeapRegionManager::initialize(G1RegionToSpaceMapper* heap_storage,
}
G1HeapRegion* G1HeapRegionManager::allocate_free_region(G1HeapRegionType type, uint requested_node_index) {
- G1HeapRegion* hr = nullptr;
bool from_head = !type.is_young();
G1NUMA* numa = G1NUMA::numa();
- if (requested_node_index != G1NUMA::AnyNodeIndex && numa->is_enabled()) {
- // Try to allocate with requested node index.
- hr = _free_list.remove_region_with_node_index(from_head, requested_node_index);
- }
-
- if (hr == nullptr) {
- // If there's a single active node or we did not get a region from our requested node,
- // try without requested node index.
- hr = _free_list.remove_region(from_head);
- }
-
+ G1HeapRegion* hr = _free_list.remove_region(from_head, requested_node_index);
if (hr != nullptr) {
assert(hr->next() == nullptr, "Single region should not have next");
assert(is_available(hr->hrm_index()), "Must be committed");
diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp
index 8edfc99e3ef8..5764532e75d8 100644
--- a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp
+++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp
@@ -202,7 +202,8 @@ class G1HeapRegionManager: public CHeapObj {
_free_list.add_ordered(list);
}
- // Allocate a free region with specific node index. If fails allocate with next node index.
+ // Allocate a free region on a specific node. If that fails, use the next available
+ // on another node.
G1HeapRegion* allocate_free_region(G1HeapRegionType type, uint requested_node_index);
// Allocate a humongous object from the free list
diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp
index f710b64d7f29..d39eed88b262 100644
--- a/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp
+++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp
@@ -131,14 +131,9 @@ void G1FreeRegionList::add_list_common_start(G1FreeRegionList* from_list) {
}
#ifdef ASSERT
- G1FreeRegionListIterator iter(from_list);
- while (iter.more_available()) {
- G1HeapRegion* hr = iter.get_next();
- // In set_containing_set() we check that we either set the value
- // from null to non-null or vice versa to catch bugs. So, we have
- // to null it first before setting it to the value.
- hr->set_containing_set(nullptr);
- hr->set_containing_set(this);
+ for (G1HeapRegion* r = from_list->_head; r != nullptr; r = r->next()) {
+ r->set_containing_set(nullptr);
+ r->set_containing_set(this);
}
#endif // ASSERT
}
@@ -373,8 +368,8 @@ G1FreeRegionList::~G1FreeRegionList() {
}
}
-G1FreeRegionList::NodeInfo::NodeInfo() : _numa(G1NUMA::numa()), _num_regions_on_node(nullptr),
- _num_nodes(_numa->num_active_nodes()) {
+G1FreeRegionList::NodeInfo::NodeInfo() : _num_regions_on_node(nullptr),
+ _num_nodes(G1NUMA::numa()->num_active_nodes()) {
assert(UseNUMA, "Invariant");
_num_regions_on_node = NEW_C_HEAP_ARRAY(uint, _num_nodes, mtGC);
diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp
index 6db3001b117f..41b1cf4bb82d 100644
--- a/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp
+++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp
@@ -62,7 +62,6 @@ class G1HeapRegionSetChecker : public CHeapObj {
// Base class for all the classes that represent heap region sets. It contains the
// basic attributes that each set needs to maintain (e.g., number of regions) plus
// any shared functionality (e.g., verification).
-
class G1HeapRegionSetBase {
friend class VMStructs;
@@ -105,7 +104,6 @@ class G1HeapRegionSetBase {
virtual void verify();
void verify_start();
- void verify_next_region(G1HeapRegion* hr);
void verify_end();
void verify_optional() { DEBUG_ONLY(verify();) }
@@ -117,7 +115,6 @@ class G1HeapRegionSetBase {
// explicitly tracked. It's helpful to group regions using such sets
// so that we can reason about all the region groups in the heap using
// the same interface (namely, the G1HeapRegionSetBase API).
-
class G1HeapRegionSet : public G1HeapRegionSetBase {
public:
G1HeapRegionSet(const char* name, G1HeapRegionSetChecker* checker):
@@ -133,18 +130,9 @@ class G1HeapRegionSet : public G1HeapRegionSetBase {
// sorted list. We should try to avoid doing operations that iterate over
// such lists in performance critical paths. Typically we should
// add / remove one region at a time or concatenate two lists.
-
-class G1FreeRegionListIterator;
-class G1NUMA;
-
class G1FreeRegionList : public G1HeapRegionSetBase {
- friend class G1FreeRegionListIterator;
-
-private:
-
// This class is only initialized if there are multiple active nodes.
class NodeInfo : public CHeapObj {
- G1NUMA* _numa;
uint* _num_regions_on_node;
uint _num_nodes;
@@ -173,8 +161,8 @@ class G1FreeRegionList : public G1HeapRegionSetBase {
static uint _unrealistically_large_num_regions;
- inline G1HeapRegion* remove_from_head_impl();
- inline G1HeapRegion* remove_from_tail_impl();
+ inline void unlink_from_list(G1HeapRegion* cur);
+ inline void remove_from_list(G1HeapRegion* cur);
inline void increase_num_regions(uint node_index);
inline void decrease_num_regions(uint node_index);
@@ -184,9 +172,10 @@ class G1FreeRegionList : public G1HeapRegionSetBase {
void add_list_common_end(G1FreeRegionList* from_list);
void verify_region_to_remove(G1HeapRegion* curr, G1HeapRegion* next) NOT_DEBUG_RETURN;
-protected:
- // See the comment for G1HeapRegionSetBase::clear()
- virtual void clear();
+
+ void clear();
+
+ G1HeapRegion* find_region_to_remove(bool from_head, uint requested_node_index);
public:
G1FreeRegionList(const char* name, G1HeapRegionSetChecker* checker = nullptr);
@@ -209,11 +198,11 @@ class G1FreeRegionList : public G1HeapRegionSetBase {
// Same restrictions as above, but adds the region last in the list.
inline void add_to_tail(G1HeapRegion* region_to_add);
- // Removes from head or tail based on the given argument.
- G1HeapRegion* remove_region(bool from_head);
-
- G1HeapRegion* remove_region_with_node_index(bool from_head,
- uint requested_node_index);
+ // Finds and removes the next fitting region from the free list from head or tail.
+ // If NUMA support is enabled, initially tries to match the result to be on the
+ // requested node. This search is limited by G1NUMA's maximum search depth; if no
+ // matching region is found or not using NUMA, just takes the first.
+ G1HeapRegion* remove_region(bool from_head, uint requested_node_index);
// Merge two ordered lists. The result is also ordered. The order is
// determined by hrm_index.
@@ -234,40 +223,7 @@ class G1FreeRegionList : public G1HeapRegionSetBase {
virtual void verify();
- using G1HeapRegionSetBase::num_regions;
uint num_regions_on_node(uint node_index) const;
};
-// Iterator class that provides a convenient way to iterate over the
-// regions of a FreeRegionList.
-
-class G1FreeRegionListIterator : public StackObj {
-private:
- G1FreeRegionList* _list;
- G1HeapRegion* _curr;
-
-public:
- bool more_available() {
- return _curr != nullptr;
- }
-
- G1HeapRegion* get_next() {
- assert(more_available(),
- "get_next() should be called when more regions are available");
-
- // If we are going to introduce a count in the iterator we should
- // do the "cycle" check.
-
- G1HeapRegion* hr = _curr;
- _list->verify_region(hr);
- _curr = hr->next();
- return hr;
- }
-
- G1FreeRegionListIterator(G1FreeRegionList* list)
- : _list(list),
- _curr(list->_head) {
- }
-};
-
#endif // SHARE_GC_G1_G1HEAPREGIONSET_HPP
diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp
index c18ac68e0648..a6074145aa81 100644
--- a/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp
+++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp
@@ -120,116 +120,81 @@ inline void G1FreeRegionList::add_ordered(G1HeapRegion* hr) {
increase_num_regions(hr->node_index());
}
-inline G1HeapRegion* G1FreeRegionList::remove_from_head_impl() {
- G1HeapRegion* result = _head;
- _head = result->next();
- if (_head == nullptr) {
- _tail = nullptr;
+inline void G1FreeRegionList::unlink_from_list(G1HeapRegion* r) {
+ precond(r != nullptr);
+ // Splice the region out of the list.
+ G1HeapRegion* prev = r->prev();
+ G1HeapRegion* next = r->next();
+ if (prev == nullptr) {
+ _head = next;
} else {
- _head->set_prev(nullptr);
+ prev->set_next(next);
}
- result->set_next(nullptr);
- return result;
-}
-
-inline G1HeapRegion* G1FreeRegionList::remove_from_tail_impl() {
- G1HeapRegion* result = _tail;
-
- _tail = result->prev();
- if (_tail == nullptr) {
- _head = nullptr;
+ if (next == nullptr) {
+ _tail = prev;
} else {
- _tail->set_next(nullptr);
+ next->set_prev(prev);
}
- result->set_prev(nullptr);
- return result;
+ r->set_prev(nullptr);
+ r->set_next(nullptr);
}
-inline G1HeapRegion* G1FreeRegionList::remove_region(bool from_head) {
- check_mt_safety();
- verify_optional();
+inline void G1FreeRegionList::remove_from_list(G1HeapRegion* r) {
+ precond(r != nullptr);
- if (is_empty()) {
- return nullptr;
- }
- assert_free_region_list(num_regions() > 0 && _head != nullptr && _tail != nullptr, "invariant");
-
- G1HeapRegion* hr;
-
- if (from_head) {
- hr = remove_from_head_impl();
- } else {
- hr = remove_from_tail_impl();
- }
-
- if (_last == hr) {
+ unlink_from_list(r);
+ if (_last == r) {
_last = nullptr;
}
- // remove() will verify the region and check mt safety.
- remove(hr);
-
- decrease_num_regions(hr->node_index());
-
- return hr;
+ remove(r);
+ decrease_num_regions(r->node_index());
}
-inline G1HeapRegion* G1FreeRegionList::remove_region_with_node_index(bool from_head,
- uint requested_node_index) {
- assert(UseNUMA, "Invariant");
-
- const uint max_search_depth = G1NUMA::numa()->max_search_depth();
- G1HeapRegion* cur;
-
- // Find the region to use, searching from _head or _tail as requested.
- size_t cur_depth = 0;
- if (from_head) {
- for (cur = _head;
- cur != nullptr && cur_depth < max_search_depth;
- cur = cur->next(), ++cur_depth) {
- if (requested_node_index == cur->node_index()) {
- break;
+inline G1HeapRegion* G1FreeRegionList::find_region_to_remove(bool from_head, uint requested_node_index) {
+ G1HeapRegion* cur = nullptr;
+
+ if (G1NUMA::numa()->is_enabled() && requested_node_index != G1NUMA::AnyNodeIndex) {
+ const uint max_search_depth = G1NUMA::numa()->max_search_depth();
+ // Find the region to use, searching from _head or _tail as requested.
+ uint cur_depth = 0;
+ if (from_head) {
+ for (cur = _head;
+ cur != nullptr && cur_depth < max_search_depth;
+ cur = cur->next(), ++cur_depth) {
+ if (requested_node_index == cur->node_index()) {
+ return cur;
+ }
}
- }
- } else {
- for (cur = _tail;
- cur != nullptr && cur_depth < max_search_depth;
- cur = cur->prev(), ++cur_depth) {
- if (requested_node_index == cur->node_index()) {
- break;
+ } else {
+ for (cur = _tail;
+ cur != nullptr && cur_depth < max_search_depth;
+ cur = cur->prev(), ++cur_depth) {
+ if (requested_node_index == cur->node_index()) {
+ return cur;
+ }
}
}
}
+ // Either we could not find a region on the correct NUMA node within the search depth, or
+ // we do not care about the node placement. In either case take the next region from the
+ // requested end of the list.
+ cur = from_head ? _head : _tail;
+ return cur;
+}
- // Didn't find a region to use.
- if (cur == nullptr || cur_depth >= max_search_depth) {
- return nullptr;
- }
-
- // Splice the region out of the list.
- G1HeapRegion* prev = cur->prev();
- G1HeapRegion* next = cur->next();
- if (prev == nullptr) {
- _head = next;
- } else {
- prev->set_next(next);
- }
- if (next == nullptr) {
- _tail = prev;
- } else {
- next->set_prev(prev);
- }
- cur->set_prev(nullptr);
- cur->set_next(nullptr);
+inline G1HeapRegion* G1FreeRegionList::remove_region(bool from_head, uint requested_node_index) {
+ check_mt_safety();
+ verify_optional();
- if (_last == cur) {
- _last = nullptr;
+ if (is_empty()) {
+ return nullptr;
}
+ assert_free_region_list(num_regions() > 0 && _head != nullptr && _tail != nullptr, "invariant");
- remove(cur);
- decrease_num_regions(cur->node_index());
-
- return cur;
+ G1HeapRegion* r = find_region_to_remove(from_head, requested_node_index);
+ remove_from_list(r);
+ return r;
}
inline void G1FreeRegionList::NodeInfo::increase_num_regions(uint node_index) {
diff --git a/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp b/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp
index 72a17f563a09..01f8f67c3d66 100644
--- a/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp
+++ b/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp
@@ -367,6 +367,10 @@ G1MonitoringScope::~G1MonitoringScope() {
MemoryService::track_memory_usage();
}
+void G1MonitoringScope::set_all_memory_pools_affected() {
+ _tms.set_all_memory_pools_affected();
+}
+
G1YoungGCMonitoringScope::G1YoungGCMonitoringScope(G1MonitoringSupport* monitoring_support,
bool all_memory_pools_affected) :
G1MonitoringScope(monitoring_support,
@@ -376,6 +380,14 @@ G1YoungGCMonitoringScope::G1YoungGCMonitoringScope(G1MonitoringSupport* monitori
all_memory_pools_affected) {
}
+G1YoungGCMonitoringScope::~G1YoungGCMonitoringScope() {
+ G1GCPhaseTimes* phase_times = G1CollectedHeap::heap()->phase_times();
+ if (phase_times->sum_thread_work_items(G1GCPhaseTimes::EagerlyReclaimHumongousObjects,
+ G1GCPhaseTimes::EagerlyReclaimNumReclaimed) != 0) {
+ set_all_memory_pools_affected();
+ }
+}
+
G1FullGCMonitoringScope::G1FullGCMonitoringScope(G1MonitoringSupport* monitoring_support) :
G1MonitoringScope(monitoring_support,
monitoring_support->_full_collection_counters,
diff --git a/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp b/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp
index 8a7040e626d8..7cdbad9054fd 100644
--- a/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp
+++ b/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp
@@ -232,11 +232,13 @@ class G1MonitoringScope : public StackObj {
const char* end_message,
bool all_memory_pools_affected = true);
~G1MonitoringScope();
+ void set_all_memory_pools_affected();
};
class G1YoungGCMonitoringScope : public G1MonitoringScope {
public:
G1YoungGCMonitoringScope(G1MonitoringSupport* monitoring_support, bool all_memory_pools_affected);
+ ~G1YoungGCMonitoringScope();
};
class G1FullGCMonitoringScope : public G1MonitoringScope {
diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp
index f120ac320357..f644625cbb42 100644
--- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp
+++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp
@@ -175,9 +175,7 @@ void G1ParScanThreadState::verify_task(oop* task) const {
void G1ParScanThreadState::verify_task(PartialArrayState* task) const {
assert(task != nullptr, "invariant");
- // Source isn't used for processing, so not recorded in task.
- assert(task->source() == nullptr, "invariant");
- oop p = task->destination();
+ oop p = task->array();
assert(_g1h->is_in_reserved(p),
"task=" PTR_FORMAT " dest=" PTR_FORMAT, p2i(task), p2i(p));
}
@@ -238,10 +236,9 @@ void G1ParScanThreadState::process_array_chunk(objArrayOop obj, size_t start, si
MAYBE_INLINE_EVACUATION
void G1ParScanThreadState::do_partial_array(PartialArrayState* state, bool stolen) {
- // Access state before release by claim().
- objArrayOop to_array = objArrayOop(state->destination());
PartialArraySplitter::Claim claim =
_partial_array_splitter.claim(state, _task_queue, stolen);
+ objArrayOop to_array = claim._array;
G1HeapRegionAttr dest_attr = _g1h->region_attr(to_array);
G1SkipCardMarkSetter x(&_scanner, dest_attr.is_new_survivor());
// Process claimed task.
@@ -250,21 +247,17 @@ void G1ParScanThreadState::do_partial_array(PartialArrayState* state, bool stole
}
MAYBE_INLINE_EVACUATION
-void G1ParScanThreadState::start_partial_objarray(oop from_obj,
- oop to_obj) {
- assert(from_obj->is_forwarded(), "precondition");
- assert(from_obj->forwardee() == to_obj, "precondition");
+void G1ParScanThreadState::start_partial_objarray(oop to_obj) {
assert(to_obj->is_objArray(), "precondition");
assert(!_scanner.do_metadata(), "precondition");
assert(_scanner.skip_card_mark_set(), "precondition");
objArrayOop to_array = objArrayOop(to_obj);
size_t array_length = to_array->length();
- size_t initial_chunk_size =
- // The source array is unused when processing states.
- _partial_array_splitter.start(_task_queue, nullptr, to_array, array_length, ParGCArrayScanChunk);
+ PartialArraySplitter::Claim chunk =
+ _partial_array_splitter.start(_task_queue, to_array, array_length, ParGCArrayScanChunk);
- process_array_chunk(to_array, 0, initial_chunk_size);
+ process_array_chunk(chunk._array, chunk._start, chunk._end);
}
MAYBE_INLINE_EVACUATION
@@ -432,13 +425,16 @@ void G1ParScanThreadState::do_iterate_object(oop const obj,
G1HeapRegionAttr const region_attr,
G1HeapRegionAttr const dest_attr,
uint age) {
+ assert(old->is_forwarded(), "precondition");
+ assert(old->forwardee() == obj, "precondition");
+
// Most objects are not arrays, so do one array check rather than
// checking for each array category for each object.
if (klass->is_array_klass()) {
assert(!klass->is_stack_chunk_instance_klass(), "must be");
if (klass->is_objArray_klass()) {
- start_partial_objarray(old, obj);
+ start_partial_objarray(obj);
} else {
// Nothing needs to be done for typeArrays. Body doesn't contain
// any oops to scan, and the type in the klass will already be handled
diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp
index 65859c5ade7e..a9478c55b9d2 100644
--- a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp
+++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp
@@ -183,7 +183,7 @@ class G1ParScanThreadState : public CHeapObj {
private:
void do_partial_array(PartialArrayState* state, bool stolen);
- void start_partial_objarray(oop from, oop to);
+ void start_partial_objarray(oop to);
void process_array_chunk(objArrayOop obj, size_t start, size_t end);
HeapWord* allocate_copy_slow(G1HeapRegionAttr* dest_attr,
diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp
index 5fdfdec67e03..dcad3bea8b24 100644
--- a/src/hotspot/share/gc/g1/g1Policy.cpp
+++ b/src/hotspot/share/gc/g1/g1Policy.cpp
@@ -47,6 +47,7 @@
#include "logging/log.hpp"
#include "runtime/java.hpp"
#include "runtime/mutexLocker.hpp"
+#include "utilities/checkedCast.hpp"
#include "utilities/debug.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/pair.hpp"
@@ -1431,7 +1432,7 @@ bool G1Policy::next_gc_should_be_mixed() const {
}
size_t G1Policy::allowed_waste_in_collection_set() const {
- return G1HeapWastePercent * _g1h->capacity() / 100;
+ return checked_cast(static_cast(_g1h->capacity()) * G1HeapWastePercent / 100);
}
bool G1Policy::try_get_available_bytes_estimate(size_t& available_bytes) const {
diff --git a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp
index df8838bcd974..3f7632d6aa11 100644
--- a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp
+++ b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp
@@ -28,6 +28,7 @@
#include "gc/shared/gc_globals.hpp"
#include "logging/log.hpp"
#include "runtime/globals_extension.hpp"
+#include "utilities/checkedCast.hpp"
G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults),
_use_adaptive_sizing(true), _min_desired_num_regions(), _max_desired_num_regions(0) {
@@ -119,12 +120,12 @@ G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults),
}
uint G1YoungGenSizer::calculate_default_min_num_regions(uint new_num_regions) {
- uint default_value = (new_num_regions * G1NewSizePercent) / 100;
+ uint default_value = checked_cast(((uint64_t)new_num_regions * G1NewSizePercent) / 100);
return MAX2(1U, default_value);
}
uint G1YoungGenSizer::calculate_default_max_num_regions(uint new_num_regions) {
- uint default_value = (new_num_regions * G1MaxNewSizePercent) / 100;
+ uint default_value = checked_cast(((uint64_t)new_num_regions * G1MaxNewSizePercent) / 100);
return MAX2(1U, default_value);
}
diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp
index 0cb3887833ae..a28e094572b5 100644
--- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp
+++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp
@@ -272,7 +272,7 @@ class ParallelScavengeHeap : public CollectedHeap {
void print_gc_on(outputStream* st) const override;
void gc_threads_do(ThreadClosure* tc) const override;
- WorkerThreads* safepoint_workers() override { return &_workers; }
+ bool supports_parallel_object_iteration() const override { return true; }
PreGenGCValues get_pre_gc_values() const;
void print_heap_change(const PreGenGCValues& pre_gc_values) const;
diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.cpp b/src/hotspot/share/gc/parallel/psCompactionManager.cpp
index 9b96db0e5a68..f8f23dabf8cd 100644
--- a/src/hotspot/share/gc/parallel/psCompactionManager.cpp
+++ b/src/hotspot/share/gc/parallel/psCompactionManager.cpp
@@ -124,17 +124,15 @@ void ParCompactionManager::push_objArray(objArrayOop obj) {
_mark_and_push_closure.do_klass(obj->klass());
size_t array_length = obj->length();
- size_t initial_chunk_size =
- _partial_array_splitter.start(&_marking_stack, obj, nullptr, array_length, ObjArrayMarkingStride);
- follow_array(obj, 0, initial_chunk_size);
+ PartialArraySplitter::Claim chunk =
+ _partial_array_splitter.start(&_marking_stack, obj, array_length, ObjArrayMarkingStride);
+ follow_array(chunk._array, chunk._start, chunk._end);
}
void ParCompactionManager::process_array_chunk(PartialArrayState* state, bool stolen) {
- // Access before release by claim().
- oop obj = state->source();
PartialArraySplitter::Claim claim =
_partial_array_splitter.claim(state, &_marking_stack, stolen);
- follow_array(objArrayOop(obj), claim._start, claim._end);
+ follow_array(claim._array, claim._start, claim._end);
}
void ParCompactionManager::follow_marking_stacks() {
@@ -172,15 +170,13 @@ void ParCompactionManager::drain_region_stacks() {
size_t ParCompactionManager::pop_shadow_region_mt_safe(PSParallelCompact::RegionData* region_ptr) {
MonitorLocker ml(_shadow_region_monitor, Mutex::_no_safepoint_check_flag);
while (true) {
+ // If the heap region has no remaining destination dependencies, use it directly.
+ if (region_ptr->available()) {
+ return InvalidShadow;
+ }
if (!_shadow_region_array->is_empty()) {
return _shadow_region_array->pop();
}
- // Check if the corresponding heap region is available now.
- // If so, we don't need to get a shadow region anymore, and
- // we return InvalidShadow to indicate such a case.
- if (region_ptr->claimed()) {
- return InvalidShadow;
- }
ml.wait(1);
}
}
diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp
index dc2663f38ffd..ede4c02a0a68 100644
--- a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp
+++ b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp
@@ -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
@@ -33,7 +33,6 @@
#include "gc/parallel/psParallelCompact.inline.hpp"
#include "gc/parallel/psStringDedup.hpp"
#include "gc/shared/partialArrayState.hpp"
-#include "gc/shared/partialArrayTaskStepper.inline.hpp"
#include "gc/shared/taskqueue.inline.hpp"
#include "oops/access.inline.hpp"
#include "oops/arrayOop.hpp"
@@ -61,7 +60,7 @@ void ParCompactionManager::push_region(size_t index)
#ifdef ASSERT
const ParallelCompactData& sd = PSParallelCompact::summary_data();
ParallelCompactData::RegionData* const region_ptr = sd.region(index);
- assert(region_ptr->claimed(), "must be claimed");
+ assert(region_ptr->is_normal(), "must be normal fill work");
assert(region_ptr->_pushed++ == 0, "should only be pushed once");
#endif
region_stack()->push(index);
@@ -107,7 +106,7 @@ inline void ParCompactionManager::follow_array(objArrayOop obj, size_t start, si
inline void ParCompactionManager::follow_contents(const ScannerTask& task, bool stolen) {
if (task.is_partial_array_state()) {
- assert(PSParallelCompact::mark_bitmap()->is_marked(task.to_partial_array_state()->source()), "should be marked");
+ assert(PSParallelCompact::mark_bitmap()->is_marked(task.to_partial_array_state()->array()), "should be marked");
process_array_chunk(task.to_partial_array_state(), stolen);
} else {
oop obj = task.to_oop();
diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp
index 8913c90a4bd3..bd8b1c3af95c 100644
--- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp
+++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp
@@ -111,31 +111,13 @@ const size_t ParallelCompactData::RegionSizeOffsetMask = RegionSize - 1;
const size_t ParallelCompactData::RegionAddrOffsetMask = RegionSizeBytes - 1;
const size_t ParallelCompactData::RegionAddrMask = ~RegionAddrOffsetMask;
-const ParallelCompactData::RegionData::region_sz_t
-ParallelCompactData::RegionData::dc_shift = 27;
-
-const ParallelCompactData::RegionData::region_sz_t
-ParallelCompactData::RegionData::dc_mask = ~0U << dc_shift;
-
-const ParallelCompactData::RegionData::region_sz_t
-ParallelCompactData::RegionData::dc_one = 0x1U << dc_shift;
-
-const ParallelCompactData::RegionData::region_sz_t
-ParallelCompactData::RegionData::los_mask = ~dc_mask;
-
-const ParallelCompactData::RegionData::region_sz_t
-ParallelCompactData::RegionData::dc_claimed = 0x8U << dc_shift;
-
-const ParallelCompactData::RegionData::region_sz_t
-ParallelCompactData::RegionData::dc_completed = 0xcU << dc_shift;
-
bool ParallelCompactData::RegionData::is_clear() {
return (_destination == nullptr) &&
(_source_region == 0) &&
(_partial_obj_addr == nullptr) &&
(_partial_obj_size == 0) &&
- (dc_and_los() == 0) &&
- (shadow_state() == 0);
+ (_live_obj_size.load_relaxed() == 0) &&
+ (state() == 0);
}
#ifdef ASSERT
@@ -144,11 +126,97 @@ void ParallelCompactData::RegionData::verify_clear() {
assert(_source_region == 0, "inv");
assert(_partial_obj_addr == nullptr, "inv");
assert(_partial_obj_size == 0, "inv");
- assert(dc_and_los() == 0, "inv");
- assert(shadow_state() == 0, "inv");
+ assert(_live_obj_size.load_relaxed() == 0, "inv");
+ assert(state() == 0, "inv");
}
#endif
+void ParallelCompactData::RegionData::set_destination_count(uint count)
+{
+ assert(count <= max_destination_count, "count too large");
+ const state_t old_state = state();
+ assert(state_status(old_state) == status_init, "cannot reset processed region");
+ _state.store_relaxed(with_dest_count(old_state, static_cast(count)));
+}
+
+void ParallelCompactData::RegionData::decrement_destination_count()
+{
+ state_t old_state = _state.fetch_then_sub(static_cast(1));
+ assert(state_dest_count(old_state) > 0, "count would go negative");
+}
+
+void ParallelCompactData::RegionData::set_completed()
+{
+ const state_t old_state = state();
+ assert(state_status(old_state) == status_normal, "can only complete normal regions");
+ _state.store_relaxed(with_status(old_state, status_completed));
+}
+
+bool ParallelCompactData::RegionData::available_to_normal()
+{
+ const state_t old_state = state();
+ assert(state_status(old_state) == status_init, "initial regions must be unprocessed");
+ if (state_dest_count(old_state) == 0) {
+ _state.store_relaxed(make_state(0, status_normal));
+ return true;
+ }
+ return false;
+}
+
+bool ParallelCompactData::RegionData::try_mark_normal()
+{
+ assert(available(), "region must have no remaining destination dependencies");
+ return _state.compare_set(make_state(0, status_init), make_state(0, status_normal));
+}
+
+bool ParallelCompactData::RegionData::try_complete_filled_shadow()
+{
+ assert(available(), "region must have no remaining destination dependencies");
+ return _state.compare_set(make_state(0, status_filled_shadow), make_state(0, status_completed));
+}
+
+bool ParallelCompactData::RegionData::try_mark_shadow() {
+ state_t old_state = state();
+ while (true) {
+ if (state_dest_count(old_state) == 0 ||
+ state_status(old_state) != status_init) {
+ return false;
+ }
+ const state_t new_state = with_status(old_state, status_shadow);
+ if (_state.compare_set(old_state, new_state)) {
+ return true;
+ }
+ old_state = state();
+ }
+}
+
+bool ParallelCompactData::RegionData::finish_shadow_fill() {
+ state_t old_state = state();
+ while (true) {
+ state_t new_state;
+ bool copy_now = false;
+ assert(state_status(old_state) == status_shadow, "must be shadow");
+ if (state_dest_count(old_state) == 0) {
+ new_state = with_status(old_state, status_completed);
+ copy_now = true;
+ } else {
+ new_state = with_status(old_state, status_filled_shadow);
+ }
+ if (_state.compare_set(old_state, new_state)) {
+ return copy_now;
+ }
+ old_state = state();
+ }
+}
+
+void ParallelCompactData::RegionData::shadow_to_normal() {
+ assert(available(), "region must have no remaining destination dependencies");
+ const state_t old_state = make_state(0, status_shadow);
+ const state_t new_state = make_state(0, status_normal);
+ bool result = _state.compare_set(old_state, new_state);
+ assert(result, "Fail to mark the region as normal");
+}
+
SpaceInfo PSParallelCompact::_space_info[PSParallelCompact::last_space_id];
HeapWord* PSParallelCompact::_old_space_dense_prefix = nullptr;
HeapWord* PSParallelCompact::_old_space_new_top = nullptr;
@@ -1508,10 +1576,8 @@ void PSParallelCompact::prepare_region_draining_tasks(uint parallel_gc_threads)
// Populate with [beg_region, end_region)
for (size_t cur = end_region - 1; cur + 1 > beg_region; --cur) {
- if (sd.region(cur)->claim_unsafe()) {
+ if (sd.region(cur)->available_to_normal()) {
ParCompactionManager* cm = ParCompactionManager::gc_thread_compaction_manager(worker_id);
- bool result = sd.region(cur)->mark_normal();
- assert(result, "Must succeed at this point.");
cm->push_region(cur);
region_logger.handle(cur);
// Assign regions to tasks in round-robin fashion.
@@ -1724,15 +1790,15 @@ void PSParallelCompact::verify_regions_after_compaction() {
// [beg_region, new_top_region) must be completed (as compaction targets)
for (cur_region = beg_region; cur_region < new_top_region; ++cur_region) {
const RegionData* const c = sd.region(cur_region);
- assert(c->completed(), "region %zu not filled: destination_count=%u",
- cur_region, c->destination_count());
+ assert(c->completed(), "region %zu not filled: destination_count=%u status=%s",
+ cur_region, c->destination_count(), c->status_name());
}
// [new_top_region, old_top_region) must be empty.
for (cur_region = new_top_region; cur_region < old_top_region; ++cur_region) {
const RegionData* const c = sd.region(cur_region);
- assert(c->available(), "region %zu not empty: destination_count=%u",
- cur_region, c->destination_count());
+ assert(c->available() && c->is_init(), "region %zu not empty: destination_count=%u status=%s",
+ cur_region, c->destination_count(), c->status_name());
}
}
{
@@ -1748,8 +1814,8 @@ void PSParallelCompact::verify_regions_after_compaction() {
for (size_t cur_region = beg_region; cur_region < end_region; ++cur_region) {
const RegionData* const c = sd.region(cur_region);
- assert(c->available(), "region %zu not empty: destination_count=%u",
- cur_region, c->destination_count());
+ assert(c->available() && c->is_init(), "region %zu not empty: destination_count=%u status=%s",
+ cur_region, c->destination_count(), c->status_name());
}
}
}
@@ -1861,17 +1927,16 @@ void PSParallelCompact::decrement_destination_counts(ParCompactionManager* cm,
for (RegionData* cur = beg; cur < end; ++cur) {
assert(cur->data_size() > 0, "region must have live data");
cur->decrement_destination_count();
- if (cur < enqueue_end && cur->available() && cur->claim()) {
- if (cur->mark_normal()) {
+ if (cur < enqueue_end && cur->available()) {
+ if (cur->try_mark_normal()) {
cm->push_region(sd.region(cur));
- } else if (cur->mark_copied()) {
+ } else if (cur->try_complete_filled_shadow()) {
// Try to copy the content of the shadow region back to its corresponding
// heap region if the shadow region is filled. Otherwise, the GC thread
// fills the shadow region will copy the data back (see
// MoveAndUpdateShadowClosure::complete_region).
copy_back(sd.region_to_addr(cur->shadow_region()), sd.region_to_addr(cur));
ParCompactionManager::push_shadow_region_mt_safe(cur->shadow_region());
- cur->set_completed();
}
}
}
@@ -2144,7 +2209,7 @@ bool PSParallelCompact::steal_unavailable_region(ParCompactionManager* cm, size_
uint active_gc_threads = ParallelScavengeHeap::heap()->workers().active_workers();
while (next < old_new_top) {
- if (sd.region(next)->mark_shadow()) {
+ if (sd.region(next)->try_mark_shadow()) {
region_idx = next;
return true;
}
@@ -2205,7 +2270,7 @@ void MoveAndUpdateClosure::copy_partial_obj(size_t partial_obj_size)
}
void MoveAndUpdateClosure::complete_region(HeapWord* dest_addr, PSParallelCompact::RegionData* region_ptr) {
- assert(region_ptr->shadow_state() == ParallelCompactData::RegionData::NormalRegion, "Region should be finished");
+ assert(region_ptr->is_normal(), "Region should be normal");
region_ptr->set_completed();
}
@@ -2235,18 +2300,16 @@ void MoveAndUpdateClosure::do_addr(HeapWord* addr, size_t words, markWord mark)
}
void MoveAndUpdateShadowClosure::complete_region(HeapWord* dest_addr, PSParallelCompact::RegionData* region_ptr) {
- assert(region_ptr->shadow_state() == ParallelCompactData::RegionData::ShadowRegion, "Region should be shadow");
+ assert(region_ptr->is_shadow(), "Region should be shadow");
// Record the shadow region index
region_ptr->set_shadow_region(_shadow);
- // Mark the shadow region as filled to indicate the data is ready to be
- // copied back
- region_ptr->mark_filled();
+ // Finish shadow filling and determine whether this worker must copy back immediately.
+ bool copy_now = region_ptr->finish_shadow_fill();
// Try to copy the content of the shadow region back to its corresponding
// heap region if available; the GC thread that decreases the destination
// count to zero will do the copying otherwise (see
// PSParallelCompact::decrement_destination_counts).
- if (((region_ptr->available() && region_ptr->claim()) || region_ptr->claimed()) && region_ptr->mark_copied()) {
- region_ptr->set_completed();
+ if (copy_now) {
PSParallelCompact::copy_back(PSParallelCompact::summary_data().region_to_addr(_shadow), dest_addr);
ParCompactionManager::push_shadow_region_mt_safe(_shadow);
}
diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp
index 3eb3691c8c0f..c568b26dc04f 100644
--- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp
+++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp
@@ -107,7 +107,7 @@ class ParallelCompactData
// in this region (words). This does not include the partial object
// extending onto the region (if any), or the part of an object that extends
// onto the next region (if any).
- size_t live_obj_size() const { return dc_and_los() & los_mask; }
+ size_t live_obj_size() const { return _live_obj_size.load_relaxed(); }
// Total live data that lies within the region (words).
size_t data_size() const { return partial_obj_size() + live_obj_size(); }
@@ -117,31 +117,29 @@ class ParallelCompactData
// values of destination_count are
//
// 0 - data from the region will be compacted completely into itself, or the
- // region is empty. The region can be claimed and then filled.
+ // region is empty. The region can be filled immediately.
// 1 - data from the region will be compacted into 1 other region; some
// data from the region may also be compacted into the region itself.
// 2 - data from the region will be copied to 2 other regions.
//
// During compaction as regions are emptied, the destination_count is
- // decremented (atomically) and when it reaches 0, it can be claimed and
- // then filled.
- //
- // A region is claimed for processing by atomically changing the
- // destination_count to the claimed value (dc_claimed). After a region has
- // been filled, the destination_count should be set to the completed value
- // (dc_completed).
- inline uint destination_count() const;
- inline uint destination_count_raw() const;
-
- // Whether this region is available to be claimed, has been claimed, or has
- // been completed.
- //
- // Minor subtlety: claimed() returns true if the region is marked
- // completed(), which is desirable since a region must be claimed before it
- // can be completed.
- bool available() const { return dc_and_los() < dc_one; }
- bool claimed() const { return dc_and_los() >= dc_claimed; }
- bool completed() const { return dc_and_los() >= dc_completed; }
+ // decremented atomically. When it reaches 0, the region has no remaining
+ // destination dependencies.
+ uint destination_count() const { return state_dest_count(state()); }
+
+ // Whether this region has no remaining destination dependencies.
+ bool available() const { return destination_count() == 0; }
+ bool completed() const { return state_status(state()) == status_completed; }
+ const char* status_name() const {
+ switch (state_status(state())) {
+ case status_init: return "init";
+ case status_normal: return "normal";
+ case status_shadow: return "shadow";
+ case status_filled_shadow: return "filled_shadow";
+ case status_completed: return "completed";
+ default: return "unknown";
+ }
+ }
// These are not atomic.
void set_destination(HeapWord* addr) { _destination = addr; }
@@ -152,40 +150,29 @@ class ParallelCompactData
_partial_obj_size = (region_sz_t) words;
}
- inline void set_destination_count(uint count);
+ void set_destination_count(uint count);
- inline void set_completed();
- inline bool claim_unsafe();
+ void set_completed();
+ bool available_to_normal();
// These are atomic.
- inline void add_live_obj(size_t words);
- inline void decrement_destination_count();
- inline bool claim();
-
- // Possible values of _shadow_state, and transition is as follows
- // Normal Path:
- // UnusedRegion -> mark_normal() -> NormalRegion
- // Shadow Path:
- // UnusedRegion -> mark_shadow() -> ShadowRegion ->
- // mark_filled() -> FilledShadow -> mark_copied() -> CopiedShadow
- static const int UnusedRegion = 0; // The region is not collected yet
- static const int ShadowRegion = 1; // Stolen by an idle thread, and a shadow region is created for it
- static const int FilledShadow = 2; // Its shadow region has been filled and ready to be copied back
- static const int CopiedShadow = 3; // The data of the shadow region has been copied back
- static const int NormalRegion = 4; // The region will be collected by the original parallel algorithm
-
- // Mark the current region as normal or shadow to enter different processing paths
- inline bool mark_normal();
- inline bool mark_shadow();
- // Mark the shadow region as filled and ready to be copied back
- inline void mark_filled();
- // Mark the shadow region as copied back to avoid double copying.
- inline bool mark_copied();
- // Special case: see the comment in PSParallelCompact::fill_and_update_shadow_region.
- // Return to the normal path here
- inline void shadow_to_normal();
-
- int shadow_state() { return _shadow_state.load_relaxed(); }
+ void add_live_obj(size_t words) {
+ assert(words <= RegionSize - live_obj_size(), "overflow");
+ _live_obj_size.add_then_fetch(static_cast(words));
+ }
+ void decrement_destination_count();
+ bool try_mark_normal();
+ bool try_complete_filled_shadow();
+
+ // Mark the current region as shadow to enter the shadow processing path.
+ bool try_mark_shadow();
+ // Finish shadow filling. Returns true if the caller must copy back immediately.
+ bool finish_shadow_fill();
+ // Special case: see PSParallelCompact::fill_and_update_shadow_region.
+ void shadow_to_normal();
+ bool is_init() const { return state_status(state()) == status_init; }
+ bool is_normal() const { return state_status(state()) == status_normal; }
+ bool is_shadow() const { return state_status(state()) == status_shadow; }
bool is_clear();
@@ -194,25 +181,76 @@ class ParallelCompactData
private:
// The type used to represent object sizes within a region.
typedef uint region_sz_t;
+ // The type used to represent destination count and compaction state.
+ typedef uint state_t;
- // Constants for manipulating the _dc_and_los field, which holds both the
- // destination count and live obj size. The live obj size lives at the
- // least significant end so no masking is necessary when adding.
- static const region_sz_t dc_shift; // Shift amount.
- static const region_sz_t dc_mask; // Mask for destination count.
- static const region_sz_t dc_one; // 1, shifted appropriately.
- static const region_sz_t dc_claimed; // Region has been claimed.
- static const region_sz_t dc_completed; // Region has been completed.
- static const region_sz_t los_mask; // Mask for live obj size.
+ // Layout of _state:
+ //
+ // 31-5 4-2 1-0
+ // +--------+---------+----------+
+ // unused status dest_count
+ // +--------+---------+----------+
+ // | | | |
+ // | | |* 1-0 dest_count: destination regions still to be copied
+ // | | | from this source region (0-2)
+ // | |* 2-4 status: compaction path progress, see StateBits
+ // |* 5-31 unused
+ //
+ // Complete status routes:
+ // 1. Normal fill, initially or later available:
+ // status_init -> status_normal -> status_completed
+ // The first transition happens during initial task setup or when dest_count reaches 0.
+ //
+ // 2. Shadow selected, but no shadow region is available when dest_count reaches 0:
+ // status_init -> status_shadow -> status_normal -> status_completed
+ // status_shadow with dest_count 0 can fall back to normal filling.
+ //
+ // 3. Shadow fill completes before dest_count reaches 0:
+ // status_init -> status_shadow -> status_filled_shadow -> status_completed
+ // The last transition claims copy-back when dest_count reaches 0.
+ //
+ // 4. Dest_count reaches 0 before shadow fill completes:
+ // status_init -> status_shadow -> status_completed
+ // The last transition claims immediate copy-back when shadow fill completes.
+ enum StateBits : state_t {
+ max_destination_count = 2,
+ dest_count_mask = 0x3,
+ status_shift = 2,
+ status_mask = 0x7 << status_shift,
+
+ status_init = 0 << status_shift, // Not yet selected for filling.
+ status_normal = 1 << status_shift, // Selected for direct filling.
+ status_shadow = 2 << status_shift, // Selected for filling via a shadow region.
+ status_filled_shadow = 3 << status_shift, // Shadow filled, waiting for destination dependencies.
+ status_completed = 4 << status_shift // Direct fill done, or shadow copy-back exclusively claimed.
+ };
+
+ // The destination count occupies the low status_shift bits, so decrementing the whole
+ // word in decrement_destination_count() decrements the count alone.
+ static_assert(dest_count_mask == ((1U << status_shift) - 1),
+ "dest_count field width must equal status_shift");
+ static_assert(max_destination_count <= dest_count_mask,
+ "max_destination_count must fit in the dest_count field");
HeapWord* _destination;
size_t _source_region;
HeapWord* _partial_obj_addr;
region_sz_t _partial_obj_size;
- Atomic _dc_and_los;
- Atomic _shadow_state;
+ Atomic _live_obj_size;
+ Atomic _state;
- region_sz_t dc_and_los() const { return _dc_and_los.load_relaxed(); }
+ state_t state() const { return _state.load_relaxed(); }
+ static state_t make_state(state_t dest_count, state_t status) {
+ return dest_count | status;
+ }
+ static state_t state_dest_count(state_t state) { return state & dest_count_mask; }
+ static state_t state_status(state_t state) { return state & status_mask; }
+ static state_t with_dest_count(state_t state, state_t dest_count) {
+ return (state & ~dest_count_mask) | dest_count;
+ }
+ static state_t with_status(state_t state, state_t status) {
+ return (state & ~status_mask) | status;
+ }
#ifdef ASSERT
public:
uint _pushed; // 0 until region is pushed onto a stack
@@ -269,85 +307,6 @@ class ParallelCompactData
size_t _region_count;
};
-inline uint
-ParallelCompactData::RegionData::destination_count_raw() const
-{
- return dc_and_los() & dc_mask;
-}
-
-inline uint
-ParallelCompactData::RegionData::destination_count() const
-{
- return destination_count_raw() >> dc_shift;
-}
-
-inline void
-ParallelCompactData::RegionData::set_destination_count(uint count)
-{
- assert(count <= (dc_completed >> dc_shift), "count too large");
- const region_sz_t live_sz = (region_sz_t) live_obj_size();
- _dc_and_los.store_relaxed((count << dc_shift) | live_sz);
-}
-
-inline void ParallelCompactData::RegionData::decrement_destination_count()
-{
- assert(dc_and_los() < dc_claimed, "already claimed");
- assert(dc_and_los() >= dc_one, "count would go negative");
- _dc_and_los.add_then_fetch(dc_mask);
-}
-
-inline void ParallelCompactData::RegionData::set_completed()
-{
- assert(claimed(), "must be claimed first");
- _dc_and_los.store_relaxed(dc_completed | (region_sz_t) live_obj_size());
-}
-
-// MT-unsafe claiming of a region. Should only be used during single threaded
-// execution.
-inline bool ParallelCompactData::RegionData::claim_unsafe()
-{
- if (available()) {
- _dc_and_los.store_relaxed(dc_and_los() | dc_claimed);
- return true;
- }
- return false;
-}
-
-inline void ParallelCompactData::RegionData::add_live_obj(size_t words)
-{
- assert(words <= (size_t)los_mask - live_obj_size(), "overflow");
- _dc_and_los.add_then_fetch(static_cast(words));
-}
-
-inline bool ParallelCompactData::RegionData::claim()
-{
- const region_sz_t los = static_cast(live_obj_size());
- return _dc_and_los.compare_set(los, dc_claimed | los);
-}
-
-inline bool ParallelCompactData::RegionData::mark_normal() {
- return _shadow_state.compare_set(UnusedRegion, NormalRegion);
-}
-
-inline bool ParallelCompactData::RegionData::mark_shadow() {
- if (shadow_state() != UnusedRegion) return false;
- return _shadow_state.compare_set(UnusedRegion, ShadowRegion);
-}
-
-inline void ParallelCompactData::RegionData::mark_filled() {
- int old = _shadow_state.compare_exchange(ShadowRegion, FilledShadow);
- assert(old == ShadowRegion, "Fail to mark the region as filled");
-}
-
-inline bool ParallelCompactData::RegionData::mark_copied() {
- return _shadow_state.compare_set(FilledShadow, CopiedShadow);
-}
-
-void ParallelCompactData::RegionData::shadow_to_normal() {
- int old = _shadow_state.compare_exchange(ShadowRegion, NormalRegion);
- assert(old == ShadowRegion, "Fail to mark the region as finish");
-}
-
inline ParallelCompactData::RegionData*
ParallelCompactData::region(size_t region_idx) const
{
diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.cpp b/src/hotspot/share/gc/parallel/psPromotionManager.cpp
index 157db5c07ddc..628935ba4d53 100644
--- a/src/hotspot/share/gc/parallel/psPromotionManager.cpp
+++ b/src/hotspot/share/gc/parallel/psPromotionManager.cpp
@@ -263,28 +263,22 @@ void PSPromotionManager::process_array_chunk(objArrayOop obj, size_t start, size
}
void PSPromotionManager::process_array_chunk(PartialArrayState* state, bool stolen) {
- // Access before release by claim().
- objArrayOop to_array = objArrayOop(state->destination());
- precond(to_array->is_array_with_oops());
-
PartialArraySplitter::Claim claim =
_partial_array_splitter.claim(state, &_claimed_stack_depth, stolen);
+ precond(claim._array->is_array_with_oops());
- process_array_chunk(to_array, claim._start, claim._end);
+ process_array_chunk(claim._array, claim._start, claim._end);
}
-void PSPromotionManager::push_objArray(oop old_obj, oop new_obj) {
- assert(old_obj->is_forwarded(), "precondition");
- assert(old_obj->forwardee() == new_obj, "precondition");
+void PSPromotionManager::push_objArray(oop new_obj) {
precond(new_obj->is_array_with_oops());
objArrayOop to_array = objArrayOop(new_obj);
size_t array_length = to_array->length();
- size_t initial_chunk_size =
- // The source array is unused when processing states.
- _partial_array_splitter.start(&_claimed_stack_depth, nullptr, to_array, array_length, ParGCArrayScanChunk);
+ PartialArraySplitter::Claim chunk =
+ _partial_array_splitter.start(&_claimed_stack_depth, to_array, array_length, ParGCArrayScanChunk);
- process_array_chunk(to_array, 0, initial_chunk_size);
+ process_array_chunk(chunk._array, chunk._start, chunk._end);
}
oop PSPromotionManager::oop_promotion_failed(oop obj, markWord obj_mark) {
diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.hpp
index 287808429d3a..dd91fccd27c7 100644
--- a/src/hotspot/share/gc/parallel/psPromotionManager.hpp
+++ b/src/hotspot/share/gc/parallel/psPromotionManager.hpp
@@ -100,7 +100,7 @@ class PSPromotionManager {
void process_array_chunk(PartialArrayState* state, bool stolen);
void process_array_chunk(objArrayOop obj, size_t start, size_t end);
- void push_objArray(oop old_obj, oop new_obj);
+ void push_objArray(oop new_obj);
inline void promotion_trace_event(oop new_obj, Klass* klass, size_t obj_size,
uint age, bool tenured,
diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp
index 2f7fb7cd970e..fa2f1809db23 100644
--- a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp
+++ b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp
@@ -299,7 +299,7 @@ inline oop PSPromotionManager::copy_unmarked_to_survivor_space(oop o,
// So, the is_array_with_oops test would be very infrequent.
if (new_obj_size > _min_array_size_for_chunking &&
new_obj->is_array_with_oops()) {
- push_objArray(o, new_obj);
+ push_objArray(new_obj);
} else {
// we'll just push its contents
push_contents(new_obj, klass);
diff --git a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp
index 7a773640466c..511ef579b543 100644
--- a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp
+++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp
@@ -747,7 +747,7 @@ void BarrierSetC2::clone(GraphKit* kit, Node* src_base, Node* dst_base, Node* si
Node* n = kit->gvn().transform(ac);
if (n == ac) {
const TypePtr* raw_adr_type = TypeRawPtr::BOTTOM;
- ac->set_adr_type(TypeRawPtr::BOTTOM);
+ ac->set_out_adr_type(TypeRawPtr::BOTTOM);
kit->set_predefined_output_for_runtime_call(ac, ac->in(TypeFunc::Memory), raw_adr_type);
} else {
kit->set_all_memory(n);
diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp
index c1305f961fbb..e153565b59c0 100644
--- a/src/hotspot/share/gc/shared/collectedHeap.hpp
+++ b/src/hotspot/share/gc/shared/collectedHeap.hpp
@@ -402,6 +402,10 @@ class CollectedHeap : public CHeapObj {
// Iterate over all objects, calling "cl.do_object" on each.
virtual void object_iterate(ObjectClosure* cl) = 0;
+ // Returns true if the collector implements parallel_object_iterator(), so
+ // heap dump and heap inspection may iterate the heap with multiple worker threads.
+ virtual bool supports_parallel_object_iteration() const { return false; }
+
virtual ParallelObjectIteratorImpl* parallel_object_iterator(uint thread_num) {
return nullptr;
}
@@ -475,17 +479,6 @@ class CollectedHeap : public CHeapObj {
// this collector. The default implementation returns false.
virtual bool supports_concurrent_gc_breakpoints() const;
- // Workers used in non-GC safepoints for parallel safepoint cleanup. If this
- // method returns null, cleanup tasks are done serially in the VMThread. See
- // `SafepointSynchronize::do_cleanup_tasks` for details.
- // GCs using a GC worker thread pool inside GC safepoints may opt to share
- // that pool with non-GC safepoints, avoiding creating extraneous threads.
- // Such sharing is safe, because GC safepoints and non-GC safepoints never
- // overlap. For example, `G1CollectedHeap::workers()` (for GC safepoints) and
- // `G1CollectedHeap::safepoint_workers()` (for non-GC safepoints) return the
- // same thread-pool.
- virtual WorkerThreads* safepoint_workers() { return nullptr; }
-
// Support for object pinning. This is used by JNI Get*Critical()
// and Release*Critical() family of functions. The GC must guarantee
// that pinned objects never move and don't get reclaimed as garbage.
diff --git a/src/hotspot/share/gc/shared/gcArguments.cpp b/src/hotspot/share/gc/shared/gcArguments.cpp
index ec3d758c00a6..569072845f67 100644
--- a/src/hotspot/share/gc/shared/gcArguments.cpp
+++ b/src/hotspot/share/gc/shared/gcArguments.cpp
@@ -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.
* Copyright (c) 2017, Red Hat, Inc. and/or its affiliates.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@@ -279,10 +279,6 @@ void GCArguments::initialize_heap_flags_and_sizes() {
}
}
- if (FLAG_IS_DEFAULT(SoftMaxHeapSize)) {
- FLAG_SET_ERGO(SoftMaxHeapSize, MaxHeapSize);
- }
-
FLAG_SET_ERGO(MinHeapDeltaBytes, align_up(MinHeapDeltaBytes, SpaceAlignment));
if (checked_cast(ObjectAlignmentInBytes) > GCCardSizeInBytes) {
diff --git a/src/hotspot/share/gc/shared/gcVMOperations.cpp b/src/hotspot/share/gc/shared/gcVMOperations.cpp
index 6dbfd56b4e97..59209fb8de44 100644
--- a/src/hotspot/share/gc/shared/gcVMOperations.cpp
+++ b/src/hotspot/share/gc/shared/gcVMOperations.cpp
@@ -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
@@ -204,16 +204,7 @@ void VM_GC_HeapInspection::doit() {
}
}
HeapInspection inspect;
- WorkerThreads* workers = Universe::heap()->safepoint_workers();
- if (workers != nullptr) {
- // The GC provided a WorkerThreads to be used during a safepoint.
- // Can't run with more threads than provided by the WorkerThreads.
- const uint capped_parallel_thread_num = MIN2(_parallel_thread_num, workers->max_workers());
- WithActiveWorkers with_active_workers(workers, capped_parallel_thread_num);
- inspect.heap_inspection(_out, workers);
- } else {
- inspect.heap_inspection(_out, nullptr);
- }
+ inspect.heap_inspection(_out, _parallel_thread_num);
}
VM_CollectForMetadataAllocation::VM_CollectForMetadataAllocation(ClassLoaderData* loader_data,
diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp
index 1d2f45397aa7..236388b14dc4 100644
--- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp
+++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp
@@ -34,6 +34,9 @@
#if INCLUDE_PARALLELGC
#include "gc/parallel/jvmFlagConstraintsParallel.hpp"
#endif
+#if INCLUDE_ZGC
+#include "gc/z/jvmFlagConstraintsZ.hpp"
+#endif
/*
* Here we have GC arguments constraints functions, which are called automatically
@@ -76,6 +79,7 @@ JVMFlag::Error MaxPLABSizeBounds(const char* name, size_t value, bool verbose);
#define GC_CONSTRAINTS(f) \
SHARED_GC_CONSTRAINTS(f) \
G1GC_ONLY(G1_GC_CONSTRAINTS(f)) \
- PARALLELGC_ONLY(PARALLEL_GC_CONSTRAINTS(f))
+ PARALLELGC_ONLY(PARALLEL_GC_CONSTRAINTS(f)) \
+ ZGC_ONLY(Z_GC_CONSTRAINTS(f))
#endif // SHARE_GC_SHARED_JVMFLAGCONSTRAINTSGC_HPP
diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.cpp b/src/hotspot/share/gc/shared/partialArraySplitter.cpp
index 04884d5e666c..68f494eb67ec 100644
--- a/src/hotspot/share/gc/shared/partialArraySplitter.cpp
+++ b/src/hotspot/share/gc/shared/partialArraySplitter.cpp
@@ -22,7 +22,6 @@
*
*/
-#include "gc/shared/gc_globals.hpp"
#include "gc/shared/partialArraySplitter.hpp"
#include "gc/shared/partialArrayState.hpp"
#include "utilities/macros.hpp"
diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.hpp
index 340f370d1d5d..5235d2cd11da 100644
--- a/src/hotspot/share/gc/shared/partialArraySplitter.hpp
+++ b/src/hotspot/share/gc/shared/partialArraySplitter.hpp
@@ -32,16 +32,18 @@
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
-class outputStream;
-
-// Helper class for splitting the processing of a large objArray into multiple
-// tasks, to permit multiple threads to work on different pieces of the array
-// in parallel.
+// Each worker uses its own splitter to scan arrays in parallel.
+// Queued tasks share an atomic progress record; each claim returns a
+// disjoint, self-contained chunk and decrements the record's ref-count.
class PartialArraySplitter {
PartialArrayStateAllocator _allocator;
PartialArrayTaskStepper _stepper;
TASKQUEUE_STATS_ONLY(PartialArrayTaskStats _stats;)
+ // The state's ref-count must already include these tasks before publication.
+ template
+ void enqueue(Queue* queue, PartialArrayState* state, uint count);
+
public:
PartialArraySplitter(PartialArrayStateManager* manager,
uint num_workers);
@@ -49,53 +51,30 @@ class PartialArraySplitter {
NONCOPYABLE(PartialArraySplitter);
- // Setup to process an objArray in chunks.
- //
- // from_array is the array found by the collector that needs processing. It
- // may be null if to_array contains everything needed for processing.
- //
- // to_array is an unprocessed (possibly partial) copy of from_array, or null
- // if a copy of from_array is not required.
- //
- // length is their length in elements.
- //
- // chunk_size the size of a single chunk.
- //
- // If t is a ScannerTask, queue->push(t) must be a valid expression. The
- // result of that expression is ignored.
- //
- // Returns the size of the initial chunk that is to be processed by the
- // caller.
- //
- // Adds PartialArrayState ScannerTasks to the queue if needed to process the
- // array in chunks. This permits other workers to steal and process them
- // even while the caller is processing the initial chunk. If length doesn't
- // exceed the chunk size then the result will be length, indicating the
- // caller is to process the entire array. In this case, no tasks will have
- // been added to the queue.
- template
- size_t start(Queue* queue,
- objArrayOop from_array,
- objArrayOop to_array,
- size_t length,
- size_t chunk_size);
-
- // Result type for claim(), carrying multiple values. Provides the claimed
- // chunk's start and end array indices.
+ // A chunk remains valid after its progress record has been recycled. It
+ // does not own or keep the array alive; that is the collector's responsibility.
struct Claim {
+ objArrayOop _array;
size_t _start;
size_t _end;
};
+ // Start processing array[0, length). Return the initial chunk and enqueue
+ // a continuation if any full-sized chunks remain. Process the remainder
+ // first so every queued task claims exactly chunk_size elements.
+ template
+ Claim start(Queue* queue,
+ objArrayOop array,
+ size_t length,
+ size_t chunk_size);
+
// Claims a chunk from state, returning the index range for that chunk. The
// caller is expected to process that chunk. Adds more state-based tasks to
// the queue if needed, permitting other workers to steal and process them
// even while the caller is processing this claim.
//
- // Releases the state. Callers must not use state after the call to this
- // function. The state may have been recycled and reused.
- //
- // The queue has the same requirements as for start().
+ // Decrements the state's ref-count for the current task. Callers must not
+ // use state after this call; it may have been recycled and reused.
//
// stolen indicates whether the state task was obtained from this queue or
// stolen from some other queue.
diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp
index 7679358e2188..dc702072f3f9 100644
--- a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp
+++ b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp
@@ -36,26 +36,28 @@
#include "utilities/macros.hpp"
template
-size_t PartialArraySplitter::start(Queue* queue,
- objArrayOop source,
- objArrayOop destination,
- size_t length,
- size_t chunk_size) {
+void PartialArraySplitter::enqueue(Queue* queue, PartialArrayState* state, uint count) {
+ TASKQUEUE_STATS_ONLY(_stats.inc_pushed(count);)
+ for (uint i = 0; i < count; ++i) {
+ queue->push(ScannerTask(state));
+ }
+}
+
+template
+PartialArraySplitter::Claim
+PartialArraySplitter::start(Queue* queue,
+ objArrayOop array,
+ size_t length,
+ size_t chunk_size) {
precond(chunk_size > 0);
- PartialArrayTaskStepper::Step step = _stepper.start(length, chunk_size);
- // Push initial partial scan tasks.
- if (step._ncreate > 0) {
- TASKQUEUE_STATS_ONLY(_stats.inc_split(););
- TASKQUEUE_STATS_ONLY(_stats.inc_pushed(step._ncreate);)
+ size_t end = length % chunk_size;
+ if (end < length) {
+ TASKQUEUE_STATS_ONLY(_stats.inc_split();)
PartialArrayState* state =
- _allocator.allocate(source, destination, step._index, length, chunk_size, step._ncreate);
- for (uint i = 0; i < step._ncreate; ++i) {
- queue->push(ScannerTask(state));
- }
- } else {
- assert(step._index == length, "invariant");
+ _allocator.allocate(array, end, length, chunk_size);
+ enqueue(queue, state, 1);
}
- return step._index;
+ return Claim{array, 0, end};
}
template
@@ -66,21 +68,20 @@ PartialArraySplitter::claim(PartialArrayState* state, Queue* queue, bool stolen)
_stats.inc_processed();
#endif // TASKQUEUE_STATS
- // Claim a chunk and get number of additional tasks to enqueue.
- PartialArrayTaskStepper::Step step = _stepper.next(state);
- // Push additional tasks.
- if (step._ncreate > 0) {
- TASKQUEUE_STATS_ONLY(_stats.inc_pushed(step._ncreate);)
- // Adjust reference count for tasks being added to the queue.
- state->add_references(step._ncreate);
- for (uint i = 0; i < step._ncreate; ++i) {
- queue->push(ScannerTask(state));
- }
+ size_t start = state->claim_next();
+ size_t chunk_size = state->_chunk_size;
+ uint count = _stepper.continuation_tasks(start, state->_length, chunk_size);
+ if (count > 0) {
+ // Increment the ref-count for all new tasks before publication. A thief
+ // may immediately claim and release any task we push.
+ state->add_references(count);
+ enqueue(queue, state, count);
}
- size_t chunk_size = state->chunk_size();
- // Release state, decrementing refcount, now that we're done with it.
+ Claim result{state->array(), start, start + chunk_size};
+ // Capture everything needed for scanning before decrementing the state's
+ // ref-count.
_allocator.release(state);
- return Claim{step._index, step._index + chunk_size};
+ return result;
}
#endif // SHARE_GC_SHARED_PARTIALARRAYSPLITTER_INLINE_HPP
diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp
index 3ef2927fe9a7..81d80b71ceb0 100644
--- a/src/hotspot/share/gc/shared/partialArrayState.cpp
+++ b/src/hotspot/share/gc/shared/partialArrayState.cpp
@@ -33,18 +33,18 @@
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
-PartialArrayState::PartialArrayState(oop src, oop dst,
+PartialArrayState::PartialArrayState(objArrayOop array,
size_t index, size_t length,
- size_t chunk_size,
- size_t initial_refcount)
- : _source(src),
- _destination(dst),
+ size_t chunk_size)
+ : _array(array),
_length(length),
_chunk_size(chunk_size),
_index(index),
- _refcount(initial_refcount)
+ _refcount(1)
{
assert(index <= length, "precondition");
+ assert(chunk_size > 0, "precondition");
+ assert((length - index) % chunk_size == 0, "whole chunks remain");
}
void PartialArrayState::add_references(size_t count) {
@@ -76,11 +76,10 @@ PartialArrayStateAllocator::~PartialArrayStateAllocator() {
_manager->release_allocator();
}
-PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst,
+PartialArrayState* PartialArrayStateAllocator::allocate(objArrayOop array,
size_t index,
size_t length,
- size_t chunk_size,
- size_t initial_refcount) {
+ size_t chunk_size) {
void* p;
FreeListEntry* head = _free_list;
if (head == nullptr) {
@@ -90,7 +89,7 @@ PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst,
head->~FreeListEntry();
p = head;
}
- return ::new (p) PartialArrayState(src, dst, index, length, chunk_size, initial_refcount);
+ return ::new (p) PartialArrayState(array, index, length, chunk_size);
}
void PartialArrayStateAllocator::release(PartialArrayState* state) {
@@ -118,6 +117,7 @@ PartialArrayStateManager::~PartialArrayStateManager() {
}
Arena* PartialArrayStateManager::register_allocator() {
+ assert(_released_allocators.load_relaxed() == 0, "allocator destruction has begun");
uint idx = _registered_allocators.fetch_then_add(1u, memory_order_relaxed);
assert(idx < _max_allocators, "exceeded configured max number of allocators");
return ::new (&_arenas[idx]) Arena(mtGC);
diff --git a/src/hotspot/share/gc/shared/partialArrayState.hpp b/src/hotspot/share/gc/shared/partialArrayState.hpp
index 75e297526aef..302d73ae879c 100644
--- a/src/hotspot/share/gc/shared/partialArrayState.hpp
+++ b/src/hotspot/share/gc/shared/partialArrayState.hpp
@@ -28,6 +28,7 @@
#include "memory/allocation.hpp"
#include "oops/oopsHierarchy.hpp"
#include "runtime/atomic.hpp"
+#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
#include "utilities/macros.hpp"
@@ -43,33 +44,39 @@ class PartialArrayStateManager;
// for later processing, either by the current thread or by some other thread
// that steals one of those tasks.
//
-// Processing a state involves using the state to claim a segment of the
-// array, and processing that segment. Claiming is done by atomically
-// incrementing the index, thereby claiming the segment from the old to new
-// index values. New tasks should also be added as needed to ensure the
-// entire array will be processed. A PartialArrayTaskStepper can be used to
-// help with this.
+// PartialArraySplitter atomically claims disjoint chunks and publishes enough
+// continuation tasks for parallel processing. The state is only needed while
+// claiming a chunk, not while scanning it.
//
// States are allocated and released using a PartialArrayStateAllocator.
-// States are reference counted to aid in that management. Each task
-// referring to a given state that is added to a taskqueue must increase the
-// reference count by one. When the processing of a task referring to a state
-// is complete, the reference count must be decreased by one. When the
-// reference count reaches zero the state is released to the allocator for
-// later reuse.
+// Each queued or currently claiming task contributes one to the state's
+// ref-count. Increment the ref-count before publishing continuation tasks.
+// After a task has claimed its chunk and published any continuations, decrement
+// the ref-count by one. The worker that decrements it to zero recycles the
+// state on its own allocator for later reuse.
class PartialArrayState {
- oop _source;
- oop _destination;
+ objArrayOop _array;
size_t _length;
size_t _chunk_size;
Atomic _index;
Atomic _refcount;
friend class PartialArrayStateAllocator;
+ friend class PartialArraySplitter;
- PartialArrayState(oop src, oop dst,
+ PartialArrayState(objArrayOop array,
size_t index, size_t length,
- size_t chunk_size, size_t initial_refcount);
+ size_t chunk_size);
+
+ // Increment the ref-count by count before publishing that many tasks to a queue.
+ void add_references(size_t count);
+
+ size_t claim_next() {
+ size_t start = _index.fetch_then_add(_chunk_size, memory_order_relaxed);
+ assert(start < _length, "every task must have a chunk to claim");
+ assert((_length - start) % _chunk_size == 0, "whole chunks remain");
+ return start;
+ }
public:
// Deleted to require management by allocator object.
@@ -77,24 +84,7 @@ class PartialArrayState {
NONCOPYABLE(PartialArrayState);
- // Add count references, one per referring task being added to a taskqueue.
- void add_references(size_t count);
-
- // The source array oop.
- oop source() const { return _source; }
-
- // The destination array oop. In some circumstances the source and
- // destination may be the same.
- oop destination() const { return _destination; }
-
- // The length of the array oop.
- size_t length() const { return _length; }
-
- size_t chunk_size() const { return _chunk_size; }
-
- // A pointer to the start index for the next segment to process, for atomic
- // update.
- Atomic* index_addr() { return &_index; }
+ objArrayOop array() const { return _array; }
};
// This class provides memory management for PartialArrayStates.
@@ -121,7 +111,7 @@ class PartialArrayStateAllocator : public CHeapObj {
PartialArrayStateManager* _manager;
FreeListEntry* _free_list;
- Arena* _arena; // Obtained from _manager.
+ Arena* _arena;
public:
explicit PartialArrayStateAllocator(PartialArrayStateManager* manager);
@@ -129,17 +119,12 @@ class PartialArrayStateAllocator : public CHeapObj {
NONCOPYABLE(PartialArrayStateAllocator);
- // Create a new state, obtaining the memory for it from the free-list or
- // from the associated manager.
- PartialArrayState* allocate(oop src, oop dst,
+ PartialArrayState* allocate(objArrayOop array,
size_t index, size_t length,
- size_t chunk_size,
- size_t initial_refcount);
+ size_t chunk_size);
- // Decrement the state's refcount. If the new refcount is zero, add the
- // state to the free-list associated with worker_id. The state must have
- // been allocated by this allocator, but that allocation doesn't need to
- // have been associated with worker_id.
+ // Decrement the state's ref-count by one. If it reaches zero, recycle on
+ // this allocator's free-list. The state must belong to the same manager.
void release(PartialArrayState* state);
};
diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp
index 126c6c302201..8a5b70a34b67 100644
--- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp
+++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp
@@ -28,12 +28,6 @@
#include "utilities/globalDefinitions.hpp"
#include "utilities/powerOfTwo.hpp"
-static uint compute_task_limit(uint n_workers) {
- // Don't need more than n_workers tasks at a time. But allowing up to
- // that maximizes available parallelism.
- return n_workers;
-}
-
static uint compute_task_fanout(uint task_limit) {
assert(task_limit > 0, "precondition");
// There is a tradeoff between providing parallelism more quickly and
@@ -48,8 +42,8 @@ static uint compute_task_fanout(uint task_limit) {
return result;
}
-PartialArrayTaskStepper::PartialArrayTaskStepper(uint n_workers) :
- _task_limit(compute_task_limit(n_workers)),
+PartialArrayTaskStepper::PartialArrayTaskStepper(uint num_workers) :
+ _task_limit(num_workers),
_task_fanout(compute_task_fanout(_task_limit))
{
// Current stepper code only supports up to 2^31 elements in the array.
diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp
index 594cc7b245a9..fd9706aa9878 100644
--- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp
+++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp
@@ -25,12 +25,8 @@
#ifndef SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP
#define SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP
-#include "oops/arrayOop.hpp"
-#include "runtime/atomic.hpp"
#include "utilities/globalDefinitions.hpp"
-class PartialArrayState;
-
// Helper for partial array chunking tasks.
//
// When an array is large, we want to split it up into chunks that can be
@@ -40,36 +36,19 @@ class PartialArrayState;
// substantially expand the task queues.
class PartialArrayTaskStepper {
public:
- PartialArrayTaskStepper(uint n_workers);
-
- struct Step {
- size_t _index; // Array index for the step.
- uint _ncreate; // Number of new tasks to create.
- };
-
- // Called with the length of the array to be processed and chunk size.
- // Returns a Step with _index being the end of the initial chunk, which the
- // caller should process. This is also the starting index for the next chunk to process.
- // The _ncreate is the number of tasks to enqueue to continue processing the
- // array. If _ncreate is zero then _index will be length.
- inline Step start(size_t length, size_t chunk_size) const;
+ explicit PartialArrayTaskStepper(uint num_workers);
- // Atomically increment state's index by chunk_size() to claim the next
- // chunk. Returns a Step with _index being the starting index of the
- // claimed chunk and _ncreate being the number of additional partial tasks
- // to enqueue.
- inline Step next(PartialArrayState* state) const;
-
- class TestSupport; // For unit tests
+ // Number of continuation tasks to publish after claiming the full-sized
+ // chunk starting at start. Initially there is one queued task. Every claim
+ // must publish the count returned here, even if other claims have advanced
+ // the shared index in the meantime.
+ inline uint continuation_tasks(size_t start, size_t length, size_t chunk_size) const;
private:
- // Limit on the number of partial array tasks to create for a given array.
+ // Limit on pending tasks for one array.
uint _task_limit;
// Maximum number of new tasks to create when processing an existing task.
uint _task_fanout;
-
- // For unit tests.
- inline Step next_impl(size_t length, size_t chunk_size, Atomic* index_addr) const;
};
#endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP
diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp
index fb58400cf271..9d28bb9fbe8f 100644
--- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp
+++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp
@@ -27,28 +27,11 @@
#include "gc/shared/partialArrayTaskStepper.hpp"
-#include "gc/shared/partialArrayState.hpp"
#include "utilities/checkedCast.hpp"
#include "utilities/debug.hpp"
-PartialArrayTaskStepper::Step
-PartialArrayTaskStepper::start(size_t length, size_t chunk_size) const {
- size_t end = length % chunk_size; // End of initial chunk.
- // If the initial chunk is the complete array, then don't need any partial
- // tasks. Otherwise, start with just one partial task; see new task
- // calculation in next().
- return Step{ end, (length > end) ? 1u : 0u };
-}
-
-PartialArrayTaskStepper::Step
-PartialArrayTaskStepper::next_impl(size_t length, size_t chunk_size, Atomic* index_addr) const {
- // The start of the next task is in the state's index.
- // Atomically increment by the chunk size to claim the associated chunk.
- // Because we limit the number of enqueued tasks to being no more than the
- // number of remaining chunks to process, we can use an atomic add for the
- // claim, rather than a CAS loop.
- size_t start = index_addr->fetch_then_add(chunk_size, memory_order_relaxed);
-
+uint PartialArrayTaskStepper::continuation_tasks(size_t start, size_t length, size_t chunk_size) const {
+ precond(chunk_size > 0);
assert(start < length, "invariant: start %zu, length %zu", start, length);
assert(((length - start) % chunk_size) == 0,
"invariant: start %zu, length %zu, chunk size %zu",
@@ -80,12 +63,7 @@ PartialArrayTaskStepper::next_impl(size_t length, size_t chunk_size, Atomic(max_pending, remaining_tasks, _task_limit);
uint ncreate = MIN2(_task_fanout, MIN2(remaining_tasks, _task_limit + 1) - pending);
- return Step{ start, ncreate };
-}
-
-PartialArrayTaskStepper::Step
-PartialArrayTaskStepper::next(PartialArrayState* state) const {
- return next_impl(state->length(), state->chunk_size(), state->index_addr());
+ return ncreate;
}
#endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_INLINE_HPP
diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp
index e2e78c7bc4a0..e9213eeeca80 100644
--- a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp
+++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp
@@ -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
@@ -22,8 +22,8 @@
*
*/
+#include "cds/aotHeap.inline.hpp"
#include "cds/aotMappedHeapLoader.hpp"
-#include "cds/heapShared.inline.hpp"
#include "classfile/altHashing.hpp"
#include "classfile/javaClasses.inline.hpp"
#include "classfile/stringTable.hpp"
@@ -518,7 +518,7 @@ void StringDedup::Table::install(typeArrayOop obj, uint hash_code) {
// access to a String that is incompletely constructed; the value could be
// set before the coder.
bool StringDedup::Table::try_deduplicate_shared(oop java_string) {
- assert(!HeapShared::is_loading_streaming_mode(), "should not reach here");
+ assert(!AOTHeap::is_loading_streaming_mode(), "should not reach here");
typeArrayOop value = java_lang_String::value(java_string);
assert(value != nullptr, "precondition");
assert(TypeArrayKlass::cast(value->klass())->element_type() == T_BYTE, "precondition");
@@ -564,7 +564,7 @@ bool StringDedup::Table::try_deduplicate_shared(oop java_string) {
}
bool StringDedup::Table::try_deduplicate_found_shared(oop java_string, oop found) {
- assert(!HeapShared::is_loading_streaming_mode(), "should not reach here");
+ assert(!AOTHeap::is_loading_streaming_mode(), "should not reach here");
_cur_stat.inc_known_shared();
typeArrayOop found_value = java_lang_String::value(found);
if (found_value == java_lang_String::value(java_string)) {
diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp
index de0b838fe45d..26c4ab99c19e 100644
--- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp
+++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp
@@ -106,7 +106,7 @@ void ShenandoahBarrierSetC1::enter_if_gc_state(LIRGenerator* gen, int flags, Cod
__ branch_destination(slow_stub->continuation());
}
-void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators) {
+void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info, DecoratorSet decorators) {
CodeStub* slow_stub;
if (obj == LIR_OprFact::illegalOpr) {
// Caller wants us to do the load.
@@ -118,10 +118,11 @@ void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, L
addr = LIR_OprFact::address(new LIR_Address(addr, T_OBJECT));
}
- slow_stub = new ShenandoahKeepaliveBarrierStub(obj, addr);
+ slow_stub = new ShenandoahKeepaliveBarrierStub(obj, addr, info);
} else {
// Caller gave us the obj to work with.
assert(addr == LIR_OprFact::illegalOpr, "sanity");
+ assert(info == nullptr, "sanity");
assert(obj->is_register(), "must be");
assert(obj->type() == T_OBJECT, "must be an object");
@@ -172,7 +173,11 @@ void ShenandoahBarrierSetC1::store_at_resolved(LIRAccess& access, LIR_Opr value)
LIRGenerator* gen = access.gen();
if (ShenandoahSATBBarrier && access.is_oop()) {
- keepalive_barrier(gen, /* obj = */ LIR_OprFact::illegalOpr, /* addr = */ access.resolved_addr(), decorators);
+ CodeEmitInfo* info = access.access_emit_info();
+ if (info != nullptr) {
+ info = new CodeEmitInfo(info);
+ }
+ keepalive_barrier(gen, /* obj = */ LIR_OprFact::illegalOpr, /* addr = */ access.resolved_addr(), info, decorators);
}
BarrierSetC1::store_at_resolved(access, value);
@@ -225,7 +230,7 @@ void ShenandoahBarrierSetC1::load_at_resolved(LIRAccess& access, LIR_Opr result)
Lcont_anonymous = new LabelObj();
generate_referent_check(access, Lcont_anonymous);
}
- keepalive_barrier(gen, /* obj = */ result, /* addr = */ LIR_OprFact::illegalOpr, decorators);
+ keepalive_barrier(gen, /* obj = */ result, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators);
if (is_anonymous) {
__ branch_destination(Lcont_anonymous->label());
}
@@ -365,7 +370,7 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LI
// Handle the previous value through SATB, as we are about to perform the store.
__ load(addr->as_address_ptr(), tmp);
if (ShenandoahSATBBarrier) {
- keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, decorators);
+ keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators);
}
// Perform LRB on location to fix it up for this and all following accesses.
@@ -398,7 +403,7 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRIt
// Handle the previous value through SATB, as we are about to perform the store.
__ load(addr->as_address_ptr(), tmp);
if (ShenandoahSATBBarrier) {
- keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, decorators);
+ keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators);
}
// Perform LRB on location to fix it up for this and all following accesses.
diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp
index ade3d9a79520..62d167aff67c 100644
--- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp
+++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp
@@ -35,10 +35,11 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub {
LIR_Opr _obj;
LIR_Opr _addr;
bool _do_load;
+ CodeEmitInfo* _info;
public:
- ShenandoahKeepaliveBarrierStub(LIR_Opr obj, LIR_Opr addr) :
- _obj(obj), _addr(addr), _do_load(true)
+ ShenandoahKeepaliveBarrierStub(LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info) :
+ _obj(obj), _addr(addr), _do_load(true), _info(info)
{
assert(_obj->is_register(), "should be temporary register");
assert(_addr->is_address(), "should be the address of the field");
@@ -47,7 +48,7 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub {
}
ShenandoahKeepaliveBarrierStub(LIR_Opr obj) :
- _obj(obj), _addr(LIR_OprFact::illegalOpr), _do_load(false)
+ _obj(obj), _addr(LIR_OprFact::illegalOpr), _do_load(false), _info(nullptr)
{
assert(_obj->is_register(), "should be a register");
FrameMap* f = Compilation::current()->frame_map();
@@ -57,10 +58,15 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub {
LIR_Opr addr() const { return _addr; }
LIR_Opr obj() const { return _obj; }
bool do_load() const { return _do_load; }
+ CodeEmitInfo* info() const { return _info; }
virtual void emit_code(LIR_Assembler* e);
virtual void visit(LIR_OpVisitState* visitor) {
- visitor->do_slow_case();
+ if (_info != nullptr) {
+ visitor->do_slow_case(_info);
+ } else {
+ visitor->do_slow_case();
+ }
if (_do_load) {
visitor->do_input(_addr);
visitor->do_temp(_addr);
@@ -121,7 +127,7 @@ class ShenandoahBarrierSetC1 : public BarrierSetC1 {
void enter_if_gc_state(LIRGenerator* gen, int flags, CodeStub* slow_stub);
- void keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators);
+ void keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info, DecoratorSet decorators);
void load_reference_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators);
void card_barrier(LIRGenerator* gen, LIR_Opr addr, DecoratorSet decorators);
diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp
index 2b8c12c41a68..d876a5517755 100644
--- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp
+++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp
@@ -600,7 +600,7 @@ void ShenandoahBarrierSetC2::clone(GraphKit* kit, Node* src_base, Node* dst_base
ac->set_clone_array();
Node* n = kit->gvn().transform(ac);
if (n == ac) {
- ac->set_adr_type(TypeRawPtr::BOTTOM);
+ ac->set_out_adr_type(TypeRawPtr::BOTTOM);
kit->set_predefined_output_for_runtime_call(ac, ac->in(TypeFunc::Memory), TypeRawPtr::BOTTOM);
} else {
kit->set_all_memory(n);
@@ -775,13 +775,13 @@ void ShenandoahBarrierSetC2::verify_gc_barriers(Compile* compile, CompilePhase p
const TypePtr* adr_type = nullptr;
if (is_Load(opc)) {
bd = n->as_Load()->barrier_data();
- adr_type = n->as_Load()->adr_type();
+ adr_type = n->as_Load()->in_adr_type();
} else if (is_Store(opc)) {
bd = n->as_Store()->barrier_data();
- adr_type = n->as_Store()->adr_type();
+ adr_type = n->as_Store()->out_adr_type();
} else if (is_LoadStore(opc)) {
bd = n->as_LoadStore()->barrier_data();
- adr_type = n->as_LoadStore()->adr_type();
+ adr_type = n->as_LoadStore()->out_adr_type();
} else if (n->is_Mem()) {
bd = MemNode::barrier_data(n);
verify_gc_barrier_assert(bd == 0, "Other mem nodes should have no barrier data", bd, n);
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
index 55a582cd54c2..43277c11eda4 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
@@ -77,7 +77,7 @@ void ShenandoahAdaptiveHeuristics::post_initialize() {
}
void ShenandoahAdaptiveHeuristics::compute_headroom_adjustment() {
- // The trigger threshold represents mutator available - "head room".
+ // The trigger threshold represents mutator available - "headroom".
// We plan for GC to finish before the amount of allocated memory exceeds trigger threshold. This is the same as saying we
// intend to finish GC before the amount of available memory is less than the allocation headroom. Headroom is the planned
// safety buffer to allow a small amount of additional allocation to take place in case we were overly optimistic in delaying
@@ -152,15 +152,8 @@ void ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(Shenand
}
}
-void ShenandoahAdaptiveHeuristics::add_degenerated_gc_time(double time_at_start, double gc_time) {
- // Conservatively add sample into linear model, if this time is above the predicted concurrent gc time
- if (_cycles.predict_duration(time_at_start, _margin_of_error_sd) < gc_time) {
- _cycles.record_duration(time_at_start, gc_time);
- }
-}
-
-void ShenandoahAdaptiveHeuristics::record_success_concurrent() {
- ShenandoahHeuristics::record_success_concurrent();
+void ShenandoahAdaptiveHeuristics::record_concurrent_completion() {
+ ShenandoahHeuristics::record_concurrent_completion();
// We add this time even if it is a shortened cycle. There is a risk that this pulls
// the gc time trend down, but it is still a more accurate view than excluding times
@@ -213,13 +206,6 @@ void ShenandoahAdaptiveHeuristics::record_success_concurrent() {
}
}
-void ShenandoahAdaptiveHeuristics::record_degenerated(bool is_generational_global) {
- ShenandoahHeuristics::record_degenerated(is_generational_global);
- if (!is_generational_global) {
- add_degenerated_gc_time(_precursor_cycle_start, elapsed_degenerated_cycle_time());
- }
-}
-
bool ShenandoahAdaptiveHeuristics::should_start_gc() {
const size_t capacity = ShenandoahHeap::heap()->soft_max_capacity();
const size_t available = _space_info->soft_mutator_available();
@@ -227,7 +213,7 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() {
log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT,
PROPERFMTARGS(available), PROPERFMTARGS(capacity));
- if (_start_gc_is_pending) {
+ if (_start_gc_is_pending.load_relaxed()) {
log_info(gc, ergo)("GC start is already pending");
return true;
}
@@ -308,7 +294,7 @@ bool ShenandoahAdaptiveHeuristics::trigger_average_allocation_rate(const Shenand
// The following trace represents an actual workload, with allocation rates sampled at 10 Hz, the default behavior before
// introduction of accelerated allocation rate detection. Though the allocation rate is seen to be increasing at times
// 101.907 and 102.007 and 102.108, the newly sampled allocation rate is not enough to trigger GC because the headroom is
-// still quite large. In fact, GC is not triggered until time 102.409s, and this GC degenerates.
+// still quite large. In fact, GC is not triggered until time 102.409s, and this GC stalls.
//
// Sample Time (s) Allocation Rate (MB/s) Headroom (GB)
// 101.807 0.0 26.93
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp
index b571f81140c4..8705bf86a54a 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp
@@ -29,7 +29,6 @@
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
#include "gc/shenandoah/shenandoahAllocRate.hpp"
#include "gc/shenandoah/shenandoahCycleDuration.hpp"
-#include "gc/shenandoah/shenandoahPhaseTimings.hpp"
#include "utilities/numberSeq.hpp"
@@ -37,8 +36,7 @@
* The adaptive heuristic tracks the allocation behavior and average cycle
* time of the application. It attempts to start a cycle with enough time
* to complete before the available memory is exhausted. It errors on the
- * side of starting cycles early to avoid allocation failures (degenerated
- * cycles).
+ * side of starting cycles early to avoid allocation failures.
*
* This heuristic limits the number of regions for evacuation such that the
* evacuation reserve is respected. This helps it avoid allocation failures
@@ -56,8 +54,7 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics {
// before we need to start the next GC.
void start_idle_span() override;
- void record_success_concurrent() override;
- void record_degenerated(bool is_generational_global) override;
+ void record_concurrent_completion() override;
bool should_start_gc() override;
@@ -113,8 +110,6 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics {
// bytes of headroom at which we should trigger GC
size_t _headroom_adjustment;
- void add_degenerated_gc_time(double timestamp_at_start, double duration);
-
// A conservative minimum threshold of free space that we'll try to maintain when possible.
// For example, we might trigger a concurrent gc if we are likely to drop below
// this threshold, or we might consider this when dynamically resizing generations
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
index 31f32836ceb3..c941d024e2ff 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp
@@ -24,6 +24,7 @@
*/
#include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp"
+#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahAllocRate.inline.hpp"
#include "gc/shenandoah/shenandoahCollectionSet.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
index 5c07fcb6b59f..21ac29895538 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp
@@ -24,6 +24,7 @@
*/
#include "gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp"
+#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahAsserts.hpp"
#include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp"
#include "gc/shenandoah/shenandoahGlobalGeneration.hpp"
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp
index 1e96a665704b..da3998f58543 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp
@@ -159,15 +159,17 @@ class ShenandoahGlobalCSetBudget {
class ShenandoahGlobalHeuristics : public ShenandoahGenerationalHeuristics {
public:
- ShenandoahGlobalHeuristics(ShenandoahGlobalGeneration* generation);
+ explicit ShenandoahGlobalHeuristics(ShenandoahGlobalGeneration* generation);
+
+protected:
void select_collection_set_regions(ShenandoahCollectionSet* cset,
RegionData* data, size_t size,
size_t actual_free) override;
private:
void choose_global_collection_set(ShenandoahCollectionSet* cset,
- const ShenandoahHeuristics::RegionData* data,
+ const RegionData* data,
size_t size, size_t actual_free,
size_t cur_young_garbage) const;
};
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp
index d0a5d0e0cc10..aa2cb70e0442 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp
@@ -55,12 +55,10 @@ ShenandoahHeuristics::ShenandoahHeuristics(ShenandoahSpaceInfo* space_info) :
_most_recent_planned_sleep_interval(0.0),
_start_gc_is_pending(false),
_declined_trigger_count(0),
- _most_recent_declined_trigger_count(0),
_space_info(space_info),
_region_data(nullptr),
_guaranteed_gc_interval(0),
- _precursor_cycle_start(os::elapsedTime()),
- _cycle_start(_precursor_cycle_start),
+ _cycle_start(os::elapsedTime()),
_last_cycle_end(0),
_gc_times_learned(0),
_gc_time_penalties(0),
@@ -164,17 +162,10 @@ void ShenandoahHeuristics::start_idle_span() {
// do nothing
}
-void ShenandoahHeuristics::record_degenerated_cycle_start(bool out_of_cycle) {
- if (out_of_cycle) {
- _precursor_cycle_start = _cycle_start = os::elapsedTime();
- } else {
- _precursor_cycle_start = _cycle_start;
- _cycle_start = os::elapsedTime();
- }
-}
-
void ShenandoahHeuristics::record_cycle_start() {
_cycle_start = os::elapsedTime();
+ cancel_trigger_request();
+ log_debug(gc, ergo)("Declined trigger count at start: %zu", _declined_trigger_count.load_relaxed());
}
void ShenandoahHeuristics::record_cycle_end() {
@@ -188,7 +179,7 @@ void ShenandoahHeuristics::record_cycle_end() {
}
bool ShenandoahHeuristics::should_start_gc() {
- if (_start_gc_is_pending) {
+ if (_start_gc_is_pending.load_relaxed()) {
log_info(gc, ergo)("GC start is already pending");
return true;
}
@@ -213,33 +204,15 @@ bool ShenandoahHeuristics::should_start_gc() {
return false;
}
-bool ShenandoahHeuristics::should_degenerate_cycle() {
- return ShenandoahHeap::heap()->shenandoah_policy()->consecutive_degenerated_gc_count() <= ShenandoahFullGCThreshold;
-}
-
void ShenandoahHeuristics::adjust_penalty(intx step) {
- assert(0 <= _gc_time_penalties && _gc_time_penalties <= 100,
- "In range before adjustment: %zd", _gc_time_penalties);
- if ((_most_recent_declined_trigger_count <= Penalty_Free_Declinations) && (step > 0)) {
- // Don't penalize if heuristics are not responsible for a negative outcome. Allow Penalty_Free_Declinations following
- // previous GC for self calibration without penalty.
- step = 0;
- }
-
- intx new_val = _gc_time_penalties + step;
- if (new_val < 0) {
- new_val = 0;
+ const intx adjusted = clamp(_gc_time_penalties + step, Min_Penalty, Max_Penalty);
+ if (adjusted != _gc_time_penalties) {
+ _gc_time_penalties = adjusted;
+ log_info(gc, ergo)("Adjusted heuristic penalty: %zd", _gc_time_penalties);
}
- if (new_val > 100) {
- new_val = 100;
- }
- _gc_time_penalties = new_val;
-
- assert(0 <= _gc_time_penalties && _gc_time_penalties <= 100,
- "In range after adjustment: %zd", _gc_time_penalties);
}
-void ShenandoahHeuristics::log_trigger(const char* fmt, ...) {
+void ShenandoahHeuristics::log_trigger(const char* fmt, ...) const {
LogTarget(Info, gc) lt;
if (lt.is_enabled()) {
ResourceMark rm;
@@ -257,30 +230,36 @@ void ShenandoahHeuristics::log_trigger(const char* fmt, ...) {
}
}
-void ShenandoahHeuristics::record_success_concurrent() {
- _gc_times_learned++;
-
- adjust_penalty(Concurrent_Adjust);
+ShenandoahHeuristics::PenaltyData ShenandoahHeuristics::consume_penalty_data() {
+ const size_t declines = _declined_trigger_count.exchange(0, memory_order_relaxed);
+ const bool stalls = _allocation_stalls.exchange(false, memory_order_relaxed);
+ log_debug(gc, ergo)("Declined trigger count at end: %zu", declines);
+ return { declines, stalls };
}
-void ShenandoahHeuristics::record_degenerated(bool is_generational_global) {
-
- if (!is_generational_global) {
- // We don't penalize generational GC heuristics for global GC because heuristics predict based on assumption of young GC.
- _most_recent_declined_trigger_count = _declined_trigger_count;
- _declined_trigger_count = 0;
- } else {
- _most_recent_declined_trigger_count = _declined_trigger_count = 0;
+void ShenandoahHeuristics::record_concurrent_completion() {
+ _gc_times_learned++;
+ const PenaltyData data = consume_penalty_data();
+ if (!data.stalls) {
+ adjust_penalty(Concurrent_Adjust);
+ } else if (data.declined_triggers > Penalty_Free_Declinations) {
+ // There were stalls _and_ the trigger was lazy, penalize it.
+ adjust_penalty(Stall_Penalty);
}
- adjust_penalty(Degenerated_Penalty);
}
-void ShenandoahHeuristics::record_success_full() {
- adjust_penalty(Full_Penalty);
+void ShenandoahHeuristics::record_allocation_stall() {
+ _allocation_stalls.store_relaxed(true);
}
-void ShenandoahHeuristics::record_allocation_failure_gc() {
- // Do nothing.
+void ShenandoahHeuristics::record_full_gc(GCCause::Cause cause) {
+ const PenaltyData data = consume_penalty_data();
+ if (cause == GCCause::_shenandoah_upgrade_to_full_gc) {
+ adjust_penalty(Full_Penalty);
+ } else if (data.stalls && data.declined_triggers > Penalty_Free_Declinations) {
+ // There were stalls _and_ the trigger was lazy, penalize it.
+ adjust_penalty(Stall_Penalty);
+ }
}
void ShenandoahHeuristics::record_requested_gc() {
@@ -309,13 +288,6 @@ double ShenandoahHeuristics::elapsed_cycle_time() const {
return os::elapsedTime() - _cycle_start;
}
-
-// Includes the time spent in abandoned concurrent GC cycle that may have triggered this degenerated cycle.
-double ShenandoahHeuristics::elapsed_degenerated_cycle_time() const {
- double now = os::elapsedTime();
- return now - _precursor_cycle_start;
-}
-
#ifdef ASSERT
void ShenandoahHeuristics::assert_humongous_mark_consistency(ShenandoahHeapRegion* region) {
assert(region->is_humongous(), "Region %zu must be humongous", region->index());
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
index 1deb98c3f8a9..21bad3058166 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp
@@ -26,11 +26,11 @@
#ifndef SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHHEURISTICS_HPP
#define SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHHEURISTICS_HPP
+#include "gc/shared/gcCause.hpp"
#include "gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp"
#include "gc/shenandoah/shenandoahSharedVariables.hpp"
#include "memory/allocation.hpp"
#include "runtime/globals_extension.hpp"
-#include "utilities/numberSeq.hpp"
#define SHENANDOAH_ERGO_DISABLE_FLAG(name) \
do { \
@@ -66,12 +66,14 @@ class ShenandoahHeapRegion;
* cycle.
*/
class ShenandoahHeuristics : public CHeapObj {
- static const intx Concurrent_Adjust = -1; // recover from penalties
- static const intx Degenerated_Penalty = 10; // how much to penalize average GC duration history on Degenerated GC
- static const intx Full_Penalty = 20; // how much to penalize average GC duration history on Full GC
+ static constexpr intx Concurrent_Adjust = -1; // recover from penalties
+ static constexpr intx Stall_Penalty = 10; // how much to penalize average GC duration history on allocation stall
+ static constexpr intx Full_Penalty = 20; // how much to penalize average GC duration history on Full GC
+ static constexpr intx Min_Penalty = 0;
+ static constexpr intx Max_Penalty = 100;
// How many times can I decline a trigger opportunity without being penalized for excessive idle span before trigger?
- static const size_t Penalty_Free_Declinations = 16;
+ static constexpr size_t Penalty_Free_Declinations = 16;
#ifdef ASSERT
enum UnionTag {
@@ -79,24 +81,34 @@ class ShenandoahHeuristics : public CHeapObj {
};
#endif
-private:
double _most_recent_trigger_evaluation_time;
double _most_recent_planned_sleep_interval;
protected:
static constexpr uint Moving_Average_Samples = 10; // Number of samples to store in moving averages
- bool _start_gc_is_pending; // True denotes that GC has been triggered, so no need to trigger again.
- size_t _declined_trigger_count; // This counts how many times since previous GC finished that this
- // heuristic has answered false to should_start_gc().
- size_t _most_recent_declined_trigger_count;
- // This represents the value of _declined_trigger_count as captured at the
- // moment the most recent GC effort was triggered. In case the most recent
- // concurrent GC effort degenerates, the value of this variable allows us to
- // differentiate between degeneration because heuristic was overly optimistic
- // in delaying the trigger vs. degeneration for other reasons (such as the
- // most recent GC triggered "immediately" after previous GC finished, but the
- // free headroom has already been depleted).
+ // True denotes that GC has been triggered, so no need to trigger again.
+ shenandoah_padding(0);
+ Atomic _start_gc_is_pending;
+
+ // This counts how many times since previous GC finished that this heuristic has answered false to should_start_gc().
+ // If allocations stall during a concurrent gc and this is above Penalty_Free_Declinations at the end of the cycle,
+ // the heuristics will be 'penalized' in a way that will make them start the next concurrent cycle sooner. Also
+ // note that once the trigger has been accepted, _start_gc_is_pending will be set and subsequent attempts to evaluate
+ // the trigger conditions will return early and will not increase _declined_trigger_count. This is written to
+ // by both the regulator and control thread, read by control thread.
+ shenandoah_padding(1);
+ Atomic _declined_trigger_count;
+ shenandoah_padding(2);
+ Atomic _allocation_stalls;
+ shenandoah_padding(3);
+
+ // Snapshot declined trigger count and alloc stalls to compute appropriate penalties (if warranted)
+ struct PenaltyData {
+ size_t declined_triggers;
+ bool stalls;
+ };
+
class RegionData {
private:
ShenandoahHeapRegion* _region;
@@ -110,7 +122,7 @@ class ShenandoahHeuristics : public CHeapObj {
public:
- inline void clear() {
+ void clear() {
_region = nullptr;
_region_union._garbage = 0;
#ifdef ASSERT
@@ -118,7 +130,7 @@ class ShenandoahHeuristics : public CHeapObj {
#endif
}
- inline void set_region_and_garbage(ShenandoahHeapRegion* region, size_t garbage) {
+ void set_region_and_garbage(ShenandoahHeapRegion* region, size_t garbage) {
_region = region;
_region_union._garbage = garbage;
#ifdef ASSERT
@@ -126,7 +138,7 @@ class ShenandoahHeuristics : public CHeapObj {
#endif
}
- inline void set_region_and_livedata(ShenandoahHeapRegion* region, size_t live) {
+ void set_region_and_livedata(ShenandoahHeapRegion* region, size_t live) {
_region = region;
_region_union._live_data = live;
#ifdef ASSERT
@@ -134,24 +146,24 @@ class ShenandoahHeuristics : public CHeapObj {
#endif
}
- inline void update_livedata(size_t live) {
+ void update_livedata(size_t live) {
_region_union._live_data = live;
#ifdef ASSERT
_union_tag = is_live_data;
#endif
}
- inline ShenandoahHeapRegion* get_region() const {
+ ShenandoahHeapRegion* get_region() const {
assert(_union_tag != is_uninitialized, "Cannot fetch region from uninitialized RegionData");
return _region;
}
- inline size_t get_garbage() const {
+ size_t get_garbage() const {
assert(_union_tag == is_garbage, "Invalid union fetch");
return _region_union._garbage;
}
- inline size_t get_livedata() const {
+ size_t get_livedata() const {
assert(_union_tag == is_live_data, "Invalid union fetch");
return _region_union._live_data;
}
@@ -176,7 +188,6 @@ class ShenandoahHeuristics : public CHeapObj {
size_t _guaranteed_gc_interval;
- double _precursor_cycle_start;
double _cycle_start;
double _last_cycle_end;
@@ -198,19 +209,15 @@ class ShenandoahHeuristics : public CHeapObj {
virtual void adjust_penalty(intx step);
- inline void accept_trigger() {
- _start_gc_is_pending = true;
- }
-
- inline void decline_trigger() {
- _declined_trigger_count++;
+ void decline_trigger() {
+ _declined_trigger_count.add_then_fetch(1UL, memory_order_relaxed);
}
- inline double get_most_recent_wake_time() const {
+ double get_most_recent_wake_time() const {
return _most_recent_trigger_evaluation_time;
}
- inline double get_planned_sleep_interval() const {
+ double get_planned_sleep_interval() const {
return _most_recent_planned_sleep_interval;
}
@@ -236,9 +243,6 @@ class ShenandoahHeuristics : public CHeapObj {
}
virtual void record_cycle_start();
-
- void record_degenerated_cycle_start(bool out_of_cycle);
-
virtual void record_cycle_end();
void update_should_start_query_times(double now, double planned_sleep_interval) {
@@ -246,24 +250,24 @@ class ShenandoahHeuristics : public CHeapObj {
_most_recent_planned_sleep_interval = planned_sleep_interval;
}
- virtual bool should_start_gc();
-
- inline void cancel_trigger_request() {
- _start_gc_is_pending = false;
+ void accept_trigger() {
+ _start_gc_is_pending.store_relaxed(true);
}
- virtual bool should_degenerate_cycle();
-
- virtual void record_success_concurrent();
+ void cancel_trigger_request() {
+ _start_gc_is_pending.store_relaxed(false);
+ }
- virtual void record_degenerated(bool is_generational_global);
+ virtual bool should_start_gc();
- virtual void record_success_full();
+ virtual void record_concurrent_completion();
- virtual void record_allocation_failure_gc();
+ virtual void record_full_gc(GCCause::Cause cause);
virtual void record_requested_gc();
+ virtual void record_allocation_stall();
+
// Choose the collection set, returning the number of regions that need to be transferred to the old reserve from the young
// reserve in order to effectively evacuate the chosen collection set. In non-generational mode, the return value is 0.
virtual void choose_collection_set(ShenandoahCollectionSet* collection_set);
@@ -281,10 +285,11 @@ class ShenandoahHeuristics : public CHeapObj {
virtual void post_initialize();
double elapsed_cycle_time() const;
- double elapsed_degenerated_cycle_time() const;
// Format prefix and emit log message indicating a GC cycle hs been triggered
- void log_trigger(const char* fmt, ...) ATTRIBUTE_PRINTF(2, 3);
+ void log_trigger(const char* fmt, ...) const ATTRIBUTE_PRINTF(2, 3);
+
+ PenaltyData consume_penalty_data();
DEBUG_ONLY(static void assert_humongous_mark_consistency(ShenandoahHeapRegion* region));
};
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp
index 1b956b6994a3..6ce679555d8c 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp
@@ -848,22 +848,16 @@ void ShenandoahOldHeuristics::adjust_old_garbage_threshold() {
}
}
-void ShenandoahOldHeuristics::record_success_concurrent() {
+void ShenandoahOldHeuristics::record_concurrent_completion() {
// Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger.
clear_triggers();
- this->ShenandoahHeuristics::record_success_concurrent();
+ this->ShenandoahHeuristics::record_concurrent_completion();
}
-void ShenandoahOldHeuristics::record_degenerated(bool is_generational_global) {
+void ShenandoahOldHeuristics::record_full_gc(GCCause::Cause cause) {
// Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger.
clear_triggers();
- this->ShenandoahHeuristics::record_degenerated(is_generational_global);
-}
-
-void ShenandoahOldHeuristics::record_success_full() {
- // Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger.
- clear_triggers();
- this->ShenandoahHeuristics::record_success_full();
+ this->ShenandoahHeuristics::record_full_gc(cause);
}
const char* ShenandoahOldHeuristics::name() {
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp
index 37d186a991f9..63a4cbed7069 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp
@@ -244,11 +244,9 @@ class ShenandoahOldHeuristics : public ShenandoahHeuristics {
// Returns true if the old generation needs to prepare for marking, or continue marking.
bool should_resume_old_cycle();
- void record_success_concurrent() override;
+ void record_concurrent_completion() override;
- void record_degenerated(bool is_generational_global) override;
-
- void record_success_full() override;
+ void record_full_gc(GCCause::Cause cause) override;
const char* name() override;
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp
index b5e9cc433ea6..f1619c2e3848 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2018, 2019, Red Hat, Inc. 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
@@ -25,11 +25,8 @@
#include "gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp"
-#include "gc/shenandoah/shenandoahCollectionSet.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
-#include "logging/log.hpp"
-#include "logging/logTag.hpp"
ShenandoahPassiveHeuristics::ShenandoahPassiveHeuristics(ShenandoahSpaceInfo* space_info) :
ShenandoahHeuristics(space_info) {}
@@ -45,35 +42,9 @@ bool ShenandoahPassiveHeuristics::should_unload_classes() {
return can_unload_classes();
}
-bool ShenandoahPassiveHeuristics::should_degenerate_cycle() {
- // Always fail to Degenerated GC, if enabled
- return ShenandoahDegeneratedGC;
-}
-
void ShenandoahPassiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset,
RegionData* data, size_t size,
size_t actual_free) {
- assert(ShenandoahDegeneratedGC, "This path is only taken for Degenerated GC");
-
- // Do not select too large CSet that would overflow the available free space.
- // Take at least the entire evacuation reserve, and be free to overflow to free space.
- size_t max_capacity = _space_info->max_capacity();
- size_t available = MAX2(max_capacity / 100 * ShenandoahEvacReserve, actual_free);
- size_t max_cset = (size_t)(available / ShenandoahEvacWaste);
-
- log_info(gc, ergo)("CSet Selection. Actual Free: %zu%s, Max CSet: %zu%s",
- byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free),
- byte_size_in_proper_unit(max_cset), proper_unit_for_byte_size(max_cset));
-
- size_t threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100;
-
- size_t live_cset = 0;
- for (size_t idx = 0; idx < size; idx++) {
- ShenandoahHeapRegion* r = data[idx].get_region();
- size_t new_cset = live_cset + r->get_live_data_bytes();
- if (new_cset < max_cset && r->garbage() > threshold) {
- live_cset = new_cset;
- cset->add_region(r);
- }
- }
+ // Passive mode only runs full GCs.
+ ShouldNotReachHere();
}
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp
index 3cb85f5d05f0..809c8df60036 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp
@@ -29,7 +29,7 @@
/*
* The passive heuristic is for use only with the passive mode. In
- * the passive mode, Shenandoah only performs STW (i.e., degenerated)
+ * the passive mode, Shenandoah only performs STW (i.e., full)
* collections. All the barriers are disabled and there are no concurrent
* activities. Therefore, this heuristic _never_ triggers a cycle. It
* will select regions for evacuation based on ShenandoahEvacReserve,
@@ -44,8 +44,6 @@ class ShenandoahPassiveHeuristics : public ShenandoahHeuristics {
bool should_unload_classes() override;
- bool should_degenerate_cycle() override;
-
void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set,
RegionData* data, size_t data_size,
size_t free) override;
diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp
index 723fb631e755..c8304ac1836f 100644
--- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp
+++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp
@@ -41,6 +41,9 @@ class ShenandoahYoungHeuristics : public ShenandoahGenerationalHeuristics {
bool should_start_gc() override;
+ // Young collections can never unload classes
+ bool can_unload_classes() override { return false; }
+
size_t bytes_of_allocation_runway_before_gc_trigger(size_t young_regions_to_be_reclaimed);
protected:
diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
index a880baccdf7d..481e28e74272 100644
--- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
+++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp
@@ -22,26 +22,20 @@
*
*/
-#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
#include "gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp"
#include "gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp"
#include "gc/shenandoah/mode/shenandoahPassiveMode.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
-#include "logging/log.hpp"
-#include "logging/logTag.hpp"
#include "runtime/globals_extension.hpp"
-#include "runtime/java.hpp"
void ShenandoahPassiveMode::initialize_flags() const {
// Do not allow concurrent cycles.
FLAG_SET_DEFAULT(ExplicitGCInvokesConcurrent, false);
- // No need for evacuation reserve with Full GC, only for Degenerated GC.
- if (!ShenandoahDegeneratedGC) {
- if (FLAG_IS_DEFAULT(ShenandoahEvacReserve)) {
- log_info(gc)("Heuristics sets -XX:ShenandoahEvacReserve=0");
- FLAG_SET_DEFAULT(ShenandoahEvacReserve, 0);
- }
+ // No need for evacuation reserve with Full GC
+ if (FLAG_IS_DEFAULT(ShenandoahEvacReserve)) {
+ log_info(gc)("Heuristics sets -XX:ShenandoahEvacReserve=0");
+ FLAG_SET_DEFAULT(ShenandoahEvacReserve, 0);
}
// Disable known barriers by default.
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
index 3c23dd0bf233..d90100953920 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp
@@ -44,24 +44,17 @@ ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers)
}
_global_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, MAX_SNAPSHOTS, mtGC);
- CENSUS_NOISE(_global_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, MAX_SNAPSHOTS, mtGC);)
_tenuring_threshold = NEW_C_HEAP_ARRAY(uint, MAX_SNAPSHOTS, mtGC);
- CENSUS_NOISE(_skipped = 0);
- NOT_PRODUCT(_counted = 0);
- NOT_PRODUCT(_total = 0);
for (int i = 0; i < MAX_SNAPSHOTS; i++) {
// Note that we don't now get perfdata from age_table
_global_age_tables[i] = new AgeTable(false);
- CENSUS_NOISE(_global_noise[i].clear();)
// Sentinel value
_tenuring_threshold[i] = MAX_COHORTS;
}
_local_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, _max_workers, mtGC);
- CENSUS_NOISE(_local_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, max_workers, mtGC);)
for (uint i = 0; i < _max_workers; i++) {
_local_age_tables[i] = new AgeTable(false);
- CENSUS_NOISE(_local_noise[i].clear();)
}
_epoch = MAX_SNAPSHOTS - 1; // see prepare_for_census_update()
}
@@ -72,34 +65,14 @@ ShenandoahAgeCensus::~ShenandoahAgeCensus() {
}
FREE_C_HEAP_ARRAY(_global_age_tables);
FREE_C_HEAP_ARRAY(_tenuring_threshold);
- CENSUS_NOISE(FREE_C_HEAP_ARRAY(_global_noise));
if (_local_age_tables) {
for (uint i = 0; i < _max_workers; i++) {
delete _local_age_tables[i];
}
FREE_C_HEAP_ARRAY(_local_age_tables);
- CENSUS_NOISE(FREE_C_HEAP_ARRAY(_local_noise));
}
}
-#ifdef SHENANDOAH_CENSUS_NOISE
-void ShenandoahAgeCensus::add_skipped(size_t size, uint worker_id) {
- _local_noise[worker_id].skipped += size;
-}
-
-void ShenandoahAgeCensus::add_aged(size_t size, uint worker_id) {
- _local_noise[worker_id].aged += size;
-}
-
-void ShenandoahAgeCensus::add_clamped(size_t size, uint worker_id) {
- _local_noise[worker_id].clamped += size;
-}
-
-void ShenandoahAgeCensus::add_young(size_t size, uint worker_id) {
- _local_noise[worker_id].young += size;
-}
-#endif // SHENANDOAH_CENSUS_NOISE
-
// Prepare for a new census update, by clearing appropriate global slots.
void ShenandoahAgeCensus::prepare_for_census_update() {
assert(_epoch < MAX_SNAPSHOTS, "Out of bounds");
@@ -107,7 +80,6 @@ void ShenandoahAgeCensus::prepare_for_census_update() {
_epoch=0;
}
_global_age_tables[_epoch]->clear();
- CENSUS_NOISE(_global_noise[_epoch].clear();)
}
// Update the census data from appropriate sources,
@@ -115,7 +87,6 @@ void ShenandoahAgeCensus::prepare_for_census_update() {
void ShenandoahAgeCensus::update_census(size_t age0_pop) {
prepare_for_census_update();
assert(_global_age_tables[_epoch]->is_clear(), "Dirty decks");
- CENSUS_NOISE(assert(_global_noise[_epoch].is_clear(), "Dirty decks");)
// Seed cohort 0 with population that may have been missed during
// regular census.
@@ -124,19 +95,11 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop) {
// Merge data from local age tables into the global age table for the epoch,
// clearing the local tables.
for (uint i = 0; i < _max_workers; i++) {
- // age stats
_global_age_tables[_epoch]->merge(_local_age_tables[i]);
_local_age_tables[i]->clear(); // clear for next census
- // Merge noise stats
- CENSUS_NOISE(_global_noise[_epoch].merge(_local_noise[i]);)
- CENSUS_NOISE(_local_noise[i].clear();)
}
update_tenuring_threshold();
-
- // used for checking reasonableness of census coverage, non-product
- // only.
- NOT_PRODUCT(update_total();)
}
size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const {
@@ -155,7 +118,6 @@ void ShenandoahAgeCensus::reset_global() {
assert(_epoch < MAX_SNAPSHOTS, "Out of bounds");
for (uint i = 0; i < MAX_SNAPSHOTS; i++) {
_global_age_tables[i]->clear();
- CENSUS_NOISE(_global_noise[i].clear();)
}
_epoch = MAX_SNAPSHOTS;
assert(_epoch < MAX_SNAPSHOTS, "Error");
@@ -165,7 +127,6 @@ void ShenandoahAgeCensus::reset_global() {
void ShenandoahAgeCensus::reset_local() {
for (uint i = 0; i < _max_workers; i++) {
_local_age_tables[i]->clear();
- CENSUS_NOISE(_local_noise[i].clear();)
}
}
@@ -174,9 +135,7 @@ void ShenandoahAgeCensus::reset_local() {
bool ShenandoahAgeCensus::is_clear_global() {
assert(_epoch < MAX_SNAPSHOTS, "Out of bounds");
for (uint i = 0; i < MAX_SNAPSHOTS; i++) {
- bool clear = _global_age_tables[i]->is_clear();
- CENSUS_NOISE(clear |= _global_noise[i].is_clear();)
- if (!clear) {
+ if (!_global_age_tables[i]->is_clear()) {
return false;
}
}
@@ -186,35 +145,12 @@ bool ShenandoahAgeCensus::is_clear_global() {
// Is local census information clear?
bool ShenandoahAgeCensus::is_clear_local() {
for (uint i = 0; i < _max_workers; i++) {
- bool clear = _local_age_tables[i]->is_clear();
- CENSUS_NOISE(clear |= _local_noise[i].is_clear();)
- if (!clear) {
+ if (!_local_age_tables[i]->is_clear()) {
return false;
}
}
return true;
}
-
-size_t ShenandoahAgeCensus::get_all_ages(uint snap) {
- assert(snap < MAX_SNAPSHOTS, "Out of bounds");
- size_t pop = 0;
- const AgeTable* pv = _global_age_tables[snap];
- for (uint i = 0; i < MAX_COHORTS; i++) {
- pop += pv->sizes[i];
- }
- return pop;
-}
-
-size_t ShenandoahAgeCensus::get_skipped(uint snap) {
- assert(snap < MAX_SNAPSHOTS, "Out of bounds");
- return _global_noise[snap].skipped;
-}
-
-void ShenandoahAgeCensus::update_total() {
- _counted = get_all_ages(_epoch);
- _skipped = get_skipped(_epoch);
- _total = _counted + _skipped;
-}
#endif // !PRODUCT
void ShenandoahAgeCensus::update_tenuring_threshold() {
@@ -332,7 +268,6 @@ void ShenandoahAgeCensus::print() {
const uint tt = tenuring_threshold();
- size_t total= 0;
for (uint i = 1; i < MAX_COHORTS; i++) {
const size_t prev_pop = prev_pv->sizes[i-1]; // (i-1) OK because i >= 1
const size_t cur_pop = cur_pv->sizes[i];
@@ -342,26 +277,9 @@ void ShenandoahAgeCensus::print() {
ls.print_cr(" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ",
i, prev_pop * oopSize, cur_pop * oopSize, mr);
}
- total += cur_pop;
if (i == tt) {
// Underline the cohort for tenuring threshold (if < MAX_COHORTS)
ls.print_cr("----------------------------------------------------------------------------");
}
}
- CENSUS_NOISE(_global_noise[cur_epoch].print(ls, total);)
-}
-
-#ifdef SHENANDOAH_CENSUS_NOISE
-void ShenandoahNoiseStats::print(LogStream& ls, const size_t total) {
- if (total > 0) {
- const float f_skipped = (float)skipped/(float)total;
- const float f_aged = (float)aged/(float)total;
- const float f_clamped = (float)clamped/(float)total;
- const float f_young = (float)young/(float)total;
- ls.print_cr("Skipped: %10zu (%.2f), R-Aged: %10zu (%.2f), "
- "Clamped: %10zu (%.2f), R-Young: %10zu (%.2f)",
- skipped*oopSize, f_skipped, aged*oopSize, f_aged,
- clamped*oopSize, f_clamped, young*oopSize, f_young);
- }
}
-#endif // SHENANDOAH_CENSUS_NOISE
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
index 2d79eb559609..6577715f44d3 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp
@@ -27,55 +27,6 @@
#include "gc/shared/ageTable.hpp"
-#ifndef PRODUCT
-// Enable noise instrumentation
-#define SHENANDOAH_CENSUS_NOISE 1
-#endif // PRODUCT
-
-#ifdef SHENANDOAH_CENSUS_NOISE
-
-#define CENSUS_NOISE(x) x
-#define NO_CENSUS_NOISE(x)
-
-class LogStream;
-
-struct ShenandoahNoiseStats {
- size_t skipped; // Volume of objects skipped
- size_t aged; // Volume of objects from aged regions
- size_t clamped; // Volume of objects whose ages were clamped
- size_t young; // Volume of (rejuvenated) objects of retrograde age
-
- ShenandoahNoiseStats() {
- clear();
- }
-
- void clear() {
- skipped = 0;
- aged = 0;
- clamped = 0;
- young = 0;
- }
-
-#ifndef PRODUCT
- bool is_clear() {
- return (skipped + aged + clamped + young) == 0;
- }
-#endif // !PRODUCT
-
- void merge(ShenandoahNoiseStats& other) {
- skipped += other.skipped;
- aged += other.aged;
- clamped += other.clamped;
- young += other.young;
- }
-
- void print(LogStream& ls, size_t total);
-};
-#else // SHENANDOAH_CENSUS_NOISE
-#define CENSUS_NOISE(x)
-#define NO_CENSUS_NOISE(x) x
-#endif // SHENANDOAH_CENSUS_NOISE
-
// A class for tracking a sequence of cohort population vectors (or,
// interchangeably, age tables) for up to C=MAX_COHORTS age cohorts, where a cohort
// represents the set of objects allocated during a specific inter-GC epoch.
@@ -102,19 +53,6 @@ class ShenandoahAgeCensus: public CHeapObj {
AgeTable** _global_age_tables; // Global age tables used for adapting tenuring threshold, one per snapshot
AgeTable** _local_age_tables; // Local scratch age tables to track object ages, one per worker
-#ifdef SHENANDOAH_CENSUS_NOISE
- ShenandoahNoiseStats* _global_noise; // Noise stats, one per snapshot
- ShenandoahNoiseStats* _local_noise; // Local scratch table for noise stats, one per worker
-
- size_t _skipped; // net size of objects encountered, but skipped during census,
- // because their age was indeterminate
-#endif // SHENANDOAH_CENSUS_NOISE
-
-#ifndef PRODUCT
- size_t _counted; // net size of objects counted in census
- size_t _total; // net size of objects encountered (counted or skipped) in census
-#endif
-
uint _epoch; // Current epoch (modulo max age)
uint* _tenuring_threshold; // An array of the last N tenuring threshold values we
// computed.
@@ -156,20 +94,6 @@ class ShenandoahAgeCensus: public CHeapObj {
// to force is_tenurable() to be true for every age during WB.fullGC tests.
void set_always_tenure(bool always_tenure) { _always_tenure = always_tenure; }
-#ifndef PRODUCT
- // Return the sum of size of objects of all ages recorded in the
- // census at snapshot indexed by snap.
- size_t get_all_ages(uint snap);
-
- // Return the size of all objects that were encountered, but skipped,
- // during the census, because their age was indeterminate.
- size_t get_skipped(uint snap);
-
- // Update the total size of objects counted or skipped at the census for
- // the most recent epoch.
- void update_total();
-#endif // !PRODUCT
-
public:
enum {
MAX_COHORTS = AgeTable::table_size, // = markWord::max_age + 1
@@ -204,20 +128,8 @@ class ShenandoahAgeCensus: public CHeapObj {
bool is_always_tenure() const { return _always_tenure; }
// Update the local age table for worker_id by size for
- // given obj_age, region_age, and region_youth
- CENSUS_NOISE(inline void add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id);)
- NO_CENSUS_NOISE(inline void add(uint obj_age, uint region_age, size_t size, uint worker_id);)
-
-#ifdef SHENANDOAH_CENSUS_NOISE
- // Update the local skip table for worker_id by size
- void add_skipped(size_t size, uint worker_id);
- // Update the local aged region volume table for worker_id by size
- void add_aged(size_t size, uint worker_id);
- // Update the local clamped object volume table for worker_id by size
- void add_clamped(size_t size, uint worker_id);
- // Update the local (rejuvenated) object volume (retrograde age) for worker_id by size
- void add_young(size_t size, uint worker_id);
-#endif // SHENANDOAH_CENSUS_NOISE
+ // given obj_age and region_age
+ inline void add(uint obj_age, uint region_age, size_t size, uint worker_id);
// Update the census data, and compute the new tenuring threshold.
// This method should be called at the end of each marking cycle to update
@@ -245,10 +157,6 @@ class ShenandoahAgeCensus: public CHeapObj {
// Check whether census information is clear
bool is_clear_global();
bool is_clear_local();
-
- // Return the net size of objects encountered (counted or skipped) in census
- // at most recent epoch.
- size_t get_total() const { return _total; }
#endif // !PRODUCT
// Print the age census information
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp
index 299b9a3f8b7a..704915375fa3 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp
@@ -27,35 +27,11 @@
#include "gc/shenandoah/shenandoahAgeCensus.hpp"
-CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id) {)
-NO_CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) {)
+void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) {
if (obj_age <= markWord::max_age) {
assert(obj_age < MAX_COHORTS && region_age < MAX_COHORTS, "Should have been tenured");
-#ifdef SHENANDOAH_CENSUS_NOISE
- // Region ageing is stochastic and non-monotonic; this vitiates mortality
- // demographics in ways that might defeat our algorithms. Marking may be a
- // time when we might be able to correct this, but we currently do not do
- // this. Like skipped statistics further below, we want to track the
- // impact of this noise to see if this may be worthwhile. JDK-.
- uint age = obj_age;
- if (region_age > 0) {
- add_aged(size, worker_id); // this tracking is coarse for now
- age += region_age;
- if (age >= MAX_COHORTS) {
- age = (uint)(MAX_COHORTS - 1); // clamp
- add_clamped(size, worker_id);
- }
- }
- if (region_youth > 0) { // track object volume with retrograde age
- add_young(size, worker_id);
- }
-#else // SHENANDOAH_CENSUS_NOISE
uint age = MIN2(obj_age + region_age, (uint)(MAX_COHORTS - 1)); // clamp
-#endif // SHENANDOAH_CENSUS_NOISE
get_local_age_table(worker_id)->add(age, size);
- } else {
- // update skipped statistics
- CENSUS_NOISE(add_skipped(size, worker_id);)
}
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
index f980c1f88c88..8b67bea82488 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp
@@ -240,11 +240,17 @@ void ShenandoahArguments::initialize_alignments() {
}
}
+void ShenandoahArguments::initialize_heap_flags_and_sizes() {
+ GCArguments::initialize_heap_flags_and_sizes();
+
+ FLAG_SET_ERGO_IF_DEFAULT(SoftMaxHeapSize, MaxHeapSize);
+}
+
CollectedHeap* ShenandoahArguments::create_heap() {
if (strcmp(ShenandoahGCMode, "generational") != 0) {
// Not generational
- return new ShenandoahHeap(new ShenandoahCollectorPolicy());
+ return new ShenandoahHeap();
} else {
- return new ShenandoahGenerationalHeap(new ShenandoahCollectorPolicy());
+ return new ShenandoahGenerationalHeap();
}
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp
index bc73d9a2d128..4dbb0ce561fa 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp
@@ -32,6 +32,7 @@ class CollectedHeap;
class ShenandoahArguments : public GCArguments {
private:
virtual void initialize_alignments();
+ virtual void initialize_heap_flags_and_sizes();
virtual void initialize();
virtual size_t conservative_max_heap_alignment();
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
index f8eff1ddf34e..3d43167aeac3 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp
@@ -107,8 +107,7 @@ void ShenandoahBarrierSet::on_slowpath_allocation_exit(JavaThread* thread, oop n
if (ReduceInitialCardMarks && ShenandoahCardBarrier && !ShenandoahHeap::heap()->is_in_young(new_obj)) {
log_debug(gc)("Newly allocated object (" PTR_FORMAT ") is not in the young generation", p2i(new_obj));
// This can happen when an object is newly allocated, but we come to a safepoint before returning
- // the object. If the safepoint runs a degenerated cycle that is upgraded to a full GC, this object
- // will have survived two GC cycles. If the tenuring age is very low (1), this object may be promoted.
+ // the object. If the safepoint runs a full GC, and the tenuring age is very low (1), this object may be promoted.
// In this case, we have an allocated object, but it has received no stores yet. If card marking barriers
// have been elided, we could end up with an object in old holding pointers to young that won't be in
// the remembered set. The solution here is conservative, but this problem should be rare, and it will
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp
index 33823ad1d8f4..acbb189b75e1 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp
@@ -34,6 +34,7 @@
#include "gc/shenandoah/shenandoahAsserts.hpp"
#include "gc/shenandoah/shenandoahCardTable.hpp"
#include "gc/shenandoah/shenandoahCollectionSet.inline.hpp"
+#include "gc/shenandoah/shenandoahForwarding.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.hpp"
@@ -155,7 +156,7 @@ template
inline oop ShenandoahBarrierSet::oop_load_post(DecoratorSet decorators, oop value, T* addr) {
assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "Reference strength must be known");
- shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc());
+ shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects());
// Perform LRB to handle evacuation and possibly weak loads.
value = load_reference_barrier(decorators, value, addr);
@@ -172,9 +173,9 @@ template
inline void ShenandoahBarrierSet::oop_store_pre(DecoratorSet decorators, T* addr, oop new_value) {
assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "Reference strength must be known");
- shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc());
- shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc());
- shenandoah_assert_not_forwarded_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc());
+ shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects());
+ shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value));
+ shenandoah_assert_not_forwarded_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value));
shenandoah_assert_marked_if(nullptr, new_value,
!CompressedOops::is_null(new_value) &&
@@ -199,11 +200,11 @@ inline void ShenandoahBarrierSet::oop_cmpxchg_pre(DecoratorSet decorators, T* ad
assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "CAS should have resolved ref strength");
assert((decorators & ON_STRONG_OOP_REF) != 0, "CAS only for strong refs");
- shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc());
- shenandoah_assert_not_in_cset_except(nullptr, compare_value, compare_value == nullptr || _heap->cancelled_gc());
- shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc());
- shenandoah_assert_not_forwarded_except(addr, compare_value, compare_value == nullptr || _heap->cancelled_gc());
- shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || _heap->cancelled_gc());
+ shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects());
+ shenandoah_assert_not_in_cset_except(nullptr, compare_value, compare_value == nullptr || ShenandoahForwarding::is_self_forwarded(compare_value));
+ shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value));
+ shenandoah_assert_not_forwarded_except(addr, compare_value, compare_value == nullptr || ShenandoahForwarding::is_self_forwarded(compare_value));
+ shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value));
// Handle the previous value through SATB, as we are about to perform the store.
oop prev = RawAccess<>::oop_load(addr);
@@ -221,9 +222,9 @@ inline void ShenandoahBarrierSet::oop_xchg_pre(DecoratorSet decorators, T* addr,
assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "XCHG should have resolved ref strength");
assert((decorators & ON_STRONG_OOP_REF) != 0, "XCHG only for strong refs");
- shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc());
- shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc());
- shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || _heap->cancelled_gc());
+ shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects());
+ shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value));
+ shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value));
// Handle the previous value through SATB, as we are about to perform the store.
oop prev = RawAccess<>::oop_load(addr);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
index 58e47841819f..a9d3e5a3d9bf 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp
@@ -204,22 +204,8 @@ class ShenandoahCleanUpdateWeakOopsClosure : public OopClosure {
// ========= Update References
//
-template
-class ShenandoahMarkUpdateRefsClosure : public ShenandoahMarkRefsSuperClosure {
-private:
- template
- inline void work(T* p);
-
-public:
- ShenandoahMarkUpdateRefsClosure(ShenandoahObjToScanQueue* q, ShenandoahReferenceProcessor* rp, ShenandoahObjToScanQueue* old_q);
-
- virtual void do_oop(narrowOop* p) { work(p); }
- virtual void do_oop(oop* p) { work(p); }
-};
-
-class ShenandoahUpdateRefsSuperClosure : public ShenandoahSuperClosure {};
-
-class ShenandoahNonConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosure {
+template
+class ShenandoahUpdateRefsClosure : public ShenandoahSuperClosure {
private:
template
inline void work(T* p);
@@ -229,17 +215,6 @@ class ShenandoahNonConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosu
virtual void do_oop(oop* p) { work(p); }
};
-class ShenandoahConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosure {
-private:
- template
- inline void work(T* p);
-
-public:
- virtual void do_oop(narrowOop* p) { work(p); }
- virtual void do_oop(oop* p) { work(p); }
-};
-
-
class ShenandoahFlushSATB : public ThreadClosure {
private:
SATBMarkQueueSet& _satb_qset;
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp
index 16280d35c5d5..0ff970ae9362 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp
@@ -201,33 +201,26 @@ void ShenandoahCleanUpdateWeakOopsClosure::do_oo
//
// ========= Update References
//
-
-template
-ShenandoahMarkUpdateRefsClosure::ShenandoahMarkUpdateRefsClosure(ShenandoahObjToScanQueue* q,
- ShenandoahReferenceProcessor* rp,
- ShenandoahObjToScanQueue* old_q) :
- ShenandoahMarkRefsSuperClosure(q, rp, old_q) {
- assert(_heap->is_stw_gc_in_progress(), "Can only be used for STW GC");
-}
-
-template
-template
-inline void ShenandoahMarkUpdateRefsClosure::work(T* p) {
- // Update the location
- _heap->non_conc_update_with_forwarded(p);
-
- // ...then do the usual thing
- ShenandoahMarkRefsSuperClosure::work(p);
-}
-
+template
template
-inline void ShenandoahNonConcUpdateRefsClosure::work(T* p) {
- _heap->non_conc_update_with_forwarded(p);
-}
+void ShenandoahUpdateRefsClosure::work(T* p) {
+ T o = RawAccess<>::oop_load(p);
+ if (!CompressedOops::is_null(o)) {
+ oop obj = CompressedOops::decode_not_null(o);
+ if (_heap->in_collection_set(obj)) {
+ shenandoah_assert_forwarded(p, obj);
+ oop resolved = ShenandoahForwarding::forwardee(obj);
+ shenandoah_assert_not_in_cset_except(p, resolved, _heap->cancelled_gc() || _heap->has_self_forwarded_objects());
-template
-inline void ShenandoahConcUpdateRefsClosure::work(T* p) {
- _heap->conc_update_with_forwarded(p);
+ if (resolved != obj) {
+ if (CONCURRENT) {
+ _heap->atomic_update_oop(resolved, p, o);
+ } else {
+ RawAccess::oop_store(p, resolved);
+ }
+ }
+ }
+ }
}
inline void ShenandoahFlushSATB::do_thread(Thread* thread) {
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp
index d2a489a15bee..de816149faf2 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp
@@ -187,6 +187,16 @@ ShenandoahHeapRegion* ShenandoahCollectionSet::next() {
return nullptr;
}
+bool ShenandoahCollectionSet::all_regions_claimed() const {
+ const size_t max = _heap->num_regions();
+ for (size_t index = _current_index.load_relaxed(); index < max; index++) {
+ if (is_in(index)) {
+ return false;
+ }
+ }
+ return true;
+}
+
void ShenandoahCollectionSet::print_on(outputStream* out) const {
out->print_cr("Collection Set: Regions: "
"%zu, Garbage: %zu%s, Live: %zu%s, Used: %zu%s", count(),
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp
index 7722423709d9..eec93f5b0ac2 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp
@@ -84,6 +84,9 @@ class ShenandoahCollectionSet : public CHeapObj {
// Single-thread version
ShenandoahHeapRegion* next();
+ // Not MT safe, use to test if gc workers processed all regions
+ bool all_regions_claimed() const;
+
size_t count() const { return _region_count; }
bool is_empty() const { return _region_count == 0; }
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp
index 0eaf91f38fff..4d7d7992111c 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp
@@ -25,27 +25,29 @@
*/
+#include "gc/shared/gc_globals.hpp"
+#include "gc/shared/plab.hpp"
+#include "gc/shenandoah/mode/shenandoahMode.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
-#include "gc/shenandoah/shenandoahGC.hpp"
-#include "gc/shenandoah/shenandoahHeap.inline.hpp"
-#include "runtime/os.hpp"
-
-ShenandoahCollectorPolicy::ShenandoahCollectorPolicy() :
+#include "gc/shenandoah/shenandoahController.hpp"
+#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
+#include "gc/shenandoah/shenandoahThreadLocalData.hpp"
+#include "logging/log.hpp"
+#include "utilities/copy.hpp"
+#include "utilities/ostream.hpp"
+
+ShenandoahCollectorPolicy::ShenandoahCollectorPolicy(ShenandoahMode* mode) :
_success_concurrent_gcs(0),
_abbreviated_concurrent_gcs(0),
- _success_degenerated_gcs(0),
- _abbreviated_degenerated_gcs(0),
+ _last_whole_heap_gc_id(0),
_success_full_gcs(0),
- _consecutive_degenerated_gcs(0),
- _consecutive_degenerated_gcs_without_progress(0),
_consecutive_young_gcs(0),
_mixed_gcs(0),
_success_old_gcs(0),
_interrupted_old_gcs(0),
- _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);
+ _alloc_failure_full(0),
+ _mode(mode) {
+ Copy::zero_to_bytes(_stall_counts, sizeof(size_t) * ShenandoahController::PHASE_LIMIT);
Copy::zero_to_bytes(_collection_cause_counts, sizeof(size_t) * GCCause::_last_gc_cause);
_tracer = new ShenandoahTracer();
@@ -60,21 +62,19 @@ void ShenandoahCollectorPolicy::record_alloc_failure_to_full() {
_alloc_failure_full++;
}
-void ShenandoahCollectorPolicy::record_alloc_failure_to_degenerated(ShenandoahGC::ShenandoahDegenPoint point) {
- assert(point < ShenandoahGC::_DEGENERATED_LIMIT, "sanity");
- _alloc_failure_degenerated++;
- _degen_point_counts[point]++;
-}
-
-void ShenandoahCollectorPolicy::record_degenerated_upgrade_to_full() {
- reset_consecutive_degenerated_gcs();
- _alloc_failure_degenerated_upgrade_to_full++;
+void ShenandoahCollectorPolicy::record_allocation_stall(ShenandoahController::ShenandoahCollectorPhase phase) {
+ assert(phase < ShenandoahController::PHASE_LIMIT, "Invalid phase: %d", phase);
+ assert(ShenandoahController::UNSET <= phase, "Invalid phase: %d", phase);
+ _stall_counts[phase].add_then_fetch(1UL, memory_order_relaxed);
}
-void ShenandoahCollectorPolicy::record_success_concurrent(bool is_young, bool is_abbreviated) {
+void ShenandoahCollectorPolicy::record_success_concurrent(size_t gc_id, bool is_young, bool is_abbreviated) {
update_young(is_young);
- reset_consecutive_degenerated_gcs();
+ if (!is_young) {
+ _last_whole_heap_gc_id.store_relaxed(gc_id);
+ }
+
_success_concurrent_gcs++;
if (is_abbreviated) {
_abbreviated_concurrent_gcs++;
@@ -95,23 +95,6 @@ void ShenandoahCollectorPolicy::record_interrupted_old() {
_interrupted_old_gcs++;
}
-void ShenandoahCollectorPolicy::record_degenerated(bool is_young, bool is_abbreviated, bool progress) {
- update_young(is_young);
-
- _success_degenerated_gcs++;
- _consecutive_degenerated_gcs++;
-
- if (progress) {
- _consecutive_degenerated_gcs_without_progress = 0;
- } else {
- _consecutive_degenerated_gcs_without_progress++;
- }
-
- if (is_abbreviated) {
- _abbreviated_degenerated_gcs++;
- }
-}
-
void ShenandoahCollectorPolicy::update_young(bool is_young) {
if (is_young) {
_consecutive_young_gcs++;
@@ -120,10 +103,10 @@ void ShenandoahCollectorPolicy::update_young(bool is_young) {
}
}
-void ShenandoahCollectorPolicy::record_success_full() {
- reset_consecutive_degenerated_gcs();
+void ShenandoahCollectorPolicy::record_success_full(size_t gc_id) {
_consecutive_young_gcs = 0;
_success_full_gcs++;
+ _last_whole_heap_gc_id.store_relaxed(gc_id);
}
void ShenandoahCollectorPolicy::record_shutdown() {
@@ -176,6 +159,11 @@ bool ShenandoahCollectorPolicy::should_run_full_gc(GCCause::Cause cause) {
if (!ShenandoahHeap::heap()->mode()->is_concurrent()) {
return true;
}
+
+ if (cause == GCCause::_shenandoah_upgrade_to_full_gc) {
+ return true;
+ }
+
return is_explicit_gc(cause) && !ExplicitGCInvokesConcurrent;
}
@@ -188,11 +176,75 @@ bool ShenandoahCollectorPolicy::should_handle_requested_gc(GCCause::Cause cause)
return true;
}
+static PLAB* shenandoah_get_lab(ShenandoahHeapRegion* region) {
+ if (region->is_young()) {
+ return ShenandoahThreadLocalData::gclab(Thread::current());
+ }
+
+ assert(region->is_old(), "Region in cset(%zu) must be young or old", region->index());
+ ShenandoahPLAB* shenandoah_plab = ShenandoahThreadLocalData::shenandoah_plab(Thread::current());
+ assert(shenandoah_plab != nullptr, "Must have plab in generational mode");
+ return shenandoah_plab->plab();
+}
+
+bool ShenandoahCollectorPolicy::should_abandon_evacuations(ShenandoahHeapRegion* region) {
+ if (region->has_self_forwards() && ShenandoahThreadLocalData::has_evac_failures()) {
+ // This region and this thread are lost. This thread has evacuated all it can. If
+ // we let it continue on to other regions, it will only fail those as well. We want
+ // to let other threads try the regions that this thread could not. This isn't as
+ // precise as it could be. In a mixed collection, we would ideally have a way to
+ // stop this thread from claiming old or young regions, depending on which lab is
+ // exhausted.
+ log_debug(gc, thread)("Region (%zu) has self-forwards and this thread has evacuation failures (LAB words remaining: %zu)",
+ region->index(), shenandoah_get_lab(region)->words_remaining());
+ return true;
+ }
+ return false;
+}
+
+// Some causes should not be allowed to preempt others. We must make sure that
+// regulator requests and allocation failures do not preempt a shutdown request.
+int ShenandoahCollectorPolicy::cause_priority(GCCause::Cause cause) {
+ if (is_explicit_gc(cause)) {
+ return 5;
+ }
+
+ switch (cause) {
+ case GCCause::_shenandoah_stop_vm:
+ return 7;
+ case GCCause::_shenandoah_upgrade_to_full_gc:
+ return 6;
+ case GCCause::_shenandoah_humongous_allocation_failure:
+ return 4;
+ case GCCause::_shenandoah_allocation_failure_evac:
+ return 3;
+ case GCCause::_allocation_failure:
+ return 2;
+ case GCCause::_shenandoah_concurrent_gc:
+ return 1;
+ case GCCause::_no_gc:
+ return 0;
+ default:
+ // Unanticipated gc causes are treated as an allocation failure and cannot be
+ // preempted by regulator requests
+ return 2;
+ }
+}
+
+template
+size_t shenandoah_sum_array(T* a, size_t length) {
+ size_t sum = 0;
+ for (size_t i = 0; i < length; i++) {
+ sum += a[i].load_relaxed();
+ }
+ return sum;
+}
+
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 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->print_cr("Under allocation pressure, concurrent cycles may stall allocating threads or escalate ");
+ out->print_cr("to stop-the-world Full GC. Increase heap size, tune GC heuristics, or lower allocation rate");
+ out->print_cr("to avoid stalls and Full GC cycles. Abbreviated cycles are those which found enough");
+ out->print_cr("regions with no live objects to skip evacuation.");
out->cr();
size_t gc_attempts = 0;
@@ -200,7 +252,7 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const {
gc_attempts += _collection_cause_counts[c];
}
- size_t completed_gcs = _success_full_gcs + _success_degenerated_gcs + _success_concurrent_gcs + _success_old_gcs;
+ size_t completed_gcs = _success_full_gcs + _success_concurrent_gcs + _success_old_gcs;
out->print_cr("%5zu GC attempts. %zu Completed GCs (%.2f%%).",
gc_attempts, completed_gcs, percent_of(completed_gcs, gc_attempts));
@@ -218,27 +270,25 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const {
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()) {
+ if (_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 mixed", _mixed_gcs);
out->print_cr(" %5zu interruptions", _interrupted_old_gcs);
out->cr();
}
- size_t degenerated_gcs = _alloc_failure_degenerated_upgrade_to_full + _success_degenerated_gcs;
- out->print_cr("%5zu Degenerated GCs (%.2f%%)", degenerated_gcs, percent_of(degenerated_gcs, completed_gcs));
- out->print_cr(" %5zu upgraded to Full GC (%.2f%%)", _alloc_failure_degenerated_upgrade_to_full, percent_of(_alloc_failure_degenerated_upgrade_to_full, degenerated_gcs));
- out->print_cr(" %5zu caused by allocation failure (%.2f%%)", _alloc_failure_degenerated, percent_of(_alloc_failure_degenerated, degenerated_gcs));
- out->print_cr(" %5zu abbreviated (%.2f%%)", _abbreviated_degenerated_gcs, percent_of(_abbreviated_degenerated_gcs, degenerated_gcs));
- for (int c = 0; c < ShenandoahGC::_DEGENERATED_LIMIT; c++) {
- if (_degen_point_counts[c] > 0) {
- const char* desc = ShenandoahGC::degen_point_to_string((ShenandoahGC::ShenandoahDegenPoint)c);
- out->print_cr(" %5zu happened at %s", _degen_point_counts[c], desc);
+ const size_t total_stalls = shenandoah_sum_array(_stall_counts, ShenandoahController::PHASE_LIMIT);
+ out->print_cr("%5zu Stalls", total_stalls);
+ for (int c = 0; c < ShenandoahController::PHASE_LIMIT; c++) {
+ const size_t stall_count = _stall_counts[c].load_relaxed();
+ if (stall_count > 0) {
+ const auto phase = static_cast(c);
+ const char* desc = ShenandoahController::collector_phase_to_string(phase);
+ out->print_cr(" %5zu happened at %s", stall_count, desc);
}
}
out->cr();
out->print_cr("%5zu Full GCs (%.2f%%)", _success_full_gcs, percent_of(_success_full_gcs, completed_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 e265da9d4134..a3931b6714eb 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp
@@ -27,48 +27,39 @@
#define SHARE_GC_SHENANDOAH_SHENANDOAHCOLLECTORPOLICY_HPP
#include "gc/shared/gcTrace.hpp"
-#include "gc/shenandoah/shenandoahGC.hpp"
+#include "gc/shenandoah/shenandoahController.hpp"
#include "gc/shenandoah/shenandoahSharedVariables.hpp"
#include "gc/shenandoah/shenandoahTrace.hpp"
#include "memory/allocation.hpp"
-#include "utilities/ostream.hpp"
+
+class ShenandoahHeapRegion;
+class ShenandoahMode;
class ShenandoahCollectorPolicy : public CHeapObj {
private:
size_t _success_concurrent_gcs;
size_t _abbreviated_concurrent_gcs;
- size_t _success_degenerated_gcs;
- size_t _abbreviated_degenerated_gcs;
- // Written by control thread, read by mutators
+
+ // Written by control thread, read by mutators (infrequently)
+ Atomic _last_whole_heap_gc_id;
volatile size_t _success_full_gcs;
- uint _consecutive_degenerated_gcs;
- uint _consecutive_degenerated_gcs_without_progress;
volatile size_t _consecutive_young_gcs;
+
size_t _mixed_gcs;
size_t _success_old_gcs;
size_t _interrupted_old_gcs;
- size_t _alloc_failure_degenerated;
- size_t _alloc_failure_degenerated_upgrade_to_full;
size_t _alloc_failure_full;
size_t _collection_cause_counts[GCCause::_last_gc_cause];
- size_t _degen_point_counts[ShenandoahGC::_DEGENERATED_LIMIT];
+
+ Atomic _stall_counts[ShenandoahController::PHASE_LIMIT];
ShenandoahSharedFlag _in_shutdown;
ShenandoahTracer* _tracer;
-
- void reset_consecutive_degenerated_gcs() {
- _consecutive_degenerated_gcs = 0;
- _consecutive_degenerated_gcs_without_progress = 0;
- }
+ ShenandoahMode* _mode;
public:
- // The most common scenario for lack of good progress following a degenerated GC is an accumulation of floating
- // garbage during the most recently aborted concurrent GC effort. Usually, it is far more effective to
- // reclaim this floating garbage with another degenerated cycle (which focuses on young generation and might require
- // a pause of 200 ms) rather than a full GC cycle (which may require multiple seconds with a 10 GB old generation).
- static constexpr size_t CONSECUTIVE_BAD_DEGEN_PROGRESS_THRESHOLD = 2;
- ShenandoahCollectorPolicy();
+ ShenandoahCollectorPolicy(ShenandoahMode* mode);
void record_mixed_cycle();
void record_success_old();
@@ -77,19 +68,12 @@ class ShenandoahCollectorPolicy : public CHeapObj {
// A collection cycle may be "abbreviated" if Shenandoah finds a sufficient percentage
// of regions that contain no live objects (ShenandoahImmediateThreshold). These cycles
// end after final mark, skipping the evacuation and reference-updating phases. Such
- // cycles are very efficient and are worth tracking. Note that both degenerated and
- // concurrent cycles can be abbreviated.
- void record_success_concurrent(bool is_young, bool is_abbreviated);
-
- // Record that a degenerated cycle has been completed. Note that such a cycle may or
- // may not make "progress". We separately track the total number of degenerated cycles,
- // the number of consecutive degenerated cycles and the number of consecutive cycles that
- // fail to make good progress.
- void record_degenerated(bool is_young, bool is_abbreviated, bool progress);
- void record_success_full();
- void record_alloc_failure_to_degenerated(ShenandoahGC::ShenandoahDegenPoint point);
+ // cycles are very efficient and are worth tracking.
+ void record_success_concurrent(size_t gc_id, bool is_young, bool is_abbreviated);
+
+ void record_success_full(size_t gc_id);
+ void record_allocation_stall(ShenandoahController::ShenandoahCollectorPhase phase);
void record_alloc_failure_to_full();
- void record_degenerated_upgrade_to_full();
void record_collection_cause(GCCause::Cause cause);
void record_shutdown();
@@ -100,19 +84,11 @@ class ShenandoahCollectorPolicy : public CHeapObj {
void print_gc_stats(outputStream* out) const;
size_t full_gc_count() const {
- return _success_full_gcs + _alloc_failure_degenerated_upgrade_to_full;
+ return _success_full_gcs;
}
- // If the heuristics find that the number of consecutive degenerated cycles is above
- // ShenandoahFullGCThreshold, then they will initiate a Full GC upon an allocation
- // failure.
- size_t consecutive_degenerated_gc_count() const {
- return _consecutive_degenerated_gcs;
- }
-
- // Only upgrade to a full gc after the configured number of futile degenerated cycles.
- bool should_upgrade_degenerated_gc() const {
- return _consecutive_degenerated_gcs_without_progress >= CONSECUTIVE_BAD_DEGEN_PROGRESS_THRESHOLD;
+ size_t last_whole_heap_gc_id() const {
+ return _last_whole_heap_gc_id.load_relaxed();
}
static bool is_allocation_failure(GCCause::Cause cause);
@@ -121,11 +97,23 @@ class ShenandoahCollectorPolicy : public CHeapObj {
static bool should_run_full_gc(GCCause::Cause cause);
static bool should_handle_requested_gc(GCCause::Cause cause);
+ static bool is_higher_priority(GCCause::Cause lhs, GCCause::Cause rhs) {
+ return cause_priority(lhs) > cause_priority(rhs);
+ }
+
+ // True if the region has self-forwarded objects and the calling thread has exhausted its LABs.
+ static bool should_abandon_evacuations(ShenandoahHeapRegion* region);
+
size_t consecutive_young_gc_count() const {
return _consecutive_young_gcs;
}
+ size_t reclaiming_gc_count() const {
+ return _success_concurrent_gcs + _success_full_gcs + _success_old_gcs;
+ }
+
private:
+ static int cause_priority(GCCause::Cause cause);
void update_young(bool is_young);
};
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
index f6a614586971..0b875fa1c2ca 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp
@@ -28,12 +28,10 @@
#include "code/codeCache.hpp"
#include "gc/shared/barrierSetNMethod.hpp"
#include "gc/shared/collectorCounters.hpp"
-#include "gc/shared/continuationGCSupport.inline.hpp"
#include "gc/shenandoah/shenandoahBreakpoint.hpp"
#include "gc/shenandoah/shenandoahClosures.inline.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahConcurrentGC.hpp"
-#include "gc/shenandoah/shenandoahForwarding.inline.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
#include "gc/shenandoah/shenandoahGenerationalHeap.hpp"
@@ -93,16 +91,17 @@ class ShenandoahBreakpointMarkScope : public StackObj {
}
};
-ShenandoahConcurrentGC::ShenandoahConcurrentGC(ShenandoahGeneration* generation, bool do_old_gc_bootstrap) :
+ShenandoahConcurrentGC::ShenandoahConcurrentGC(ShenandoahController* controller, ShenandoahGeneration* generation, bool do_old_gc_bootstrap) :
ShenandoahGC(generation),
_mark(generation),
- _degen_point(ShenandoahDegenPoint::_degenerated_unset),
+ _controller(controller),
_abbreviated(false),
_do_old_gc_bootstrap(do_old_gc_bootstrap) {
+ update_phase(ShenandoahController::INITIALIZING);
}
-ShenandoahGC::ShenandoahDegenPoint ShenandoahConcurrentGC::degen_point() const {
- return _degen_point;
+ShenandoahConcurrentGC::~ShenandoahConcurrentGC() {
+ update_phase(ShenandoahController::UNSET);
}
void ShenandoahConcurrentGC::entry_concurrent_update_refs_prepare(ShenandoahHeap* const heap) {
@@ -163,13 +162,13 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) {
// Concurrent mark roots
entry_mark_roots();
- if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_roots)) {
+ if (check_cancellation_and_abort()) {
return false;
}
// Continue concurrent mark
entry_mark();
- if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_mark)) {
+ if (check_cancellation_and_abort()) {
return false;
}
}
@@ -177,12 +176,8 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) {
// Complete marking under STW, and start evacuation
vmop_entry_final_mark();
- // If the GC was cancelled before final mark, nothing happens on the safepoint. We are still
- // in the marking phase and must resume the degenerated cycle from there. If the GC was cancelled
- // after final mark, then we've entered the evacuation phase and must resume the degenerated cycle
- // from that phase.
if (_generation->is_concurrent_mark_in_progress()) {
- bool cancelled = check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_mark);
+ bool cancelled = check_cancellation_and_abort();
assert(cancelled, "GC must have been cancelled between concurrent and final mark");
return false;
}
@@ -224,16 +219,29 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) {
// This may be skipped if there is nothing to evacuate.
// If so, evac_in_progress would be unset by collection set preparation code.
if (heap->is_evacuation_in_progress()) {
+ // Final mark may have reclaimed some immediate garbage that could satisfy allocation requests.
+ // We are proceeding with evacuation, so notify alloc waiters (if we are not evacuating, completion
+ // of the abbreviated cycle would also notify them).
+ _controller->notify_alloc_waiters();
+
// Concurrently evacuate
entry_evacuate();
- if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_evac)) {
+ if (check_cancellation_and_abort()) {
return false;
}
+ if (!heap->collection_set()->all_regions_claimed()) {
+ // Self forward objects stranded in the collection set
+ entry_self_forward_stranded_objects();
+ if (check_cancellation_and_abort()) {
+ return false;
+ }
+ }
+
// Perform update-refs phase.
entry_concurrent_update_refs_prepare(heap);
- if (ShenandoahHeap::heap()->mode()->is_generational()) {
+ if (heap->mode()->is_generational()) {
entry_update_card_table();
}
@@ -242,13 +250,13 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) {
}
entry_update_refs();
- if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_update_refs)) {
+ if (check_cancellation_and_abort()) {
return false;
}
// Concurrent update thread roots
entry_update_thread_roots();
- if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_update_refs)) {
+ if (check_cancellation_and_abort()) {
return false;
}
@@ -262,12 +270,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) {
if (heap->mode()->is_generational()) {
entry_complete_abbreviated_cycle();
- // If the promote-in-place operation was cancelled, we can have the degenerated
- // cycle complete the operation. It will see that no evacuations are in progress,
- // and that there are regions wanting promotion. The risk with not handling the
- // cancellation would be failing to restore top for these regions and leaving
- // them unable to serve allocations for the old generation.
- if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_evac)) {
+ if (check_cancellation_and_abort()) {
return false;
}
}
@@ -287,7 +290,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) {
// Instead of always resetting immediately before the start of a new GC, we can often reset at the end of the
// previous GC. This allows us to start the next GC cycle more quickly after a trigger condition is detected,
- // reducing the likelihood that GC will degenerate.
+ // reducing the likelihood that mutators will experience allocation stalls.
entry_reset_after_collect();
return true;
@@ -313,11 +316,11 @@ void ShenandoahConcurrentGC::entry_complete_abbreviated_cycle() {
if (heap->old_generation()->has_in_place_promotions()) {
ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::complete_abbreviated_promote_in_place);
ShenandoahGCWorkerPhase worker_phase(ShenandoahPhaseTimings::complete_abbreviated_promote_in_place);
- heap->promote_regions_in_place(_generation, true);
+ heap->promote_regions_in_place(_generation);
}
// At this point, the cycle is effectively complete. If the cycle has been cancelled here,
- // the control thread will detect it on its next iteration and run a degenerated young cycle.
+ // the control thread will detect it on its next iteration.
if (!heap->cancelled_gc() && !_generation->is_old()) {
ShenandoahTimingsTracker tracker(ShenandoahPhaseTimings::complete_abbreviated_update_region_ages);
heap->update_region_ages(_generation->complete_marking_context());
@@ -497,6 +500,7 @@ void ShenandoahConcurrentGC::entry_mark_roots() {
ShenandoahWorkerPolicy::calc_workers_for_conc_marking(),
"concurrent marking roots");
+ update_phase(ShenandoahController::ROOTS);
heap->try_inject_alloc_failure();
op_mark_roots();
}
@@ -515,6 +519,7 @@ void ShenandoahConcurrentGC::entry_mark() {
ShenandoahWorkerPolicy::calc_workers_for_conc_marking(),
"concurrent marking");
+ update_phase(ShenandoahController::MARK);
heap->try_inject_alloc_failure();
op_mark();
heap->try_inject_pin();
@@ -613,9 +618,9 @@ void ShenandoahConcurrentGC::entry_cleanup_early() {
op_cleanup_early();
if (!heap->is_evacuation_in_progress()) {
// This is an abbreviated cycle. Rebuild the freeset in order to establish reserves for the next GC cycle. Doing
- // the rebuild ASAP also expedites availability of immediate trash, reducing the likelihood that we will degenerate
+ // the rebuild ASAP also expedites availability of immediate trash, reducing the likelihood that mutators will stall
// during promote-in-place processing.
- heap->rebuild_free_set(true /*concurrent*/);
+ heap->rebuild_free_set();
}
}
@@ -630,11 +635,28 @@ void ShenandoahConcurrentGC::entry_evacuate() {
ShenandoahWorkerPolicy::calc_workers_for_conc_evac(),
"concurrent evacuation");
+ update_phase(ShenandoahController::EVAC);
heap->try_inject_alloc_failure();
heap->try_inject_pin();
op_evacuate();
}
+void ShenandoahConcurrentGC::entry_self_forward_stranded_objects() {
+ ShenandoahHeap* const heap = ShenandoahHeap::heap();
+ TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters());
+ SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Concurrent self forwarding", "");
+ ShenandoahConcurrentSubphase gc_phase(msg, ShenandoahPhaseTimings::conc_self_forward_stranded);
+ EventMark em("%s", msg);
+
+ ShenandoahWorkerScope scope(heap->workers(),
+ ShenandoahWorkerPolicy::calc_workers_for_conc_evac(),
+ "concurrent self forwarding");
+
+ heap->try_inject_alloc_failure();
+ heap->try_inject_pin();
+ op_self_forward_stranded_objects();
+}
+
void ShenandoahConcurrentGC::entry_update_thread_roots() {
ShenandoahHeap* const heap = ShenandoahHeap::heap();
TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters());
@@ -659,6 +681,7 @@ void ShenandoahConcurrentGC::entry_update_refs() {
ShenandoahWorkerPolicy::calc_workers_for_conc_update_ref(),
"concurrent reference update");
+ update_phase(ShenandoahController::UPDATE_REFS);
heap->try_inject_alloc_failure();
heap->try_inject_pin();
op_update_refs();
@@ -686,6 +709,12 @@ void ShenandoahConcurrentGC::entry_reset_after_collect() {
op_reset_after_collect();
}
+void ShenandoahConcurrentGC::update_phase(ShenandoahController::ShenandoahCollectorPhase phase) const {
+ if (_controller != nullptr) {
+ _controller->set_phase(phase);
+ }
+}
+
void ShenandoahConcurrentGC::op_reset() {
ShenandoahHeap* const heap = ShenandoahHeap::heap();
@@ -825,7 +854,7 @@ void ShenandoahConcurrentGC::op_final_mark() {
// The collection set is chosen by prepare_regions_and_collection_set(). Additionally, certain parameters have been
// established to govern the evacuation efforts that are about to begin. Refer to comments on reserve members in
// ShenandoahGeneration and ShenandoahOldGeneration for more detail.
- _generation->prepare_regions_and_collection_set(true /*concurrent*/);
+ _generation->prepare_regions_and_collection_set();
// Has to be done after cset selection
heap->prepare_concurrent_roots();
@@ -1143,7 +1172,12 @@ void ShenandoahConcurrentGC::op_cleanup_early() {
}
void ShenandoahConcurrentGC::op_evacuate() {
- ShenandoahHeap::heap()->evacuate_collection_set(_generation, true /*concurrent*/);
+ ShenandoahHeap* heap = ShenandoahHeap::heap();
+ heap->evacuate_collection_set(_generation);
+}
+
+void ShenandoahConcurrentGC::op_self_forward_stranded_objects() {
+ ShenandoahHeap::heap()->self_forward_stranded_objects();
}
void ShenandoahConcurrentGC::op_init_update_refs() {
@@ -1155,7 +1189,7 @@ void ShenandoahConcurrentGC::op_init_update_refs() {
}
void ShenandoahConcurrentGC::op_update_refs() {
- ShenandoahHeap::heap()->update_heap_references(_generation, true /*concurrent*/);
+ ShenandoahHeap::heap()->update_heap_references(_generation);
}
class ShenandoahUpdateThreadHandshakeClosure : public HandshakeClosure {
@@ -1163,7 +1197,7 @@ class ShenandoahUpdateThreadHandshakeClosure : public HandshakeClosure {
// This closure runs when thread is stopped for handshake, which means
// we can use non-concurrent closure here, as long as it only updates
// locations modified by the thread itself, i.e. stack locations.
- ShenandoahNonConcUpdateRefsClosure _cl;
+ ShenandoahUpdateRefsClosure _cl;
public:
ShenandoahUpdateThreadHandshakeClosure();
void do_thread(Thread* thread) override;
@@ -1229,7 +1263,7 @@ void ShenandoahConcurrentGC::op_update_thread_roots() {
void ShenandoahConcurrentGC::op_final_update_refs() {
ShenandoahHeap* const heap = ShenandoahHeap::heap();
assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint");
- assert(!heap->_update_refs_iterator.has_next(), "Should have finished update references");
+ assert(heap->is_update_refs_in_progress(), "Should have run update references first");
heap->finish_concurrent_roots();
@@ -1246,21 +1280,23 @@ void ShenandoahConcurrentGC::op_final_update_refs() {
// If we are running in generational mode, this will also age active regions that
// haven't been used for allocation.
- heap->update_heap_region_states(true /*concurrent*/);
+ heap->update_heap_region_states();
heap->set_update_refs_in_progress(false);
heap->set_has_forwarded_objects(false);
+ heap->unset_has_self_forwarded_objects();
+
+ if (VerifyAfterGC) {
+ Universe::verify();
+ }
+
+ heap->rebuild_free_set();
if (ShenandoahVerify) {
ShenandoahTimingsTracker v(ShenandoahPhaseTimings::final_update_refs_verify);
heap->verifier()->verify_after_update_refs(_generation);
}
- if (VerifyAfterGC) {
- Universe::verify();
- }
-
- heap->rebuild_free_set(true /*concurrent*/);
_generation->heuristics()->start_idle_span();
// Final pause: update GC barriers to idle state.
@@ -1312,10 +1348,6 @@ void ShenandoahConcurrentGC::op_reset_after_collect() {
}
}
-bool ShenandoahConcurrentGC::check_cancellation_and_abort(ShenandoahDegenPoint point) {
- if (ShenandoahHeap::heap()->cancelled_gc()) {
- _degen_point = point;
- return true;
- }
- return false;
+bool ShenandoahConcurrentGC::check_cancellation_and_abort() {
+ return ShenandoahHeap::heap()->cancelled_gc();
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp
index 7b49b6626e23..f3cdede2bc09 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp
@@ -28,11 +28,11 @@
#include "gc/shared/gcCause.hpp"
#include "gc/shenandoah/shenandoahConcurrentMark.hpp"
+#include "gc/shenandoah/shenandoahController.hpp"
#include "gc/shenandoah/shenandoahGC.hpp"
#include "gc/shenandoah/shenandoahHeap.hpp"
class ShenandoahGeneration;
-
class VM_ShenandoahInitMark;
class VM_ShenandoahFinalMarkStartEvac;
class VM_ShenandoahInitUpdateRefs;
@@ -50,15 +50,15 @@ class ShenandoahConcurrentGC : public ShenandoahGC {
ShenandoahConcurrentMark _mark;
private:
- ShenandoahDegenPoint _degen_point;
- bool _abbreviated;
- const bool _do_old_gc_bootstrap;
+ ShenandoahController* _controller;
+ bool _abbreviated;
+ const bool _do_old_gc_bootstrap;
public:
- ShenandoahConcurrentGC(ShenandoahGeneration* generation, bool do_old_gc_bootstrap);
+ ShenandoahConcurrentGC(ShenandoahController* controller, ShenandoahGeneration* generation, bool do_old_gc_bootstrap);
+ ~ShenandoahConcurrentGC();
bool collect(GCCause::Cause cause) override;
- ShenandoahDegenPoint degen_point() const;
// Return true if this cycle found enough immediate garbage to skip evacuation
bool abbreviated() const { return _abbreviated; }
@@ -96,6 +96,7 @@ class ShenandoahConcurrentGC : public ShenandoahGC {
void entry_cleanup_early();
void entry_complete_abbreviated_cycle();
void entry_evacuate();
+ void entry_self_forward_stranded_objects();
void entry_update_thread_roots();
void entry_update_card_table();
void entry_concurrent_update_refs_prepare(ShenandoahHeap* heap);
@@ -115,6 +116,7 @@ class ShenandoahConcurrentGC : public ShenandoahGC {
void op_strong_roots();
void op_cleanup_early();
void op_evacuate();
+ void op_self_forward_stranded_objects();
void op_init_update_refs();
void op_update_refs();
void op_update_thread_roots();
@@ -125,11 +127,12 @@ class ShenandoahConcurrentGC : public ShenandoahGC {
void op_reset_after_collect();
// Check GC cancellation and abort concurrent GC
- bool check_cancellation_and_abort(ShenandoahDegenPoint point);
+ bool check_cancellation_and_abort();
// Called when concurrent GC succeeds.
void entry_reset_after_collect();
+ void update_phase(ShenandoahController::ShenandoahCollectorPhase phase) const;
private:
void start_mark();
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
index 1eec38027752..3ba9e5e9fac7 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
@@ -23,12 +23,12 @@
*
*/
+#include "gc/shared/allocTracer.hpp"
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
#include "gc/shenandoah/mode/shenandoahMode.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahConcurrentGC.hpp"
#include "gc/shenandoah/shenandoahControlThread.hpp"
-#include "gc/shenandoah/shenandoahDegeneratedGC.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahFullGC.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
@@ -40,66 +40,40 @@
#include "memory/metaspaceStats.hpp"
#include "memory/metaspaceUtils.hpp"
-ShenandoahControlThread::ShenandoahControlThread() :
- ShenandoahController(),
- _requested_gc_cause(GCCause::_no_gc),
- _degen_point(ShenandoahGC::_degenerated_outside_cycle),
- _control_lock(CONTROL_LOCK_RANK, "ShenandoahControl_lock", true) {
+ShenandoahControlThread::ShenandoahControlThread() {
set_name("ShenControl");
create_and_start();
}
void ShenandoahControlThread::run_service() {
ShenandoahHeap* const heap = ShenandoahHeap::heap();
- const GCMode default_mode = concurrent_normal;
- const GCCause::Cause default_cause = GCCause::_shenandoah_concurrent_gc;
- int sleep = ShenandoahControlIntervalMin;
+ constexpr GCMode default_mode = concurrent_normal;
+ constexpr GCCause::Cause default_cause = GCCause::_shenandoah_concurrent_gc;
+ int sleep = ShenandoahControlIntervalMin;
double last_sleep_adjust_time = os::elapsedTime();
+ double most_recent_wake_time = last_sleep_adjust_time;
- ShenandoahCollectorPolicy* const policy = heap->shenandoah_policy();
ShenandoahHeuristics* const heuristics = heap->heuristics();
- double most_recent_wake_time = os::elapsedTime();
while (!should_terminate()) {
- const GCCause::Cause cancelled_cause = heap->cancelled_cause();
- if (cancelled_cause == GCCause::_shenandoah_stop_vm) {
- break;
- }
+ assert(!heap->cancelled_gc() || should_terminate(), "Can only be cancelled for shutdown");
// Figure out if we have pending requests.
- const bool alloc_failure_pending = ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause);
- const bool is_gc_requested = _gc_requested.try_unset();
- const GCCause::Cause requested_gc_cause = _requested_gc_cause;
+ GCCause::Cause cause;
+ {
+ MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag);
+ cause = _requested_gc_cause;
+ _requested_gc_cause = GCCause::_no_gc;
+ }
// Choose which GC mode to run in. The block below should select a single mode.
GCMode mode = none;
- GCCause::Cause cause = GCCause::_last_gc_cause;
- ShenandoahGC::ShenandoahDegenPoint degen_point = ShenandoahGC::_degenerated_unset;
-
- if (alloc_failure_pending) {
- // Allocation failure takes precedence: we have to deal with it first thing
- heuristics->log_trigger("Handle Allocation Failure");
-
- cause = GCCause::_allocation_failure;
-
- // Consume the degen point, and seed it with default value
- degen_point = _degen_point;
- _degen_point = ShenandoahGC::_degenerated_outside_cycle;
-
- if (ShenandoahDegeneratedGC && heuristics->should_degenerate_cycle()) {
- heuristics->record_allocation_failure_gc();
- policy->record_alloc_failure_to_degenerated(degen_point);
- mode = stw_degenerated;
- } else {
- heuristics->record_allocation_failure_gc();
- policy->record_alloc_failure_to_full();
- mode = stw_full;
- }
- } else if (is_gc_requested) {
- cause = requested_gc_cause;
+ bool clear_soft_references = ShenandoahAlwaysClearSoftRefs;
+ if (cause != GCCause::_no_gc) {
+ // A cycle was requested, clear soft references
heuristics->log_trigger("GC Request (%s)", GCCause::to_string(cause));
heuristics->record_requested_gc();
-
+ clear_soft_references = true;
if (ShenandoahCollectorPolicy::should_run_full_gc(cause)) {
mode = stw_full;
} else {
@@ -107,26 +81,19 @@ void ShenandoahControlThread::run_service() {
// Unload and clean up everything
heap->set_unload_classes(heuristics->can_unload_classes());
}
- } else {
- // Potential normal cycle: ask heuristics if it wants to act
- if (heuristics->should_start_gc()) {
- mode = default_mode;
- cause = default_cause;
- }
-
+ } else if (heuristics->should_start_gc()) {
+ // Nobody requested a cycle, but heuristics want to run one
+ mode = default_mode;
+ cause = default_cause;
// Ask policy if this cycle wants to process references or unload classes
heap->set_unload_classes(heuristics->should_unload_classes());
}
- 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);
- }
+ if (mode != none) {
+ assert(cause != GCCause::_no_gc, "GC cause should be set");
- const bool gc_requested = (mode != none);
- assert (!gc_requested || cause != GCCause::_last_gc_cause, "GC cause should be set");
+ heap->global_generation()->ref_processor()->set_soft_reference_policy(clear_soft_references);
- if (gc_requested) {
// Cannot uncommit bitmap slices during concurrent reset
ShenandoahNoUncommitMark forbid_region_uncommit(heap);
@@ -151,9 +118,6 @@ void ShenandoahControlThread::run_service() {
case concurrent_normal:
service_concurrent_normal_cycle(cause);
break;
- case stw_degenerated:
- service_stw_degenerated_cycle(cause, degen_point);
- break;
case stw_full:
service_stw_full_cycle(cause);
break;
@@ -162,15 +126,12 @@ void ShenandoahControlThread::run_service() {
}
heap->print_after_gc();
- // If this was the requested GC cycle, notify waiters about it
- if (is_gc_requested) {
- notify_gc_waiters();
- }
+ // Try to reduce concurrent worker count
+ decrease_concurrent_worker_count();
- // If this cycle completed without being cancelled, notify waiters about it
- if (!heap->cancelled_gc()) {
- notify_alloc_failure_waiters();
- }
+ // Notify waiters that a cycle is completed. They'll decide for themselves to continue waiting or not.
+ notify_gc_waiters();
+ clear_allocation_failure_and_notify_waiters();
// Report current free set state at the end of cycle, whether
// it is a normal completion, or the abort.
@@ -212,7 +173,7 @@ void ShenandoahControlThread::run_service() {
if (ShenandoahUncommit) {
if (heap->check_soft_max_changed()) {
heap->notify_soft_max_changed();
- } else if (is_gc_requested) {
+ } else if (ShenandoahCollectorPolicy::is_explicit_gc(cause)) {
heap->notify_explicit_gc_requested();
}
}
@@ -231,59 +192,35 @@ void ShenandoahControlThread::run_service() {
ml.wait(sleep);
most_recent_wake_time = os::elapsedTime();
// Record a conservative estimate of the longest anticipated sleep duration until we sample again.
- double planned_sleep_interval = MIN2(ShenandoahControlIntervalMax, MAX2(1, sleep * 2)) / 1000.0;
+ const double planned_sleep_interval = MIN2(ShenandoahControlIntervalMax, MAX2(1, sleep * 2)) / 1000.0;
heuristics->update_should_start_query_times(most_recent_wake_time, planned_sleep_interval);
if (LogTarget(Debug, gc, thread)::is_enabled()) {
- double elapsed = most_recent_wake_time - before_sleep_time;
- double hiccup = elapsed - double(sleep) / 1000.0;
+ const double elapsed = most_recent_wake_time - before_sleep_time;
+ const double hiccup = elapsed - static_cast(sleep) / 1000.0;
if (hiccup > 0.001) {
log_debug(gc, thread)("Control Thread hiccup time: %.3fs", hiccup);
}
}
}
+ // In case any threads are waiting for a cycle to happen, notify them so they observe the shutdown.
+ notify_gc_waiters();
+ notify_alloc_waiters();
+
// We're done writing GC stats, so print them here.
heap->print_gc_stats_at_exit();
}
void ShenandoahControlThread::service_concurrent_normal_cycle(GCCause::Cause cause) {
// Normal cycle goes via all concurrent phases. If allocation failure (af) happens during
- // any of the concurrent phases, it first degrades to Degenerated GC and completes GC there.
- // If second allocation failure happens during Degenerated GC cycle (for example, when GC
- // tries to evac something and no memory is available), cycle degrades to Full GC.
- //
- // There are also a shortcut through the normal cycle: immediate garbage shortcut, when
- // heuristics says there are no regions to compact, and all the collection comes from immediately
- // reclaimable regions.
- //
- // ................................................................................................
- //
- // (immediate garbage shortcut) Concurrent GC
- // /-------------------------------------------\
- // | |
- // | |
- // | |
- // | v
- // [START] ----> Conc Mark ----o----> Conc Evac --o--> Conc Update-Refs ---o----> [END]
- // | | | ^
- // | (af) | (af) | (af) |
- // ..................|....................|.................|..............|.......................
- // | | | |
- // | | | | Degenerated GC
- // v v v |
- // STW Mark ----------> STW Evac ----> STW Update-Refs ----->o
- // | | | ^
- // | (af) | (af) | (af) |
- // ..................|....................|.................|..............|.......................
- // | | | |
- // | v | | Full GC
- // \------------------->o<----------------/ |
- // | |
- // v |
- // Full GC --------------------------/
+ // any of the concurrent phases, the allocating thread will block until the concurrent
+ // cycle completes.
//
+ // There is also a shortcut through the normal cycle: immediate garbage shortcut. When
+ // heuristics say there are no regions to compact, and all the collection comes from immediately
+ // reclaimable regions, Shenandoah can skip the evacuation phase.
ShenandoahHeap* heap = ShenandoahHeap::heap();
- if (check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle)) {
+ if (heap->cancelled_gc()) {
// Need to report at "gc" level to report GC ID proper.
log_info(gc)("Cancelled before cycle started");
return;
@@ -294,44 +231,22 @@ void ShenandoahControlThread::service_concurrent_normal_cycle(GCCause::Cause cau
TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters());
- ShenandoahConcurrentGC gc(heap->global_generation(), false);
+ ShenandoahConcurrentGC gc(this, heap->global_generation(), false);
if (gc.collect(cause)) {
- // Cycle is complete. There were no failed allocation requests and no degeneration, so count this as good progress.
- heap->notify_gc_progress();
- heap->global_generation()->heuristics()->record_success_concurrent();
- heap->shenandoah_policy()->record_success_concurrent(false, gc.abbreviated());
+ if (alloc_stall_count() == 0) {
+ heap->notify_gc_progress();
+ }
+ heap->global_generation()->heuristics()->record_concurrent_completion();
+ heap->shenandoah_policy()->record_success_concurrent(get_gc_id(), false, gc.abbreviated());
heap->log_heap_status("At end of GC");
} else {
assert(heap->cancelled_gc(), "Must have been cancelled");
- check_cancellation_or_degen(gc.degen_point());
heap->log_heap_status("At end of cancelled GC");
}
}
-bool ShenandoahControlThread::check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point) {
- // Only read the cancellation cause once. Other threads may change it.
- ShenandoahHeap* heap = ShenandoahHeap::heap();
- const GCCause::Cause cancelled_cause = heap->cancelled_cause();
- if (cancelled_cause == GCCause::_no_gc) {
- return false;
- }
-
- if (cancelled_cause == GCCause::_shenandoah_stop_vm) {
- return true;
- }
-
- if (ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause)) {
- assert (_degen_point == ShenandoahGC::_degenerated_outside_cycle,
- "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point));
- _degen_point = point;
- return true;
- }
-
- fatal("Unexpected reason for cancellation: %s", GCCause::to_string(cancelled_cause));
-}
-
void ShenandoahControlThread::stop_service() {
- ShenandoahHeap::heap()->cancel_gc(GCCause::_shenandoah_stop_vm);
+ // Nothing to do here, worker threads will also check should_terminate, by way of shHeap::is_stopping.
}
void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) {
@@ -344,69 +259,25 @@ void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) {
gc.collect(cause);
}
-void ShenandoahControlThread::service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point) {
- assert (point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set");
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- ShenandoahGCSession session(cause, heap->global_generation(), true,
- point == ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle);
-
- heap->increment_total_collections(false);
-
- ShenandoahDegenGC gc(point, heap->global_generation());
- gc.collect(cause);
-}
-
-void ShenandoahControlThread::request_gc(GCCause::Cause cause) {
- if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) {
- handle_requested_gc(cause);
+bool ShenandoahControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* ignored) {
+ MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag);
+ if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) {
+ log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause));
+ return false;
}
-}
-void ShenandoahControlThread::notify_control_thread(GCCause::Cause cause) {
- // Although setting gc request is under _controller_lock, the read side (run_service())
- // does not take the lock. We need to enforce following order, so that read side sees
- // latest requested gc cause when the flag is set.
- MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag);
_requested_gc_cause = cause;
- _gc_requested.set();
- controller.notify();
+ controller.notify_all();
+ return true;
}
-void ShenandoahControlThread::handle_requested_gc(GCCause::Cause cause) {
- if (should_terminate()) {
- log_info(gc, phases)("Control thread is terminating, no more GCs");
- return;
- }
-
- // For normal requested GCs (System.gc) we want to block the caller. However,
- // for whitebox requested GC, we want to initiate the GC and return immediately.
- // The whitebox caller thread will arrange for itself to wait until the GC notifies
- // it that has reached the requested breakpoint (phase in the GC).
- if (cause == GCCause::_wb_breakpoint) {
- notify_control_thread(cause);
- return;
- }
+void ShenandoahControlThread::notify_alloc_stall(GCCause::Cause cause) {
+ ShenandoahHeap* heap = ShenandoahHeap::heap();
+ ShenandoahGeneration* generation = heap->global_generation();
- // Make sure we have at least one complete GC cycle before unblocking
- // from the explicit GC request.
- //
- // This is especially important for weak references cleanup and/or native
- // resources (e.g. DirectByteBuffers) machinery: when explicit GC request
- // comes very late in the already running cycle, it would miss lots of new
- // opportunities for cleanup that were made available before the caller
- // requested the GC.
-
- MonitorLocker ml(&_gc_waiters_lock);
- size_t current_gc_id = get_gc_id();
- size_t required_gc_id = current_gc_id + 1;
- while (current_gc_id < required_gc_id && !should_terminate()) {
- notify_control_thread(cause);
- ml.wait();
- current_gc_id = get_gc_id();
- }
-}
+ // Inform global heuristics of allocation stall
+ generation->heuristics()->record_allocation_stall();
-void ShenandoahControlThread::notify_gc_waiters() {
- MonitorLocker ml(&_gc_waiters_lock);
- ml.notify_all();
+ // Run a global cycle (no other kind for this control thread).
+ notify_control_thread(cause, generation);
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp
index c9bb64192013..ac9d6f365a8a 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp
@@ -25,51 +25,33 @@
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP
-#include "gc/shared/concurrentGCThread.hpp"
#include "gc/shared/gcCause.hpp"
#include "gc/shenandoah/shenandoahController.hpp"
-#include "gc/shenandoah/shenandoahGC.hpp"
-#include "gc/shenandoah/shenandoahPadding.hpp"
#include "gc/shenandoah/shenandoahSharedVariables.hpp"
+class ShenandoahGeneration;
+
class ShenandoahControlThread: public ShenandoahController {
-private:
typedef enum {
none,
concurrent_normal,
- stw_degenerated,
stw_full
} GCMode;
- ShenandoahSharedFlag _gc_requested;
- GCCause::Cause _requested_gc_cause;
- ShenandoahGC::ShenandoahDegenPoint _degen_point;
-
- // This lock is used to coordinate waking up the control thread
- Monitor _control_lock;
-
public:
ShenandoahControlThread();
+protected:
void run_service() override;
void stop_service() override;
- void request_gc(GCCause::Cause cause) override;
+ // Sets the requested cause, flag and notifies the control thread
+ bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) override;
+ void notify_alloc_stall(GCCause::Cause cause) override;
private:
- // Sets the requested cause and flag and notifies the control thread
- void notify_control_thread(GCCause::Cause cause);
-
- bool check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point);
void service_concurrent_normal_cycle(GCCause::Cause cause);
void service_stw_full_cycle(GCCause::Cause cause);
- void service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point);
-
- void notify_gc_waiters();
-
- // Handle GC request.
- // Blocks until GC is over.
- void handle_requested_gc(GCCause::Cause cause);
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp
index 0096aad2570d..e4eef635b6e6 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp
@@ -1,6 +1,6 @@
/*
* 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
@@ -23,55 +23,173 @@
*
*/
-#include "gc/shared/allocTracer.hpp"
#include "gc/shared/gc_globals.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahController.hpp"
#include "gc/shenandoah/shenandoahHeap.hpp"
#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp"
+ShenandoahController::ShenandoahController():
+ _gc_id(0),
+ _phase(UNSET),
+ _control_lock(CONTROL_LOCK_RANK, "ShenandoahControl_lock", true),
+ _gc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahGCWaiters_lock", true),
+ _alloc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahAllocWaiters_lock", true),
+ _requested_gc_cause(GCCause::_no_gc),
+ _alloc_stall_count(0),
+ _concurrent_worker_count(ConcGCThreads) { }
+
void ShenandoahController::update_gc_id() {
- _gc_id.add_then_fetch((size_t)1);
+ _gc_id.add_then_fetch(1UL);
}
-size_t ShenandoahController::get_gc_id() {
+size_t ShenandoahController::get_gc_id() const {
return _gc_id.load_relaxed();
}
-void ShenandoahController::handle_alloc_failure(const ShenandoahAllocRequest& req, bool block) {
+void ShenandoahController::handle_alloc_failure(const ShenandoahAllocRequest &req) {
assert(current()->is_Java_thread(), "expect Java thread here");
+ _alloc_stall_count.add_then_fetch(1UL, memory_order_relaxed);
+ increase_concurrent_worker_count();
+ ShenandoahHeap::heap()->shenandoah_policy()->record_allocation_stall(get_phase());
const bool is_humongous = ShenandoahHeapRegion::requires_humongous(req.size());
const GCCause::Cause cause = is_humongous ? GCCause::_shenandoah_humongous_allocation_failure : GCCause::_allocation_failure;
+ MonitorLocker ml(&_alloc_waiters_lock);
+ notify_alloc_stall(cause);
+ if (!should_terminate()) {
+ // This is the inner part of a larger retry loop, so just wait here
+ ml.wait();
+ }
+}
+
+void ShenandoahController::handle_alloc_failure_full() {
+ if (should_terminate()) {
+ log_info(gc)("Control thread is terminating, no more GCs");
+ return;
+ }
+
+ // Make sure we have at least one full GC cycle before unblocking from the explicit GC request.
+ ShenandoahHeap* heap = ShenandoahHeap::heap();
+ const ShenandoahCollectorPolicy* policy = heap->shenandoah_policy();
+ MonitorLocker ml(&_gc_waiters_lock);
+ size_t full_gc_count = policy->full_gc_count();
+ const size_t required_count = full_gc_count + 1;
+ while (full_gc_count < required_count && !should_terminate()) {
+ notify_control_thread(GCCause::_shenandoah_upgrade_to_full_gc, heap->global_generation());
+ ml.wait();
+ full_gc_count = policy->full_gc_count();
+ }
+}
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- size_t req_byte = req.size() * HeapWordSize;
- if (heap->cancel_gc(cause)) {
- log_info(gc)("Failed to allocate %s, " PROPERFMT, req.type_string(), PROPERFMTARGS(req_byte));
- request_gc(cause);
+void ShenandoahController::request_gc(GCCause::Cause cause) {
+ if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) {
+ handle_requested_gc(cause);
}
- AllocTracer::send_allocation_requiring_gc_event(req_byte, checked_cast(get_gc_id()));
+}
- if (block) {
- MonitorLocker ml(&_alloc_failure_waiters_lock);
- while (!should_terminate() && ShenandoahCollectorPolicy::is_allocation_failure(heap->cancelled_cause())) {
- ml.wait();
+void ShenandoahController::handle_requested_gc(GCCause::Cause cause) {
+ // Requested gc's always operate on the entire heap
+ const ShenandoahHeap* heap = ShenandoahHeap::heap();
+ const ShenandoahCollectorPolicy* policy = heap->shenandoah_policy();
+ ShenandoahGeneration* global = heap->global_generation();
+
+ // For normal requested GCs (System.gc) we want to block the caller. However,
+ // for whitebox requested GC, we want to initiate the GC and return immediately.
+ // The whitebox caller thread will arrange for itself to wait until the GC notifies
+ // it that has reached the requested breakpoint (phase in the GC).
+ if (cause == GCCause::_wb_breakpoint) {
+ notify_control_thread(cause, global);
+ return;
+ }
+
+ // Make sure we have at least one complete GC cycle before unblocking
+ // from the explicit GC request.
+ //
+ // This is especially important for weak references cleanup and/or native
+ // resources (e.g. DirectByteBuffers) machinery: when explicit GC request
+ // comes very late in the already running cycle, it would miss lots of new
+ // opportunities for cleanup that were made available before the caller
+ // requested the GC.
+ MonitorLocker ml(&_gc_waiters_lock);
+ const size_t current_gc_id = get_gc_id();
+ while (policy->last_whole_heap_gc_id() <= current_gc_id && !should_terminate()) {
+ notify_control_thread(cause, global);
+ ml.wait();
+ }
+}
+
+void ShenandoahController::clear_allocation_failure_and_notify_waiters() {
+ MonitorLocker waiters(&_alloc_waiters_lock, Mutex::_no_safepoint_check_flag);
+ {
+ MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
+ if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) {
+ // If an allocation failure occurred during this cycle, we'll have threads waiting
+ // for reclaimed memory. We'll wake them up, and they'll retry their allocation.
+ // If our waiters cannot allocate, they will signal the control thread again
+ // to start another cycle. If we didn't clear the request here, the control thread would
+ // immediately begin another allocation failure cycle.
+ _requested_gc_cause = GCCause::_no_gc;
}
}
+ waiters.notify_all();
}
-void ShenandoahController::handle_alloc_failure_evac(size_t words) {
+void ShenandoahController::increase_concurrent_worker_count() {
+ while (true) {
+ const size_t workers = _concurrent_worker_count.load_relaxed();
+ if (workers == ParallelGCThreads) {
+ break;
+ }
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- const bool is_humongous = ShenandoahHeapRegion::requires_humongous(words);
- const GCCause::Cause cause = is_humongous ? GCCause::_shenandoah_humongous_allocation_failure : GCCause::_shenandoah_allocation_failure_evac;
+ const size_t new_value = MIN2(workers + 1, checked_cast(ParallelGCThreads));
+ if (_concurrent_worker_count.compare_set(workers, new_value, memory_order_relaxed)) {
+ break;
+ }
+ }
+}
+
+void ShenandoahController::decrease_concurrent_worker_count() {
+ const size_t alloc_stalls = _alloc_stall_count.exchange(0);
+ const int max_conc_gc_threads = checked_cast(ConcGCThreads);
+ if (alloc_stalls != 0) {
+ log_info(gc, ergo)("Allocation stalls: %zu, holding concurrent worker count at: %zu",
+ alloc_stalls, _concurrent_worker_count.load_relaxed());
+ } else {
+ // There were no stalls during this cycle, try to reduce the concurrent gc workers
+ while (true) {
+ const int workers = _concurrent_worker_count.load_relaxed();
+ if (workers == max_conc_gc_threads) {
+ break;
+ }
- if (heap->cancel_gc(cause)) {
- log_info(gc)("Failed to allocate " PROPERFMT " for evacuation", PROPERFMTARGS(words * HeapWordSize));
+ const size_t new_value = MAX2(workers - 1, max_conc_gc_threads);
+ if (_concurrent_worker_count.compare_set(workers, new_value, memory_order_relaxed)) {
+ break;
+ }
+ }
}
}
-void ShenandoahController::notify_alloc_failure_waiters() {
- MonitorLocker ml(&_alloc_failure_waiters_lock);
+void ShenandoahController::notify_gc_waiters() {
+ MonitorLocker ml(&_gc_waiters_lock);
ml.notify_all();
}
+
+void ShenandoahController::notify_alloc_waiters() {
+ MonitorLocker ml(&_alloc_waiters_lock);
+ ml.notify_all();
+}
+
+const char* ShenandoahController::collector_phase_to_string(ShenandoahCollectorPhase phase) {
+ switch(phase) {
+ case UNSET: return "Outside of Cycle";
+ case INITIALIZING: return "Initializing";
+ case ROOTS: return "Roots";
+ case MARK: return "Mark";
+ case EVAC: return "Evacuation";
+ case UPDATE_REFS: return "Update References";
+ default:
+ ShouldNotReachHere();
+ }
+}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp
index 60b41a5fe995..3f462e6b8773 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp
@@ -28,56 +28,129 @@
#include "gc/shared/concurrentGCThread.hpp"
#include "gc/shared/gcCause.hpp"
#include "gc/shenandoah/shenandoahAllocRequest.hpp"
-#include "gc/shenandoah/shenandoahSharedVariables.hpp"
+#include "gc/shenandoah/shenandoahPadding.hpp"
#include "runtime/atomic.hpp"
+class ShenandoahGeneration;
+
/**
* This interface exposes methods necessary for the heap to interact
* with the threads responsible for driving the collection cycle.
*/
class ShenandoahController: public ConcurrentGCThread {
+public:
+ enum ShenandoahCollectorPhase {
+ UNSET,
+ INITIALIZING,
+ ROOTS,
+ MARK,
+ EVAC,
+ UPDATE_REFS,
+ PHASE_LIMIT
+ };
+
private:
shenandoah_padding(0);
// A monotonically increasing GC count.
Atomic _gc_id;
shenandoah_padding(1);
+ // Written by control thread, read by mutators
+ Atomic _phase;
+
protected:
const Mutex::Rank WAITERS_LOCK_RANK = Mutex::safepoint - 5;
const Mutex::Rank CONTROL_LOCK_RANK = Mutex::nosafepoint - 2;
- // While we could have a single lock for these, it may risk unblocking
- // GC waiters when alloc failure GC cycle finishes. We want instead
- // to make complete explicit cycle for demanding customers.
- Monitor _alloc_failure_waiters_lock;
+ // This lock is used to coordinate waking up the control thread and protecting _requested_gc_cause.
+ // The generational derived class also uses this lock to protect _gc_mode and _requested_generation
+ Monitor _control_lock;
+
+ // Threads waiting for a complete gc cycle (full gc or concurrent global) will wait here.
Monitor _gc_waiters_lock;
+ // Threads waiting for the gc to free memory will wait here. Note that immediate garbage may be reclaimed
+ // midcycle during final mark, so we want to notify alloc waiters independently of cycle waiters. Similarly,
+ // no threads should be notified when a concurrent old mark increment is interrupted.
+ Monitor _alloc_waiters_lock;
+
+ // Represents a gc request
+ GCCause::Cause _requested_gc_cause;
+
+ // The number of stalls experienced during the cycle. Incremented by mutators, reset by control thread.
+ shenandoah_padding(2);
+ Atomic _alloc_stall_count;
+ shenandoah_padding(3);
+
+ // Written by control thread and mutators, clamped between ConcGCThreads and ParallelGCThreads.
+ Atomic _concurrent_worker_count;
+ shenandoah_padding(4);
+
// Increments the internal GC count.
void update_gc_id();
+ // Increase worker count when a stall is reported. Called from mutator thread.
+ void increase_concurrent_worker_count();
+
+ // Decrease worker count if no stalls were detected in a cycle. Called from control thread.
+ void decrease_concurrent_worker_count();
+
+ // Returns the total number of allocation stalls during a cycle
+ size_t alloc_stall_count() const {
+ return _alloc_stall_count.load_relaxed();
+ }
+
+ // Notify the control thread to run a cycle for the given generation
+ virtual bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) = 0;
+
+ // Notify the control thread about an allocation stall specifically. Implementation
+ // will decide which generation to use and any other mode specific work that must be done.
+ virtual void notify_alloc_stall(GCCause::Cause cause) = 0;
+
public:
- ShenandoahController():
- _gc_id(0),
- _alloc_failure_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahAllocFailureWaiters_lock", true),
- _gc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahGCWaiters_lock", true)
- { }
+ ShenandoahController();
// Request a collection cycle. This handles "explicit" gc requests
// like System.gc and "implicit" gc requests, like metaspace oom.
- virtual void request_gc(GCCause::Cause cause) = 0;
+ virtual void request_gc(GCCause::Cause cause);
- // This cancels the collection cycle and has an option to block
- // until another cycle completes successfully.
- void handle_alloc_failure(const ShenandoahAllocRequest& req, bool block);
+ // Notify threads that the gc has completed a cycle, or it's exiting.
+ void notify_gc_waiters();
- // Invoked for allocation failures during evacuation. This cancels
- // the collection cycle without blocking.
- void handle_alloc_failure_evac(size_t words);
+ // Notify threads that the gc has recovered memory, or it's exiting.
+ void notify_alloc_waiters();
- // Notify threads waiting for GC to complete.
- void notify_alloc_failure_waiters();
+ // Inform the control thread that this allocation request failed. Caller will block until memory is reclaimed.
+ void handle_alloc_failure(const ShenandoahAllocRequest &req);
+
+ // Run a full GC, callers will block until at least one full GC cycle is completed.
+ void handle_alloc_failure_full();
+
+ // Return suggested number of concurrent worker threads
+ size_t concurrent_worker_count() const {
+ return _concurrent_worker_count.load_relaxed();
+ }
// Return the value of a monotonic increasing GC count, maintained by the control thread.
- size_t get_gc_id();
+ size_t get_gc_id() const;
+
+ ShenandoahCollectorPhase get_phase() const {
+ return _phase.load_relaxed();
+ }
+
+ // Record the current phase for the cycle. Used to track where allocation stalls occur.
+ void set_phase(ShenandoahCollectorPhase phase) {
+ assert(ShenandoahCollectorPhase::UNSET <= phase, "Phase out of bounds: %d", phase);
+ assert(phase < ShenandoahCollectorPhase::PHASE_LIMIT, "Phase out of bounds %d", phase);
+ _phase.store_relaxed(phase);
+ }
+
+ static const char* collector_phase_to_string(ShenandoahCollectorPhase phase);
+
+protected:
+ virtual void handle_requested_gc(GCCause::Cause cause);
+
+ // Used at the end of a cycle to clear allocation failure and notify alloc waiters
+ virtual void clear_allocation_failure_and_notify_waiters();
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLLER_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
deleted file mode 100644
index 071a06d823dd..000000000000
--- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp
+++ /dev/null
@@ -1,526 +0,0 @@
-/*
- * Copyright (c) 2021, Red Hat, Inc. All rights reserved.
- * 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.
- *
- */
-
-
-#include "code/codeCache.hpp"
-#include "gc/shared/collectorCounters.hpp"
-#include "gc/shenandoah/shenandoahClosures.inline.hpp"
-#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
-#include "gc/shenandoah/shenandoahConcurrentMark.hpp"
-#include "gc/shenandoah/shenandoahDegeneratedGC.hpp"
-#include "gc/shenandoah/shenandoahFullGC.hpp"
-#include "gc/shenandoah/shenandoahGeneration.hpp"
-#include "gc/shenandoah/shenandoahGenerationalHeap.hpp"
-#include "gc/shenandoah/shenandoahHeap.inline.hpp"
-#include "gc/shenandoah/shenandoahMetrics.hpp"
-#include "gc/shenandoah/shenandoahMonitoringSupport.hpp"
-#include "gc/shenandoah/shenandoahOldGeneration.hpp"
-#include "gc/shenandoah/shenandoahRootProcessor.inline.hpp"
-#include "gc/shenandoah/shenandoahSATBMarkQueueSet.hpp"
-#include "gc/shenandoah/shenandoahSTWMark.hpp"
-#include "gc/shenandoah/shenandoahUtils.hpp"
-#include "gc/shenandoah/shenandoahVerifier.hpp"
-#include "gc/shenandoah/shenandoahVMOperations.hpp"
-#include "gc/shenandoah/shenandoahWorkerPolicy.hpp"
-#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
-#include "runtime/threads.hpp"
-#include "runtime/vmThread.hpp"
-#include "utilities/events.hpp"
-
-ShenandoahDegenGC::ShenandoahDegenGC(ShenandoahDegenPoint degen_point, ShenandoahGeneration* generation, bool do_old_gc_bootstrap) :
- ShenandoahGC(generation),
- _degen_point(degen_point),
- _abbreviated(false),
- _do_old_gc_bootstrap(do_old_gc_bootstrap) {
-}
-
-bool ShenandoahDegenGC::collect(GCCause::Cause cause) {
- vmop_degenerated();
- ShenandoahHeap* heap = ShenandoahHeap::heap();
- if (heap->mode()->is_generational()) {
- FormatBuffer<32> buf("Degenerated %s GC", _generation->name());
- const char* msg = _do_old_gc_bootstrap ? "Degenerated Bootstrap Old GC" : buf.buffer();
- heap->mmu_tracker()->record_degenerated(GCId::current(), msg);
- heap->log_heap_status(FormatBuffer<64>("At end of %s", msg));
- }
- return true;
-}
-
-void ShenandoahDegenGC::vmop_degenerated() {
- TraceCollectorStats tcs(ShenandoahHeap::heap()->monitoring_support()->full_stw_collection_counters());
- ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::degen_gc_gross);
- VM_ShenandoahDegeneratedGC degenerated_gc(this);
- VMThread::execute(°enerated_gc);
-}
-
-void ShenandoahDegenGC::entry_degenerated() {
- const char* msg = degen_event_message(_degen_point);
- ShenandoahPausePhase gc_phase(msg, ShenandoahPhaseTimings::degen_gc, true /* log_heap_usage */);
- EventMark em("%s", msg);
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- ShenandoahWorkerScope scope(heap->workers(),
- ShenandoahWorkerPolicy::calc_workers_for_stw_degenerated(),
- "stw degenerated gc");
-
- heap->set_degenerated_gc_in_progress(true);
- op_degenerated();
- heap->set_degenerated_gc_in_progress(false);
- {
- ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::degen_gc_propagate_gc_state);
- heap->propagate_gc_state_to_all_threads();
- }
-}
-
-void ShenandoahDegenGC::op_degenerated() {
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- heap->release_injected_pins();
- // Degenerated GC is STW, but it can also fail. Current mechanics communicates
- // GC failure via cancelled_concgc() flag. So, if we detect the failure after
- // some phase, we have to upgrade the Degenerate GC to Full GC.
- heap->clear_cancelled_gc();
-
- // If we degenerated from evacuation or update-refs, some objects in cset may
- // have been self-forwarded by the failing thread. Clear those marks now so
- // the remainder of this cycle (re-evac, update-refs, verification) sees a
- // clean forwarding state.
- if (_degen_point == ShenandoahDegenPoint::_degenerated_evac ||
- _degen_point == ShenandoahDegenPoint::_degenerated_update_refs) {
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_un_self_forward);
- heap->un_self_forward_cset_regions();
- }
-
- // If it's passive mode with ShenandoahCardBarrier turned on: clean the write table
- // without swapping the tables since no scan happens in passive mode anyway
- if (ShenandoahCardBarrier && !heap->mode()->is_generational()) {
- heap->old_generation()->card_scan()->mark_write_table_as_clean();
- }
-
- if (heap->mode()->is_generational()) {
- const ShenandoahOldGeneration* old_generation = heap->old_generation();
- if (!heap->is_concurrent_old_mark_in_progress()) {
- // If we are not marking the old generation, there should be nothing in the old mark queues
- assert(old_generation->task_queues()->is_empty(), "Old gen task queues should be empty");
- } else {
- // This is still necessary for degenerated cycles because the degeneration point may occur
- // after final mark of the young generation. See ShenandoahConcurrentGC::op_final_update_refs for
- // a more detailed explanation.
- old_generation->transfer_pointers_from_satb();
- }
-
- if (_generation->is_global()) {
- // If we are in a global cycle, the old generation should not be marking. It is, however,
- // allowed to be holding regions for evacuation or coalescing.
- assert(old_generation->is_idle()
- || old_generation->is_doing_mixed_evacuations()
- || old_generation->is_preparing_for_mark(),
- "Old generation cannot be in state: %s", old_generation->state_name());
- }
- }
-
- ShenandoahMetricsSnapshot metrics(heap->free_set());
-
- switch (_degen_point) {
- // The cases below form the Duff's-like device: it describes the actual GC cycle,
- // but enters it at different points, depending on which concurrent phase had
- // degenerated.
-
- case _degenerated_outside_cycle:
- // We have degenerated from outside the cycle, which means something is bad with
- // the heap, most probably heavy humongous fragmentation, or we are very low on free
- // space. It makes little sense to wait for Full GC to reclaim as much as it can, when
- // we can do the most aggressive degen cycle, which includes processing references and
- // class unloading, unless those features are explicitly disabled.
-
- // Note that we can only do this for "outside-cycle" degens, otherwise we would risk
- // changing the cycle parameters mid-cycle during concurrent -> degenerated handover.
- heap->set_unload_classes(_generation->heuristics()->can_unload_classes() &&
- (!heap->mode()->is_generational() || _generation->is_global()));
-
- if (heap->mode()->is_generational()) {
- // Clean the read table before swapping it. The end goal here is to have a clean
- // write table, and to have the read table updated with the previous write table.
- heap->old_generation()->card_scan()->mark_read_table_as_clean();
-
- if (_generation->is_young()) {
- // Swap remembered sets for young
- _generation->swap_card_tables();
- }
- }
-
- case _degenerated_roots:
- // Degenerated from concurrent root mark, reset the flag for STW mark
- if (!heap->mode()->is_generational()) {
- if (heap->is_concurrent_mark_in_progress()) {
- heap->cancel_concurrent_mark();
- }
- } else {
- if (_generation->is_concurrent_mark_in_progress()) {
- // We want to allow old generation marking to be punctuated by young collections
- // (even if they have degenerated). If this is a global cycle, we'd have cancelled
- // the entire old gc before coming into this switch. Note that cancel_marking on
- // the generation does NOT abandon incomplete SATB buffers as cancel_concurrent_mark does.
- // We need to separate out the old pointers which is done below.
- _generation->cancel_marking();
- }
-
- if (_degen_point == ShenandoahDegenPoint::_degenerated_roots) {
- // We only need this if the concurrent cycle has already swapped the card tables.
- // Marking will use the 'read' table, but interesting pointers may have been
- // recorded in the 'write' table in the time between the cancelled concurrent cycle
- // and this degenerated cycle. These pointers need to be included in the 'read' table
- // used to scan the remembered set during the STW mark which follows here.
- _generation->merge_write_table();
- }
- }
-
- op_reset();
-
- // STW mark
- op_mark();
-
- case _degenerated_mark:
- // No fallthrough. Continue mark, handed over from concurrent mark if
- // concurrent mark has yet completed
- if (_degen_point == ShenandoahDegenPoint::_degenerated_mark && heap->is_concurrent_mark_in_progress()) {
- assert(!ShenandoahBarrierSet::satb_mark_queue_set().get_filter_out_young(),
- "Should not be filtering out young pointers when concurrent mark degenerates");
- op_finish_mark();
- }
- assert(!heap->cancelled_gc(), "STW mark can not OOM");
-
- /* Degen select Collection Set. etc. */
- op_prepare_evacuation();
-
- op_cleanup_early();
-
- case _degenerated_evac:
- // If heuristics thinks we should do the cycle, this flag would be set,
- // and we can do evacuation. Otherwise, it would be the shortcut cycle.
- if (heap->is_evacuation_in_progress()) {
-
- if (_degen_point == _degenerated_evac) {
- // Degeneration under oom-evac protocol allows the mutator LRB to expose
- // references to from-space objects. This is okay, in theory, because we
- // will come to the safepoint here to complete the evacuations and update
- // the references. However, if the from-space reference is written to a
- // region that was EC during final mark or was recycled after final mark
- // it will not have TAMS or UWM updated. Such a region is effectively
- // skipped during update references which can lead to crashes and corruption
- // if the from-space reference is accessed.
- if (UseTLAB) {
- heap->labs_make_parsable();
- }
-
- for (size_t i = 0; i < heap->num_regions(); i++) {
- ShenandoahHeapRegion* r = heap->get_region(i);
- if (r->is_active() && r->top() > r->get_update_watermark()) {
- r->set_update_watermark_at_safepoint(r->top());
- }
- }
- }
-
- // Degeneration under oom-evac protocol might have left some objects in
- // collection set un-evacuated. Restart evacuation from the beginning to
- // capture all objects. For all the objects that are already evacuated,
- // it would be a simple check, which is supposed to be fast. This is also
- // safe to do even without degeneration, as CSet iterator is at beginning
- // in preparation for evacuation anyway.
- //
- // Before doing that, we need to make sure we never had any cset-pinned
- // regions. This may happen if allocation failure happened when evacuating
- // the about-to-be-pinned object, oom-evac protocol left the object in
- // the collection set, and then the pin reached the cset region. If we continue
- // the cycle here, we would trash the cset and alive objects in it. To avoid
- // it, we fail degeneration right away and slide into Full GC to recover.
-
- {
- heap->sync_pinned_region_status();
- heap->collection_set()->clear_current_index();
- ShenandoahHeapRegion* r;
- while ((r = heap->collection_set()->next()) != nullptr) {
- if (r->is_pinned()) {
- op_degenerated_fail();
- return;
- }
- }
-
- heap->collection_set()->clear_current_index();
- }
- op_evacuate();
- if (heap->cancelled_gc()) {
- op_degenerated_fail();
- return;
- }
- } else if (has_in_place_promotions(heap)) {
- // We have nothing to evacuate, but there are still regions to promote in place.
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_promote_regions);
- ShenandoahGenerationalHeap::heap()->promote_regions_in_place(_generation, false /* concurrent*/);
- }
-
- // Update collector state regardless of whether there are forwarded objects
- heap->set_evacuation_in_progress(false);
- heap->set_concurrent_weak_root_in_progress(false);
- heap->set_concurrent_strong_root_in_progress(false);
-
- // If heuristics thinks we should do the cycle, this flag would be set,
- // and we need to do update-refs. Otherwise, it would be the shortcut cycle.
- if (heap->has_forwarded_objects()) {
- op_init_update_refs();
- assert(!heap->cancelled_gc(), "STW reference update can not OOM");
- } else {
- _abbreviated = true;
- }
-
- // labs are retired, walk the old regions and update remembered set
- if (ShenandoahHeap::heap()->mode()->is_generational()) {
- ShenandoahGenerationalHeap::heap()->old_generation()->update_card_table();
- }
-
- case _degenerated_update_refs:
- if (heap->has_forwarded_objects()) {
- op_update_refs();
- op_update_roots();
- assert(!heap->cancelled_gc(), "STW reference update can not OOM");
- }
-
- op_cleanup_complete();
-
- if (heap->mode()->is_generational()) {
- ShenandoahGenerationalHeap::heap()->complete_degenerated_cycle();
- }
-
- break;
- default:
- ShouldNotReachHere();
- }
-
- DEBUG_ONLY(heap->assert_no_self_forwards());
-
- // Leaving degenerated GC, we need to flip barriers back to idle.
- CodeCache::arm_all_nmethods();
-
- if (ShenandoahVerify) {
- heap->verifier()->verify_after_degenerated(_generation);
- }
-
- if (VerifyAfterGC) {
- Universe::verify();
- }
-
- // Decide if this cycle made good progress, and, if not, should it upgrade to a full GC.
- const bool progress = metrics.is_good_progress();
- ShenandoahCollectorPolicy* policy = heap->shenandoah_policy();
- policy->record_degenerated(_generation->is_young(), _abbreviated, progress);
- if (progress) {
- heap->notify_gc_progress();
- _generation->heuristics()->record_degenerated(heap->mode()->is_generational() && _generation->is_global());
- heap->start_idle_span();
- } else if (policy->should_upgrade_degenerated_gc()) {
- // Upgrade to full GC, register full-GC impact on heuristics.
- op_degenerated_futile();
- } else {
- _generation->heuristics()->record_degenerated(heap->mode()->is_generational() && _generation->is_global());
- }
-}
-
-void ShenandoahDegenGC::op_reset() {
- _generation->prepare_gc();
-}
-
-void ShenandoahDegenGC::op_mark() {
- assert(!_generation->is_concurrent_mark_in_progress(), "Should be reset");
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_mark);
- ShenandoahSTWMark mark(_generation, false /*full gc*/);
- mark.mark();
-}
-
-void ShenandoahDegenGC::op_finish_mark() {
- ShenandoahConcurrentMark mark(_generation);
- mark.finish_mark();
-}
-
-void ShenandoahDegenGC::op_prepare_evacuation() {
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- if (ShenandoahVerify) {
- heap->verifier()->verify_roots_no_forwarded(_generation);
- }
-
- // STW cleanup weak roots and unload classes
- heap->parallel_cleaning(_generation, false /*full gc*/);
-
- // Prepare regions and collection set
- _generation->prepare_regions_and_collection_set(false /*concurrent*/);
-
- // Retire the TLABs, which will force threads to reacquire their TLABs after the pause.
- // This is needed for two reasons. Strong one: new allocations would be with new freeset,
- // which would be outside the collection set, so no cset writes would happen there.
- // Weaker one: new allocations would happen past update watermark, and so less work would
- // be needed for reference updates (would update the large filler instead).
- if (UseTLAB) {
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_final_manage_labs);
- heap->tlabs_retire(false);
- }
-
- if (!heap->collection_set()->is_empty()) {
- if (ShenandoahVerify) {
- heap->verifier()->verify_before_evacuation(_generation);
- }
-
- heap->set_evacuation_in_progress(true);
- heap->set_has_forwarded_objects(true);
- } else {
- if (ShenandoahVerify) {
- if (has_in_place_promotions(heap)) {
- heap->verifier()->verify_after_concmark_with_promotions(_generation);
- } else {
- heap->verifier()->verify_after_concmark(_generation);
- }
- }
-
- if (VerifyAfterGC) {
- Universe::verify();
- }
- }
-}
-
-bool ShenandoahDegenGC::has_in_place_promotions(const ShenandoahHeap* heap) const {
- return heap->mode()->is_generational() && heap->old_generation()->has_in_place_promotions();
-}
-
-void ShenandoahDegenGC::op_cleanup_early() {
- ShenandoahHeap::heap()->recycle_trash();
-}
-
-void ShenandoahDegenGC::op_evacuate() {
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_evac);
- ShenandoahHeap::heap()->evacuate_collection_set(_generation, false /* concurrent*/);
-}
-
-void ShenandoahDegenGC::op_init_update_refs() {
- // Evacuation has completed
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- heap->prepare_update_heap_references();
- heap->set_update_refs_in_progress(true);
-}
-
-void ShenandoahDegenGC::op_update_refs() {
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_update_refs);
- // Handed over from concurrent update references phase
- heap->update_heap_references(_generation, false /*concurrent*/);
-
- heap->set_update_refs_in_progress(false);
- heap->set_has_forwarded_objects(false);
-}
-
-void ShenandoahDegenGC::op_update_roots() {
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
-
- update_roots(false /*full_gc*/);
-
- heap->update_heap_region_states(false /*concurrent*/);
-
- if (ShenandoahVerify) {
- heap->verifier()->verify_after_update_refs(_generation);
- }
-
- if (VerifyAfterGC) {
- Universe::verify();
- }
-
- heap->rebuild_free_set(false /*concurrent*/);
-}
-
-void ShenandoahDegenGC::op_cleanup_complete() {
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_cleanup_complete);
-
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
-
- // If we are degenerating for old mark only, we need to check that there are no accidental
- // young pointers in SATB queues. If so, we need to filter them before we perform cleanups,
- // otherwise they would dangle.
- ShenandoahSATBMarkQueueSet& satb_qs = ShenandoahBarrierSet::satb_mark_queue_set();
- if (heap->is_concurrent_old_mark_in_progress() && !heap->is_concurrent_young_mark_in_progress()) {
- assert(satb_qs.get_filter_out_young(), "Must be");
- ShenandoahFlushSATB flush_satb(satb_qs);
- Threads::threads_do(&flush_satb);
- } else {
- assert(!satb_qs.get_filter_out_young(), "Must not be");
- }
-
- heap->recycle_trash();
-}
-
-void ShenandoahDegenGC::op_degenerated_fail() {
- upgrade_to_full();
-}
-
-void ShenandoahDegenGC::op_degenerated_futile() {
- upgrade_to_full();
-}
-
-const char* ShenandoahDegenGC::degen_event_message(ShenandoahDegenPoint point) const {
- switch (point) {
- case _degenerated_unset: {
- SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " ()");
- return msg;
- }
- case _degenerated_outside_cycle: {
- SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Outside of Cycle)");
- return msg;
- }
- case _degenerated_roots: {
- SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Roots)");
- return msg;
- }
- case _degenerated_mark: {
- SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Mark)");
- return msg;
- }
- case _degenerated_evac: {
- SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Evacuation)");
- return msg;
- }
- case _degenerated_update_refs: {
- SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Update Refs)");
- return msg;
- }
- default: {
- ShouldNotReachHere();
- SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (?)");
- return msg;
- }
- }
-}
-
-void ShenandoahDegenGC::upgrade_to_full() {
- log_info(gc, phases)("Degenerated GC upgrading to Full GC");
- ShenandoahHeap* heap = ShenandoahHeap::heap();
- heap->cancel_gc(GCCause::_shenandoah_upgrade_to_full_gc);
- heap->increment_total_collections(true);
- heap->shenandoah_policy()->record_degenerated_upgrade_to_full();
- ShenandoahFullGC full_gc;
- full_gc.op_full(GCCause::_shenandoah_upgrade_to_full_gc);
-}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp
deleted file mode 100644
index f3d033af1288..000000000000
--- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2021, Red Hat, Inc. 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_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP
-#define SHARE_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP
-
-#include "gc/shenandoah/shenandoahGC.hpp"
-
-class VM_ShenandoahDegeneratedGC;
-class ShenandoahGeneration;
-
-class ShenandoahDegenGC : public ShenandoahGC {
- friend class VM_ShenandoahDegeneratedGC;
-private:
- const ShenandoahDegenPoint _degen_point;
- bool _abbreviated;
- const bool _do_old_gc_bootstrap;
-
-public:
- ShenandoahDegenGC(ShenandoahDegenPoint degen_point, ShenandoahGeneration* generation, bool do_old_gc_bootstrap = false);
- bool collect(GCCause::Cause cause) override;
-
-private:
- void vmop_degenerated();
- void entry_degenerated();
- void op_degenerated();
-
- void op_reset();
- void op_mark();
- void op_finish_mark();
- void op_prepare_evacuation();
- void op_cleanup_early();
-
- void op_evacuate();
- void op_init_update_refs();
- void op_update_refs();
- void op_update_roots();
- void op_cleanup_complete();
-
- // Fail handling
- void op_degenerated_futile();
- void op_degenerated_fail();
-
- // Turns this degenerated cycle into a full gc without leaving the safepoint
- void upgrade_to_full();
-
- const char* degen_event_message(ShenandoahDegenPoint point) const;
-
- bool has_in_place_promotions(const ShenandoahHeap* heap) const;
-};
-
-#endif // SHARE_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
index 72d0773eccdf..aad35f512c40 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp
@@ -1,6 +1,6 @@
/*
* 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
@@ -27,6 +27,7 @@
#include "gc/shenandoah/shenandoahHeap.inline.hpp"
#include "gc/shenandoah/shenandoahThreadLocalData.hpp"
#include "runtime/thread.hpp"
+#include "runtime/threads.hpp"
#include "runtime/threadSMR.inline.hpp"
ShenandoahEvacuationStats::ShenandoahEvacuations* ShenandoahEvacuationStats::get_category(
@@ -120,7 +121,7 @@ ShenandoahCycleStats ShenandoahEvacuationTracker::flush_cycle_to_global() {
java_threads_iterator.list()->threads_do(&aggregate_mutators);
ShenandoahStatAggregator aggregate_workers(&workers);
- ShenandoahHeap::heap()->gc_threads_do(&aggregate_workers);
+ Threads::non_java_threads_do(&aggregate_workers);
_mutators_global.accumulate(&mutators);
_workers_global.accumulate(&workers);
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
index 11b98a10c12d..a7379170ad52 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp
@@ -24,9 +24,10 @@
*
*/
-
+#include "classfile/systemDictionary.hpp"
#include "code/codeCache.hpp"
#include "compiler/oopMap.hpp"
+#include "gc/shared/classUnloadingContext.hpp"
#include "gc/shared/continuationGCSupport.hpp"
#include "gc/shared/fullGCForwarding.inline.hpp"
#include "gc/shared/gcTraceTime.inline.hpp"
@@ -50,6 +51,7 @@
#include "gc/shenandoah/shenandoahMarkingContext.inline.hpp"
#include "gc/shenandoah/shenandoahMetrics.hpp"
#include "gc/shenandoah/shenandoahMonitoringSupport.hpp"
+#include "gc/shenandoah/shenandoahParallelCleaning.inline.hpp"
#include "gc/shenandoah/shenandoahPhaseTimings.hpp"
#include "gc/shenandoah/shenandoahReferenceProcessor.hpp"
#include "gc/shenandoah/shenandoahRootProcessor.inline.hpp"
@@ -58,6 +60,7 @@
#include "gc/shenandoah/shenandoahVerifier.hpp"
#include "gc/shenandoah/shenandoahVMOperations.hpp"
#include "gc/shenandoah/shenandoahWorkerPolicy.hpp"
+#include "gc/shenandoah/shenandoahYoungGeneration.hpp"
#include "memory/metaspaceUtils.hpp"
#include "memory/universe.hpp"
#include "oops/compressedOops.inline.hpp"
@@ -111,7 +114,7 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) {
ShenandoahMetricsSnapshot metrics(heap->free_set());
// Perform full GC
- do_it(cause);
+ do_it();
if (heap->mode()->is_generational()) {
ShenandoahGenerationalFullGC::handle_completion(heap);
@@ -125,9 +128,19 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) {
heap->notify_gc_no_progress();
}
- // Regardless if progress was made, we record that we completed a "successful" full GC.
- _generation->heuristics()->record_success_full();
- heap->shenandoah_policy()->record_success_full();
+ // Regardless if progress was made, we record that we completed a full GC.
+ if (heap->mode()->is_generational()) {
+ // In generational mode, the young heuristics are responsible for this failure
+ heap->young_generation()->heuristics()->record_full_gc(cause);
+ } else {
+ _generation->heuristics()->record_full_gc(cause);
+ }
+
+ if (cause == GCCause::_shenandoah_upgrade_to_full_gc) {
+ heap->shenandoah_policy()->record_alloc_failure_to_full();
+ }
+
+ heap->shenandoah_policy()->record_success_full(heap->control_thread()->get_gc_id());
// Leaving full GC, we need to flip barriers back to idle.
CodeCache::arm_all_nmethods();
@@ -138,23 +151,19 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) {
}
}
-void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) {
+void ShenandoahFullGC::do_it() {
ShenandoahHeap* heap = ShenandoahHeap::heap();
heap->release_injected_pins();
- // A full GC may be entered directly, or as an upgrade from a failed
- // degenerated GC. In the latter case, self-forwarded objects may be
- // present from the failed evacuation. Drain those marks before any phase
- // (verify, update_roots, phase1_mark_heap) walks headers.
- {
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_un_self_forward);
- heap->un_self_forward_cset_regions();
- }
-
if (heap->mode()->is_generational()) {
ShenandoahGenerationalFullGC::prepare();
}
+ guarantee(heap->is_idle(), "Full GC should not be running from incomplete cycle");
+ guarantee(!heap->has_forwarded_objects(), "Concurrent cycle should have finished update references");
+ guarantee(!heap->is_concurrent_mark_in_progress(), "Cannot be marking now");
+ guarantee(!heap->has_self_forwarded_objects(), "Self forwarded objects should be cleared by concurrent cycle.");
+
if (ShenandoahVerify) {
heap->verifier()->verify_before_fullgc(_generation);
}
@@ -163,11 +172,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) {
Universe::verify();
}
- // Degenerated GC may carry concurrent root flags when upgrading to
- // full GC. We need to reset it before mutators resume.
- heap->set_concurrent_strong_root_in_progress(false);
- heap->set_concurrent_weak_root_in_progress(false);
-
heap->set_full_gc_in_progress(true);
assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at a safepoint");
@@ -180,39 +184,8 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) {
{
ShenandoahGCPhase prepare_phase(ShenandoahPhaseTimings::full_gc_prepare);
- // Full GC is supposed to recover from any GC state:
-
- // a0. Remember if we have forwarded objects
- bool has_forwarded_objects = heap->has_forwarded_objects();
- // a1. Cancel evacuation, if in progress
- if (heap->is_evacuation_in_progress()) {
- heap->set_evacuation_in_progress(false);
- }
- assert(!heap->is_evacuation_in_progress(), "sanity");
-
- // a2. Cancel update-refs, if in progress
- if (heap->is_update_refs_in_progress()) {
- heap->set_update_refs_in_progress(false);
- }
- assert(!heap->is_update_refs_in_progress(), "sanity");
-
- // b. Cancel all concurrent marks, if in progress
- if (heap->is_concurrent_mark_in_progress()) {
- heap->cancel_concurrent_mark();
- }
- assert(!heap->is_concurrent_mark_in_progress(), "sanity");
-
- // c. Update roots if this full GC is due to evac-oom, which may carry from-space pointers in roots.
- if (has_forwarded_objects) {
- update_roots(true /*full_gc*/);
- }
-
- // d. Abandon reference discovery and clear all discovered references.
- ShenandoahReferenceProcessor* rp = _generation->ref_processor();
- rp->abandon_partial_discovery();
-
- // e. Sync pinned region status from the CP marks
+ // Sync pinned region status from the CP marks
heap->sync_pinned_region_status();
if (heap->mode()->is_generational()) {
@@ -221,8 +194,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) {
// The rest of prologue:
_preserved_marks->init(heap->workers()->active_workers());
-
- assert(heap->has_forwarded_objects() == has_forwarded_objects, "This should not change");
}
if (UseTLAB) {
@@ -235,11 +206,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) {
phase1_mark_heap();
- // Once marking is done, which may have fixed up forwarded objects, we can drop it.
- // Coming out of Full GC, we would not have any forwarded objects.
- // This also prevents resolves with fwdptr from kicking in while adjusting pointers in phase3.
- heap->set_has_forwarded_objects(false);
-
heap->set_full_gc_move_in_progress(true);
// Setup workers for the rest
@@ -280,8 +246,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) {
heap->set_full_gc_move_in_progress(false);
heap->set_full_gc_in_progress(false);
- DEBUG_ONLY(heap->assert_no_self_forwards());
-
if (ShenandoahVerify) {
heap->verifier()->verify_after_fullgc(_generation);
}
@@ -312,15 +276,91 @@ void ShenandoahFullGC::phase1_mark_heap() {
// enable ("weak") refs discovery
rp->set_soft_reference_policy(true); // forcefully purge all soft references
- ShenandoahSTWMark mark(_generation, true /*full_gc*/);
+ ShenandoahSTWMark mark(_generation);
mark.mark();
- heap->parallel_cleaning(_generation, true /* full_gc */);
+ parallel_cleaning(_generation);
- if (ShenandoahHeap::heap()->mode()->is_generational()) {
+ if (heap->mode()->is_generational()) {
ShenandoahGenerationalFullGC::log_live_in_old(heap);
}
}
+void ShenandoahFullGC::parallel_cleaning(ShenandoahGeneration* generation) const {
+ assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint");
+ assert(ShenandoahHeap::heap()->is_stw_gc_in_progress(), "Only for Full GC");
+ ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_purge);
+ stw_weak_refs(generation);
+ stw_process_weak_roots();
+ stw_unload_classes();
+}
+
+void ShenandoahFullGC::stw_weak_refs(ShenandoahGeneration* generation) {
+ // Weak refs processing
+ ShenandoahPhaseTimings::Phase phase = ShenandoahPhaseTimings::full_gc_weakrefs;
+ ShenandoahTimingsTracker t(phase);
+ ShenandoahGCWorkerPhase worker_phase(phase);
+ generation->ref_processor()->process_references(phase, ShenandoahHeap::heap()->workers(), false /* concurrent */);
+}
+
+// Weak roots are either pre-evacuated (final mark) or updated (final update refs),
+// so they should not have forwarded oops.
+// However, we do need to "null" dead oops in the roots, if it cannot be done
+// in concurrent cycles.
+void ShenandoahFullGC::stw_process_weak_roots() const {
+ ShenandoahHeap* heap = ShenandoahHeap::heap();
+ uint num_workers = heap->workers()->active_workers();
+ ShenandoahPhaseTimings::Phase timing_phase = ShenandoahPhaseTimings::full_gc_purge_weak_par;
+ ShenandoahGCPhase phase(timing_phase);
+ ShenandoahGCWorkerPhase worker_phase(timing_phase);
+ // Cleanup weak roots
+ ShenandoahIsAliveClosure is_alive;
+#ifdef ASSERT
+ ShenandoahAssertNotForwardedClosure verify_cl;
+ ShenandoahParallelWeakRootsCleaningTask cleaning_task(timing_phase, &is_alive, &verify_cl, num_workers);
+#else
+ ShenandoahParallelWeakRootsCleaningTask cleaning_task(timing_phase, &is_alive, &do_nothing_cl, num_workers);
+#endif
+ heap->workers()->run_task(&cleaning_task);
+}
+
+void ShenandoahFullGC::stw_unload_classes() const {
+ ShenandoahHeap* heap = ShenandoahHeap::heap();
+ if (!heap->unload_classes()) return;
+ ClassUnloadingContext ctx(heap->workers()->active_workers(),
+ true /* unregister_nmethods_during_purge */,
+ false /* lock_nmethod_free_separately */);
+
+ // Unload classes and purge SystemDictionary.
+ {
+ ShenandoahPhaseTimings::Phase phase = ShenandoahPhaseTimings::full_gc_purge_class_unload;
+ ShenandoahIsAliveSelector is_alive;
+ {
+ CodeCache::UnlinkingScope scope(is_alive.is_alive_closure());
+ ShenandoahGCPhase gc_phase(phase);
+ ShenandoahGCWorkerPhase worker_phase(phase);
+ bool unloading_occurred = SystemDictionary::do_unloading(_gc_timer);
+
+ ShenandoahClassUnloadingTask unlink_task(phase, unloading_occurred);
+ heap->workers()->run_task(&unlink_task);
+ }
+ // Release unloaded nmethods's memory.
+ ClassUnloadingContext::context()->purge_and_free_nmethods();
+ }
+
+ {
+ ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_purge_cldg);
+ ClassLoaderDataGraph::purge(true /* at_safepoint */);
+ }
+ // Resize and verify metaspace
+ MetaspaceGC::compute_new_size();
+
+ if (heap->mode()->is_generational()) {
+ heap->old_generation()->set_parsable(false);
+ }
+
+ DEBUG_ONLY(MetaspaceUtils::verify();)
+}
+
class ShenandoahPrepareForCompactionObjectClosure : public ObjectClosure {
private:
PreservedMarks* const _preserved_marks;
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp
index c69c8cec8fcb..def0b07c8a4e 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp
@@ -25,9 +25,7 @@
#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP
#define SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP
-#include "gc/shared/gcTimer.hpp"
#include "gc/shenandoah/shenandoahGC.hpp"
-#include "gc/shenandoah/shenandoahGenerationalHeap.hpp"
#include "gc/shenandoah/shenandoahHeapRegionSet.hpp"
/**
@@ -50,19 +48,16 @@
* Parallelization is handled by assigning each GC worker the slice of the heap (the set of regions)
* where it does sliding compaction, without interfering with other threads.
*/
-
+class GCTimer;
class PreservedMarksSet;
+class ShenandoahHeap;
class VM_ShenandoahFullGC;
-class ShenandoahDegenGC;
class ShenandoahFullGC : public ShenandoahGC {
friend class ShenandoahPrepareForCompactionObjectClosure;
friend class VM_ShenandoahFullGC;
- friend class ShenandoahDegenGC;
-private:
GCTimer* _gc_timer;
-
PreservedMarksSet* _preserved_marks;
public:
@@ -76,7 +71,7 @@ class ShenandoahFullGC : public ShenandoahGC {
void entry_full(GCCause::Cause cause);
void op_full(GCCause::Cause cause);
- void do_it(GCCause::Cause gc_cause);
+ void do_it();
void phase1_mark_heap();
void phase2_calculate_target_addresses(ShenandoahHeapRegionSet** worker_slices);
@@ -86,6 +81,14 @@ class ShenandoahFullGC : public ShenandoahGC {
void distribute_slices(ShenandoahHeapRegionSet** worker_slices);
void calculate_target_humongous_objects();
void compact_humongous_objects();
+
+ // Perform STW class unloading and weak root cleaning
+ void parallel_cleaning(ShenandoahGeneration* generation) const;
+
+ void stw_unload_classes() const;
+ void stw_process_weak_roots() const;
+
+ static void stw_weak_refs(ShenandoahGeneration* generation);
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp
deleted file mode 100644
index 6c6207e1618a..000000000000
--- a/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (c) 2021, Red Hat, Inc. 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 "compiler/oopMap.hpp"
-#include "gc/shared/workerThread.hpp"
-#include "gc/shenandoah/shenandoahClosures.inline.hpp"
-#include "gc/shenandoah/shenandoahGC.hpp"
-#include "gc/shenandoah/shenandoahHeap.hpp"
-#include "gc/shenandoah/shenandoahPhaseTimings.hpp"
-#include "gc/shenandoah/shenandoahRootProcessor.inline.hpp"
-#include "gc/shenandoah/shenandoahUtils.hpp"
-
-const char* ShenandoahGC::degen_point_to_string(ShenandoahDegenPoint point) {
- switch(point) {
- case _degenerated_unset:
- return "";
- case _degenerated_outside_cycle:
- return "Outside of Cycle";
- case _degenerated_roots:
- return "Roots";
- case _degenerated_mark:
- return "Mark";
- case _degenerated_evac:
- return "Evacuation";
- case _degenerated_update_refs:
- return "Update References";
- default:
- ShouldNotReachHere();
- return "ERROR";
- }
-}
-
-class ShenandoahUpdateRootsTask : public WorkerTask {
-private:
- ShenandoahRootUpdater* _root_updater;
- bool _check_alive;
-public:
- ShenandoahUpdateRootsTask(ShenandoahRootUpdater* root_updater, bool check_alive) :
- WorkerTask("Shenandoah Update Roots"),
- _root_updater(root_updater),
- _check_alive(check_alive){
- }
-
- void work(uint worker_id) {
- assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a safepoint");
- ShenandoahParallelWorkerSession worker_session(worker_id);
-
- ShenandoahNonConcUpdateRefsClosure cl;
- if (_check_alive) {
- ShenandoahForwardedIsAliveClosure is_alive;
- _root_updater->roots_do(worker_id, &is_alive, &cl);
- } else {
- AlwaysTrueClosure always_true;
- _root_updater->roots_do(worker_id, &always_true, &cl);
- }
- }
-};
-
-void ShenandoahGC::update_roots(bool full_gc) {
- assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a safepoint");
- assert(ShenandoahHeap::heap()->is_full_gc_in_progress() ||
- ShenandoahHeap::heap()->is_degenerated_gc_in_progress(),
- "Only for degenerated GC and full GC");
-
- bool check_alive = !full_gc;
- ShenandoahPhaseTimings::Phase p = full_gc ?
- ShenandoahPhaseTimings::full_gc_update_roots :
- ShenandoahPhaseTimings::degen_gc_update_roots;
-
- ShenandoahGCPhase phase(p);
-#ifdef COMPILER2
- DerivedPointerTable::clear();
-#endif // COMPILER2
-
- ShenandoahHeap* const heap = ShenandoahHeap::heap();
- WorkerThreads* workers = heap->workers();
- uint nworkers = workers->active_workers();
-
- ShenandoahRootUpdater root_updater(nworkers, p);
- ShenandoahUpdateRootsTask update_roots(&root_updater, check_alive);
- workers->run_task(&update_roots);
-
-#ifdef COMPILER2
- DerivedPointerTable::update_pointers();
-#endif // COMPILER2
-}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp
index 7182665f2e3a..670cf6dad5a0 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp
@@ -29,45 +29,27 @@
#include "memory/allocation.hpp"
/*
- * Base class of three Shenandoah GC modes
+ * Base class of three Shenandoah GC collections
*
* The relationship of the GCs:
*
* ("normal" mode) ----> Concurrent GC ----> (finish)
- * |
- * |
- * v
- * ("passive" mode) ---> Degenerated GC ---> (finish)
- * |
- * |
- * v
- * Full GC --------> (finish)
+ * ("old" mode) --> Concurrent GC (bootstrap) --> Old GC --> (finish)
+ * ("passive" mode) ---> Full GC --------> (finish)
*/
class ShenandoahGeneration;
class ShenandoahGC : public StackObj {
public:
- // Fail point from concurrent GC
- enum ShenandoahDegenPoint {
- _degenerated_unset,
- _degenerated_outside_cycle,
- _degenerated_roots,
- _degenerated_mark,
- _degenerated_evac,
- _degenerated_update_refs,
- _DEGENERATED_LIMIT
- };
-
- explicit ShenandoahGC(ShenandoahGeneration* generation) : _generation(generation) {}
+ explicit ShenandoahGC(ShenandoahGeneration* generation)
+ : _generation(generation) { }
// Returns false if the collection was cancelled, true otherwise.
virtual bool collect(GCCause::Cause cause) = 0;
- static const char* degen_point_to_string(ShenandoahDegenPoint point);
ShenandoahGeneration* generation() const { return _generation; }
protected:
- static void update_roots(bool full_gc);
ShenandoahGeneration* _generation;
};
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
index fb99825bef93..718b32ee7fc9 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp
@@ -25,6 +25,7 @@
#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp"
#include "gc/shenandoah/shenandoahAffiliation.hpp"
+#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
@@ -83,24 +84,6 @@ class ShenandoahResetBitmapClosure final : public ShenandoahHeapRegionClosure {
bool is_thread_safe() override { return true; }
};
-// Copy the write-version of the card-table into the read-version, clearing the
-// write-copy.
-class ShenandoahMergeWriteTable: public ShenandoahHeapRegionClosure {
-private:
- ShenandoahScanRemembered* _scanner;
-public:
- ShenandoahMergeWriteTable(ShenandoahScanRemembered* scanner) : _scanner(scanner) {}
-
- void heap_region_do(ShenandoahHeapRegion* r) override {
- assert(r->is_old(), "Don't waste time doing this for non-old regions");
- _scanner->merge_write_table(r->bottom(), ShenandoahHeapRegion::region_size_words());
- }
-
- bool is_thread_safe() override {
- return true;
- }
-};
-
// Add [TAMS, top) volume over young regions. Used to correct age 0 cohort census
// for adaptive tenuring when census is taken during marking.
// In non-product builds, for the purposes of verification, we also collect the total
@@ -228,20 +211,6 @@ void ShenandoahGeneration::swap_card_tables() {
old_generation->card_scan()->swap_card_tables();
}
-// Copy the write-version of the card-table into the read-version, clearing the
-// write-version. The work is done at a safepoint and in parallel by the GC
-// worker threads.
-void ShenandoahGeneration::merge_write_table() {
- // This should only happen for degenerated cycles
- ShenandoahGenerationalHeap* heap = ShenandoahGenerationalHeap::heap();
- heap->assert_gc_workers(heap->workers()->active_workers());
- shenandoah_assert_safepoint();
-
- ShenandoahOldGeneration* old_generation = heap->old_generation();
- ShenandoahMergeWriteTable task(old_generation->card_scan());
- old_generation->parallel_heap_region_iterate(&task);
-}
-
void ShenandoahGeneration::prepare_gc() {
reset_mark_bitmap();
}
@@ -250,16 +219,15 @@ void ShenandoahGeneration::parallel_heap_region_iterate_free(ShenandoahHeapRegio
ShenandoahHeap::heap()->parallel_heap_region_iterate(cl);
}
-void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) {
+void ShenandoahGeneration::prepare_regions_and_collection_set() {
ShenandoahHeap* heap = ShenandoahHeap::heap();
ShenandoahCollectionSet* collection_set = heap->collection_set();
bool is_generational = heap->mode()->is_generational();
- assert(!heap->is_full_gc_in_progress(), "Only for concurrent and degenerated GC");
+ assert(!heap->is_full_gc_in_progress(), "Only for concurrent GC");
assert(!is_old(), "Only YOUNG and GLOBAL GC perform evacuations");
{
- ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::final_update_region_states :
- ShenandoahPhaseTimings::degen_gc_final_update_region_states);
+ ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_region_states);
// Update region state for every active region, but only update the liveness data for
// the generation we marked. We always need to update the watermark for old regions.
// If there are mixed collections pending, we also need to synchronize the pinned status
@@ -284,8 +252,7 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) {
}
{
- ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::choose_cset :
- ShenandoahPhaseTimings::degen_gc_choose_cset);
+ ShenandoahGCPhase phase(ShenandoahPhaseTimings::choose_cset);
collection_set->clear();
ShenandoahHeapLocker locker(heap->lock());
@@ -307,10 +274,9 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) {
}
{
- ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::final_rebuild_freeset :
- ShenandoahPhaseTimings::degen_gc_final_rebuild_freeset);
+ ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_rebuild_freeset);
ShenandoahHeapLocker locker(heap->lock());
- // At start of evacation, we do NOT compute_old_generation_balance()
+ // At start of evacuation, we do NOT compute_old_generation_balance()
size_t young_trashed_regions, old_trashed_regions, first_old, last_old, num_old;
_free_set->prepare_to_rebuild(young_trashed_regions, old_trashed_regions, first_old, last_old, num_old);
_free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, num_old);
@@ -426,8 +392,3 @@ size_t ShenandoahGeneration::available(size_t capacity) const {
size_t result = in_use > capacity ? 0 : capacity - in_use;
return result;
}
-
-void ShenandoahGeneration::record_success_concurrent(bool abbreviated) {
- heuristics()->record_success_concurrent();
- ShenandoahHeap::heap()->shenandoah_policy()->record_success_concurrent(is_young(), abbreviated);
-}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
index 0183db7d1b3a..fd6b69990d18 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp
@@ -32,6 +32,7 @@
#include "gc/shenandoah/shenandoahLock.hpp"
#include "gc/shenandoah/shenandoahMarkingContext.hpp"
#include "memory/allocation.hpp"
+#include "runtime/atomic.hpp"
class ShenandoahCollectionSet;
class ShenandoahHeap;
@@ -55,6 +56,10 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo {
// Bytes reserved within this generation to hold evacuated objects from the collection set
size_t _evacuation_reserve;
+ // Read by mutators, written by control thread. This records the id of the last gc cycle
+ // to collect this generation.
+ Atomic _completed_gc_id;
+
protected:
ShenandoahFreeSet* _free_set;
ShenandoahHeuristics* _heuristics;
@@ -68,6 +73,14 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo {
uint max_workers);
~ShenandoahGeneration();
+ void update_completed_gc_id(size_t gc_id) {
+ _completed_gc_id.store_relaxed(gc_id);
+ }
+
+ size_t completed_gc_id() const {
+ return _completed_gc_id.load_relaxed();
+ }
+
inline bool is_young() const { return _type == YOUNG; }
inline bool is_old() const { return _type == OLD; }
inline bool is_global() const { return _type == GLOBAL || _type == NON_GEN; }
@@ -109,12 +122,9 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo {
template
void reset_mark_bitmap();
- // Used by concurrent and degenerated GC to reset remembered set.
+ // Used by concurrent GC to reset remembered set.
void swap_card_tables();
- // Update the read cards with the state of the write table (write table is not cleared).
- void merge_write_table();
-
// Called before init mark, expected to prepare regions for marking.
virtual void prepare_gc();
@@ -135,7 +145,7 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo {
// been set aside to hold objects evacuated from the young-gen collection set. Conservatively, this value
// equals the entire amount of live young-gen memory within the collection set, even though some of this memory
// will likely be promoted.
- virtual void prepare_regions_and_collection_set(bool concurrent);
+ virtual void prepare_regions_and_collection_set();
// Cancel marking (used by Full collect and when cancelling cycle).
virtual void cancel_marking();
@@ -179,8 +189,6 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo {
virtual bool is_concurrent_mark_in_progress() = 0;
void confirm_heuristics_mode();
-
- virtual void record_success_concurrent(bool abbreviated);
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHGENERATION_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
index d73ba05b44a9..5db519df1efa 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
@@ -28,7 +28,6 @@
#include "gc/shenandoah/shenandoahAsserts.hpp"
#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahConcurrentGC.hpp"
-#include "gc/shenandoah/shenandoahDegeneratedGC.hpp"
#include "gc/shenandoah/shenandoahFreeSet.hpp"
#include "gc/shenandoah/shenandoahFullGC.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
@@ -48,13 +47,9 @@
#include "utilities/events.hpp"
ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() :
- _control_lock(CONTROL_LOCK_RANK, "ShenandoahGCRequest_lock", true),
- _requested_gc_cause(GCCause::_no_gc),
_requested_generation(nullptr),
_gc_mode(none),
- _degen_point(ShenandoahGC::_degenerated_unset),
- _heap(ShenandoahGenerationalHeap::heap()),
- _do_old_gc_bootstrap(false) {
+ _heap(ShenandoahGenerationalHeap::heap()) {
shenandoah_assert_generational();
set_name("ShenControl");
create_and_start();
@@ -62,10 +57,7 @@ ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() :
void ShenandoahGenerationalControlThread::run_service() {
- // This is the only instance of request. It is important that request.generation
- // does not change between a concurrent cycle failure and the start of a degenerated
- // cycle. We initialize it with the young generation to handle the pathological case
- // where the very first cycle is degenerated (some tests exercise this path).
+ // This is the only instance of request.
ShenandoahGCRequest request;
request.generation = _heap->young_generation();
while (!should_terminate()) {
@@ -85,6 +77,7 @@ void ShenandoahGenerationalControlThread::run_service() {
// if there was no other cycle requested, cleanup and wait for the next request.
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
if (_requested_gc_cause == GCCause::_no_gc) {
+ _heap->clear_cancelled_gc();
set_gc_mode(ml, none);
ml.wait();
}
@@ -92,7 +85,7 @@ void ShenandoahGenerationalControlThread::run_service() {
// In case any threads are waiting for a cycle to happen, notify them so they observe the shutdown.
notify_gc_waiters();
- notify_alloc_failure_waiters();
+ notify_alloc_waiters();
set_gc_mode(stopped);
// We're done writing GC stats, so print them here.
@@ -102,36 +95,41 @@ void ShenandoahGenerationalControlThread::run_service() {
void ShenandoahGenerationalControlThread::stop_service() {
log_debug(gc, thread)("Stopping control thread");
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
- _heap->cancel_gc(GCCause::_shenandoah_stop_vm);
notify_control_thread(ml, GCCause::_shenandoah_stop_vm);
// We can't wait here because it may interfere with the active cycle's ability
// to reach a safepoint (this runs on a java thread).
}
+void ShenandoahGenerationalControlThread::notify_alloc_stall(GCCause::Cause cause) {
+ ShenandoahGeneration* generation = _heap->young_generation();
+
+ // Tell the young generation heuristics about the stall.
+ generation->heuristics()->record_allocation_stall();
+
+ MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
+ notify_control_thread(ml, cause, generation);
+}
+
void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& request) {
// Hold the lock while we read request cause and generation
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
- log_debug(gc, thread)("cancelled cause: %s, requested cause: %s",
- GCCause::to_string(_heap->cancelled_cause()), GCCause::to_string(_requested_gc_cause));
+ log_debug(gc, thread)("Requested cause: %s", GCCause::to_string(_requested_gc_cause));
request.cause = _requested_gc_cause;
+ request.generation = _requested_generation;
+
+ // If the cause was an allocation failure or an explicit request, override the generation
if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) {
- if (_degen_point == ShenandoahGC::_degenerated_unset) {
+ if (_heap->old_generation()->is_concurrent_mark_in_progress()) {
+ // We know we can't allocate in young, and we know we need to collect old.
+ // We don't really know how much longer it will take to clear out old (probably
+ // a long time if it keeps getting interrupted by allocation failures). At
+ // this point, let's run a global cycle.
+ request.generation = _heap->global_generation();
+ } else if (_requested_generation == nullptr) {
request.generation = _heap->young_generation();
- _degen_point = ShenandoahGC::_degenerated_outside_cycle;
- } else {
- assert(request.generation != nullptr, "Must know which generation to use for degenerated cycle");
- }
- } else {
- if (request.cause == GCCause::_shenandoah_concurrent_gc || ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) {
- // This is a regulator request or an explicit gc request. Note that an explicit gc request is allowed to
- // "upgrade" a regulator request. It is possible that the regulator "canceled" an old mark, so we must
- // clear that cancellation here or the explicit gc cycle will erroneously detect it as a cancellation.
- // This clear operation will only clear the cancellation if it was set by regulator request.
- _heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc);
}
- request.generation = _requested_generation;
}
log_debug(gc, thread)("request.cause: %s, request.generation: %s",
@@ -139,79 +137,68 @@ void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest&
_requested_gc_cause = GCCause::_no_gc;
_requested_generation = nullptr;
+ _heap->clear_cancelled_gc();
if (request.cause == GCCause::_no_gc || request.cause == GCCause::_shenandoah_stop_vm) {
return;
}
assert(request.generation != nullptr, "request.generation cannot be null, cause is: %s", GCCause::to_string(request.cause));
+
GCMode mode;
- if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) {
- mode = prepare_for_allocation_failure_gc(request);
- } else if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) {
- mode = prepare_for_explicit_gc(request);
+ if (ShenandoahCollectorPolicy::should_run_full_gc(request.cause)) {
+ mode = prepare_for_full_gc(request);
} else {
mode = prepare_for_concurrent_gc(request);
}
set_gc_mode(ml, mode);
}
-ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_allocation_failure_gc(ShenandoahGCRequest &request) {
- // Important: not all paths update the request.generation. This is intentional.
- // A degenerated cycle must use the same generation carried over from the previous request.
- if (request.generation->is_old()) {
- // This means we degenerated during the young bootstrap for the old generation
- // cycle. The following degenerated cycle should therefore also be young.
- request.generation = _heap->young_generation();
+ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_full_gc(ShenandoahGCRequest &request) const {
+ if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) {
+ prepare_for_explicit_gc(request);
+ return stw_full;
}
- ShenandoahHeuristics* heuristics = request.generation->heuristics();
- bool old_gen_evacuation_failed = _heap->old_generation()->clear_failed_evacuation();
-
- heuristics->log_trigger("Handle Allocation Failure");
+ request.generation = _heap->global_generation();
+ request.generation->heuristics()->log_trigger("Handle Full GC: %s", GCCause::to_string(request.cause));
+ return stw_full;
+}
- // Do not bother with degenerated cycle if old generation evacuation failed
- if (ShenandoahDegeneratedGC && heuristics->should_degenerate_cycle() && !old_gen_evacuation_failed) {
- heuristics->record_allocation_failure_gc();
- _heap->shenandoah_policy()->record_alloc_failure_to_degenerated(_degen_point);
- return stw_degenerated;
- } else {
- heuristics->record_allocation_failure_gc();
- _heap->shenandoah_policy()->record_alloc_failure_to_full();
- request.generation = _heap->global_generation();
- return stw_full;
- }
+void ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest& request) const {
+ ShenandoahHeuristics* heuristics = request.generation->heuristics();
+ heuristics->record_requested_gc();
+ heuristics->log_trigger("GC Request (%s)", GCCause::to_string(request.cause));
+ _heap->set_unload_classes(heuristics->can_unload_classes());
}
-ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest &request) const {
- ShenandoahHeuristics* global_heuristics = _heap->global_generation()->heuristics();
- request.generation = _heap->global_generation();
- global_heuristics->log_trigger("GC Request (%s)", GCCause::to_string(request.cause));
- global_heuristics->record_requested_gc();
+ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_concurrent_gc(ShenandoahGCRequest &request) const {
- if (ShenandoahCollectorPolicy::should_run_full_gc(request.cause)) {
- return stw_full;
- } else {
- // Unload and clean up everything. Note that this is an _explicit_ request and so does not use
- // the same `should_unload_classes` call as the regulator's concurrent gc request.
- _heap->set_unload_classes(global_heuristics->can_unload_classes());
+ if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) {
+ prepare_for_explicit_gc(request);
return concurrent_normal;
}
-}
-
-ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_concurrent_gc(const ShenandoahGCRequest &request) const {
- assert(!(request.generation->is_old() && _heap->old_generation()->is_doing_mixed_evacuations()),
- "Old heuristic should not request cycles while it waits for mixed evacuations");
if (request.generation->is_global()) {
- ShenandoahHeuristics* global_heuristics = _heap->global_generation()->heuristics();
- _heap->set_unload_classes(global_heuristics->should_unload_classes());
+ _heap->set_unload_classes(request.generation->heuristics()->should_unload_classes());
} else {
_heap->set_unload_classes(false);
}
+ if (request.generation->is_old()) {
+ switch (_heap->old_generation()->state()) {
+ case ShenandoahOldGeneration::FILLING:
+ case ShenandoahOldGeneration::MARKING:
+ return servicing_old;
+ case ShenandoahOldGeneration::IDLE:
+ return bootstrapping_old;
+ default:
+ fatal("Should not be getting request for old collection in state: %s", _heap->old_generation()->state_name());
+ }
+ }
+
// preemption was requested or this is a regular cycle
- return request.generation->is_old() ? servicing_old : concurrent_normal;
+ return concurrent_normal;
}
void ShenandoahGenerationalControlThread::maybe_print_young_region_ages() const {
@@ -234,6 +221,15 @@ void ShenandoahGenerationalControlThread::maybe_print_young_region_ages() const
}
}
+void ShenandoahGenerationalControlThread::clear_allocation_failure_and_notify_waiters() {
+ ShenandoahController::clear_allocation_failure_and_notify_waiters();
+ if (_heap->old_generation()->clear_failed_evacuation()) {
+ // Immediately run a full GC.
+ log_info(gc, ergo)("Requesting a full gc because of evacuation failures in old");
+ notify_control_thread(GCCause::_shenandoah_upgrade_to_full_gc, _heap->global_generation());
+ }
+}
+
void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest& request) {
log_debug(gc, thread)("Starting GC (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name());
@@ -275,34 +271,25 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest
service_concurrent_normal_cycle(request);
break;
}
- case stw_degenerated: {
- service_stw_degenerated_cycle(request);
+ case bootstrapping_old:
+ case servicing_old: {
+ service_concurrent_old_cycle(request);
break;
}
case stw_full: {
service_stw_full_cycle(request.cause);
break;
}
- case servicing_old: {
- assert(request.generation->is_old(), "Expected old generation here");
- service_concurrent_old_cycle(request);
- break;
- }
default:
ShouldNotReachHere();
}
_heap->print_after_gc();
}
- // If this cycle completed successfully, notify threads waiting for gc
- if (!_heap->cancelled_gc()) {
- notify_gc_waiters();
- notify_alloc_failure_waiters();
- // Report current free set state at the end of cycle if normal completion.
- // Do not report if cancelled, since we may not have rebuilt free set and content is unreliable.
- _heap->free_set()->log_status_under_lock();
- }
+ // Try to reduce concurrent workers
+ decrease_concurrent_worker_count();
+ _heap->free_set()->log_status_under_lock();
// Notify Universe about new heap usage. This has implications for
// global soft refs policy, and we better report it every time heap
@@ -345,41 +332,23 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest
}
log_debug(gc, thread)("Completed GC (%s): %s, %s, cancelled: %s",
- gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), GCCause::to_string(_heap->cancelled_cause()));
+ gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), BOOL_TO_STR(_heap->cancelled_gc()));
}
-// 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 +-----+-----------+---------------------+
-// | + | | |
-// | | | | |
-// | | v | |
-// | | Bootstrap Old +-- | ------------+ |
-// | | + | | |
-// | | | | | |
-// | v v v v |
-// | Resume Old <----------+ Young +--> Young Degen |
-// | + + ^ + + |
-// v | | | | | |
-// Global <-+ | +----------------------------+ | |
-// + | | |
-// | v v |
-// +---> Global Degen +--------------------> Full <----+
-//
+// 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 to run on a safepoint).
+// If there is an allocation failure during old marking, it will be cancelled. The
+// next cycle will be global to complete collection of old and young together. Except for
+// old marking, all concurrent cycles run to completion before returning to IDLE to choose
+// the next action.
void ShenandoahGenerationalControlThread::service_concurrent_normal_cycle(const ShenandoahGCRequest& request) {
log_info(gc, ergo)("Start GC cycle (%s)", request.generation->name());
- if (request.generation->is_old()) {
- service_concurrent_old_cycle(request);
- } else {
- service_concurrent_cycle(request.generation, request.cause, false);
- }
+ service_concurrent_cycle(request.generation, request.cause, false);
}
void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const ShenandoahGCRequest& request) {
+ log_info(gc, ergo)("Start GC cycle (%s)", request.generation->name());
ShenandoahOldGeneration* old_generation = _heap->old_generation();
ShenandoahYoungGeneration* young_generation = _heap->young_generation();
ShenandoahOldGeneration::State original_state = old_generation->state();
@@ -389,74 +358,65 @@ void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const She
_heap->increment_total_collections(false);
switch (original_state) {
- case ShenandoahOldGeneration::FILLING: {
- ShenandoahGCSession session(request.cause, old_generation);
- assert(gc_mode() == servicing_old, "Filling should be servicing old");
- _allow_old_preemption.set();
- old_generation->entry_coalesce_and_fill();
- _allow_old_preemption.unset();
-
- // Before bootstrapping begins, we must acknowledge any cancellation request.
- // If the gc has not been cancelled, this does nothing. If it has been cancelled,
- // this will clear the cancellation request and exit before starting the bootstrap
- // phase. This will allow the young GC cycle to proceed normally. If we do not
- // acknowledge the cancellation request, the subsequent young cycle will observe
- // the request and essentially cancel itself.
- if (check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle)) {
- // Need to report at "gc" level to report GC ID proper.
- log_info(gc)("Preparation for old generation cycle was cancelled");
- return;
- }
-
- // Coalescing threads completed and nothing was cancelled. it is safe to transition from this state.
- old_generation->transition_to(ShenandoahOldGeneration::IDLE);
- return;
- }
case ShenandoahOldGeneration::IDLE:
old_generation->transition_to(ShenandoahOldGeneration::MARKING);
+ set_gc_mode(bootstrapping_old);
+
+ // This is the logical gc id for the old collection. The old cycle itself might be
+ // interrupted many times and each iteration will generate a new gc id, but the one
+ // we record at the start will span all the interruptions.
+ old_generation->record_collection_start(get_gc_id());
+
// Configure the young generation's concurrent mark to put objects in
// old regions into the concurrent mark queues associated with the old
// generation. The young cycle will run as normal except that rather than
// ignore old references it will mark and enqueue them in the old concurrent
- // task queues but it will not traverse them.
- set_gc_mode(bootstrapping_old);
+ // task queues, but it will not traverse them.
young_generation->set_old_gen_task_queues(old_generation->task_queues());
service_concurrent_cycle(young_generation, request.cause, true);
_heap->process_gc_stats();
- if (_heap->cancelled_gc()) {
- // Young generation bootstrap cycle has failed. Concurrent mark for old generation
- // is going to resume after degenerated bootstrap cycle completes.
- log_info(gc, phases)("Bootstrap cycle for old generation was cancelled");
- return;
- }
-
- assert(_degen_point == ShenandoahGC::_degenerated_unset, "Degen point should not be set if gc wasn't cancelled");
- // From here we will 'resume' the old concurrent mark. This will skip reset
- // and init mark for the concurrent mark. All of that work will have been
- // done by the bootstrapping young cycle.
- set_gc_mode(servicing_old);
+ // Here we return to the top of the control loop so that we can test
+ // if memory was exhausted before we start marking old. This will notify alloc
+ // waiters and give them a chance to resume before old marking begins. If our
+ // bootstrap cycle did not free enough memory, we will escalate to a global cycle
+ // and skip old marking.
+ break;
case ShenandoahOldGeneration::MARKING: {
ShenandoahGCSession session(request.cause, old_generation);
- bool marking_complete = resume_concurrent_old_cycle(old_generation, request.cause);
- if (marking_complete) {
- assert(old_generation->state() != ShenandoahOldGeneration::MARKING, "Should not still be marking");
- if (original_state == ShenandoahOldGeneration::MARKING) {
- _heap->mmu_tracker()->record_old_marking_increment(true);
- _heap->log_heap_status("At end of Concurrent Old Marking finishing increment");
- }
- } else if (original_state == ShenandoahOldGeneration::MARKING) {
+ resume_concurrent_old_cycle(old_generation, request.cause);
+ if (old_generation->state() != ShenandoahOldGeneration::MARKING) {
+ _heap->mmu_tracker()->record_old_marking_increment(true);
+ _heap->log_heap_status("At end of Concurrent Old Marking finishing increment");
+ } else {
_heap->mmu_tracker()->record_old_marking_increment(false);
_heap->log_heap_status("At end of Concurrent Old Marking increment");
}
break;
}
+ case ShenandoahOldGeneration::FILLING: {
+ ShenandoahGCSession session(request.cause, old_generation);
+ _allow_old_preemption.set();
+ const bool filling_complete = old_generation->entry_coalesce_and_fill();
+ _allow_old_preemption.unset();
+
+ if (!filling_complete) {
+ assert(old_generation->state() == ShenandoahOldGeneration::FILLING, "Should still be filling");
+ // Need to report at "gc" level to report GC ID proper.
+ log_info(gc)("Preparation for old generation cycle was cancelled");
+ break;
+ }
+
+ // Coalescing threads completed and nothing was cancelled. it is safe to transition from this state.
+ old_generation->transition_to(ShenandoahOldGeneration::IDLE);
+ break;
+ }
default:
fatal("Unexpected state for old GC: %s", ShenandoahOldGeneration::state_name(old_generation->state()));
}
}
-bool ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause) {
+void ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause) {
assert(_heap->is_concurrent_old_mark_in_progress(), "Old mark should be in progress");
log_debug(gc)("Resuming old generation with " UINT32_FORMAT " marking tasks queued", generation->task_queues()->tasks());
@@ -465,61 +425,29 @@ bool ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(Shenandoah
// is allowed to cancel a GC.
ShenandoahOldGC gc(generation, _allow_old_preemption);
if (gc.collect(cause)) {
- _heap->notify_gc_progress();
- generation->record_success_concurrent(false);
- }
-
- if (_heap->cancelled_gc()) {
- // It's possible the gc cycle was cancelled after the last time the collection checked for cancellation. In which
- // case, the old gc cycle is still completed, and we have to deal with this cancellation. We set the degeneration
- // point to be outside the cycle because if this is an allocation failure, that is what must be done (there is no
- // degenerated old cycle). If the cancellation was due to a heuristic wanting to start a young cycle, then we are
- // not actually going to a degenerated cycle, so don't set the degeneration point here.
- if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) {
- check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle);
- } else if (cause == GCCause::_shenandoah_concurrent_gc) {
- _heap->shenandoah_policy()->record_interrupted_old();
+ if (alloc_stall_count() == 0) {
+ _heap->notify_gc_progress();
}
- return false;
+
+ // For old GC, record that it has completed the gc it started
+ generation->update_completed_gc_id(generation->started_gc_id());
+ generation->heuristics()->record_concurrent_completion();
+ _heap->shenandoah_policy()->record_success_old();
+
+ // Old marking has completed, it will have rebuilt the free set with any immediate garbage.
+ // Notify alloc waiters. We don't notify cycle waiters because no threads should be waiting
+ // for an old cycle and because cycle waiters would have requested a global cycle that would
+ // have cancelled the old mark anyway.
+ clear_allocation_failure_and_notify_waiters();
+ } else if (cause == GCCause::_shenandoah_concurrent_gc) {
+ _heap->shenandoah_policy()->record_interrupted_old();
}
- return true;
}
-// Normal cycle goes via all concurrent phases. If allocation failure (af) happens during
-// any of the concurrent phases, it first degrades to Degenerated GC and completes GC there.
-// If second allocation failure happens during Degenerated GC cycle (for example, when GC
-// tries to evac something and no memory is available), cycle degrades to Full GC.
-//
-// There are also a shortcut through the normal cycle: immediate garbage shortcut, when
-// heuristics says there are no regions to compact, and all the collection comes from immediately
-// reclaimable regions.
-//
-// ................................................................................................
-//
-// (immediate garbage shortcut) Concurrent GC
-// /-------------------------------------------\
-// | |
-// | |
-// | |
-// | v
-// [START] ----> Conc Mark ----o----> Conc Evac --o--> Conc Update-Refs ---o----> [END]
-// | | | ^
-// | (af) | (af) | (af) |
-// ..................|....................|.................|..............|.......................
-// | | | |
-// | | | | Degenerated GC
-// v v v |
-// STW Mark ----------> STW Evac ----> STW Update-Refs ----->o
-// | | | ^
-// | (af) | (af) | (af) |
-// ..................|....................|.................|..............|.......................
-// | | | |
-// | v | | Full GC
-// \------------------->o<----------------/ |
-// | |
-// v |
-// Full GC --------------------------/
-//
+// Normal cycle goes via all concurrent phases. There is also a shortcut through the normal
+// cycle: immediate garbage shortcut. When heuristics say there are no regions to compact,
+// and all the collection comes from immediately reclaimable regions, Shenandoah can skip the
+// evacuation phase.
void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGeneration* generation,
GCCause::Cause cause,
bool do_old_gc_bootstrap) {
@@ -531,17 +459,25 @@ void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGen
assert(!generation->is_old(), "Old GC takes a different control path");
- _do_old_gc_bootstrap = do_old_gc_bootstrap;
- ShenandoahConcurrentGC gc(generation, do_old_gc_bootstrap);
+ ShenandoahConcurrentGC gc(this, generation, do_old_gc_bootstrap);
_heap->increment_total_collections(false);
if (gc.collect(cause)) {
// Cycle is complete
- _heap->notify_gc_progress();
- generation->record_success_concurrent(gc.abbreviated());
- _do_old_gc_bootstrap = false;
+ if (alloc_stall_count() == 0) {
+ _heap->notify_gc_progress();
+ }
+
+ // In the generational mode, we don't use global heuristics to trigger global cycles, so
+ // direct heuristic signals to the young heuristics
+ generation->update_completed_gc_id(get_gc_id());
+ _heap->young_generation()->heuristics()->record_concurrent_completion();
+ _heap->shenandoah_policy()->record_success_concurrent(get_gc_id(), generation->is_young(), gc.abbreviated());
+
+ // Notify threads waiting for GC and/or memory when a young or global cycle completes
+ notify_gc_waiters();
+ clear_allocation_failure_and_notify_waiters();
} else {
assert(_heap->cancelled_gc(), "Must have been cancelled");
- check_cancellation_or_degen(gc.degen_point());
}
const char* msg;
@@ -576,67 +512,16 @@ void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGen
_heap->log_heap_status(msg);
}
-bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point) {
- // Only read the cancellation cause once. Other threads may change it.
- const GCCause::Cause cancelled_cause = _heap->cancelled_cause();
- if (cancelled_cause == GCCause::_no_gc) {
- return false;
- }
-
- if (cancelled_cause == GCCause::_shenandoah_stop_vm
- || cancelled_cause == GCCause::_shenandoah_concurrent_gc) {
- log_debug(gc, thread)("Cancellation detected, reason: %s", GCCause::to_string(cancelled_cause));
- return true;
- }
-
- if (ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause)) {
- assert(_degen_point == ShenandoahGC::_degenerated_unset,
- "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point));
- MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
- _requested_gc_cause = cancelled_cause;
- _degen_point = point;
- log_debug(gc, thread)("Cancellation detected:, reason: %s, degen point: %s",
- GCCause::to_string(cancelled_cause),
- ShenandoahGC::degen_point_to_string(_degen_point));
- return true;
- }
-
- fatal("Cancel GC either for alloc failure GC, or gracefully exiting, or to pause old generation marking");
-}
-
void ShenandoahGenerationalControlThread::service_stw_full_cycle(GCCause::Cause cause) {
_heap->increment_total_collections(true);
ShenandoahGCSession session(cause, _heap->global_generation());
ShenandoahFullGC gc;
gc.collect(cause);
- _degen_point = ShenandoahGC::_degenerated_unset;
- _do_old_gc_bootstrap = false;
-}
-
-void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const ShenandoahGCRequest& request) {
- assert(_degen_point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set");
- _heap->increment_total_collections(false);
-
- ShenandoahGCSession session(request.cause, request.generation, true,
- _degen_point == ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle);
- ShenandoahDegenGC gc(_degen_point, request.generation, _do_old_gc_bootstrap);
- gc.collect(request.cause);
- _degen_point = ShenandoahGC::_degenerated_unset;
- _do_old_gc_bootstrap = false;
-
- assert(_heap->young_generation()->task_queues()->is_empty(), "Unexpected young generation marking tasks");
- if (request.generation->is_global()) {
- assert(_heap->old_generation()->task_queues()->is_empty(), "Unexpected old generation marking tasks");
- assert(_heap->global_generation()->task_queues()->is_empty(), "Unexpected global generation marking tasks");
- }
-}
-void ShenandoahGenerationalControlThread::request_gc(GCCause::Cause cause) {
- if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) {
- notify_control_thread(cause);
- } else if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) {
- handle_requested_gc(cause);
- }
+ // Notify threads waiting for GC and/or memory that a full cycle has completed
+ _heap->global_generation()->update_completed_gc_id(get_gc_id());
+ notify_gc_waiters();
+ clear_allocation_failure_and_notify_waiters();
}
bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGeneration* generation) {
@@ -659,7 +544,6 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera
// Cancel the old GC and wait for the control thread to start servicing the new request.
log_info(gc, phases)("Preempting old generation mark to allow %s GC", generation->name());
while (gc_mode() == servicing_old) {
- _heap->cancel_gc(GCCause::_shenandoah_concurrent_gc);
notify_control_thread(ml, GCCause::_shenandoah_concurrent_gc, generation);
ml.wait();
}
@@ -687,40 +571,43 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera
return false;
}
-void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) {
+bool ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) {
MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
- notify_control_thread(ml, cause, generation);
+ return notify_control_thread(ml, cause, generation);
}
-void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation) {
- assert(_control_lock.is_locked(), "Request lock must be held here");
- if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) {
- // We have already observed a request to handle an allocation failure. We cannot allow
- // another request (System.gc or regulator) to subvert the degenerated cycle.
+bool ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause,
+ ShenandoahGeneration* generation) {
+ assert(_control_lock.owned_by_self(), "Request lock must be held here");
+ if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) {
log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause));
- } else {
- log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name());
- _requested_gc_cause = cause;
- _requested_generation = generation;
- ml.notify();
+ return false;
}
+
+ log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name());
+ request_gc_and_interrupt_old(cause, generation);
+ ml.notify_all();
+ return true;
}
-void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause) {
- MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag);
- notify_control_thread(ml, cause);
+void ShenandoahGenerationalControlThread::request_gc_and_interrupt_old(GCCause::Cause cause,
+ ShenandoahGeneration* generation) {
+ assert(_control_lock.owned_by_self(), "Request lock must be held here");
+ _requested_gc_cause = cause;
+ _requested_generation = generation;
+ if (gc_mode() == servicing_old && !generation->is_old()) {
+ _heap->cancel_gc(cause);
+ }
}
void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause) {
- assert(_control_lock.is_locked(), "Request lock must be held here");
- if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) {
- // We have already observed a request to handle an allocation failure. We cannot allow
- // another request (System.gc or regulator) to subvert the degenerated cycle.
+ assert(_control_lock.owned_by_self(), "Request lock must be held here");
+ if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) {
log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause));
} else {
log_debug(gc, thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause));
_requested_gc_cause = cause;
- ml.notify();
+ ml.notify_all();
}
}
@@ -728,46 +615,100 @@ bool ShenandoahGenerationalControlThread::preempt_old_marking(ShenandoahGenerati
return generation->is_young() && _allow_old_preemption.try_unset();
}
-void ShenandoahGenerationalControlThread::handle_requested_gc(GCCause::Cause cause) {
- // For normal requested GCs (System.gc) we want to block the caller. However,
- // for whitebox requested GC, we want to initiate the GC and return immediately.
- // The whitebox caller thread will arrange for itself to wait until the GC notifies
- // it that has reached the requested breakpoint (phase in the GC).
- if (cause == GCCause::_wb_breakpoint) {
- notify_control_thread(cause, ShenandoahHeap::heap()->global_generation());
- return;
+bool ShenandoahGenerationalControlThread::wait_for_gc_cycle(GCCause::Cause cause, ShenandoahGeneration* generation, uint timeout_seconds) {
+ if (generation->is_old()) {
+ return wait_for_old_gc_cycle(cause, static_cast(generation), timeout_seconds);
}
// Make sure we have at least one complete GC cycle before unblocking
- // from the explicit GC request.
+ // from the requested GC.
//
// This is especially important for weak references cleanup and/or native
- // resources (e.g. DirectByteBuffers) machinery: when explicit GC request
+ // resources (e.g. DirectByteBuffers) machinery: when the GC request
// comes very late in the already running cycle, it would miss lots of new
// opportunities for cleanup that were made available before the caller
// requested the GC.
-
MonitorLocker ml(&_gc_waiters_lock);
- size_t current_gc_id = get_gc_id();
- const size_t required_gc_id = current_gc_id + 1;
- while (current_gc_id < required_gc_id && !should_terminate()) {
- // Make requests to run a global cycle until at least one is completed
- notify_control_thread(cause, ShenandoahHeap::heap()->global_generation());
+ const size_t entry_gc_id = get_gc_id();
+ while (generation->completed_gc_id() <= entry_gc_id && !should_terminate()) {
+ // Make requests to run cycles until at least one is completed
+ notify_control_thread(cause, generation);
ml.wait();
- current_gc_id = get_gc_id();
}
+ return generation->completed_gc_id() > entry_gc_id;
}
-void ShenandoahGenerationalControlThread::notify_gc_waiters() {
+bool ShenandoahGenerationalControlThread::wait_for_old_gc_cycle(GCCause::Cause cause, ShenandoahOldGeneration* generation, uint timeout_seconds) {
+ const double start = os::elapsedTime();
+ const double deadline = start + timeout_seconds;
MonitorLocker ml(&_gc_waiters_lock);
- ml.notify_all();
+ const size_t entry_old_gc_id = generation->started_gc_id();
+ const size_t entry_gc_id = get_gc_id();
+ generation->heuristics()->log_trigger("WhiteBox Old Mark");
+ while (generation->completed_gc_id() <= entry_old_gc_id && !should_terminate()) {
+ // Make requests to run cycles until at least one old mark cycle is completed after this method was called
+ if (os::elapsedTime() > deadline) {
+ log_info(gc, thread)("Giving up on running an old gc after: %.3f seconds", os::elapsedTime() - start);
+ log_info(gc, thread)("Entry gc id: %zu, current id: %zu, entry old gc: %zu, last completed: %zu",
+ entry_gc_id, get_gc_id(), entry_old_gc_id, generation->completed_gc_id());
+ return false;
+ }
+
+ {
+ // Take control lock for gc mode. Old gc state is changed only when the gc mode is not none.
+ MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag);
+
+ if (gc_mode() == none) {
+ switch (generation->state()) {
+ case ShenandoahOldGeneration::IDLE:
+ // Note: we must only submit a request if the old generation is IDLE
+ notify_control_thread(controller, cause, generation);
+ break;
+ case ShenandoahOldGeneration::MARKING:
+ case ShenandoahOldGeneration::FILLING:
+ // do nothing, let the existing cycle finish
+ break;
+ case ShenandoahOldGeneration::EVACUATING:
+ case ShenandoahOldGeneration::EVACUATING_AFTER_GLOBAL:
+ // Expedite the mixed collections to finish the old cycle
+ generation->heuristics()->log_trigger("WhiteBox Old Expedite Mixed");
+ notify_control_thread(controller, cause, _heap->young_generation());
+ break;
+ default:
+ ShouldNotReachHere();
+ }
+ }
+ }
+
+ ml.wait(100);
+ }
+
+ // check actual condition in case vm was stopped and thread was terminated
+ return generation->completed_gc_id() > entry_old_gc_id;
+}
+
+void ShenandoahGenerationalControlThread::handle_requested_gc(GCCause::Cause cause) {
+ // For normal requested GCs (System.gc) we want to block the caller. However,
+ // for whitebox requested GC, we want to initiate the GC and return immediately.
+ // The whitebox caller thread will arrange for itself to wait until the GC notifies
+ // it that has reached the requested breakpoint (phase in the GC).
+ if (cause == GCCause::_wb_breakpoint) {
+ notify_control_thread(cause, ShenandoahHeap::heap()->global_generation());
+ return;
+ }
+
+ // We can only run concurrent young GCs. If the request is to run a young gc, but
+ // explicit invokes concurrent is disabled, you get a global/full GC.
+ ShenandoahGeneration* generation = cause == GCCause::_wb_young_gc && ExplicitGCInvokesConcurrent
+ ? ShenandoahHeap::heap()->young_generation()
+ : ShenandoahHeap::heap()->global_generation();
+ wait_for_gc_cycle(cause, generation);
}
const char* ShenandoahGenerationalControlThread::gc_mode_name(GCMode mode) {
switch (mode) {
case none: return "idle";
case concurrent_normal: return "normal";
- case stw_degenerated: return "degenerated";
case stw_full: return "full";
case servicing_old: return "old";
case bootstrapping_old: return "bootstrap";
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
index 358299ea4693..51cefa728d5f 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp
@@ -28,7 +28,6 @@
#include "gc/shared/gcCause.hpp"
#include "gc/shenandoah/shenandoahController.hpp"
-#include "gc/shenandoah/shenandoahGC.hpp"
#include "gc/shenandoah/shenandoahSharedVariables.hpp"
#include "runtime/mutexLocker.hpp"
@@ -42,7 +41,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController {
typedef enum {
none,
concurrent_normal,
- stw_degenerated,
stw_full,
bootstrapping_old,
servicing_old,
@@ -57,14 +55,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController {
};
private:
- // This lock is used to coordinate setting the _requested_gc_cause, _requested generation
- // and _gc_mode. It is important that these be changed together and have a consistent view.
- Monitor _control_lock;
-
- // Represents a normal (non cancellation) gc request. This can be set by mutators (System.gc,
- // whitebox gc, etc.) or by the regulator thread when the heuristics want to start a cycle.
- GCCause::Cause _requested_gc_cause;
-
// This is the generation the request should operate on.
ShenandoahGeneration* _requested_generation;
@@ -73,10 +63,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController {
// before making any state changes (double-checked locking idiom).
volatile GCMode _gc_mode;
- // Only the control thread knows the correct degeneration point. This is used to have the
- // control thread resume a STW cycle from the point where the concurrent cycle was cancelled.
- ShenandoahGC::ShenandoahDegenPoint _degen_point;
-
// A reference to the heap
ShenandoahGenerationalHeap* _heap;
@@ -84,46 +70,47 @@ class ShenandoahGenerationalControlThread: public ShenandoahController {
// preparing for mark).
ShenandoahSharedFlag _allow_old_preemption;
- // True while the current cycle is the bootstrap of an old GC.
- bool _do_old_gc_bootstrap;
-
public:
ShenandoahGenerationalControlThread();
- void run_service() override;
- void stop_service() override;
-
- void request_gc(GCCause::Cause cause) override;
-
// Return true if the request to start a concurrent GC for the given generation succeeded.
bool request_concurrent_gc(ShenandoahGeneration* generation);
+ // Visible for white box API to start an old cycle
+ bool wait_for_gc_cycle(GCCause::Cause cause, ShenandoahGeneration* generation, uint timeout_seconds = 10);
+
// Returns the current state of the control thread
GCMode gc_mode() const {
return _gc_mode;
}
+
+protected:
+ void run_service() override;
+ void stop_service() override;
+
+ void notify_alloc_stall(GCCause::Cause cause) override;
+ bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) override;
+
+ // Will also schedule an immediate full gc if old evacuations failed
+ void clear_allocation_failure_and_notify_waiters() override;
+
+ // Handle whitebox requests to run old or young cycles
+ void handle_requested_gc(GCCause::Cause cause) override;
+
private:
- // Returns true if the cycle has been cancelled or degenerated.
- bool check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point);
// Executes one GC cycle
void run_gc_cycle(const ShenandoahGCRequest& request);
// Returns true if the old generation marking completed (i.e., final mark executed for old generation).
- bool resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause);
+ void resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause);
// Various service methods handle different gc cycle types
void service_concurrent_cycle(ShenandoahGeneration* generation, GCCause::Cause cause, bool reset_old_bitmap_specially);
void service_stw_full_cycle(GCCause::Cause cause);
- void service_stw_degenerated_cycle(const ShenandoahGCRequest& request);
void service_concurrent_normal_cycle(const ShenandoahGCRequest& request);
void service_concurrent_old_cycle(const ShenandoahGCRequest& request);
- void notify_gc_waiters();
-
- // Blocks until at least one global GC cycle is complete.
- void handle_requested_gc(GCCause::Cause cause);
-
// Returns true if the old generation marking was interrupted to allow a young cycle.
bool preempt_old_marking(ShenandoahGeneration* generation);
@@ -137,21 +124,25 @@ class ShenandoahGenerationalControlThread: public ShenandoahController {
// These notify the control thread after updating _requested_gc_cause and (optionally) _requested_generation.
// Updating the requested generation is not necessary for allocation failures nor when stopping the thread.
- void notify_control_thread(GCCause::Cause cause);
void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause);
- void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation);
- void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation);
+ bool notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation);
+ void request_gc_and_interrupt_old(GCCause::Cause cause, ShenandoahGeneration* generation);
// Take the _control_lock and check for a request to run a gc cycle. If a request is found,
// the `prepare` methods are used to configure the heap and update heuristics accordingly.
void check_for_request(ShenandoahGCRequest& request);
- GCMode prepare_for_allocation_failure_gc(ShenandoahGCRequest &request);
- GCMode prepare_for_explicit_gc(ShenandoahGCRequest &request) const;
- GCMode prepare_for_concurrent_gc(const ShenandoahGCRequest &request) const;
+ GCMode prepare_for_concurrent_gc(ShenandoahGCRequest &request) const;
+ GCMode prepare_for_full_gc(ShenandoahGCRequest& request) const;
+ void prepare_for_explicit_gc(ShenandoahGCRequest &request) const;
// Print table for young region ages if log is enabled
void maybe_print_young_region_ages() const;
+
+ // Specialized function that waits for one complete old gc cycle. Note that this may invoke
+ // young cycles in order to complete mixed evacuations of an existent old gc before starting
+ // another.
+ bool wait_for_old_gc_cycle(GCCause::Cause cause, ShenandoahOldGeneration* generation, uint timeout_seconds);
};
#endif // SHARE_GC_SHENANDOAH_SHENANDOAHGENERATIONALCONTROLTHREAD_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp
index 2d58fbb63ae5..3bf7d8f1fe23 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp
@@ -1,6 +1,6 @@
/*
* 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
@@ -24,6 +24,7 @@
*/
#include "gc/shenandoah/shenandoahAsserts.hpp"
+#include "gc/shenandoah/shenandoahCollectorPolicy.hpp"
#include "gc/shenandoah/shenandoahForwarding.inline.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
#include "gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp"
@@ -50,28 +51,22 @@ class ShenandoahConcurrentEvacuator : public ObjectClosure {
ShenandoahGenerationalEvacuationTask::ShenandoahGenerationalEvacuationTask(ShenandoahGenerationalHeap* heap,
ShenandoahGeneration* generation,
ShenandoahRegionIterator* iterator,
- bool concurrent, bool only_promote_regions) :
+ bool only_promote_regions) :
WorkerTask("Shenandoah Evacuation"),
_heap(heap),
_generation(generation),
_regions(iterator),
- _concurrent(concurrent),
+ _collection_set(_heap->collection_set()),
_only_promote_regions(only_promote_regions)
{
shenandoah_assert_generational();
}
void ShenandoahGenerationalEvacuationTask::work(uint worker_id) {
- if (_concurrent) {
- ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true);
- ShenandoahConcurrentWorkerSession worker_session(worker_id);
- SuspendibleThreadSetJoiner stsj;
- do_work();
- } else {
- ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_evac, ShenandoahPhaseTimings::Work, worker_id, true);
- ShenandoahParallelWorkerSession worker_session(worker_id);
- do_work();
- }
+ ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true);
+ ShenandoahConcurrentWorkerSession worker_session(worker_id);
+ SuspendibleThreadSetJoiner stsj;
+ do_work();
}
void ShenandoahGenerationalEvacuationTask::do_work() {
@@ -104,7 +99,7 @@ void ShenandoahGenerationalEvacuationTask::promote_regions() {
promoter.maybe_promote_region(r);
- if (_heap->check_cancelled_gc_and_yield(_concurrent)) {
+ if (_heap->check_cancelled_gc_and_yield()) {
break;
}
}
@@ -113,25 +108,28 @@ void ShenandoahGenerationalEvacuationTask::promote_regions() {
void ShenandoahGenerationalEvacuationTask::evacuate_and_promote_regions() {
LogTarget(Debug, gc) lt;
ShenandoahConcurrentEvacuator cl(_heap);
- ShenandoahInPlacePromoter promoter(_heap);
ShenandoahHeapRegion* r;
-
- while ((r = _regions->next()) != nullptr) {
+ while ((r = _collection_set->claim_next()) != nullptr) {
if (lt.is_enabled()) {
LogStream ls(lt);
log_region(r, &ls);
}
- if (r->is_cset()) {
- assert(r->has_live(), "Region %zu should have been reclaimed early", r->index());
- _heap->marked_object_iterate(r, &cl);
- } else {
- promoter.maybe_promote_region(r);
- }
+ assert(r->has_live(), "Region %zu should have been reclaimed early", r->index());
+ _heap->marked_object_iterate(r, &cl);
- if (_heap->check_cancelled_gc_and_yield(_concurrent)) {
+ if (ShenandoahCollectorPolicy::should_abandon_evacuations(r)) {
+ // No more evacuations for this thread, but it may yet complete in-place promotions
break;
}
+
+ if (_heap->check_cancelled_gc_and_yield()) {
+ // GC is cancelled (vm is stopping), no further work
+ assert(_heap->is_stopping(), "Can only stop evacuation for shutdown");
+ return;
+ }
}
+
+ promote_regions();
}
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp
index 1ff58b42e8cb..50a67869d6f2 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp
@@ -31,6 +31,7 @@ class ShenandoahGeneration;
class ShenandoahGenerationalHeap;
class ShenandoahHeapRegion;
class ShenandoahRegionIterator;
+class ShenandoahCollectionSet;
// Unlike ShenandoahEvacuationTask, this iterates over all regions rather than just the collection set.
// This is needed in order to promote humongous start regions if age() >= tenure threshold.
@@ -39,22 +40,19 @@ class ShenandoahGenerationalEvacuationTask : public WorkerTask {
ShenandoahGenerationalHeap* const _heap;
ShenandoahGeneration* const _generation;
ShenandoahRegionIterator* _regions;
- bool _concurrent;
+ ShenandoahCollectionSet* _collection_set;
bool _only_promote_regions;
public:
ShenandoahGenerationalEvacuationTask(ShenandoahGenerationalHeap* sh,
ShenandoahGeneration* generation,
ShenandoahRegionIterator* iterator,
- bool concurrent, bool only_promote_regions);
+ bool only_promote_regions);
void work(uint worker_id) override;
private:
void do_work();
void promote_regions();
void evacuate_and_promote_regions();
- void maybe_promote_region(ShenandoahHeapRegion* region);
- void promote_in_place(ShenandoahHeapRegion* region);
- void promote_humongous(ShenandoahHeapRegion* region);
};
#endif //SHARE_GC_SHENANDOAH_SHENANDOAHGENERATIONALEVACUATIONTASK_HPP
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
index 46230c9654f4..5c3ebc47a7fc 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp
@@ -26,6 +26,7 @@
#include "gc/shared/fullGCForwarding.inline.hpp"
#include "gc/shared/preservedMarks.inline.hpp"
#include "gc/shenandoah/shenandoahAffiliation.hpp"
+#include "gc/shenandoah/shenandoahAgeCensus.hpp"
#include "gc/shenandoah/shenandoahForwarding.inline.hpp"
#include "gc/shenandoah/shenandoahGeneration.hpp"
#include "gc/shenandoah/shenandoahGenerationalFullGC.hpp"
@@ -55,7 +56,7 @@ void assert_usage_not_more_than_regions_used(ShenandoahGeneration* generation) {
void ShenandoahGenerationalFullGC::prepare() {
auto heap = ShenandoahGenerationalHeap::heap();
- // Since we may arrive here from degenerated GC failure of either young or old, establish generation as GLOBAL.
+ // Full GC always operates on the GLOBAL generation.
heap->set_active_generation(heap->global_generation());
// Full GC supersedes any marking or coalescing in old generation.
@@ -69,6 +70,7 @@ void ShenandoahGenerationalFullGC::handle_completion(ShenandoahHeap* heap) {
ShenandoahOldGeneration* old = gen_heap->old_generation();
young->heuristics()->record_cycle_end();
old->heuristics()->record_cycle_end();
+ old->clear_failed_evacuation();
gen_heap->mmu_tracker()->record_full(GCId::current());
gen_heap->log_heap_status("At end of Full GC");
@@ -155,7 +157,7 @@ void ShenandoahGenerationalFullGC::maybe_coalesce_and_fill_region(ShenandoahHeap
void ShenandoahGenerationalFullGC::compute_balances() {
auto heap = ShenandoahGenerationalHeap::heap();
- // In case this Full GC resulted from degeneration, clear the tally on anticipated promotion.
+ // Reset the tally on anticipated promotion.
heap->old_generation()->set_promotion_potential(0);
// Invoke this in case we are able to transfer memory from OLD to YOUNG
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
index c640fc010460..a54eed890b50 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp
@@ -78,8 +78,7 @@ size_t ShenandoahGenerationalHeap::unsafe_max_tlab_alloc() const {
return MIN2(ShenandoahHeapRegion::max_tlab_size_bytes(), young_generation()->available());
}
-ShenandoahGenerationalHeap::ShenandoahGenerationalHeap(ShenandoahCollectorPolicy* policy) :
- ShenandoahHeap(policy),
+ShenandoahGenerationalHeap::ShenandoahGenerationalHeap() :
_age_census(nullptr),
_min_plab_size(calculate_min_plab()),
_max_plab_size(calculate_max_plab()),
@@ -120,6 +119,11 @@ void ShenandoahGenerationalHeap::initialize_heuristics() {
_old_generation->initialize_heuristics(mode());
}
+bool ShenandoahGenerationalHeap::wait_for_old_collection(uint timeout_seconds) {
+ const auto gen_ctrl_thread = static_cast(_control_thread);
+ return gen_ctrl_thread->wait_for_gc_cycle(GCCause::_shenandoah_concurrent_gc, old_generation(), timeout_seconds);
+}
+
void ShenandoahGenerationalHeap::initialize_serviceability() {
assert(mode()->is_generational(), "Only for the generational mode");
_young_gen_memory_pool = new ShenandoahYoungGenMemoryPool(this);
@@ -178,15 +182,15 @@ bool ShenandoahGenerationalHeap::requires_barriers(stackChunkOop obj) const {
return false;
}
-void ShenandoahGenerationalHeap::evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) {
+void ShenandoahGenerationalHeap::evacuate_collection_set(ShenandoahGeneration* generation) {
ShenandoahRegionIterator regions;
- ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, concurrent, false /* only promote regions */);
+ ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, false /* only promote regions */);
workers()->run_task(&task);
}
-void ShenandoahGenerationalHeap::promote_regions_in_place(ShenandoahGeneration* generation, bool concurrent) {
+void ShenandoahGenerationalHeap::promote_regions_in_place(ShenandoahGeneration* generation) {
ShenandoahRegionIterator regions;
- ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, concurrent, true /* only promote regions */);
+ ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, true /* only promote regions */);
workers()->run_task(&task);
}
@@ -195,6 +199,11 @@ oop ShenandoahGenerationalHeap::evacuate_object(oop p, Thread* thread) {
ShenandoahHeapRegion* from_region = heap_region_containing(p);
assert(!from_region->is_humongous(), "never evacuate humongous objects");
+ if (has_self_forwarded_objects() && from_region->has_self_forwards()) {
+ // We don't want GC threads to evacuate objects in regions that have evacuation failures. We'd
+ // rather have them concentrate on regions that still have a chance of being completely evacuated.
+ return ShenandoahForwarding::try_forward_to(p, p);
+ }
// Try to keep the object in the same generation
const ShenandoahAffiliation target_gen = from_region->affiliation();
@@ -206,26 +215,27 @@ oop ShenandoahGenerationalHeap::evacuate_object(oop p, Thread* thread) {
}
if (age_census()->is_tenurable(from_region->age() + ShenandoahForwarding::age(p))) {
// If the object is tenurable, try to promote it
- oop result = try_evacuate_object(p, thread, from_region->age());
+ oop result = try_evacuate_object(p, thread, from_region);
// If we failed to promote this aged object, we'll fall through to code below and evacuate to young-gen.
if (result != nullptr) {
return result;
}
}
- return try_evacuate_object(p, thread, from_region->age());
+ return try_evacuate_object(p, thread, from_region);
}
assert(target_gen == OLD_GENERATION, "Expected evacuation to old");
- return try_evacuate_object(p, thread, from_region->age());
+ return try_evacuate_object(p, thread, from_region);
}
// try_evacuate_object registers the object and dirties the associated remembered set information when evacuating
// to OLD_GENERATION.
template
-oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age) {
+oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region) {
bool alloc_from_lab = true;
bool has_plab = false;
+ uint from_region_age = from_region->age();
HeapWord* copy = nullptr;
size_t size = ShenandoahForwarding::size(p);
constexpr bool is_promotion = (TO_GENERATION == OLD_GENERATION) && (FROM_GENERATION == YOUNG_GENERATION);
@@ -299,7 +309,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint
if (copy == nullptr) {
if (TO_GENERATION == OLD_GENERATION) {
if (FROM_GENERATION == YOUNG_GENERATION) {
- // Signal that promotion failed. Will evacuate this old object somewhere in young gen.
+ // Signal that promotion failed. We will retry to evacuate this old object somewhere in young gen.
old_generation()->handle_failed_promotion(thread, size);
return nullptr;
} else {
@@ -309,16 +319,19 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint
}
}
- control_thread()->handle_alloc_failure_evac(size);
-
// Install the self-forwarded bit so other evacuators/LRBs see the
// object as "already handled, do not try to evacuate".
oop fwd = ShenandoahForwarding::try_forward_to(p, p);
if (fwd == p) {
- // Now self-forwarded. Flag the from-region so the degen/full
- // GC entry drain knows to scan it for self_fwd bits to clear.
- heap_region_containing(p)->set_has_self_forwards();
+ // Now self-forwarded. Flag the from-region so that other threads
+ // don't waste time evacuating this region.
+ set_has_self_forwarded_objects();
+ from_region->set_has_self_forwards();
}
+
+ // It does not matter if this thread did the self forwarding or not.
+ // This thread failed to allocate an evacuation copy.
+ ShenandoahThreadLocalData::set_has_evac_failures(thread, true);
return fwd;
}
@@ -391,9 +404,9 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint
return result;
}
-template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age);
-template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age);
-template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age);
+template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region);
+template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region);
+template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region);
// Call this function at the end of a GC cycle in order to establish proper sizes of young and old reserves,
// setting the old-generation balance so that GC can perform the anticipated evacuations.
@@ -670,17 +683,12 @@ void ShenandoahGenerationalHeap::coalesce_and_fill_old_regions(bool concurrent)
}
};
- ShenandoahPhaseTimings::Phase phase = concurrent ?
- ShenandoahPhaseTimings::conc_coalesce_and_fill :
- ShenandoahPhaseTimings::degen_gc_coalesce_and_fill;
-
// This is not cancellable
- ShenandoahGlobalCoalesceAndFill coalesce(phase);
+ ShenandoahGlobalCoalesceAndFill coalesce(ShenandoahPhaseTimings::conc_coalesce_and_fill);
workers()->run_task(&coalesce);
old_generation()->set_parsable(true);
}
-template
class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
private:
// For update refs, _generation will be young or global. Mixed collections use the young generation.
@@ -704,16 +712,10 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
}
void work(uint worker_id) override {
- if (CONCURRENT) {
- ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true);
- ShenandoahConcurrentWorkerSession worker_session(worker_id);
- SuspendibleThreadSetJoiner stsj;
- do_work(worker_id);
- } else {
- ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true);
- ShenandoahParallelWorkerSession worker_session(worker_id);
- do_work(worker_id);
- }
+ ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true);
+ ShenandoahConcurrentWorkerSession worker_session(worker_id);
+ SuspendibleThreadSetJoiner stsj;
+ do_work>(worker_id);
}
private:
@@ -721,7 +723,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
void do_work(uint worker_id) {
T cl;
- if (CONCURRENT && (worker_id == 0)) {
+ if (worker_id == 0) {
// We ask the first worker to replenish the Mutator free set by moving regions previously reserved to hold the
// results of evacuation. These reserves are no longer necessary because evacuation has completed.
size_t cset_regions = _heap->collection_set()->count();
@@ -732,7 +734,6 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
// next GC cycle.
_heap->free_set()->move_regions_from_collector_to_mutator(cset_regions);
}
- // If !CONCURRENT, there's no value in expanding Mutator free set
ShenandoahHeapRegion* r = _regions->next();
// We update references for global, mixed, and young collections.
@@ -744,7 +745,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
assert(update_watermark >= r->bottom(), "sanity");
log_debug(gc)("Update refs worker " UINT32_FORMAT ", looking at region %zu", worker_id, r->index());
- if (r->is_active() && !r->is_cset()) {
+ if (r->is_update_required()) {
if (r->is_young()) {
_heap->marked_object_oop_iterate(r, &cl, update_watermark);
} else if (r->is_old()) {
@@ -770,7 +771,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
}
}
- if (_heap->check_cancelled_gc_and_yield(CONCURRENT)) {
+ if (_heap->check_cancelled_gc_and_yield()) {
return;
}
@@ -791,13 +792,15 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
template
void update_references_in_remembered_set(uint worker_id, T &cl, const ShenandoahMarkingContext* ctx, bool is_mixed) {
- struct ShenandoahRegionChunk assignment;
+ ShenandoahRegionChunk assignment;
ShenandoahScanRemembered* scanner = _heap->old_generation()->card_scan();
- while (!_heap->check_cancelled_gc_and_yield(CONCURRENT) && _work_chunks->next(&assignment)) {
+ while (!_heap->check_cancelled_gc_and_yield() && _work_chunks->next(&assignment)) {
// Keep grabbing next work chunk to process until finished, or asked to yield
ShenandoahHeapRegion* r = assignment._r;
- if (r->is_active() && !r->is_cset() && r->is_old()) {
+ if (r->is_update_required() && r->is_old()) {
+ // allocations into old (i.e., promotions or evacuations) do _not_ update references
+ // when they copy, so we move the UWM up for each such allocation.
HeapWord* start_of_range = r->bottom() + assignment._chunk_offset;
HeapWord* end_of_range = r->get_update_watermark();
if (end_of_range > start_of_range + assignment._chunk_size) {
@@ -831,8 +834,8 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
template
void update_references_in_old_region(T &cl, const ShenandoahMarkingContext* ctx, ShenandoahScanRemembered* scanner,
- const ShenandoahHeapRegion* r, HeapWord* start_of_range,
- HeapWord* end_of_range) const {
+ const ShenandoahHeapRegion* r, HeapWord* start_of_range,
+ HeapWord* end_of_range) const {
// In case last object in my range spans boundary of my chunk, I may need to scan all the way to top()
ShenandoahObjectToOopBoundedClosure objs(&cl, start_of_range, r->top());
@@ -848,7 +851,10 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask {
while (p < end_of_range) {
// p is known to point to the beginning of marked object obj
oop obj = cast_to_oop(p);
- objs.do_object(obj);
+ if (!ShenandoahForwarding::is_real_forwarded(obj)) {
+ // Update references in the object if it was not evacuated
+ objs.do_object(obj);
+ }
HeapWord* prev_p = p;
p += ShenandoahForwarding::size(obj);
if (p < tams) {
@@ -891,16 +897,12 @@ 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");
+void ShenandoahGenerationalHeap::update_heap_references(ShenandoahGeneration* generation) {
+ assert(!is_full_gc_in_progress(), "Only for concurrent GC");
ShenandoahRegionChunkIterator work_list(this);
- if (concurrent) {
- ShenandoahGenerationalUpdateHeapRefsTask task(generation, &_update_refs_iterator, &work_list);
- workers()->run_task(&task);
- } else {
- ShenandoahGenerationalUpdateHeapRefsTask task(generation, &_update_refs_iterator, &work_list);
- workers()->run_task(&task);
- }
+ ShenandoahRegionIterator update_refs_iterator(this);
+ ShenandoahGenerationalUpdateHeapRefsTask task(generation, &update_refs_iterator, &work_list);
+ workers()->run_task(&task);
if (ShenandoahEnableCardStats) {
// Only do this if we are collecting card stats
@@ -979,16 +981,6 @@ void ShenandoahGenerationalHeap::final_update_refs_update_region_states() {
parallel_heap_region_iterate(&cl);
}
-void ShenandoahGenerationalHeap::complete_degenerated_cycle() {
- shenandoah_assert_heaplocked_or_safepoint();
- if (!old_generation()->is_parsable()) {
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_coalesce_and_fill);
- coalesce_and_fill_old_regions(false);
- }
-
- old_generation()->maybe_log_promotion_failure_stats(false);
-}
-
void ShenandoahGenerationalHeap::complete_concurrent_cycle() {
if (!old_generation()->is_parsable()) {
// Class unloading may render the card offsets unusable, so we must rebuild them before
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
index 6080b465dc13..b7932d32223a 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp
@@ -38,12 +38,14 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap {
void stop() override;
public:
- explicit ShenandoahGenerationalHeap(ShenandoahCollectorPolicy* policy);
+ ShenandoahGenerationalHeap();
void post_initialize() override;
void initialize_generations() override;
void initialize_heuristics() override;
void post_initialize_heuristics() override;
+ bool wait_for_old_collection(uint timeout_seconds);
+
static ShenandoahGenerationalHeap* heap() {
assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap");
CollectedHeap* heap = Universe::heap();
@@ -81,12 +83,12 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap {
oop evacuate_object(oop p, Thread* thread) override;
template
- oop try_evacuate_object(oop p, Thread* thread, uint from_region_age);
+ oop try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region);
// In the generational mode, we will use these two functions for young, mixed, and global collections.
// For young and mixed, the generation argument will be the young generation, otherwise it will be the global generation.
- void evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) override;
- void promote_regions_in_place(ShenandoahGeneration* generation, bool concurrent);
+ void evacuate_collection_set(ShenandoahGeneration* generation) override;
+ void promote_regions_in_place(ShenandoahGeneration* generation);
size_t plab_min_size() const { return _min_plab_size; }
size_t plab_max_size() const { return _max_plab_size; }
@@ -95,7 +97,7 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap {
//
// In the generational mode, we will use this function for young, mixed, and global collections.
// For young and mixed, the generation argument will be the young generation, otherwise it will be the global generation.
- void update_heap_references(ShenandoahGeneration* generation, bool concurrent) override;
+ void update_heap_references(ShenandoahGeneration* generation) override;
void final_update_refs_update_region_states() override;
private:
@@ -124,7 +126,6 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap {
void compute_old_generation_balance(size_t old_xfer_limit, size_t old_trashed_regions, size_t young_trashed_regions);
// Balances generations, coalesces and fills old regions if necessary
- void complete_degenerated_cycle();
void complete_concurrent_cycle();
private:
void initialize_controller() override;
diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
index b03610066334..d8fef7e37acd 100644
--- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
+++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
@@ -26,7 +26,7 @@
#include "cds/aotMappedHeapWriter.hpp"
-#include "classfile/systemDictionary.hpp"
+#include "gc/shared/allocTracer.hpp"
#include "gc/shared/classUnloadingContext.hpp"
#include "gc/shared/fullGCForwarding.hpp"
#include "gc/shared/gc_globals.hpp"
@@ -37,6 +37,7 @@
#include "gc/shared/memAllocator.hpp"
#include "gc/shared/plab.hpp"
#include "gc/shared/tlab_globals.hpp"
+#include "gc/shared/workerUtils.hpp"
#include "gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp"
#include "gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp"
#include "gc/shenandoah/mode/shenandoahGenerationalMode.hpp"
@@ -271,11 +272,6 @@ jint ShenandoahHeap::initialize() {
_workers->initialize_workers();
}
- if (ParallelGCThreads > 1) {
- _safepoint_workers = new ShenandoahWorkerThreads("Safepoint Cleanup Thread", ParallelGCThreads);
- _safepoint_workers->initialize_workers();
- }
-
//
// Reserve and commit memory for bitmap(s)
//
@@ -558,7 +554,7 @@ void ShenandoahHeap::initialize_heuristics() {
#pragma warning( disable:4355 ) // 'this' : used in base member initializer list
#endif
-ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) :
+ShenandoahHeap::ShenandoahHeap() :
CollectedHeap(),
_active_generation(nullptr),
_initial_size(0),
@@ -566,7 +562,6 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) :
_alloc_rate_decay(&_alloc_rate),
_max_workers(MAX3(ConcGCThreads, ParallelGCThreads, 1U)),
_workers(nullptr),
- _safepoint_workers(nullptr),
_heap_region_special(false),
_num_regions(0),
_regions(nullptr),
@@ -575,13 +570,11 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) :
_gc_state_changed(false),
_gc_no_progress_count(0),
_cancel_requested_time(0),
- _update_refs_iterator(this),
_global_generation(nullptr),
_control_thread(nullptr),
_uncommit_thread(nullptr),
_young_generation(nullptr),
_old_generation(nullptr),
- _shenandoah_policy(policy),
_gc_mode(nullptr),
_free_set(nullptr),
_allocator(nullptr),
@@ -606,7 +599,8 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) :
{
// Initialize GC mode early, many subsequent initialization procedures depend on it
initialize_mode();
- _cancelled_gc.set(GCCause::_no_gc);
+ _cancelled_gc.unset();
+ _shenandoah_policy = new ShenandoahCollectorPolicy(mode());
}
#ifdef _MSC_VER
@@ -650,7 +644,6 @@ void ShenandoahHeap::print_heap_on(outputStream* st) const {
}
if (is_evacuation_in_progress()) st->print("evacuating, ");
if (is_update_refs_in_progress()) st->print("updating refs, ");
- if (is_degenerated_gc_in_progress()) st->print("degenerated gc, ");
if (is_full_gc_in_progress()) st->print("full gc, ");
if (is_full_gc_move_in_progress()) st->print("full gc move, ");
if (is_concurrent_weak_root_in_progress()) st->print("concurrent weak roots, ");
@@ -725,13 +718,6 @@ void ShenandoahHeap::post_initialize() {
// Now, we will let WorkerThreads to initialize gclab when new worker is created.
_workers->set_initialize_gclab();
- // Note that the safepoint workers may require gclabs if the threads are used to create a heap dump
- // during a concurrent evacuation phase.
- if (_safepoint_workers != nullptr) {
- _safepoint_workers->threads_do(&init_gclabs);
- _safepoint_workers->set_initialize_gclab();
- }
-
JFR_ONLY(ShenandoahJFRSupport::register_jfr_type_serializers();)
}
@@ -960,11 +946,7 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) {
// Shenandoah will grind along for quite a while allocating one
// object at a time using shared (non-tlab) allocations. This check
// is testing that the GC overhead limit has not been exceeded.
- // This will notify the collector to start a cycle, but will raise
- // an OOME to the mutator if the last Full GCs have not made progress.
- // gc_no_progress_count is incremented following each degen or full GC that fails to achieve is_good_progress().
- if (result == nullptr && !req.is_lab_alloc() && get_gc_no_progress_count() > ShenandoahNoProgressThreshold) {
- control_thread()->handle_alloc_failure(req, false);
+ if (result == nullptr && !req.is_lab_alloc() && get_gc_no_progress_count() >= ShenandoahNoProgressThreshold) {
req.set_actual_size(0);
return nullptr;
}
@@ -972,26 +954,41 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) {
if (result == nullptr) {
// Block until control thread reacted, then retry allocation.
//
- // It might happen that one of the threads requesting allocation would unblock
- // way later after GC happened, only to fail the second allocation, because
- // other threads have already depleted the free storage. In this case, a better
- // strategy is to try again, until at least one full GC has completed.
- //
- // Stop retrying and return nullptr to cause OOMError exception if our allocation failed even after:
- // a) We experienced a GC that had good progress, or
- // b) We experienced at least one Full GC (whether or not it had good progress)
-
- const size_t original_count = shenandoah_policy()->full_gc_count();
- while (result == nullptr && should_retry_allocation(original_count)) {
- control_thread()->handle_alloc_failure(req, true);
+ // When a thread cannot allocate, it will signal the control thread to run a concurrent cycle. It
+ // will then wait for the control thread to signal in turn that the cycle has completed. The thread
+ // then retries the allocation. It will do this ShenandoahFullGCThreshold times. If no full gc has
+ // yet run, it will request one and retry the allocation one more time. If another thread has caused
+ // a full GC to run, other threads will not request another full GC.
+
+ const size_t req_byte = req.size() * HeapWordSize;
+ AllocTracer::send_allocation_requiring_gc_event(req_byte, checked_cast(control_thread()->get_gc_id()));
+ ShenandoahAllocationStallLogger stall_logger;
+
+ const size_t original_full_count = shenandoah_policy()->full_gc_count();
+ const size_t original_count = shenandoah_policy()->reclaiming_gc_count();
+ while (result == nullptr && !control_thread()->should_terminate()) {
+ control_thread()->handle_alloc_failure(req);
result = allocate_memory_work(req, in_new_region);
+ if (result == nullptr) {
+ const size_t current_count = shenandoah_policy()->reclaiming_gc_count();
+ if (current_count - original_count >= ShenandoahFullGCThreshold) {
+ if (original_full_count == shenandoah_policy()->full_gc_count()) {
+ // We are not getting what we need from concurrent collections, and no full gcs ran
+ // while we waited, so request a full gc. Whether this satisfies the allocation or not,
+ // we are done trying.
+ control_thread()->handle_alloc_failure_full();
+ result = allocate_memory_work(req, in_new_region);
+ }
+ break;
+ }
+ }
}
+
if (result != nullptr) {
// If our allocation request has been satisfied after it initially failed, we count this as good gc progress
notify_gc_progress();
}
if (log_develop_is_enabled(Debug, gc, alloc)) {
- ResourceMark rm;
log_debug(gc, alloc)("Thread: %s, Result: " PTR_FORMAT ", Request: %s, Size: %zu"
", Original: %zu, Latest: %zu",
Thread::current()->name(), p2i(result), req.type_string(), req.size(),
@@ -1001,8 +998,6 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) {
} else {
assert(req.is_gc_alloc(), "Can only accept GC allocs here");
result = allocate_memory_work(req, in_new_region);
- // Do not call handle_alloc_failure() here, because we cannot block.
- // The allocation failure would be handled by the LRB slowpath with handle_alloc_failure_evac().
}
if (in_new_region) {
@@ -1025,11 +1020,6 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) {
return result;
}
-inline bool ShenandoahHeap::should_retry_allocation(size_t original_full_gc_count) const {
- return shenandoah_policy()->full_gc_count() == original_full_gc_count
- && !shenandoah_policy()->is_at_shutdown();
-}
-
HeapWord* ShenandoahHeap::allocate_memory_work(ShenandoahAllocRequest& req, bool& in_new_region) {
// Reserve the promotion budget up front so it is enforced atomically without the heap lock.
// If the reserve is exhausted, deny the promotion rather than overshoot it; the reservation
@@ -1125,28 +1115,19 @@ class ShenandoahEvacuationTask : public WorkerTask {
private:
ShenandoahHeap* const _sh;
ShenandoahCollectionSet* const _cs;
- bool _concurrent;
public:
ShenandoahEvacuationTask(ShenandoahHeap* sh,
- ShenandoahCollectionSet* cs,
- bool concurrent) :
+ ShenandoahCollectionSet* cs) :
WorkerTask("Shenandoah Evacuation"),
_sh(sh),
- _cs(cs),
- _concurrent(concurrent)
- {}
+ _cs(cs) {
+ }
void work(uint worker_id) {
- if (_concurrent) {
- ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true);
- ShenandoahConcurrentWorkerSession worker_session(worker_id);
- SuspendibleThreadSetJoiner stsj;
- do_work();
- } else {
- ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_evac, ShenandoahPhaseTimings::Work, worker_id, true);
- ShenandoahParallelWorkerSession worker_session(worker_id);
- do_work();
- }
+ ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true);
+ ShenandoahConcurrentWorkerSession worker_session(worker_id);
+ SuspendibleThreadSetJoiner stsj;
+ do_work();
}
private:
@@ -1157,7 +1138,11 @@ class ShenandoahEvacuationTask : public WorkerTask {
assert(r->has_live(), "Region %zu should have been reclaimed early", r->index());
_sh->marked_object_iterate(r, &cl);
- if (_sh->check_cancelled_gc_and_yield(_concurrent)) {
+ if (ShenandoahCollectorPolicy::should_abandon_evacuations(r)) {
+ break;
+ }
+
+ if (_sh->check_cancelled_gc_and_yield()) {
break;
}
}
@@ -1177,6 +1162,9 @@ class ShenandoahRetireGCLABClosure : public ThreadClosure {
ShenandoahThreadLocalData::set_gclab_size(thread, 0);
}
+ // Clear this for next evacuation phase
+ ShenandoahThreadLocalData::set_has_evac_failures(thread, false);
+
if (ShenandoahHeap::heap()->mode()->is_generational()) {
ShenandoahPLAB* shenandoah_plab = ShenandoahThreadLocalData::shenandoah_plab(thread);
assert(shenandoah_plab != nullptr, "PLAB should be initialized for %s", thread->name());
@@ -1227,12 +1215,68 @@ class ShenandoahPrepareForUpdateRefsHandshakeClosure : public HandshakeClosure {
ShenandoahGCStatePropagatorHandshakeClosure _propagator;
};
-void ShenandoahHeap::evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) {
+class ShenandoahSelfForwardClosure : public ObjectClosure {
+ bool _self_forwarded;
+public:
+ ShenandoahSelfForwardClosure() : _self_forwarded(false) {}
+
+ void do_object(oop obj) override {
+ oop resolved = ShenandoahForwarding::try_forward_to(obj, obj);
+ if (resolved == obj) {
+ _self_forwarded = true;
+ }
+ }
+
+ bool self_forwarded_objects() const { return _self_forwarded; }
+};
+
+ShenandoahSelfForwardTask::ShenandoahSelfForwardTask(ShenandoahHeap* heap, ShenandoahCollectionSet* cs) :
+ WorkerTask("Shenandoah Self-Forward"),
+ _heap(heap),
+ _cs(cs) {
+ // Do not reset claim index, these workers pick up from where the evacuation workers left off
+ assert(!_cs->all_regions_claimed(), "Only here to self forward objects in unclaimed regions");
+ assert(_heap->has_self_forwarded_objects(), "Workers that did not evacuate a region must set this flag");
+}
+
+void ShenandoahSelfForwardTask::work(uint worker_id) {
+ ShenandoahConcurrentWorkerSession worker_session(worker_id);
+ SuspendibleThreadSetJoiner joiner;
+ ShenandoahHeapRegion* r;
+ while ((r = _cs->claim_next()) != nullptr) {
+ ShenandoahSelfForwardClosure cl;
+ _heap->marked_object_iterate(r, &cl);
+ if (cl.self_forwarded_objects()) {
+ r->set_has_self_forwards();
+ }
+
+ if (_heap->check_cancelled_gc_and_yield()) {
+ break;
+ }
+ }
+}
+
+void ShenandoahHeap::evacuate_collection_set(ShenandoahGeneration* generation) {
assert(generation->is_global(), "Only global generation expected here");
- ShenandoahEvacuationTask task(this, _collection_set, concurrent);
+ ShenandoahEvacuationTask task(this, _collection_set);
workers()->run_task(&task);
}
+void ShenandoahHeap::self_forward_stranded_objects() {
+ // When an object cannot be evacuated, it will be self-forwarded. Regions with self-forwarded objects
+ // can only be "partially" recycled. We have made a policy/design decision to stop trying to evacuate
+ // such regions. Instead, the workers will focus on evacuating regions that still have a chance of being
+ // completely evacuated. However, once a worker itself cannot refill its LABs, it will do nothing more
+ // beside create more partially evacuated regions. For this reason, such a worker exits the evacuation
+ // task. This leaves the remaining regions to the remaining workers who, we hope, may yet complete more
+ // successful evacuations. For the case that all workers exit before all the collection set regions are
+ // evacuated, we have this step below which simply self forwards all the objects in all the regions that
+ // were not evacuated out of the collection set.
+ log_debug(gc)("Cleaning up failed evacuations");
+ ShenandoahSelfForwardTask self_forward_task(this, collection_set());
+ workers()->run_task(&self_forward_task);
+}
+
class ShenandoahCompleteStackWatermarkHandshakeClosure : public HandshakeClosure {
public:
ShenandoahCompleteStackWatermarkHandshakeClosure() : HandshakeClosure("Shenandoah Complete stacks handshake") {}
@@ -1258,7 +1302,6 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() {
// by the following handshake.
MutexLocker lock(Threads_lock);
- // A cancellation at this point means the degenerated cycle must resume from update-refs.
set_gc_state_concurrent(EVACUATION, false);
set_gc_state_concurrent(UPDATE_REFS, true);
}
@@ -1271,8 +1314,6 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() {
// Now retire gclabs and plabs and propagate gc_state for mutator threads
Handshake::execute(&prepare_for_update_refs);
-
- _update_refs_iterator.reset();
}
void ShenandoahHeap::op_final_roots() {
@@ -1307,16 +1348,20 @@ oop ShenandoahHeap::evacuate_object(oop p, Thread* thread) {
assert(thread == Thread::current(), "Expected thread parameter to be current thread.");
ShenandoahHeapRegion* r = heap_region_containing(p);
- assert(!r->is_humongous(), "never evacuate humongous objects");
+ if (has_self_forwarded_objects() && r->has_self_forwards()) {
+ // We don't want GC threads to evacuate objects in regions that have evacuation failures. We'd
+ // rather have them concentrate on regions that still have a chance of being completely evacuated.
+ return ShenandoahForwarding::try_forward_to(p, p);
+ }
- ShenandoahAffiliation target_gen = r->affiliation();
- return try_evacuate_object(p, thread, r, target_gen);
+ assert(!r->is_humongous(), "never evacuate humongous objects");
+ assert(r->is_young(), "Only expect evacuations from young in this mode");
+ return try_evacuate_object(p, thread, r, YOUNG_GENERATION);
}
oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region,
ShenandoahAffiliation target_gen) {
assert(target_gen == YOUNG_GENERATION, "Only expect evacuations to young in this mode");
- assert(from_region->is_young(), "Only expect evacuations from young in this mode");
bool alloc_from_lab = true;
HeapWord* copy = nullptr;
size_t size = ShenandoahForwarding::size(p);
@@ -1341,18 +1386,21 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg
#endif
if (copy == nullptr) {
- control_thread()->handle_alloc_failure_evac(size);
-
// Install the self-forwarded bit on p so other evacuators/LRBs see
// the object as "already handled, do not try to evacuate". The CAS
// may fail if another thread concurrently installed a real forwardee
// (they succeeded where we failed) or self-forwarded first.
oop fwd = ShenandoahForwarding::try_forward_to(p, p);
if (fwd == p) {
- // Now self-forwarded. Flag the region so the degen/full GC
- // entry drain knows to scan it for self_fwd bits to clear.
+ // Now self-forwarded. Flag the region so other threads will not
+ // try to evacuate objects from here.
+ set_has_self_forwarded_objects();
from_region->set_has_self_forwards();
}
+
+ // It does not matter if this thread did the self forwarding or not.
+ // This thread failed to allocate an evacuation copy.
+ ShenandoahThreadLocalData::set_has_evac_failures(thread, true);
return fwd;
}
@@ -1408,78 +1456,48 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg
}
}
-// Clear the self_fwd bit on a live cset object, if set. Runs at a safepoint,
-// so a plain store is sufficient — no concurrent writers to the mark word.
-class ShenandoahUnSelfForwardObjectClosure : public ObjectClosure {
+class ShenandoahTrashRegionTask : public WorkerTask {
+ ShenandoahCollectionSet* _collection_set;
+ Atomic _free_bytes_in_evac_failed_regions;
public:
- void do_object(oop obj) override {
- ShenandoahForwarding::unset_self_forwarded(obj);
- }
-};
+ explicit ShenandoahTrashRegionTask(ShenandoahCollectionSet* collection_set)
+ : WorkerTask("ShenandoahTrashRegions")
+ , _collection_set(collection_set), _free_bytes_in_evac_failed_regions(0) {}
-// Parallel task over flagged cset regions. Iterates the live objects via the
-// mark bitmap (skipping evacuated and never-marked memory), clears self_fwd
-// bits, and resets the region flag once done.
-class ShenandoahUnSelfForwardTask : public WorkerTask {
-private:
- ShenandoahHeap* const _heap;
- ShenandoahCollectionSet* const _cs;
-
-public:
- ShenandoahUnSelfForwardTask(ShenandoahHeap* heap, ShenandoahCollectionSet* cs) :
- WorkerTask("Shenandoah Un-Self-Forward"),
- _heap(heap),
- _cs(cs) {}
+ size_t free_bytes_in_evac_failed_regions() const {
+ return _free_bytes_in_evac_failed_regions.load_relaxed();
+ }
void work(uint worker_id) override {
- ShenandoahParallelWorkerSession worker_session(worker_id);
- ShenandoahUnSelfForwardObjectClosure cl;
ShenandoahHeapRegion* r;
- while ((r = _cs->claim_next()) != nullptr) {
+ size_t free_bytes = 0;
+ while ((r = _collection_set->claim_next()) != nullptr) {
if (r->has_self_forwards()) {
- _heap->marked_object_iterate(r, &cl);
- r->clear_has_self_forwards();
+ r->partially_recycle();
+ free_bytes += r->free();
+ } else {
+ r->make_trash();
}
}
+ _free_bytes_in_evac_failed_regions.add_then_fetch(free_bytes, memory_order_relaxed);
}
};
-void ShenandoahHeap::un_self_forward_cset_regions() {
- assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint");
- ShenandoahCollectionSet* cs = collection_set();
- if (cs == nullptr || cs->is_empty()) {
- return;
- }
- cs->clear_current_index();
- ShenandoahUnSelfForwardTask task(this, cs);
- workers()->run_task(&task);
- DEBUG_ONLY(assert_no_self_forwards());
-}
-
-#ifdef ASSERT
-void ShenandoahHeap::assert_no_self_forwards() const {
- assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint");
- ShenandoahCollectionSet* cs = collection_set();
- if (cs == nullptr) return;
- cs->clear_current_index();
- ShenandoahHeapRegion* r;
- while ((r = cs->next()) != nullptr) {
- assert(!r->has_self_forwards(), "region still flagged after drain");
- }
- cs->clear_current_index();
-}
-#endif
-
void ShenandoahHeap::trash_cset_regions() {
- ShenandoahHeapLocker locker(lock());
-
ShenandoahCollectionSet* set = collection_set();
- ShenandoahHeapRegion* r;
set->clear_current_index();
- while ((r = set->next()) != nullptr) {
- r->make_trash();
+ if (has_self_forwarded_objects()) {
+ ShenandoahTrashRegionTask task(set);
+ workers()->run_task(&task);
+ log_debug(gc, free)("Memory available in regions that failed evacuation: " PROPERFMT,
+ PROPERFMTARGS(task.free_bytes_in_evac_failed_regions()));
+ } else {
+ ShenandoahHeapRegion* r;
+ while ((r = set->next()) != nullptr) {
+ r->make_trash();
+ }
}
- collection_set()->clear();
+ set->clear();
}
void ShenandoahHeap::print_heap_regions_on(outputStream* st) const {
@@ -1566,10 +1584,6 @@ void ShenandoahHeap::labs_make_parsable() {
}
workers()->threads_do(&cl);
-
- if (safepoint_workers() != nullptr) {
- safepoint_workers()->threads_do(&cl);
- }
}
void ShenandoahHeap::tlabs_retire(bool resize) {
@@ -1606,10 +1620,6 @@ void ShenandoahHeap::gclabs_retire(bool resize) {
}
workers()->threads_do(&cl);
-
- if (safepoint_workers() != nullptr) {
- safepoint_workers()->threads_do(&cl);
- }
}
// Returns size in bytes
@@ -1626,7 +1636,7 @@ size_t ShenandoahHeap::max_tlab_size() const {
void ShenandoahHeap::collect_as_vm_thread(GCCause::Cause cause) {
// These requests are ignored because we can't easily have Shenandoah jump into
- // a synchronous (degenerated or full) cycle while it is in the middle of a concurrent
+ // a synchronous (full) cycle while it is in the middle of a concurrent
// cycle. We _could_ cancel the concurrent cycle and then try to run a cycle directly
// on the VM thread, but this would confuse the control thread mightily and doesn't
// seem worth the trouble. Instead, we will have the caller thread run (and wait for) a
@@ -1684,9 +1694,6 @@ void ShenandoahHeap::gc_threads_do(ThreadClosure* tcl) const {
}
workers()->threads_do(tcl);
- if (_safepoint_workers != nullptr) {
- _safepoint_workers->threads_do(tcl);
- }
}
void ShenandoahHeap::print_tracing_info() const {
@@ -1726,8 +1733,7 @@ void ShenandoahHeap::set_active_generation(ShenandoahGeneration* generation) {
_active_generation = generation;
}
-void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation,
- bool is_degenerated, bool is_out_of_cycle) {
+void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation) {
shenandoah_policy()->record_collection_cause(cause);
const GCCause::Cause current = gc_cause();
@@ -1736,10 +1742,11 @@ void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration*
set_gc_cause(cause);
- if (is_degenerated) {
- generation->heuristics()->record_degenerated_cycle_start(is_out_of_cycle);
- } else {
- generation->heuristics()->record_cycle_start();
+ generation->heuristics()->record_cycle_start();
+ if (mode()->is_generational() && generation->is_global()) {
+ // We are starting a global cycle, for heuristic's purposes, inform young and old too
+ young_generation()->heuristics()->record_cycle_start();
+ old_generation()->heuristics()->record_cycle_start();
}
}
@@ -1833,7 +1840,7 @@ void ShenandoahHeap::object_iterate(ObjectClosure* cl) {
ShenandoahScanObjectStack oop_stack;
ObjectIterateScanRootClosure oops(&_aux_bit_map, &oop_stack);
// Seed the stack with root scan
- scan_roots_for_iteration(&oop_stack, &oops);
+ scan_roots_for_iteration(&oops, 1);
// Work through the oop stack to traverse heap
while (! oop_stack.is_empty()) {
@@ -1862,13 +1869,12 @@ bool ShenandoahHeap::prepare_aux_bitmap_for_iteration() {
return true;
}
-void ShenandoahHeap::scan_roots_for_iteration(ShenandoahScanObjectStack* oop_stack, ObjectIterateScanRootClosure* oops) {
+void ShenandoahHeap::scan_roots_for_iteration(ObjectIterateScanRootClosure* oops, uint num_workers) {
// Process GC roots according to current GC cycle
// This populates the work stack with initial objects
// It is important to relinquish the associated locks before diving
// into heap dumper
- uint n_workers = safepoint_workers() != nullptr ? safepoint_workers()->active_workers() : 1;
- ShenandoahHeapIterationRootScanner rp(n_workers);
+ ShenandoahHeapIterationRootScanner rp(num_workers);
rp.roots_do(oops);
}
@@ -1933,7 +1939,7 @@ class ShenandoahParallelObjectIterator : public ParallelObjectIteratorImpl {
}
ObjectIterateScanRootClosure oops(_aux_bit_map, &_roots_stack);
- _heap->scan_roots_for_iteration(&_roots_stack, &oops);
+ _heap->scan_roots_for_iteration(&oops, _num_workers);
_init_ready = prepare_worker_queues();
}
@@ -2130,29 +2136,6 @@ void ShenandoahHeap::do_class_unloading() {
}
}
-void ShenandoahHeap::stw_weak_refs(ShenandoahGeneration* generation, bool full_gc) {
- // Weak refs processing
- ShenandoahPhaseTimings::Phase phase = full_gc ? ShenandoahPhaseTimings::full_gc_weakrefs
- : ShenandoahPhaseTimings::degen_gc_weakrefs;
- ShenandoahTimingsTracker t(phase);
- ShenandoahGCWorkerPhase worker_phase(phase);
- generation->ref_processor()->process_references(phase, workers(), false /* concurrent */);
-}
-
-void ShenandoahHeap::prepare_update_heap_references() {
- assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint");
-
- // Evacuation is over, no GCLABs are needed anymore. GCLABs are under URWM, so we need to
- // make them parsable for update code to work correctly. Plus, we can compute new sizes
- // for future GCLABs here.
- if (UseTLAB) {
- ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_init_update_refs_manage_gclabs);
- gclabs_retire(ResizeTLAB);
- }
-
- _update_refs_iterator.reset();
-}
-
void ShenandoahHeap::propagate_gc_state_to_all_threads() {
assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at Shenandoah safepoint");
if (_gc_state_changed) {
@@ -2270,19 +2253,8 @@ size_t ShenandoahHeap::tlab_used() const {
return _free_set->used_not_holding_lock();
}
-bool ShenandoahHeap::try_cancel_gc(GCCause::Cause cause) {
- while (true) {
- const GCCause::Cause prev = _cancelled_gc.get();
- if (prev != GCCause::_no_gc && prev != GCCause::_shenandoah_concurrent_gc && cause != GCCause::_shenandoah_stop_vm) {
- // Only when the gc has not been cancelled, or it has been cancelled to interrupt an old marking cycle
- // do we allow the new cancellation request to happen. We make an exception for stopping the VM.
- return false;
- }
-
- if (_cancelled_gc.cmpxchg(cause, prev) == prev) {
- return true;
- }
- }
+bool ShenandoahHeap::try_cancel_gc() {
+ return !is_stopping() && _cancelled_gc.try_set();
}
void ShenandoahHeap::cancel_concurrent_mark() {
@@ -2297,7 +2269,7 @@ void ShenandoahHeap::cancel_concurrent_mark() {
}
bool ShenandoahHeap::cancel_gc(GCCause::Cause cause) {
- if (try_cancel_gc(cause)) {
+ if (try_cancel_gc()) {
FormatBuffer<> msg("Cancelling GC: %s", GCCause::to_string(cause));
log_info(gc,thread)("%s", msg.buffer());
Events::log(Thread::current(), "%s", msg.buffer());
@@ -2332,90 +2304,6 @@ void ShenandoahHeap::stop() {
}
}
-void ShenandoahHeap::stw_unload_classes(bool full_gc) {
- if (!unload_classes()) return;
- ClassUnloadingContext ctx(_workers->active_workers(),
- true /* unregister_nmethods_during_purge */,
- false /* lock_nmethod_free_separately */);
-
- // Unload classes and purge SystemDictionary.
- {
- ShenandoahPhaseTimings::Phase phase = full_gc ?
- ShenandoahPhaseTimings::full_gc_purge_class_unload :
- ShenandoahPhaseTimings::degen_gc_purge_class_unload;
- ShenandoahIsAliveSelector is_alive;
- {
- CodeCache::UnlinkingScope scope(is_alive.is_alive_closure());
- ShenandoahGCPhase gc_phase(phase);
- ShenandoahGCWorkerPhase worker_phase(phase);
- bool unloading_occurred = SystemDictionary::do_unloading(gc_timer());
-
- ShenandoahClassUnloadingTask unlink_task(phase, unloading_occurred);
- _workers->run_task(&unlink_task);
- }
- // Release unloaded nmethods's memory.
- ClassUnloadingContext::context()->purge_and_free_nmethods();
- }
-
- {
- ShenandoahGCPhase phase(full_gc ?
- ShenandoahPhaseTimings::full_gc_purge_cldg :
- ShenandoahPhaseTimings::degen_gc_purge_cldg);
- ClassLoaderDataGraph::purge(true /* at_safepoint */);
- }
- // Resize and verify metaspace
- MetaspaceGC::compute_new_size();
-
- if (mode()->is_generational()) {
- old_generation()->set_parsable(false);
- }
-
- DEBUG_ONLY(MetaspaceUtils::verify();)
-}
-
-// Weak roots are either pre-evacuated (final mark) or updated (final update refs),
-// so they should not have forwarded oops.
-// However, we do need to "null" dead oops in the roots, if can not be done
-// in concurrent cycles.
-void ShenandoahHeap::stw_process_weak_roots(bool full_gc) {
- uint num_workers = _workers->active_workers();
- ShenandoahPhaseTimings::Phase timing_phase = full_gc ?
- ShenandoahPhaseTimings::full_gc_purge_weak_par :
- ShenandoahPhaseTimings::degen_gc_purge_weak_par;
- ShenandoahGCPhase phase(timing_phase);
- ShenandoahGCWorkerPhase worker_phase(timing_phase);
- // Cleanup weak roots
- if (has_forwarded_objects()) {
- ShenandoahForwardedIsAliveClosure is_alive;
- ShenandoahNonConcUpdateRefsClosure keep_alive;
- ShenandoahParallelWeakRootsCleaningTask
- cleaning_task(timing_phase, &is_alive, &keep_alive, num_workers);
- _workers->run_task(&cleaning_task);
- } else {
- ShenandoahIsAliveClosure is_alive;
-#ifdef ASSERT
- ShenandoahAssertNotForwardedClosure verify_cl;
- ShenandoahParallelWeakRootsCleaningTask
- cleaning_task(timing_phase, &is_alive, &verify_cl, num_workers);
-#else
- ShenandoahParallelWeakRootsCleaningTask
- cleaning_task(timing_phase, &is_alive, &do_nothing_cl, num_workers);
-#endif
- _workers->run_task(&cleaning_task);
- }
-}
-
-void ShenandoahHeap::parallel_cleaning(ShenandoahGeneration* generation, bool full_gc) {
- assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint");
- assert(is_stw_gc_in_progress(), "Only for Degenerated and Full GC");
- ShenandoahGCPhase phase(full_gc ?
- ShenandoahPhaseTimings::full_gc_purge :
- ShenandoahPhaseTimings::degen_gc_purge);
- stw_weak_refs(generation, full_gc);
- stw_process_weak_roots(full_gc);
- stw_unload_classes(full_gc);
-}
-
void ShenandoahHeap::set_has_forwarded_objects(bool cond) {
set_gc_state_at_safepoint(HAS_FORWARDED, cond);
}
@@ -2434,10 +2322,6 @@ address ShenandoahHeap::in_cset_fast_test_addr() {
return (address) heap->collection_set()->biased_map_address();
}
-void ShenandoahHeap::set_degenerated_gc_in_progress(bool in_progress) {
- _degenerated_gc_in_progress.set_cond(in_progress);
-}
-
void ShenandoahHeap::set_full_gc_in_progress(bool in_progress) {
_full_gc_in_progress.set_cond(in_progress);
}
@@ -2574,10 +2458,6 @@ void ShenandoahHeap::assert_gc_workers(uint nworkers) {
// Use ParallelGCThreads inside safepoints
assert(nworkers == ParallelGCThreads, "Use ParallelGCThreads (%u) within safepoint, not %u",
ParallelGCThreads, nworkers);
- } else {
- // Use ConcGCThreads outside safepoints
- assert(nworkers == ConcGCThreads, "Use ConcGCThreads (%u) outside safepoints, %u",
- ConcGCThreads, nworkers);
}
}
#endif
@@ -2588,7 +2468,6 @@ ShenandoahVerifier* ShenandoahHeap::verifier() {
return _verifier;
}
-template