Compare commits
68 Commits
a5a0c7f8b6
...
d4984d5e5a
Author | SHA1 | Date | |
---|---|---|---|
|
d4984d5e5a | ||
|
8d33ea7395 | ||
|
3c53057fa6 | ||
|
1fcede053c | ||
|
fae9c7a3f0 | ||
|
dd68829017 | ||
|
b85fe02be5 | ||
|
e18277b470 | ||
|
e5ce5c57c8 | ||
|
91fdd72c97 | ||
|
b6ec93b038 | ||
|
65e63b6ab4 | ||
|
3f0fef2c9c | ||
|
3e0ef832cc | ||
|
7b7136b4ec | ||
|
5886ef728f | ||
|
d7aa349820 | ||
|
3b32f6a8ec | ||
|
c4bcb39577 | ||
|
8f73357004 | ||
|
429158218b | ||
|
ef4cbec6fb | ||
|
e9216efefc | ||
|
e5196fc24d | ||
|
c98dffa186 | ||
|
7d7fc69355 | ||
|
42ab8fcfb9 | ||
|
4cee27b5ec | ||
|
bf7d40d048 | ||
|
5ae32c4c86 | ||
|
56ce70c5df | ||
|
abc76c6b5b | ||
|
9586817cea | ||
|
38b877e941 | ||
|
8f487d26c0 | ||
|
500a3a2d0a | ||
|
a2f99fd88b | ||
|
0582bd290d | ||
|
3ff83ec49e | ||
|
7c9c8ba363 | ||
|
ca7b885873 | ||
|
92be7821f5 | ||
|
bcf860703d | ||
|
d186dacdb7 | ||
|
ef45c8154c | ||
|
cd9b1bc820 | ||
|
fcb68ea22d | ||
|
eb256deb80 | ||
|
156187accc | ||
|
a377773fa7 | ||
|
cae1fd3385 | ||
|
eb8ee8bdc7 | ||
|
2103dc15cb | ||
|
1c72b350e4 | ||
|
ac6499c558 | ||
|
52338c94f6 | ||
|
91f12600d2 | ||
|
6c616c71ec | ||
|
e94ad551c6 | ||
|
d735255919 | ||
|
d024f58e61 | ||
|
026975a1aa | ||
|
8adb052b46 | ||
|
9658cecde3 | ||
|
75bd7fb4de | ||
|
2140d5433d | ||
|
7c1f31d7b3 | ||
|
b2e7cda6a0 |
4
make/autoconf/configure
vendored
4
make/autoconf/configure
vendored
@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
# Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
#
|
||||
# This code is free software; you can redistribute it and/or modify it
|
||||
@ -366,7 +366,7 @@ EOT
|
||||
|
||||
# Print additional help, e.g. a list of toolchains and JVM features.
|
||||
# This must be done by the autoconf script.
|
||||
( CONFIGURE_PRINT_ADDITIONAL_HELP=true . $generated_script PRINTF=printf )
|
||||
( CONFIGURE_PRINT_ADDITIONAL_HELP=true . $generated_script PRINTF=printf ECHO=echo )
|
||||
|
||||
cat <<EOT
|
||||
|
||||
|
@ -1192,8 +1192,8 @@ var getJibProfilesDependencies = function (input, common) {
|
||||
server: "jpg",
|
||||
product: "jcov",
|
||||
version: "3.0",
|
||||
build_number: "1",
|
||||
file: "bundles/jcov-3.0+1.zip",
|
||||
build_number: "3",
|
||||
file: "bundles/jcov-3.0+3.zip",
|
||||
environment_name: "JCOV_HOME",
|
||||
},
|
||||
|
||||
|
@ -46,6 +46,8 @@ CLDR_GEN_DONE := $(GENSRC_DIR)/_cldr-gensrc.marker
|
||||
TZ_DATA_DIR := $(MODULE_SRC)/share/data/tzdata
|
||||
ZONENAME_TEMPLATE := $(MODULE_SRC)/share/classes/java/time/format/ZoneName.java.template
|
||||
|
||||
# The `-utf8` option is used even for US English, as some names
|
||||
# may contain non-ASCII characters, such as “Türkiye”.
|
||||
$(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \
|
||||
$(wildcard $(CLDR_DATA_DIR)/main/en*.xml) \
|
||||
$(wildcard $(CLDR_DATA_DIR)/supplemental/*.xml) \
|
||||
@ -61,7 +63,8 @@ $(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \
|
||||
-basemodule \
|
||||
-year $(COPYRIGHT_YEAR) \
|
||||
-zntempfile $(ZONENAME_TEMPLATE) \
|
||||
-tzdatadir $(TZ_DATA_DIR))
|
||||
-tzdatadir $(TZ_DATA_DIR) \
|
||||
-utf8)
|
||||
$(TOUCH) $@
|
||||
|
||||
TARGETS += $(CLDR_GEN_DONE)
|
||||
|
@ -45,7 +45,8 @@ $(CLDR_GEN_DONE): $(wildcard $(CLDR_DATA_DIR)/dtd/*.dtd) \
|
||||
-baselocales "en-US" \
|
||||
-year $(COPYRIGHT_YEAR) \
|
||||
-o $(GENSRC_DIR) \
|
||||
-tzdatadir $(TZ_DATA_DIR))
|
||||
-tzdatadir $(TZ_DATA_DIR) \
|
||||
-utf8)
|
||||
$(TOUCH) $@
|
||||
|
||||
TARGETS += $(CLDR_GEN_DONE)
|
||||
|
@ -187,22 +187,18 @@ public class HelloWorld {
|
||||
new Run("none", "Hello from Cupertino")
|
||||
}),
|
||||
new Paragraph("title", new Run[] {
|
||||
new Run("none", "\u53F0\u5317\u554F\u5019\u60A8\u0021")
|
||||
new Run("none", "台北問候您!")
|
||||
}),
|
||||
new Paragraph("title", new Run[] {
|
||||
new Run("none", "\u0391\u03B8\u03B7\u03BD\u03B1\u03B9\u0020" // Greek
|
||||
+ "\u03B1\u03C3\u03C0\u03B1\u03B6\u03BF\u03BD"
|
||||
+ "\u03C4\u03B1\u03B9\u0020\u03C5\u03BC\u03B1"
|
||||
+ "\u03C2\u0021")
|
||||
new Run("none", "Αθηναι ασπαζονται υμας!") // Greek
|
||||
}),
|
||||
new Paragraph("title", new Run[] {
|
||||
new Run("none", "\u6771\u4eac\u304b\u3089\u4eca\u65e5\u306f")
|
||||
new Run("none", "東京から今日は")
|
||||
}),
|
||||
new Paragraph("title", new Run[] {
|
||||
new Run("none", "\u05e9\u05dc\u05d5\u05dd \u05de\u05d9\u05e8\u05d5"
|
||||
+ "\u05e9\u05dc\u05d9\u05dd")
|
||||
new Run("none", "שלום מירושלים")
|
||||
}),
|
||||
new Paragraph("title", new Run[] {
|
||||
new Run("none", "\u0633\u0644\u0627\u0645")
|
||||
new Run("none", "سلام")
|
||||
}), };
|
||||
}
|
||||
|
@ -3921,6 +3921,10 @@ ins_attrib ins_alignment(4); // Required alignment attribute (must
|
||||
// compute_padding() function must be
|
||||
// provided for the instruction
|
||||
|
||||
// Whether this node is expanded during code emission into a sequence of
|
||||
// instructions and the first instruction can perform an implicit null check.
|
||||
ins_attrib ins_is_late_expanded_null_check_candidate(false);
|
||||
|
||||
//----------OPERANDS-----------------------------------------------------------
|
||||
// Operand definitions must precede instruction definitions for correct parsing
|
||||
// in the ADLC because operands constitute user defined types which are used in
|
||||
|
@ -106,6 +106,13 @@ instruct zLoadP(iRegPNoSp dst, memory8 mem, rFlagsReg cr)
|
||||
match(Set dst (LoadP mem));
|
||||
predicate(UseZGC && !needs_acquiring_load(n) && n->as_Load()->barrier_data() != 0);
|
||||
effect(TEMP dst, KILL cr);
|
||||
// The main load is a candidate to implement implicit null checks, as long as
|
||||
// legitimize_address() does not require a preceding lea instruction to
|
||||
// materialize the memory operand. The absence of a preceding lea instruction
|
||||
// is guaranteed for immLoffset8 memory operands, because these do not lead to
|
||||
// out-of-range offsets (see definition of immLoffset8). Fortunately,
|
||||
// immLoffset8 memory operands are the most common ones in practice.
|
||||
ins_is_late_expanded_null_check_candidate(opnd_array(1)->opcode() == INDOFFL8);
|
||||
|
||||
ins_cost(4 * INSN_COST);
|
||||
|
||||
@ -117,7 +124,11 @@ instruct zLoadP(iRegPNoSp dst, memory8 mem, rFlagsReg cr)
|
||||
// Fix up any out-of-range offsets.
|
||||
assert_different_registers(rscratch2, as_Register($mem$$base));
|
||||
assert_different_registers(rscratch2, $dst$$Register);
|
||||
ref_addr = __ legitimize_address(ref_addr, 8, rscratch2);
|
||||
int size = 8;
|
||||
assert(!this->is_late_expanded_null_check_candidate() ||
|
||||
!MacroAssembler::legitimize_address_requires_lea(ref_addr, size),
|
||||
"an instruction that can be used for implicit null checking should emit the candidate memory access first");
|
||||
ref_addr = __ legitimize_address(ref_addr, size, rscratch2);
|
||||
}
|
||||
__ ldr($dst$$Register, ref_addr);
|
||||
z_load_barrier(masm, this, ref_addr, $dst$$Register, rscratch1);
|
||||
|
@ -129,16 +129,21 @@ class MacroAssembler: public Assembler {
|
||||
a.lea(this, r);
|
||||
}
|
||||
|
||||
// Whether materializing the given address for a LDR/STR requires an
|
||||
// additional lea instruction.
|
||||
static bool legitimize_address_requires_lea(const Address &a, int size) {
|
||||
return a.getMode() == Address::base_plus_offset &&
|
||||
!Address::offset_ok_for_immed(a.offset(), exact_log2(size));
|
||||
}
|
||||
|
||||
/* Sometimes we get misaligned loads and stores, usually from Unsafe
|
||||
accesses, and these can exceed the offset range. */
|
||||
Address legitimize_address(const Address &a, int size, Register scratch) {
|
||||
if (a.getMode() == Address::base_plus_offset) {
|
||||
if (! Address::offset_ok_for_immed(a.offset(), exact_log2(size))) {
|
||||
block_comment("legitimize_address {");
|
||||
lea(scratch, a);
|
||||
block_comment("} legitimize_address");
|
||||
return Address(scratch);
|
||||
}
|
||||
if (legitimize_address_requires_lea(a, size)) {
|
||||
block_comment("legitimize_address {");
|
||||
lea(scratch, a);
|
||||
block_comment("} legitimize_address");
|
||||
return Address(scratch);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
@ -141,6 +141,7 @@ instruct zLoadP(iRegPdst dst, memoryAlg4 mem, flagsRegCR0 cr0)
|
||||
%{
|
||||
match(Set dst (LoadP mem));
|
||||
effect(TEMP_DEF dst, KILL cr0);
|
||||
ins_is_late_expanded_null_check_candidate(true);
|
||||
ins_cost(MEMORY_REF_COST);
|
||||
|
||||
predicate((UseZGC && n->as_Load()->barrier_data() != 0)
|
||||
@ -160,6 +161,7 @@ instruct zLoadP_acq(iRegPdst dst, memoryAlg4 mem, flagsRegCR0 cr0)
|
||||
%{
|
||||
match(Set dst (LoadP mem));
|
||||
effect(TEMP_DEF dst, KILL cr0);
|
||||
ins_is_late_expanded_null_check_candidate(true);
|
||||
ins_cost(3 * MEMORY_REF_COST);
|
||||
|
||||
// Predicate on instruction order is implicitly present due to the predicate of the cheaper zLoadP operation
|
||||
|
@ -4036,6 +4036,10 @@ ins_attrib ins_field_cbuf_insts_offset(-1);
|
||||
ins_attrib ins_field_load_ic_hi_node(0);
|
||||
ins_attrib ins_field_load_ic_node(0);
|
||||
|
||||
// Whether this node is expanded during code emission into a sequence of
|
||||
// instructions and the first instruction can perform an implicit null check.
|
||||
ins_attrib ins_is_late_expanded_null_check_candidate(false);
|
||||
|
||||
//----------OPERANDS-----------------------------------------------------------
|
||||
// Operand definitions must precede instruction definitions for correct
|
||||
// parsing in the ADLC because operands constitute user defined types
|
||||
@ -6683,6 +6687,7 @@ instruct reinterpretL(iRegLdst dst) %{
|
||||
match(Set dst (VectorReinterpret dst));
|
||||
ins_cost(0);
|
||||
format %{ "reinterpret $dst" %}
|
||||
size(0);
|
||||
ins_encode( /*empty*/ );
|
||||
ins_pipe(pipe_class_empty);
|
||||
%}
|
||||
@ -6691,6 +6696,7 @@ instruct reinterpretX(vecX dst) %{
|
||||
match(Set dst (VectorReinterpret dst));
|
||||
ins_cost(0);
|
||||
format %{ "reinterpret $dst" %}
|
||||
size(0);
|
||||
ins_encode( /*empty*/ );
|
||||
ins_pipe(pipe_class_empty);
|
||||
%}
|
||||
@ -6810,7 +6816,6 @@ instruct cond_set_0_oop(iRegNdst dst, flagsRegSrc crx, iRegPsrc src1) %{
|
||||
format %{ "CMOVE $dst, $crx eq, 0, $src1 \t// encode: preserve 0" %}
|
||||
size(4);
|
||||
ins_encode %{
|
||||
// This is a Power7 instruction for which no machine description exists.
|
||||
__ isel_0($dst$$Register, $crx$$CondRegister, Assembler::equal, $src1$$Register);
|
||||
%}
|
||||
ins_pipe(pipe_class_default);
|
||||
@ -6942,7 +6947,6 @@ instruct cond_set_0_ptr(iRegPdst dst, flagsRegSrc crx, iRegPsrc src1) %{
|
||||
format %{ "CMOVE $dst, $crx eq, 0, $src1 \t// decode: preserve 0" %}
|
||||
size(4);
|
||||
ins_encode %{
|
||||
// This is a Power7 instruction for which no machine description exists.
|
||||
__ isel_0($dst$$Register, $crx$$CondRegister, Assembler::equal, $src1$$Register);
|
||||
%}
|
||||
ins_pipe(pipe_class_default);
|
||||
@ -7419,8 +7423,6 @@ instruct cmovI_reg_isel(cmpOp cmp, flagsRegSrc crx, iRegIdst dst, iRegIsrc src)
|
||||
format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %}
|
||||
size(4);
|
||||
ins_encode %{
|
||||
// This is a Power7 instruction for which no machine description
|
||||
// exists. Anyways, the scheduler should be off on Power7.
|
||||
int cc = $cmp$$cmpcode;
|
||||
__ isel($dst$$Register, $crx$$CondRegister,
|
||||
(Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register);
|
||||
@ -7436,8 +7438,6 @@ instruct cmovL_reg_isel(cmpOp cmp, flagsRegSrc crx, iRegLdst dst, iRegLsrc src)
|
||||
format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %}
|
||||
size(4);
|
||||
ins_encode %{
|
||||
// This is a Power7 instruction for which no machine description
|
||||
// exists. Anyways, the scheduler should be off on Power7.
|
||||
int cc = $cmp$$cmpcode;
|
||||
__ isel($dst$$Register, $crx$$CondRegister,
|
||||
(Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register);
|
||||
@ -7453,8 +7453,6 @@ instruct cmovN_reg_isel(cmpOp cmp, flagsRegSrc crx, iRegNdst dst, iRegNsrc src)
|
||||
format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %}
|
||||
size(4);
|
||||
ins_encode %{
|
||||
// This is a Power7 instruction for which no machine description
|
||||
// exists. Anyways, the scheduler should be off on Power7.
|
||||
int cc = $cmp$$cmpcode;
|
||||
__ isel($dst$$Register, $crx$$CondRegister,
|
||||
(Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register);
|
||||
@ -7470,8 +7468,6 @@ instruct cmovP_reg_isel(cmpOp cmp, flagsRegSrc crx, iRegPdst dst, iRegPsrc src)
|
||||
format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %}
|
||||
size(4);
|
||||
ins_encode %{
|
||||
// This is a Power7 instruction for which no machine description
|
||||
// exists. Anyways, the scheduler should be off on Power7.
|
||||
int cc = $cmp$$cmpcode;
|
||||
__ isel($dst$$Register, $crx$$CondRegister,
|
||||
(Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register);
|
||||
@ -9917,13 +9913,6 @@ instruct andcL_reg_reg(iRegLdst dst, iRegLsrc src1, iRegLsrc src2) %{
|
||||
// of java.lang.Float etc., e.g.
|
||||
// int floatToIntBits(float value)
|
||||
// float intBitsToFloat(int bits)
|
||||
//
|
||||
// Notes on the implementation on ppc64:
|
||||
// For Power7 and earlier, the rules are limited to those which move between a
|
||||
// register and a stack-location, because we always have to go through memory
|
||||
// when moving between a float register and an integer register.
|
||||
// This restriction is removed in Power8 with the introduction of the mtfprd
|
||||
// and mffprd instructions.
|
||||
|
||||
instruct moveL2D_reg(regD dst, iRegLsrc src) %{
|
||||
match(Set dst (MoveL2D src));
|
||||
@ -12430,6 +12419,7 @@ instruct minI_reg_reg_isel(iRegIdst dst, iRegIsrc src1, iRegIsrc src2, flagsRegC
|
||||
effect(KILL cr0);
|
||||
ins_cost(DEFAULT_COST*2);
|
||||
|
||||
size(8);
|
||||
ins_encode %{
|
||||
__ cmpw(CR0, $src1$$Register, $src2$$Register);
|
||||
__ isel($dst$$Register, CR0, Assembler::less, /*invert*/false, $src1$$Register, $src2$$Register);
|
||||
@ -12443,6 +12433,7 @@ instruct maxI_reg_reg_isel(iRegIdst dst, iRegIsrc src1, iRegIsrc src2, flagsRegC
|
||||
effect(KILL cr0);
|
||||
ins_cost(DEFAULT_COST*2);
|
||||
|
||||
size(8);
|
||||
ins_encode %{
|
||||
__ cmpw(CR0, $src1$$Register, $src2$$Register);
|
||||
__ isel($dst$$Register, CR0, Assembler::greater, /*invert*/false, $src1$$Register, $src2$$Register);
|
||||
@ -12452,7 +12443,6 @@ instruct maxI_reg_reg_isel(iRegIdst dst, iRegIsrc src1, iRegIsrc src2, flagsRegC
|
||||
|
||||
//---------- Population Count Instructions ------------------------------------
|
||||
|
||||
// Popcnt for Power7.
|
||||
instruct popCountI(iRegIdst dst, iRegIsrc src) %{
|
||||
match(Set dst (PopCountI src));
|
||||
predicate(UsePopCountInstruction);
|
||||
@ -12466,7 +12456,6 @@ instruct popCountI(iRegIdst dst, iRegIsrc src) %{
|
||||
ins_pipe(pipe_class_default);
|
||||
%}
|
||||
|
||||
// Popcnt for Power7.
|
||||
instruct popCountL(iRegIdst dst, iRegLsrc src) %{
|
||||
predicate(UsePopCountInstruction);
|
||||
match(Set dst (PopCountL src));
|
||||
@ -13295,6 +13284,7 @@ instruct repl2F_immF0(iRegLdst dst, immF_0 zero) %{
|
||||
Matcher::vector_element_basic_type(n) == T_FLOAT);
|
||||
|
||||
format %{ "LI $dst, #0 \t// replicate2F" %}
|
||||
size(4);
|
||||
ins_encode %{
|
||||
__ li($dst$$Register, 0x0);
|
||||
%}
|
||||
@ -13923,6 +13913,7 @@ instruct overflowAddL_reg_reg(flagsRegCR0 cr0, iRegLsrc op1, iRegLsrc op2) %{
|
||||
match(Set cr0 (OverflowAddL op1 op2));
|
||||
|
||||
format %{ "add_ $op1, $op2\t# overflow check long" %}
|
||||
size(12);
|
||||
ins_encode %{
|
||||
__ li(R0, 0);
|
||||
__ mtxer(R0); // clear XER.SO
|
||||
@ -13935,6 +13926,7 @@ instruct overflowSubL_reg_reg(flagsRegCR0 cr0, iRegLsrc op1, iRegLsrc op2) %{
|
||||
match(Set cr0 (OverflowSubL op1 op2));
|
||||
|
||||
format %{ "subfo_ R0, $op2, $op1\t# overflow check long" %}
|
||||
size(12);
|
||||
ins_encode %{
|
||||
__ li(R0, 0);
|
||||
__ mtxer(R0); // clear XER.SO
|
||||
@ -13947,6 +13939,7 @@ instruct overflowNegL_reg(flagsRegCR0 cr0, immL_0 zero, iRegLsrc op2) %{
|
||||
match(Set cr0 (OverflowSubL zero op2));
|
||||
|
||||
format %{ "nego_ R0, $op2\t# overflow check long" %}
|
||||
size(12);
|
||||
ins_encode %{
|
||||
__ li(R0, 0);
|
||||
__ mtxer(R0); // clear XER.SO
|
||||
@ -13959,6 +13952,7 @@ instruct overflowMulL_reg_reg(flagsRegCR0 cr0, iRegLsrc op1, iRegLsrc op2) %{
|
||||
match(Set cr0 (OverflowMulL op1 op2));
|
||||
|
||||
format %{ "mulldo_ R0, $op1, $op2\t# overflow check long" %}
|
||||
size(12);
|
||||
ins_encode %{
|
||||
__ li(R0, 0);
|
||||
__ mtxer(R0); // clear XER.SO
|
||||
@ -13997,6 +13991,7 @@ instruct repl4F_immF0(vecX dst, immF_0 zero) %{
|
||||
Matcher::vector_element_basic_type(n) == T_FLOAT);
|
||||
|
||||
format %{ "XXLXOR $dst, $zero \t// replicate4F" %}
|
||||
size(4);
|
||||
ins_encode %{
|
||||
__ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister);
|
||||
%}
|
||||
|
@ -96,6 +96,7 @@ instruct zLoadP(iRegPNoSp dst, memory mem, iRegPNoSp tmp, rFlagsReg cr)
|
||||
match(Set dst (LoadP mem));
|
||||
predicate(UseZGC && n->as_Load()->barrier_data() != 0);
|
||||
effect(TEMP dst, TEMP tmp, KILL cr);
|
||||
ins_is_late_expanded_null_check_candidate(true);
|
||||
|
||||
ins_cost(4 * DEFAULT_COST);
|
||||
|
||||
|
@ -2619,6 +2619,10 @@ ins_attrib ins_alignment(4); // Required alignment attribute (must
|
||||
// compute_padding() function must be
|
||||
// provided for the instruction
|
||||
|
||||
// Whether this node is expanded during code emission into a sequence of
|
||||
// instructions and the first instruction can perform an implicit null check.
|
||||
ins_attrib ins_is_late_expanded_null_check_candidate(false);
|
||||
|
||||
//----------OPERANDS-----------------------------------------------------------
|
||||
// Operand definitions must precede instruction definitions for correct parsing
|
||||
// in the ADLC because operands constitute user defined types which are used in
|
||||
|
@ -203,15 +203,15 @@ void VM_Version::common_initialize() {
|
||||
}
|
||||
}
|
||||
|
||||
// Misc Intrinsics could depend on RVV
|
||||
// Misc Intrinsics that could depend on RVV.
|
||||
|
||||
if (UseZba || UseRVV) {
|
||||
if (!AvoidUnalignedAccesses && (UseZba || UseRVV)) {
|
||||
if (FLAG_IS_DEFAULT(UseCRC32Intrinsics)) {
|
||||
FLAG_SET_DEFAULT(UseCRC32Intrinsics, true);
|
||||
}
|
||||
} else {
|
||||
if (!FLAG_IS_DEFAULT(UseCRC32Intrinsics)) {
|
||||
warning("CRC32 intrinsic requires Zba or RVV instructions (not available on this CPU)");
|
||||
warning("CRC32 intrinsic are not available on this CPU.");
|
||||
}
|
||||
FLAG_SET_DEFAULT(UseCRC32Intrinsics, false);
|
||||
}
|
||||
|
@ -118,6 +118,10 @@ instruct zLoadP(rRegP dst, memory mem, rFlagsReg cr)
|
||||
predicate(UseZGC && n->as_Load()->barrier_data() != 0);
|
||||
match(Set dst (LoadP mem));
|
||||
effect(TEMP dst, KILL cr);
|
||||
// The main load is a candidate to implement implicit null checks. The
|
||||
// barrier's slow path includes an identical reload, which does not need to be
|
||||
// registered in the exception table because it is dominated by the main one.
|
||||
ins_is_late_expanded_null_check_candidate(true);
|
||||
|
||||
ins_cost(125);
|
||||
|
||||
|
@ -465,13 +465,19 @@ address TemplateInterpreterGenerator::generate_math_entry(AbstractInterpreter::M
|
||||
__ call_VM_leaf0(CAST_FROM_FN_PTR(address, SharedRuntime::dtan));
|
||||
}
|
||||
} else if (kind == Interpreter::java_lang_math_tanh) {
|
||||
assert(StubRoutines::dtanh() != nullptr, "not initialized");
|
||||
if (StubRoutines::dtanh() != nullptr) {
|
||||
__ movdbl(xmm0, Address(rsp, wordSize));
|
||||
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dtanh())));
|
||||
} else {
|
||||
return nullptr; // Fallback to default implementation
|
||||
}
|
||||
} else if (kind == Interpreter::java_lang_math_cbrt) {
|
||||
assert(StubRoutines::dcbrt() != nullptr, "not initialized");
|
||||
__ movdbl(xmm0, Address(rsp, wordSize));
|
||||
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dcbrt())));
|
||||
if (StubRoutines::dcbrt() != nullptr) {
|
||||
__ movdbl(xmm0, Address(rsp, wordSize));
|
||||
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dcbrt())));
|
||||
} else {
|
||||
return nullptr; // Fallback to default implementation
|
||||
}
|
||||
} else if (kind == Interpreter::java_lang_math_abs) {
|
||||
assert(StubRoutines::x86::double_sign_mask() != nullptr, "not initialized");
|
||||
__ movdbl(xmm0, Address(rsp, wordSize));
|
||||
|
@ -2055,6 +2055,10 @@ ins_attrib ins_alignment(1); // Required alignment attribute (must
|
||||
// compute_padding() function must be
|
||||
// provided for the instruction
|
||||
|
||||
// Whether this node is expanded during code emission into a sequence of
|
||||
// instructions and the first instruction can perform an implicit null check.
|
||||
ins_attrib ins_is_late_expanded_null_check_candidate(false);
|
||||
|
||||
//----------OPERANDS-----------------------------------------------------------
|
||||
// Operand definitions must precede instruction definitions for correct parsing
|
||||
// in the ADLC because operands constitute user defined types which are used in
|
||||
@ -10527,7 +10531,8 @@ instruct xorI_rReg_im1_ndd(rRegI dst, rRegI src, immI_M1 imm)
|
||||
// Xor Register with Immediate
|
||||
instruct xorI_rReg_imm(rRegI dst, immI src, rFlagsReg cr)
|
||||
%{
|
||||
predicate(!UseAPX);
|
||||
// Strict predicate check to make selection of xorI_rReg_im1 cost agnostic if immI src is -1.
|
||||
predicate(!UseAPX && n->in(2)->bottom_type()->is_int()->get_con() != -1);
|
||||
match(Set dst (XorI dst src));
|
||||
effect(KILL cr);
|
||||
flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag);
|
||||
@ -10541,7 +10546,8 @@ instruct xorI_rReg_imm(rRegI dst, immI src, rFlagsReg cr)
|
||||
|
||||
instruct xorI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr)
|
||||
%{
|
||||
predicate(UseAPX);
|
||||
// Strict predicate check to make selection of xorI_rReg_im1_ndd cost agnostic if immI src2 is -1.
|
||||
predicate(UseAPX && n->in(2)->bottom_type()->is_int()->get_con() != -1);
|
||||
match(Set dst (XorI src1 src2));
|
||||
effect(KILL cr);
|
||||
flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag);
|
||||
@ -10559,6 +10565,7 @@ instruct xorI_rReg_mem_imm_ndd(rRegI dst, memory src1, immI src2, rFlagsReg cr)
|
||||
predicate(UseAPX);
|
||||
match(Set dst (XorI (LoadI src1) src2));
|
||||
effect(KILL cr);
|
||||
ins_cost(150);
|
||||
flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag);
|
||||
|
||||
format %{ "exorl $dst, $src1, $src2\t# int ndd" %}
|
||||
@ -11201,7 +11208,8 @@ instruct xorL_rReg_im1_ndd(rRegL dst,rRegL src, immL_M1 imm)
|
||||
// Xor Register with Immediate
|
||||
instruct xorL_rReg_imm(rRegL dst, immL32 src, rFlagsReg cr)
|
||||
%{
|
||||
predicate(!UseAPX);
|
||||
// Strict predicate check to make selection of xorL_rReg_im1 cost agnostic if immL32 src is -1.
|
||||
predicate(!UseAPX && n->in(2)->bottom_type()->is_long()->get_con() != -1L);
|
||||
match(Set dst (XorL dst src));
|
||||
effect(KILL cr);
|
||||
flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag);
|
||||
@ -11215,7 +11223,8 @@ instruct xorL_rReg_imm(rRegL dst, immL32 src, rFlagsReg cr)
|
||||
|
||||
instruct xorL_rReg_rReg_imm(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr)
|
||||
%{
|
||||
predicate(UseAPX);
|
||||
// Strict predicate check to make selection of xorL_rReg_im1_ndd cost agnostic if immL32 src2 is -1.
|
||||
predicate(UseAPX && n->in(2)->bottom_type()->is_long()->get_con() != -1L);
|
||||
match(Set dst (XorL src1 src2));
|
||||
effect(KILL cr);
|
||||
flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag);
|
||||
@ -11234,6 +11243,7 @@ instruct xorL_rReg_mem_imm(rRegL dst, memory src1, immL32 src2, rFlagsReg cr)
|
||||
match(Set dst (XorL (LoadL src1) src2));
|
||||
effect(KILL cr);
|
||||
flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag);
|
||||
ins_cost(150);
|
||||
|
||||
format %{ "exorq $dst, $src1, $src2\t# long ndd" %}
|
||||
ins_encode %{
|
||||
|
@ -1261,69 +1261,6 @@ void os::pd_print_cpu_info(outputStream* st, char* buf, size_t buflen) {
|
||||
// Nothing to do beyond of what os::print_cpu_info() does.
|
||||
}
|
||||
|
||||
static char saved_jvm_path[MAXPATHLEN] = {0};
|
||||
|
||||
// Find the full path to the current module, libjvm.so.
|
||||
void os::jvm_path(char *buf, jint buflen) {
|
||||
// Error checking.
|
||||
if (buflen < MAXPATHLEN) {
|
||||
assert(false, "must use a large-enough buffer");
|
||||
buf[0] = '\0';
|
||||
return;
|
||||
}
|
||||
// Lazy resolve the path to current module.
|
||||
if (saved_jvm_path[0] != 0) {
|
||||
strcpy(buf, saved_jvm_path);
|
||||
return;
|
||||
}
|
||||
|
||||
Dl_info dlinfo;
|
||||
int ret = dladdr(CAST_FROM_FN_PTR(void *, os::jvm_path), &dlinfo);
|
||||
assert(ret != 0, "cannot locate libjvm");
|
||||
char* rp = os::realpath((char *)dlinfo.dli_fname, buf, buflen);
|
||||
assert(rp != nullptr, "error in realpath(): maybe the 'path' argument is too long?");
|
||||
|
||||
// If executing unit tests we require JAVA_HOME to point to the real JDK.
|
||||
if (Arguments::executing_unit_tests()) {
|
||||
// Look for JAVA_HOME in the environment.
|
||||
char* java_home_var = ::getenv("JAVA_HOME");
|
||||
if (java_home_var != nullptr && java_home_var[0] != 0) {
|
||||
|
||||
// Check the current module name "libjvm.so".
|
||||
const char* p = strrchr(buf, '/');
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
assert(strstr(p, "/libjvm") == p, "invalid library name");
|
||||
|
||||
stringStream ss(buf, buflen);
|
||||
rp = os::realpath(java_home_var, buf, buflen);
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert((int)strlen(buf) < buflen, "Ran out of buffer room");
|
||||
ss.print("%s/lib", buf);
|
||||
|
||||
if (0 == access(buf, F_OK)) {
|
||||
// Use current module name "libjvm.so"
|
||||
ss.print("/%s/libjvm%s", Abstract_VM_Version::vm_variant(), JNI_LIB_SUFFIX);
|
||||
assert(strcmp(buf + strlen(buf) - strlen(JNI_LIB_SUFFIX), JNI_LIB_SUFFIX) == 0,
|
||||
"buf has been truncated");
|
||||
} else {
|
||||
// Go back to path of .so
|
||||
rp = os::realpath((char *)dlinfo.dli_fname, buf, buflen);
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(saved_jvm_path, buf, sizeof(saved_jvm_path));
|
||||
saved_jvm_path[sizeof(saved_jvm_path) - 1] = '\0';
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Virtual Memory
|
||||
|
||||
|
@ -154,7 +154,8 @@ julong os::Bsd::available_memory() {
|
||||
assert(kerr == KERN_SUCCESS,
|
||||
"host_statistics64 failed - check mach_host_self() and count");
|
||||
if (kerr == KERN_SUCCESS) {
|
||||
available = vmstat.free_count * os::vm_page_size();
|
||||
// free_count is just a lowerbound, other page categories can be freed too and make memory available
|
||||
available = (vmstat.free_count + vmstat.inactive_count + vmstat.purgeable_count) * os::vm_page_size();
|
||||
}
|
||||
#endif
|
||||
return available;
|
||||
@ -1482,83 +1483,6 @@ void os::print_memory_info(outputStream* st) {
|
||||
st->cr();
|
||||
}
|
||||
|
||||
static char saved_jvm_path[MAXPATHLEN] = {0};
|
||||
|
||||
// Find the full path to the current module, libjvm
|
||||
void os::jvm_path(char *buf, jint buflen) {
|
||||
// Error checking.
|
||||
if (buflen < MAXPATHLEN) {
|
||||
assert(false, "must use a large-enough buffer");
|
||||
buf[0] = '\0';
|
||||
return;
|
||||
}
|
||||
// Lazy resolve the path to current module.
|
||||
if (saved_jvm_path[0] != 0) {
|
||||
strcpy(buf, saved_jvm_path);
|
||||
return;
|
||||
}
|
||||
|
||||
char dli_fname[MAXPATHLEN];
|
||||
dli_fname[0] = '\0';
|
||||
bool ret = dll_address_to_library_name(
|
||||
CAST_FROM_FN_PTR(address, os::jvm_path),
|
||||
dli_fname, sizeof(dli_fname), nullptr);
|
||||
assert(ret, "cannot locate libjvm");
|
||||
char *rp = nullptr;
|
||||
if (ret && dli_fname[0] != '\0') {
|
||||
rp = os::realpath(dli_fname, buf, buflen);
|
||||
}
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If executing unit tests we require JAVA_HOME to point to the real JDK.
|
||||
if (Arguments::executing_unit_tests()) {
|
||||
// Look for JAVA_HOME in the environment.
|
||||
char* java_home_var = ::getenv("JAVA_HOME");
|
||||
if (java_home_var != nullptr && java_home_var[0] != 0) {
|
||||
|
||||
// Check the current module name "libjvm"
|
||||
const char* p = strrchr(buf, '/');
|
||||
assert(strstr(p, "/libjvm") == p, "invalid library name");
|
||||
|
||||
stringStream ss(buf, buflen);
|
||||
rp = os::realpath(java_home_var, buf, buflen);
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert((int)strlen(buf) < buflen, "Ran out of buffer space");
|
||||
// Add the appropriate library and JVM variant subdirs
|
||||
ss.print("%s/lib/%s", buf, Abstract_VM_Version::vm_variant());
|
||||
|
||||
if (0 != access(buf, F_OK)) {
|
||||
ss.reset();
|
||||
ss.print("%s/lib", buf);
|
||||
}
|
||||
|
||||
// If the path exists within JAVA_HOME, add the JVM library name
|
||||
// to complete the path to JVM being overridden. Otherwise fallback
|
||||
// to the path to the current library.
|
||||
if (0 == access(buf, F_OK)) {
|
||||
// Use current module name "libjvm"
|
||||
ss.print("/libjvm%s", JNI_LIB_SUFFIX);
|
||||
assert(strcmp(buf + strlen(buf) - strlen(JNI_LIB_SUFFIX), JNI_LIB_SUFFIX) == 0,
|
||||
"buf has been truncated");
|
||||
} else {
|
||||
// Fall back to path of current library
|
||||
rp = os::realpath(dli_fname, buf, buflen);
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(saved_jvm_path, buf, MAXPATHLEN);
|
||||
saved_jvm_path[MAXPATHLEN - 1] = '\0';
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Virtual Memory
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -35,9 +35,6 @@
|
||||
range, \
|
||||
constraint) \
|
||||
\
|
||||
product(bool, UseOprofile, false, \
|
||||
"(Deprecated) enable support for Oprofile profiler") \
|
||||
\
|
||||
product(bool, UseTransparentHugePages, false, \
|
||||
"Use MADV_HUGEPAGE for large pages") \
|
||||
\
|
||||
|
@ -2746,118 +2746,9 @@ void os::get_summary_cpu_info(char* cpuinfo, size_t length) {
|
||||
#endif
|
||||
}
|
||||
|
||||
static char saved_jvm_path[MAXPATHLEN] = {0};
|
||||
|
||||
// Find the full path to the current module, libjvm.so
|
||||
void os::jvm_path(char *buf, jint buflen) {
|
||||
// Error checking.
|
||||
if (buflen < MAXPATHLEN) {
|
||||
assert(false, "must use a large-enough buffer");
|
||||
buf[0] = '\0';
|
||||
return;
|
||||
}
|
||||
// Lazy resolve the path to current module.
|
||||
if (saved_jvm_path[0] != 0) {
|
||||
strcpy(buf, saved_jvm_path);
|
||||
return;
|
||||
}
|
||||
|
||||
char dli_fname[MAXPATHLEN];
|
||||
dli_fname[0] = '\0';
|
||||
bool ret = dll_address_to_library_name(
|
||||
CAST_FROM_FN_PTR(address, os::jvm_path),
|
||||
dli_fname, sizeof(dli_fname), nullptr);
|
||||
assert(ret, "cannot locate libjvm");
|
||||
char *rp = nullptr;
|
||||
if (ret && dli_fname[0] != '\0') {
|
||||
rp = os::realpath(dli_fname, buf, buflen);
|
||||
}
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If executing unit tests we require JAVA_HOME to point to the real JDK.
|
||||
if (Arguments::executing_unit_tests()) {
|
||||
// Look for JAVA_HOME in the environment.
|
||||
char* java_home_var = ::getenv("JAVA_HOME");
|
||||
if (java_home_var != nullptr && java_home_var[0] != 0) {
|
||||
|
||||
// Check the current module name "libjvm.so".
|
||||
const char* p = strrchr(buf, '/');
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
assert(strstr(p, "/libjvm") == p, "invalid library name");
|
||||
|
||||
stringStream ss(buf, buflen);
|
||||
rp = os::realpath(java_home_var, buf, buflen);
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert((int)strlen(buf) < buflen, "Ran out of buffer room");
|
||||
ss.print("%s/lib", buf);
|
||||
|
||||
if (0 == access(buf, F_OK)) {
|
||||
// Use current module name "libjvm.so"
|
||||
ss.print("/%s/libjvm%s", Abstract_VM_Version::vm_variant(), JNI_LIB_SUFFIX);
|
||||
assert(strcmp(buf + strlen(buf) - strlen(JNI_LIB_SUFFIX), JNI_LIB_SUFFIX) == 0,
|
||||
"buf has been truncated");
|
||||
} else {
|
||||
// Go back to path of .so
|
||||
rp = os::realpath(dli_fname, buf, buflen);
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(saved_jvm_path, buf, MAXPATHLEN);
|
||||
saved_jvm_path[MAXPATHLEN - 1] = '\0';
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Virtual Memory
|
||||
|
||||
// Rationale behind this function:
|
||||
// current (Mon Apr 25 20:12:18 MSD 2005) oprofile drops samples without executable
|
||||
// mapping for address (see lookup_dcookie() in the kernel module), thus we cannot get
|
||||
// samples for JITted code. Here we create private executable mapping over the code cache
|
||||
// and then we can use standard (well, almost, as mapping can change) way to provide
|
||||
// info for the reporting script by storing timestamp and location of symbol
|
||||
void linux_wrap_code(char* base, size_t size) {
|
||||
static volatile jint cnt = 0;
|
||||
|
||||
static_assert(sizeof(off_t) == 8, "Expected Large File Support in this file");
|
||||
|
||||
if (!UseOprofile) {
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[PATH_MAX+1];
|
||||
int num = Atomic::add(&cnt, 1);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s/hs-vm-%d-%d",
|
||||
os::get_temp_directory(), os::current_process_id(), num);
|
||||
unlink(buf);
|
||||
|
||||
int fd = ::open(buf, O_CREAT | O_RDWR, S_IRWXU);
|
||||
|
||||
if (fd != -1) {
|
||||
off_t rv = ::lseek(fd, size-2, SEEK_SET);
|
||||
if (rv != (off_t)-1) {
|
||||
if (::write(fd, "", 1) == 1) {
|
||||
mmap(base, size,
|
||||
PROT_READ|PROT_WRITE|PROT_EXEC,
|
||||
MAP_PRIVATE|MAP_FIXED|MAP_NORESERVE, fd, 0);
|
||||
}
|
||||
}
|
||||
::close(fd);
|
||||
unlink(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static bool recoverable_mmap_error(int err) {
|
||||
// See if the error is one we can let the caller handle. This
|
||||
// list of errno values comes from JBS-6843484. I can't find a
|
||||
|
@ -59,6 +59,7 @@
|
||||
#ifdef AIX
|
||||
#include "loadlib_aix.hpp"
|
||||
#include "os_aix.hpp"
|
||||
#include "porting_aix.hpp"
|
||||
#endif
|
||||
#ifdef LINUX
|
||||
#include "os_linux.hpp"
|
||||
@ -1060,6 +1061,95 @@ bool os::same_files(const char* file1, const char* file2) {
|
||||
return is_same;
|
||||
}
|
||||
|
||||
static char saved_jvm_path[MAXPATHLEN] = {0};
|
||||
|
||||
// Find the full path to the current module, libjvm.so
|
||||
void os::jvm_path(char *buf, jint buflen) {
|
||||
// Error checking.
|
||||
if (buflen < MAXPATHLEN) {
|
||||
assert(false, "must use a large-enough buffer");
|
||||
buf[0] = '\0';
|
||||
return;
|
||||
}
|
||||
// Lazy resolve the path to current module.
|
||||
if (saved_jvm_path[0] != 0) {
|
||||
strcpy(buf, saved_jvm_path);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* fname;
|
||||
#ifdef AIX
|
||||
Dl_info dlinfo;
|
||||
int ret = dladdr(CAST_FROM_FN_PTR(void *, os::jvm_path), &dlinfo);
|
||||
assert(ret != 0, "cannot locate libjvm");
|
||||
if (ret == 0) {
|
||||
return;
|
||||
}
|
||||
fname = dlinfo.dli_fname;
|
||||
#else
|
||||
char dli_fname[MAXPATHLEN];
|
||||
dli_fname[0] = '\0';
|
||||
bool ret = dll_address_to_library_name(
|
||||
CAST_FROM_FN_PTR(address, os::jvm_path),
|
||||
dli_fname, sizeof(dli_fname), nullptr);
|
||||
assert(ret, "cannot locate libjvm");
|
||||
if (!ret) {
|
||||
return;
|
||||
}
|
||||
fname = dli_fname;
|
||||
#endif // AIX
|
||||
char* rp = nullptr;
|
||||
if (fname[0] != '\0') {
|
||||
rp = os::realpath(fname, buf, buflen);
|
||||
}
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If executing unit tests we require JAVA_HOME to point to the real JDK.
|
||||
if (Arguments::executing_unit_tests()) {
|
||||
// Look for JAVA_HOME in the environment.
|
||||
char* java_home_var = ::getenv("JAVA_HOME");
|
||||
if (java_home_var != nullptr && java_home_var[0] != 0) {
|
||||
|
||||
// Check the current module name "libjvm.so".
|
||||
const char* p = strrchr(buf, '/');
|
||||
if (p == nullptr) {
|
||||
return;
|
||||
}
|
||||
assert(strstr(p, "/libjvm") == p, "invalid library name");
|
||||
|
||||
stringStream ss(buf, buflen);
|
||||
rp = os::realpath(java_home_var, buf, buflen);
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert((int)strlen(buf) < buflen, "Ran out of buffer room");
|
||||
ss.print("%s/lib", buf);
|
||||
|
||||
// If the path exists within JAVA_HOME, add the VM variant directory and JVM
|
||||
// library name to complete the path to JVM being overridden. Otherwise fallback
|
||||
// to the path to the current library.
|
||||
if (0 == access(buf, F_OK)) {
|
||||
// Use current module name "libjvm.so"
|
||||
ss.print("/%s/libjvm%s", Abstract_VM_Version::vm_variant(), JNI_LIB_SUFFIX);
|
||||
assert(strcmp(buf + strlen(buf) - strlen(JNI_LIB_SUFFIX), JNI_LIB_SUFFIX) == 0,
|
||||
"buf has been truncated");
|
||||
} else {
|
||||
// Go back to path of .so
|
||||
rp = os::realpath(fname, buf, buflen);
|
||||
if (rp == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(saved_jvm_path, buf, MAXPATHLEN);
|
||||
saved_jvm_path[MAXPATHLEN - 1] = '\0';
|
||||
}
|
||||
|
||||
// Called when creating the thread. The minimum stack sizes have already been calculated
|
||||
size_t os::Posix::get_initial_stack_size(ThreadType thr_type, size_t req_stack_size) {
|
||||
size_t stack_size;
|
||||
|
@ -481,7 +481,3 @@ int get_legal_text(FileBuff &fbuf, char **legal_text)
|
||||
*legal_text = legal_start;
|
||||
return (int) (legal_end - legal_start);
|
||||
}
|
||||
|
||||
void *operator new( size_t size, int, const char *, int ) throw() {
|
||||
return ::operator new( size );
|
||||
}
|
||||
|
@ -1626,6 +1626,8 @@ void ArchDesc::declareClasses(FILE *fp) {
|
||||
while (attr != nullptr) {
|
||||
if (strcmp (attr->_ident, "ins_is_TrapBasedCheckNode") == 0) {
|
||||
fprintf(fp, " virtual bool is_TrapBasedCheckNode() const { return %s; }\n", attr->_val);
|
||||
} else if (strcmp (attr->_ident, "ins_is_late_expanded_null_check_candidate") == 0) {
|
||||
fprintf(fp, " virtual bool is_late_expanded_null_check_candidate() const { return %s; }\n", attr->_val);
|
||||
} else if (strcmp (attr->_ident, "ins_cost") != 0 &&
|
||||
strncmp(attr->_ident, "ins_field_", 10) != 0 &&
|
||||
// Must match function in node.hpp: return type bool, no prefix "ins_".
|
||||
|
@ -110,12 +110,24 @@ const char* CDSConfig::default_archive_path() {
|
||||
// before CDSConfig::ergo_initialize() is called.
|
||||
assert(_cds_ergo_initialize_started, "sanity");
|
||||
if (_default_archive_path == nullptr) {
|
||||
char jvm_path[JVM_MAXPATHLEN];
|
||||
os::jvm_path(jvm_path, sizeof(jvm_path));
|
||||
char *end = strrchr(jvm_path, *os::file_separator());
|
||||
if (end != nullptr) *end = '\0';
|
||||
stringStream tmp;
|
||||
tmp.print("%s%sclasses", jvm_path, os::file_separator());
|
||||
if (is_vm_statically_linked()) {
|
||||
// It's easier to form the path using JAVA_HOME as os::jvm_path
|
||||
// gives the path to the launcher executable on static JDK.
|
||||
const char* subdir = WINDOWS_ONLY("bin") NOT_WINDOWS("lib");
|
||||
tmp.print("%s%s%s%s%s%sclasses",
|
||||
Arguments::get_java_home(), os::file_separator(),
|
||||
subdir, os::file_separator(),
|
||||
Abstract_VM_Version::vm_variant(), os::file_separator());
|
||||
} else {
|
||||
// Assume .jsa is in the same directory where libjvm resides on
|
||||
// non-static JDK.
|
||||
char jvm_path[JVM_MAXPATHLEN];
|
||||
os::jvm_path(jvm_path, sizeof(jvm_path));
|
||||
char *end = strrchr(jvm_path, *os::file_separator());
|
||||
if (end != nullptr) *end = '\0';
|
||||
tmp.print("%s%sclasses", jvm_path, os::file_separator());
|
||||
}
|
||||
#ifdef _LP64
|
||||
if (!UseCompressedOops) {
|
||||
tmp.print_raw("_nocoops");
|
||||
|
@ -3740,6 +3740,7 @@ void ClassFileParser::apply_parsed_class_metadata(
|
||||
_cp->set_pool_holder(this_klass);
|
||||
this_klass->set_constants(_cp);
|
||||
this_klass->set_fieldinfo_stream(_fieldinfo_stream);
|
||||
this_klass->set_fieldinfo_search_table(_fieldinfo_search_table);
|
||||
this_klass->set_fields_status(_fields_status);
|
||||
this_klass->set_methods(_methods);
|
||||
this_klass->set_inner_classes(_inner_classes);
|
||||
@ -3749,6 +3750,8 @@ void ClassFileParser::apply_parsed_class_metadata(
|
||||
this_klass->set_permitted_subclasses(_permitted_subclasses);
|
||||
this_klass->set_record_components(_record_components);
|
||||
|
||||
DEBUG_ONLY(FieldInfoStream::validate_search_table(_cp, _fieldinfo_stream, _fieldinfo_search_table));
|
||||
|
||||
// Delay the setting of _local_interfaces and _transitive_interfaces until after
|
||||
// initialize_supers() in fill_instance_klass(). It is because the _local_interfaces could
|
||||
// be shared with _transitive_interfaces and _transitive_interfaces may be shared with
|
||||
@ -5056,6 +5059,7 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik,
|
||||
// note that is not safe to use the fields in the parser from this point on
|
||||
assert(nullptr == _cp, "invariant");
|
||||
assert(nullptr == _fieldinfo_stream, "invariant");
|
||||
assert(nullptr == _fieldinfo_search_table, "invariant");
|
||||
assert(nullptr == _fields_status, "invariant");
|
||||
assert(nullptr == _methods, "invariant");
|
||||
assert(nullptr == _inner_classes, "invariant");
|
||||
@ -5276,6 +5280,7 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream,
|
||||
_super_klass(),
|
||||
_cp(nullptr),
|
||||
_fieldinfo_stream(nullptr),
|
||||
_fieldinfo_search_table(nullptr),
|
||||
_fields_status(nullptr),
|
||||
_methods(nullptr),
|
||||
_inner_classes(nullptr),
|
||||
@ -5352,6 +5357,7 @@ void ClassFileParser::clear_class_metadata() {
|
||||
// deallocated if classfile parsing returns an error.
|
||||
_cp = nullptr;
|
||||
_fieldinfo_stream = nullptr;
|
||||
_fieldinfo_search_table = nullptr;
|
||||
_fields_status = nullptr;
|
||||
_methods = nullptr;
|
||||
_inner_classes = nullptr;
|
||||
@ -5374,6 +5380,7 @@ ClassFileParser::~ClassFileParser() {
|
||||
if (_fieldinfo_stream != nullptr) {
|
||||
MetadataFactory::free_array<u1>(_loader_data, _fieldinfo_stream);
|
||||
}
|
||||
MetadataFactory::free_array<u1>(_loader_data, _fieldinfo_search_table);
|
||||
|
||||
if (_fields_status != nullptr) {
|
||||
MetadataFactory::free_array<FieldStatus>(_loader_data, _fields_status);
|
||||
@ -5774,6 +5781,7 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st
|
||||
_fieldinfo_stream =
|
||||
FieldInfoStream::create_FieldInfoStream(_temp_field_info, _java_fields_count,
|
||||
injected_fields_count, loader_data(), CHECK);
|
||||
_fieldinfo_search_table = FieldInfoStream::create_search_table(_cp, _fieldinfo_stream, _loader_data, CHECK);
|
||||
_fields_status =
|
||||
MetadataFactory::new_array<FieldStatus>(_loader_data, _temp_field_info->length(),
|
||||
FieldStatus(0), CHECK);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -123,6 +123,7 @@ class ClassFileParser {
|
||||
const InstanceKlass* _super_klass;
|
||||
ConstantPool* _cp;
|
||||
Array<u1>* _fieldinfo_stream;
|
||||
Array<u1>* _fieldinfo_search_table;
|
||||
Array<FieldStatus>* _fields_status;
|
||||
Array<Method*>* _methods;
|
||||
Array<u2>* _inner_classes;
|
||||
|
@ -301,7 +301,7 @@ void FieldLayout::reconstruct_layout(const InstanceKlass* ik, bool& has_instance
|
||||
BasicType last_type;
|
||||
int last_offset = -1;
|
||||
while (ik != nullptr) {
|
||||
for (AllFieldStream fs(ik->fieldinfo_stream(), ik->constants()); !fs.done(); fs.next()) {
|
||||
for (AllFieldStream fs(ik); !fs.done(); fs.next()) {
|
||||
BasicType type = Signature::basic_type(fs.signature());
|
||||
// distinction between static and non-static fields is missing
|
||||
if (fs.access_flags().is_static()) continue;
|
||||
@ -461,7 +461,7 @@ void FieldLayout::print(outputStream* output, bool is_static, const InstanceKlas
|
||||
bool found = false;
|
||||
const InstanceKlass* ik = super;
|
||||
while (!found && ik != nullptr) {
|
||||
for (AllFieldStream fs(ik->fieldinfo_stream(), ik->constants()); !fs.done(); fs.next()) {
|
||||
for (AllFieldStream fs(ik); !fs.done(); fs.next()) {
|
||||
if (fs.offset() == b->offset()) {
|
||||
output->print_cr(" @%d \"%s\" %s %d/%d %s",
|
||||
b->offset(),
|
||||
|
@ -967,6 +967,13 @@ void java_lang_Class::fixup_mirror(Klass* k, TRAPS) {
|
||||
Array<u1>* new_fis = FieldInfoStream::create_FieldInfoStream(fields, java_fields, injected_fields, k->class_loader_data(), CHECK);
|
||||
ik->set_fieldinfo_stream(new_fis);
|
||||
MetadataFactory::free_array<u1>(k->class_loader_data(), old_stream);
|
||||
|
||||
Array<u1>* old_table = ik->fieldinfo_search_table();
|
||||
Array<u1>* search_table = FieldInfoStream::create_search_table(ik->constants(), new_fis, k->class_loader_data(), CHECK);
|
||||
ik->set_fieldinfo_search_table(search_table);
|
||||
MetadataFactory::free_array<u1>(k->class_loader_data(), old_table);
|
||||
|
||||
DEBUG_ONLY(FieldInfoStream::validate_search_table(ik->constants(), new_fis, search_table));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,8 +289,6 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) {
|
||||
case vmIntrinsics::_dsin:
|
||||
case vmIntrinsics::_dcos:
|
||||
case vmIntrinsics::_dtan:
|
||||
case vmIntrinsics::_dtanh:
|
||||
case vmIntrinsics::_dcbrt:
|
||||
case vmIntrinsics::_dlog:
|
||||
case vmIntrinsics::_dexp:
|
||||
case vmIntrinsics::_dpow:
|
||||
@ -316,6 +314,13 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) {
|
||||
case vmIntrinsics::_fmaF:
|
||||
if (!InlineMathNatives || !UseFMA) return true;
|
||||
break;
|
||||
case vmIntrinsics::_dtanh:
|
||||
case vmIntrinsics::_dcbrt:
|
||||
if (!InlineMathNatives || !InlineIntrinsics) return true;
|
||||
#if defined(AMD64) && (defined(COMPILER1) || defined(COMPILER2))
|
||||
if (!UseLibmIntrinsic) return true;
|
||||
#endif
|
||||
break;
|
||||
case vmIntrinsics::_floatToFloat16:
|
||||
case vmIntrinsics::_float16ToFloat:
|
||||
if (!InlineIntrinsics) return true;
|
||||
|
@ -98,15 +98,15 @@ void ParallelArguments::initialize() {
|
||||
FullGCForwarding::initialize_flags(heap_reserved_size_bytes());
|
||||
}
|
||||
|
||||
// The alignment used for boundary between young gen and old gen
|
||||
static size_t default_gen_alignment() {
|
||||
// The alignment used for spaces in young gen and old gen
|
||||
static size_t default_space_alignment() {
|
||||
return 64 * K * HeapWordSize;
|
||||
}
|
||||
|
||||
void ParallelArguments::initialize_alignments() {
|
||||
// Initialize card size before initializing alignments
|
||||
CardTable::initialize_card_size();
|
||||
SpaceAlignment = GenAlignment = default_gen_alignment();
|
||||
SpaceAlignment = default_space_alignment();
|
||||
HeapAlignment = compute_heap_alignment();
|
||||
}
|
||||
|
||||
@ -123,9 +123,8 @@ void ParallelArguments::initialize_heap_flags_and_sizes() {
|
||||
|
||||
// Can a page size be something else than a power of two?
|
||||
assert(is_power_of_2((intptr_t)page_sz), "must be a power of 2");
|
||||
size_t new_alignment = align_up(page_sz, GenAlignment);
|
||||
if (new_alignment != GenAlignment) {
|
||||
GenAlignment = new_alignment;
|
||||
size_t new_alignment = align_up(page_sz, SpaceAlignment);
|
||||
if (new_alignment != SpaceAlignment) {
|
||||
SpaceAlignment = new_alignment;
|
||||
// Redo everything from the start
|
||||
initialize_heap_flags_and_sizes_one_pass();
|
||||
|
@ -29,10 +29,8 @@
|
||||
void ParallelInitLogger::print_heap() {
|
||||
log_info_p(gc, init)("Alignments:"
|
||||
" Space " EXACTFMT ","
|
||||
" Generation " EXACTFMT ","
|
||||
" Heap " EXACTFMT,
|
||||
EXACTFMTARGS(SpaceAlignment),
|
||||
EXACTFMTARGS(GenAlignment),
|
||||
EXACTFMTARGS(HeapAlignment));
|
||||
GCInitLogger::print_heap();
|
||||
}
|
||||
|
@ -69,8 +69,8 @@ jint ParallelScavengeHeap::initialize() {
|
||||
|
||||
initialize_reserved_region(heap_rs);
|
||||
// Layout the reserved space for the generations.
|
||||
ReservedSpace old_rs = heap_rs.first_part(MaxOldSize, GenAlignment);
|
||||
ReservedSpace young_rs = heap_rs.last_part(MaxOldSize, GenAlignment);
|
||||
ReservedSpace old_rs = heap_rs.first_part(MaxOldSize, SpaceAlignment);
|
||||
ReservedSpace young_rs = heap_rs.last_part(MaxOldSize, SpaceAlignment);
|
||||
assert(young_rs.size() == MaxNewSize, "Didn't reserve all of the heap");
|
||||
|
||||
PSCardTable* card_table = new PSCardTable(_reserved);
|
||||
@ -107,7 +107,7 @@ jint ParallelScavengeHeap::initialize() {
|
||||
new PSAdaptiveSizePolicy(eden_capacity,
|
||||
initial_promo_size,
|
||||
young_gen()->to_space()->capacity_in_bytes(),
|
||||
GenAlignment,
|
||||
SpaceAlignment,
|
||||
max_gc_pause_sec,
|
||||
GCTimeRatio
|
||||
);
|
||||
|
@ -41,7 +41,7 @@ PSOldGen::PSOldGen(ReservedSpace rs, size_t initial_size, size_t min_size,
|
||||
_min_gen_size(min_size),
|
||||
_max_gen_size(max_size)
|
||||
{
|
||||
initialize(rs, initial_size, GenAlignment);
|
||||
initialize(rs, initial_size, SpaceAlignment);
|
||||
}
|
||||
|
||||
void PSOldGen::initialize(ReservedSpace rs, size_t initial_size, size_t alignment) {
|
||||
|
@ -47,7 +47,7 @@ PSYoungGen::PSYoungGen(ReservedSpace rs, size_t initial_size, size_t min_size, s
|
||||
_from_counters(nullptr),
|
||||
_to_counters(nullptr)
|
||||
{
|
||||
initialize(rs, initial_size, GenAlignment);
|
||||
initialize(rs, initial_size, SpaceAlignment);
|
||||
}
|
||||
|
||||
void PSYoungGen::initialize_virtual_space(ReservedSpace rs,
|
||||
@ -746,7 +746,7 @@ size_t PSYoungGen::available_to_live() {
|
||||
}
|
||||
|
||||
size_t delta_in_bytes = unused_committed + delta_in_survivor;
|
||||
delta_in_bytes = align_down(delta_in_bytes, GenAlignment);
|
||||
delta_in_bytes = align_down(delta_in_bytes, SpaceAlignment);
|
||||
return delta_in_bytes;
|
||||
}
|
||||
|
||||
|
@ -188,8 +188,8 @@ jint SerialHeap::initialize() {
|
||||
|
||||
initialize_reserved_region(heap_rs);
|
||||
|
||||
ReservedSpace young_rs = heap_rs.first_part(MaxNewSize, GenAlignment);
|
||||
ReservedSpace old_rs = heap_rs.last_part(MaxNewSize, GenAlignment);
|
||||
ReservedSpace young_rs = heap_rs.first_part(MaxNewSize, SpaceAlignment);
|
||||
ReservedSpace old_rs = heap_rs.last_part(MaxNewSize, SpaceAlignment);
|
||||
|
||||
_rem_set = new CardTableRS(_reserved);
|
||||
_rem_set->initialize(young_rs.base(), old_rs.base());
|
||||
|
@ -35,7 +35,7 @@ extern size_t SpaceAlignment;
|
||||
|
||||
class GCArguments {
|
||||
protected:
|
||||
// Initialize HeapAlignment, SpaceAlignment, and extra alignments (E.g. GenAlignment)
|
||||
// Initialize HeapAlignment, SpaceAlignment
|
||||
virtual void initialize_alignments() = 0;
|
||||
virtual void initialize_heap_flags_and_sizes();
|
||||
virtual void initialize_size_info();
|
||||
|
@ -42,17 +42,15 @@ size_t MaxOldSize = 0;
|
||||
// See more in JDK-8346005
|
||||
size_t OldSize = ScaleForWordSize(4*M);
|
||||
|
||||
size_t GenAlignment = 0;
|
||||
|
||||
size_t GenArguments::conservative_max_heap_alignment() { return (size_t)Generation::GenGrain; }
|
||||
|
||||
static size_t young_gen_size_lower_bound() {
|
||||
// The young generation must be aligned and have room for eden + two survivors
|
||||
return align_up(3 * SpaceAlignment, GenAlignment);
|
||||
return 3 * SpaceAlignment;
|
||||
}
|
||||
|
||||
static size_t old_gen_size_lower_bound() {
|
||||
return align_up(SpaceAlignment, GenAlignment);
|
||||
return SpaceAlignment;
|
||||
}
|
||||
|
||||
size_t GenArguments::scale_by_NewRatio_aligned(size_t base_size, size_t alignment) {
|
||||
@ -69,23 +67,20 @@ static size_t bound_minus_alignment(size_t desired_size,
|
||||
void GenArguments::initialize_alignments() {
|
||||
// Initialize card size before initializing alignments
|
||||
CardTable::initialize_card_size();
|
||||
SpaceAlignment = GenAlignment = (size_t)Generation::GenGrain;
|
||||
SpaceAlignment = (size_t)Generation::GenGrain;
|
||||
HeapAlignment = compute_heap_alignment();
|
||||
}
|
||||
|
||||
void GenArguments::initialize_heap_flags_and_sizes() {
|
||||
GCArguments::initialize_heap_flags_and_sizes();
|
||||
|
||||
assert(GenAlignment != 0, "Generation alignment not set up properly");
|
||||
assert(HeapAlignment >= GenAlignment,
|
||||
"HeapAlignment: %zu less than GenAlignment: %zu",
|
||||
HeapAlignment, GenAlignment);
|
||||
assert(GenAlignment % SpaceAlignment == 0,
|
||||
"GenAlignment: %zu not aligned by SpaceAlignment: %zu",
|
||||
GenAlignment, SpaceAlignment);
|
||||
assert(HeapAlignment % GenAlignment == 0,
|
||||
"HeapAlignment: %zu not aligned by GenAlignment: %zu",
|
||||
HeapAlignment, GenAlignment);
|
||||
assert(SpaceAlignment != 0, "Generation alignment not set up properly");
|
||||
assert(HeapAlignment >= SpaceAlignment,
|
||||
"HeapAlignment: %zu less than SpaceAlignment: %zu",
|
||||
HeapAlignment, SpaceAlignment);
|
||||
assert(HeapAlignment % SpaceAlignment == 0,
|
||||
"HeapAlignment: %zu not aligned by SpaceAlignment: %zu",
|
||||
HeapAlignment, SpaceAlignment);
|
||||
|
||||
// All generational heaps have a young gen; handle those flags here
|
||||
|
||||
@ -106,7 +101,7 @@ void GenArguments::initialize_heap_flags_and_sizes() {
|
||||
|
||||
// Make sure NewSize allows an old generation to fit even if set on the command line
|
||||
if (FLAG_IS_CMDLINE(NewSize) && NewSize >= InitialHeapSize) {
|
||||
size_t revised_new_size = bound_minus_alignment(NewSize, InitialHeapSize, GenAlignment);
|
||||
size_t revised_new_size = bound_minus_alignment(NewSize, InitialHeapSize, SpaceAlignment);
|
||||
log_warning(gc, ergo)("NewSize (%zuk) is equal to or greater than initial heap size (%zuk). A new "
|
||||
"NewSize of %zuk will be used to accomodate an old generation.",
|
||||
NewSize/K, InitialHeapSize/K, revised_new_size/K);
|
||||
@ -115,8 +110,8 @@ void GenArguments::initialize_heap_flags_and_sizes() {
|
||||
|
||||
// Now take the actual NewSize into account. We will silently increase NewSize
|
||||
// if the user specified a smaller or unaligned value.
|
||||
size_t bounded_new_size = bound_minus_alignment(NewSize, MaxHeapSize, GenAlignment);
|
||||
bounded_new_size = MAX2(smallest_new_size, align_down(bounded_new_size, GenAlignment));
|
||||
size_t bounded_new_size = bound_minus_alignment(NewSize, MaxHeapSize, SpaceAlignment);
|
||||
bounded_new_size = MAX2(smallest_new_size, align_down(bounded_new_size, SpaceAlignment));
|
||||
if (bounded_new_size != NewSize) {
|
||||
FLAG_SET_ERGO(NewSize, bounded_new_size);
|
||||
}
|
||||
@ -125,7 +120,7 @@ void GenArguments::initialize_heap_flags_and_sizes() {
|
||||
if (!FLAG_IS_DEFAULT(MaxNewSize)) {
|
||||
if (MaxNewSize >= MaxHeapSize) {
|
||||
// Make sure there is room for an old generation
|
||||
size_t smaller_max_new_size = MaxHeapSize - GenAlignment;
|
||||
size_t smaller_max_new_size = MaxHeapSize - SpaceAlignment;
|
||||
if (FLAG_IS_CMDLINE(MaxNewSize)) {
|
||||
log_warning(gc, ergo)("MaxNewSize (%zuk) is equal to or greater than the entire "
|
||||
"heap (%zuk). A new max generation size of %zuk will be used.",
|
||||
@ -137,8 +132,8 @@ void GenArguments::initialize_heap_flags_and_sizes() {
|
||||
}
|
||||
} else if (MaxNewSize < NewSize) {
|
||||
FLAG_SET_ERGO(MaxNewSize, NewSize);
|
||||
} else if (!is_aligned(MaxNewSize, GenAlignment)) {
|
||||
FLAG_SET_ERGO(MaxNewSize, align_down(MaxNewSize, GenAlignment));
|
||||
} else if (!is_aligned(MaxNewSize, SpaceAlignment)) {
|
||||
FLAG_SET_ERGO(MaxNewSize, align_down(MaxNewSize, SpaceAlignment));
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,13 +161,13 @@ void GenArguments::initialize_heap_flags_and_sizes() {
|
||||
// exceed it. Adjust New/OldSize as necessary.
|
||||
size_t calculated_size = NewSize + OldSize;
|
||||
double shrink_factor = (double) MaxHeapSize / calculated_size;
|
||||
size_t smaller_new_size = align_down((size_t)(NewSize * shrink_factor), GenAlignment);
|
||||
size_t smaller_new_size = align_down((size_t)(NewSize * shrink_factor), SpaceAlignment);
|
||||
FLAG_SET_ERGO(NewSize, MAX2(young_gen_size_lower_bound(), smaller_new_size));
|
||||
|
||||
// OldSize is already aligned because above we aligned MaxHeapSize to
|
||||
// HeapAlignment, and we just made sure that NewSize is aligned to
|
||||
// GenAlignment. In initialize_flags() we verified that HeapAlignment
|
||||
// is a multiple of GenAlignment.
|
||||
// SpaceAlignment. In initialize_flags() we verified that HeapAlignment
|
||||
// is a multiple of SpaceAlignment.
|
||||
OldSize = MaxHeapSize - NewSize;
|
||||
} else {
|
||||
FLAG_SET_ERGO(MaxHeapSize, align_up(NewSize + OldSize, HeapAlignment));
|
||||
@ -200,7 +195,7 @@ void GenArguments::initialize_size_info() {
|
||||
// Determine maximum size of the young generation.
|
||||
|
||||
if (FLAG_IS_DEFAULT(MaxNewSize)) {
|
||||
max_young_size = scale_by_NewRatio_aligned(MaxHeapSize, GenAlignment);
|
||||
max_young_size = scale_by_NewRatio_aligned(MaxHeapSize, SpaceAlignment);
|
||||
// Bound the maximum size by NewSize below (since it historically
|
||||
// would have been NewSize and because the NewRatio calculation could
|
||||
// yield a size that is too small) and bound it by MaxNewSize above.
|
||||
@ -229,18 +224,18 @@ void GenArguments::initialize_size_info() {
|
||||
// If NewSize is set on the command line, we should use it as
|
||||
// the initial size, but make sure it is within the heap bounds.
|
||||
initial_young_size =
|
||||
MIN2(max_young_size, bound_minus_alignment(NewSize, InitialHeapSize, GenAlignment));
|
||||
MinNewSize = bound_minus_alignment(initial_young_size, MinHeapSize, GenAlignment);
|
||||
MIN2(max_young_size, bound_minus_alignment(NewSize, InitialHeapSize, SpaceAlignment));
|
||||
MinNewSize = bound_minus_alignment(initial_young_size, MinHeapSize, SpaceAlignment);
|
||||
} else {
|
||||
// For the case where NewSize is not set on the command line, use
|
||||
// NewRatio to size the initial generation size. Use the current
|
||||
// NewSize as the floor, because if NewRatio is overly large, the resulting
|
||||
// size can be too small.
|
||||
initial_young_size =
|
||||
clamp(scale_by_NewRatio_aligned(InitialHeapSize, GenAlignment), NewSize, max_young_size);
|
||||
clamp(scale_by_NewRatio_aligned(InitialHeapSize, SpaceAlignment), NewSize, max_young_size);
|
||||
|
||||
// Derive MinNewSize from MinHeapSize
|
||||
MinNewSize = MIN2(scale_by_NewRatio_aligned(MinHeapSize, GenAlignment), initial_young_size);
|
||||
MinNewSize = MIN2(scale_by_NewRatio_aligned(MinHeapSize, SpaceAlignment), initial_young_size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,7 +247,7 @@ void GenArguments::initialize_size_info() {
|
||||
// The maximum old size can be determined from the maximum young
|
||||
// and maximum heap size since no explicit flags exist
|
||||
// for setting the old generation maximum.
|
||||
MaxOldSize = MAX2(MaxHeapSize - max_young_size, GenAlignment);
|
||||
MaxOldSize = MAX2(MaxHeapSize - max_young_size, SpaceAlignment);
|
||||
MinOldSize = MIN3(MaxOldSize,
|
||||
InitialHeapSize - initial_young_size,
|
||||
MinHeapSize - MinNewSize);
|
||||
@ -315,10 +310,10 @@ void GenArguments::assert_flags() {
|
||||
assert(NewSize >= MinNewSize, "Ergonomics decided on a too small young gen size");
|
||||
assert(NewSize <= MaxNewSize, "Ergonomics decided on incompatible initial and maximum young gen sizes");
|
||||
assert(FLAG_IS_DEFAULT(MaxNewSize) || MaxNewSize < MaxHeapSize, "Ergonomics decided on incompatible maximum young gen and heap sizes");
|
||||
assert(NewSize % GenAlignment == 0, "NewSize alignment");
|
||||
assert(FLAG_IS_DEFAULT(MaxNewSize) || MaxNewSize % GenAlignment == 0, "MaxNewSize alignment");
|
||||
assert(NewSize % SpaceAlignment == 0, "NewSize alignment");
|
||||
assert(FLAG_IS_DEFAULT(MaxNewSize) || MaxNewSize % SpaceAlignment == 0, "MaxNewSize alignment");
|
||||
assert(OldSize + NewSize <= MaxHeapSize, "Ergonomics decided on incompatible generation and heap sizes");
|
||||
assert(OldSize % GenAlignment == 0, "OldSize alignment");
|
||||
assert(OldSize % SpaceAlignment == 0, "OldSize alignment");
|
||||
}
|
||||
|
||||
void GenArguments::assert_size_info() {
|
||||
@ -327,19 +322,19 @@ void GenArguments::assert_size_info() {
|
||||
assert(MaxNewSize < MaxHeapSize, "Ergonomics decided on incompatible maximum young and heap sizes");
|
||||
assert(MinNewSize <= NewSize, "Ergonomics decided on incompatible minimum and initial young gen sizes");
|
||||
assert(NewSize <= MaxNewSize, "Ergonomics decided on incompatible initial and maximum young gen sizes");
|
||||
assert(MinNewSize % GenAlignment == 0, "_min_young_size alignment");
|
||||
assert(NewSize % GenAlignment == 0, "_initial_young_size alignment");
|
||||
assert(MaxNewSize % GenAlignment == 0, "MaxNewSize alignment");
|
||||
assert(MinNewSize <= bound_minus_alignment(MinNewSize, MinHeapSize, GenAlignment),
|
||||
assert(MinNewSize % SpaceAlignment == 0, "_min_young_size alignment");
|
||||
assert(NewSize % SpaceAlignment == 0, "_initial_young_size alignment");
|
||||
assert(MaxNewSize % SpaceAlignment == 0, "MaxNewSize alignment");
|
||||
assert(MinNewSize <= bound_minus_alignment(MinNewSize, MinHeapSize, SpaceAlignment),
|
||||
"Ergonomics made minimum young generation larger than minimum heap");
|
||||
assert(NewSize <= bound_minus_alignment(NewSize, InitialHeapSize, GenAlignment),
|
||||
assert(NewSize <= bound_minus_alignment(NewSize, InitialHeapSize, SpaceAlignment),
|
||||
"Ergonomics made initial young generation larger than initial heap");
|
||||
assert(MaxNewSize <= bound_minus_alignment(MaxNewSize, MaxHeapSize, GenAlignment),
|
||||
assert(MaxNewSize <= bound_minus_alignment(MaxNewSize, MaxHeapSize, SpaceAlignment),
|
||||
"Ergonomics made maximum young generation lager than maximum heap");
|
||||
assert(MinOldSize <= OldSize, "Ergonomics decided on incompatible minimum and initial old gen sizes");
|
||||
assert(OldSize <= MaxOldSize, "Ergonomics decided on incompatible initial and maximum old gen sizes");
|
||||
assert(MaxOldSize % GenAlignment == 0, "MaxOldSize alignment");
|
||||
assert(OldSize % GenAlignment == 0, "OldSize alignment");
|
||||
assert(MaxOldSize % SpaceAlignment == 0, "MaxOldSize alignment");
|
||||
assert(OldSize % SpaceAlignment == 0, "OldSize alignment");
|
||||
assert(MaxHeapSize <= (MaxNewSize + MaxOldSize), "Total maximum heap sizes must be sum of generation maximum sizes");
|
||||
assert(MinNewSize + MinOldSize <= MinHeapSize, "Minimum generation sizes exceed minimum heap size");
|
||||
assert(NewSize + OldSize == InitialHeapSize, "Initial generation sizes should match initial heap size");
|
||||
|
@ -35,8 +35,6 @@ extern size_t MaxOldSize;
|
||||
|
||||
extern size_t OldSize;
|
||||
|
||||
extern size_t GenAlignment;
|
||||
|
||||
class GenArguments : public GCArguments {
|
||||
friend class TestGenCollectorPolicy; // Testing
|
||||
private:
|
||||
|
@ -625,6 +625,34 @@ void ShenandoahBarrierC2Support::verify(RootNode* root) {
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ShenandoahBarrierC2Support::is_anti_dependent_load_at_control(PhaseIdealLoop* phase, Node* maybe_load, Node* store,
|
||||
Node* control) {
|
||||
return maybe_load->is_Load() && phase->C->can_alias(store->adr_type(), phase->C->get_alias_index(maybe_load->adr_type())) &&
|
||||
phase->ctrl_or_self(maybe_load) == control;
|
||||
}
|
||||
|
||||
void ShenandoahBarrierC2Support::maybe_push_anti_dependent_loads(PhaseIdealLoop* phase, Node* maybe_store, Node* control, Unique_Node_List &wq) {
|
||||
if (!maybe_store->is_Store() && !maybe_store->is_LoadStore()) {
|
||||
return;
|
||||
}
|
||||
Node* mem = maybe_store->in(MemNode::Memory);
|
||||
for (DUIterator_Fast imax, i = mem->fast_outs(imax); i < imax; i++) {
|
||||
Node* u = mem->fast_out(i);
|
||||
if (is_anti_dependent_load_at_control(phase, u, maybe_store, control)) {
|
||||
wq.push(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ShenandoahBarrierC2Support::push_data_inputs_at_control(PhaseIdealLoop* phase, Node* n, Node* ctrl, Unique_Node_List &wq) {
|
||||
for (uint i = 0; i < n->req(); i++) {
|
||||
Node* in = n->in(i);
|
||||
if (in != nullptr && phase->has_ctrl(in) && phase->get_ctrl(in) == ctrl) {
|
||||
wq.push(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ShenandoahBarrierC2Support::is_dominator_same_ctrl(Node* c, Node* d, Node* n, PhaseIdealLoop* phase) {
|
||||
// That both nodes have the same control is not sufficient to prove
|
||||
// domination, verify that there's no path from d to n
|
||||
@ -639,22 +667,9 @@ bool ShenandoahBarrierC2Support::is_dominator_same_ctrl(Node* c, Node* d, Node*
|
||||
if (m->is_Phi() && m->in(0)->is_Loop()) {
|
||||
assert(phase->ctrl_or_self(m->in(LoopNode::EntryControl)) != c, "following loop entry should lead to new control");
|
||||
} else {
|
||||
if (m->is_Store() || m->is_LoadStore()) {
|
||||
// Take anti-dependencies into account
|
||||
Node* mem = m->in(MemNode::Memory);
|
||||
for (DUIterator_Fast imax, i = mem->fast_outs(imax); i < imax; i++) {
|
||||
Node* u = mem->fast_out(i);
|
||||
if (u->is_Load() && phase->C->can_alias(m->adr_type(), phase->C->get_alias_index(u->adr_type())) &&
|
||||
phase->ctrl_or_self(u) == c) {
|
||||
wq.push(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint i = 0; i < m->req(); i++) {
|
||||
if (m->in(i) != nullptr && phase->ctrl_or_self(m->in(i)) == c) {
|
||||
wq.push(m->in(i));
|
||||
}
|
||||
}
|
||||
// Take anti-dependencies into account
|
||||
maybe_push_anti_dependent_loads(phase, m, c, wq);
|
||||
push_data_inputs_at_control(phase, m, c, wq);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@ -1006,7 +1021,20 @@ void ShenandoahBarrierC2Support::call_lrb_stub(Node*& ctrl, Node*& val, Node* lo
|
||||
phase->register_new_node(val, ctrl);
|
||||
}
|
||||
|
||||
void ShenandoahBarrierC2Support::fix_ctrl(Node* barrier, Node* region, const MemoryGraphFixer& fixer, Unique_Node_List& uses, Unique_Node_List& uses_to_ignore, uint last, PhaseIdealLoop* phase) {
|
||||
void ShenandoahBarrierC2Support::collect_nodes_above_barrier(Unique_Node_List &nodes_above_barrier, PhaseIdealLoop* phase, Node* ctrl, Node* init_raw_mem) {
|
||||
nodes_above_barrier.clear();
|
||||
if (phase->has_ctrl(init_raw_mem) && phase->get_ctrl(init_raw_mem) == ctrl && !init_raw_mem->is_Phi()) {
|
||||
nodes_above_barrier.push(init_raw_mem);
|
||||
}
|
||||
for (uint next = 0; next < nodes_above_barrier.size(); next++) {
|
||||
Node* n = nodes_above_barrier.at(next);
|
||||
// Take anti-dependencies into account
|
||||
maybe_push_anti_dependent_loads(phase, n, ctrl, nodes_above_barrier);
|
||||
push_data_inputs_at_control(phase, n, ctrl, nodes_above_barrier);
|
||||
}
|
||||
}
|
||||
|
||||
void ShenandoahBarrierC2Support::fix_ctrl(Node* barrier, Node* region, const MemoryGraphFixer& fixer, Unique_Node_List& uses, Unique_Node_List& nodes_above_barrier, uint last, PhaseIdealLoop* phase) {
|
||||
Node* ctrl = phase->get_ctrl(barrier);
|
||||
Node* init_raw_mem = fixer.find_mem(ctrl, barrier);
|
||||
|
||||
@ -1017,30 +1045,17 @@ void ShenandoahBarrierC2Support::fix_ctrl(Node* barrier, Node* region, const Mem
|
||||
// control will be after the expanded barrier. The raw memory (if
|
||||
// its memory is control dependent on the barrier's input control)
|
||||
// must stay above the barrier.
|
||||
uses_to_ignore.clear();
|
||||
if (phase->has_ctrl(init_raw_mem) && phase->get_ctrl(init_raw_mem) == ctrl && !init_raw_mem->is_Phi()) {
|
||||
uses_to_ignore.push(init_raw_mem);
|
||||
}
|
||||
for (uint next = 0; next < uses_to_ignore.size(); next++) {
|
||||
Node *n = uses_to_ignore.at(next);
|
||||
for (uint i = 0; i < n->req(); i++) {
|
||||
Node* in = n->in(i);
|
||||
if (in != nullptr && phase->has_ctrl(in) && phase->get_ctrl(in) == ctrl) {
|
||||
uses_to_ignore.push(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
collect_nodes_above_barrier(nodes_above_barrier, phase, ctrl, init_raw_mem);
|
||||
for (DUIterator_Fast imax, i = ctrl->fast_outs(imax); i < imax; i++) {
|
||||
Node* u = ctrl->fast_out(i);
|
||||
if (u->_idx < last &&
|
||||
u != barrier &&
|
||||
!u->depends_only_on_test() && // preserve dependency on test
|
||||
!uses_to_ignore.member(u) &&
|
||||
!nodes_above_barrier.member(u) &&
|
||||
(u->in(0) != ctrl || (!u->is_Region() && !u->is_Phi())) &&
|
||||
(ctrl->Opcode() != Op_CatchProj || u->Opcode() != Op_CreateEx)) {
|
||||
Node* old_c = phase->ctrl_or_self(u);
|
||||
Node* c = old_c;
|
||||
if (c != ctrl ||
|
||||
if (old_c != ctrl ||
|
||||
is_dominator_same_ctrl(old_c, barrier, u, phase) ||
|
||||
ShenandoahBarrierSetC2::is_shenandoah_state_load(u)) {
|
||||
phase->igvn().rehash_node_delayed(u);
|
||||
@ -1315,7 +1330,7 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) {
|
||||
|
||||
// Expand load-reference-barriers
|
||||
MemoryGraphFixer fixer(Compile::AliasIdxRaw, true, phase);
|
||||
Unique_Node_List uses_to_ignore;
|
||||
Unique_Node_List nodes_above_barriers;
|
||||
for (int i = state->load_reference_barriers_count() - 1; i >= 0; i--) {
|
||||
ShenandoahLoadReferenceBarrierNode* lrb = state->load_reference_barrier(i);
|
||||
uint last = phase->C->unique();
|
||||
@ -1410,7 +1425,7 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) {
|
||||
Node* out_val = val_phi;
|
||||
phase->register_new_node(val_phi, region);
|
||||
|
||||
fix_ctrl(lrb, region, fixer, uses, uses_to_ignore, last, phase);
|
||||
fix_ctrl(lrb, region, fixer, uses, nodes_above_barriers, last, phase);
|
||||
|
||||
ctrl = orig_ctrl;
|
||||
|
||||
|
@ -62,8 +62,12 @@ private:
|
||||
PhaseIdealLoop* phase, int flags);
|
||||
static void call_lrb_stub(Node*& ctrl, Node*& val, Node* load_addr,
|
||||
DecoratorSet decorators, PhaseIdealLoop* phase);
|
||||
|
||||
static void collect_nodes_above_barrier(Unique_Node_List &nodes_above_barrier, PhaseIdealLoop* phase, Node* ctrl,
|
||||
Node* init_raw_mem);
|
||||
|
||||
static void test_in_cset(Node*& ctrl, Node*& not_cset_ctrl, Node* val, Node* raw_mem, PhaseIdealLoop* phase);
|
||||
static void fix_ctrl(Node* barrier, Node* region, const MemoryGraphFixer& fixer, Unique_Node_List& uses, Unique_Node_List& uses_to_ignore, uint last, PhaseIdealLoop* phase);
|
||||
static void fix_ctrl(Node* barrier, Node* region, const MemoryGraphFixer& fixer, Unique_Node_List& uses, Unique_Node_List& nodes_above_barrier, uint last, PhaseIdealLoop* phase);
|
||||
|
||||
static Node* get_load_addr(PhaseIdealLoop* phase, VectorSet& visited, Node* lrb);
|
||||
public:
|
||||
@ -76,6 +80,11 @@ public:
|
||||
static bool expand(Compile* C, PhaseIterGVN& igvn);
|
||||
static void pin_and_expand(PhaseIdealLoop* phase);
|
||||
|
||||
static void push_data_inputs_at_control(PhaseIdealLoop* phase, Node* n, Node* ctrl,
|
||||
Unique_Node_List &wq);
|
||||
static bool is_anti_dependent_load_at_control(PhaseIdealLoop* phase, Node* maybe_load, Node* store, Node* control);
|
||||
|
||||
static void maybe_push_anti_dependent_loads(PhaseIdealLoop* phase, Node* maybe_store, Node* control, Unique_Node_List &wq);
|
||||
#ifdef ASSERT
|
||||
static void verify(RootNode* root);
|
||||
#endif
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include "gc/z/zAllocator.hpp"
|
||||
#include "gc/z/zObjectAllocator.hpp"
|
||||
#include "gc/z/zPageAge.inline.hpp"
|
||||
|
||||
ZAllocatorEden* ZAllocator::_eden;
|
||||
ZAllocatorForRelocation* ZAllocator::_relocation[ZAllocator::_relocation_allocators];
|
||||
@ -47,7 +48,7 @@ ZPageAge ZAllocatorForRelocation::install() {
|
||||
for (uint i = 0; i < ZAllocator::_relocation_allocators; ++i) {
|
||||
if (_relocation[i] == nullptr) {
|
||||
_relocation[i] = this;
|
||||
return static_cast<ZPageAge>(i + 1);
|
||||
return to_zpageage(i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -35,7 +35,7 @@ class ZPage;
|
||||
|
||||
class ZAllocator {
|
||||
public:
|
||||
static constexpr uint _relocation_allocators = static_cast<uint>(ZPageAge::old);
|
||||
static constexpr uint _relocation_allocators = ZPageAgeCount - 1;
|
||||
|
||||
protected:
|
||||
ZObjectAllocator _object_allocator;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,13 +28,14 @@
|
||||
|
||||
#include "gc/z/zAddress.inline.hpp"
|
||||
#include "gc/z/zHeap.hpp"
|
||||
#include "gc/z/zPageAge.inline.hpp"
|
||||
|
||||
inline ZAllocatorEden* ZAllocator::eden() {
|
||||
return _eden;
|
||||
}
|
||||
|
||||
inline ZAllocatorForRelocation* ZAllocator::relocation(ZPageAge page_age) {
|
||||
return _relocation[static_cast<uint>(page_age) - 1];
|
||||
return _relocation[untype(page_age - 1)];
|
||||
}
|
||||
|
||||
inline ZAllocatorForRelocation* ZAllocator::old() {
|
||||
|
@ -41,6 +41,7 @@
|
||||
#include "gc/z/zHeap.inline.hpp"
|
||||
#include "gc/z/zJNICritical.hpp"
|
||||
#include "gc/z/zMark.inline.hpp"
|
||||
#include "gc/z/zPageAge.inline.hpp"
|
||||
#include "gc/z/zPageAllocator.hpp"
|
||||
#include "gc/z/zRelocationSet.inline.hpp"
|
||||
#include "gc/z/zRelocationSetSelector.inline.hpp"
|
||||
@ -699,11 +700,10 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st
|
||||
uint last_populated_age = 0;
|
||||
size_t last_populated_live = 0;
|
||||
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
const ZPageAge age = static_cast<ZPageAge>(i);
|
||||
for (ZPageAge age : ZPageAgeRange()) {
|
||||
const size_t young_live = stats.small(age).live() + stats.medium(age).live() + stats.large(age).live();
|
||||
if (young_live > 0) {
|
||||
last_populated_age = i;
|
||||
last_populated_age = untype(age);
|
||||
last_populated_live = young_live;
|
||||
if (young_live_last > 0) {
|
||||
young_life_expectancy_sum += double(young_live) / double(young_live_last);
|
||||
@ -842,8 +842,8 @@ void ZGenerationYoung::mark_start() {
|
||||
|
||||
// Retire allocating pages
|
||||
ZAllocator::eden()->retire_pages();
|
||||
for (ZPageAge i = ZPageAge::survivor1; i <= ZPageAge::survivor14; i = static_cast<ZPageAge>(static_cast<uint>(i) + 1)) {
|
||||
ZAllocator::relocation(i)->retire_pages();
|
||||
for (ZPageAge age : ZPageAgeRangeSurvivor) {
|
||||
ZAllocator::relocation(age)->retire_pages();
|
||||
}
|
||||
|
||||
// Reset allocated/reclaimed/used statistics
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -24,6 +24,7 @@
|
||||
#ifndef SHARE_GC_Z_ZPAGEAGE_HPP
|
||||
#define SHARE_GC_Z_ZPAGEAGE_HPP
|
||||
|
||||
#include "utilities/enumIterator.hpp"
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
|
||||
enum class ZPageAge : uint8_t {
|
||||
@ -45,6 +46,19 @@ enum class ZPageAge : uint8_t {
|
||||
old
|
||||
};
|
||||
|
||||
constexpr uint ZPageAgeMax = static_cast<uint>(ZPageAge::old);
|
||||
constexpr uint ZPageAgeCount = static_cast<uint>(ZPageAge::old) + 1;
|
||||
constexpr ZPageAge ZPageAgeLastPlusOne = static_cast<ZPageAge>(ZPageAgeCount);
|
||||
|
||||
ENUMERATOR_RANGE(ZPageAge,
|
||||
ZPageAge::eden,
|
||||
ZPageAge::old);
|
||||
|
||||
using ZPageAgeRange = EnumRange<ZPageAge>;
|
||||
|
||||
constexpr ZPageAgeRange ZPageAgeRangeEden = ZPageAgeRange::create<ZPageAge::eden, ZPageAge::survivor1>();
|
||||
constexpr ZPageAgeRange ZPageAgeRangeYoung = ZPageAgeRange::create<ZPageAge::eden, ZPageAge::old>();
|
||||
constexpr ZPageAgeRange ZPageAgeRangeSurvivor = ZPageAgeRange::create<ZPageAge::survivor1, ZPageAge::old>();
|
||||
constexpr ZPageAgeRange ZPageAgeRangeRelocation = ZPageAgeRange::create<ZPageAge::survivor1, ZPageAgeLastPlusOne>();
|
||||
constexpr ZPageAgeRange ZPageAgeRangeOld = ZPageAgeRange::create<ZPageAge::old, ZPageAgeLastPlusOne>();
|
||||
|
||||
#endif // SHARE_GC_Z_ZPAGEAGE_HPP
|
||||
|
@ -1,12 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
* 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
|
||||
@ -23,13 +21,32 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package sun.text.resources;
|
||||
#ifndef SHARE_GC_Z_ZPAGEAGE_INLINE_HPP
|
||||
#define SHARE_GC_Z_ZPAGEAGE_INLINE_HPP
|
||||
|
||||
import java.util.spi.ResourceBundleProvider;
|
||||
#include "gc/z/zPageAge.hpp"
|
||||
|
||||
/**
|
||||
* An interface for the internal locale data provider for which {@code ResourceBundle}
|
||||
* searches.
|
||||
*/
|
||||
public interface JavaTimeSupplementaryProvider extends ResourceBundleProvider {
|
||||
#include "utilities/checkedCast.hpp"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
inline uint untype(ZPageAge age) {
|
||||
return static_cast<uint>(age);
|
||||
}
|
||||
|
||||
inline ZPageAge to_zpageage(uint age) {
|
||||
assert(age < ZPageAgeCount, "Invalid age");
|
||||
return static_cast<ZPageAge>(age);
|
||||
}
|
||||
|
||||
inline ZPageAge operator+(ZPageAge age, size_t size) {
|
||||
const auto size_value = checked_cast<std::underlying_type_t<ZPageAge>>(size);
|
||||
return to_zpageage(untype(age) + size_value);
|
||||
}
|
||||
|
||||
inline ZPageAge operator-(ZPageAge age, size_t size) {
|
||||
const auto size_value = checked_cast<std::underlying_type_t<ZPageAge>>(size);
|
||||
return to_zpageage(untype(age) - size_value);
|
||||
}
|
||||
|
||||
#endif // SHARE_GC_Z_ZPAGEAGE_INLINE_HPP
|
@ -488,11 +488,11 @@ public:
|
||||
}
|
||||
|
||||
ZPage* shared(ZPageAge age) {
|
||||
return _shared[static_cast<uint>(age) - 1];
|
||||
return _shared[untype(age - 1)];
|
||||
}
|
||||
|
||||
void set_shared(ZPageAge age, ZPage* page) {
|
||||
_shared[static_cast<uint>(age) - 1] = page;
|
||||
_shared[untype(age - 1)] = page;
|
||||
}
|
||||
|
||||
ZPage* alloc_and_retire_target_page(ZForwarding* forwarding, ZPage* target) {
|
||||
@ -570,11 +570,11 @@ private:
|
||||
|
||||
|
||||
ZPage* target(ZPageAge age) {
|
||||
return _target[static_cast<uint>(age) - 1];
|
||||
return _target[untype(age - 1)];
|
||||
}
|
||||
|
||||
void set_target(ZPageAge age, ZPage* page) {
|
||||
_target[static_cast<uint>(age) - 1] = page;
|
||||
_target[untype(age - 1)] = page;
|
||||
}
|
||||
|
||||
size_t object_alignment() const {
|
||||
@ -1232,12 +1232,12 @@ ZPageAge ZRelocate::compute_to_age(ZPageAge from_age) {
|
||||
return ZPageAge::old;
|
||||
}
|
||||
|
||||
const uint age = static_cast<uint>(from_age);
|
||||
const uint age = untype(from_age);
|
||||
if (age >= ZGeneration::young()->tenuring_threshold()) {
|
||||
return ZPageAge::old;
|
||||
}
|
||||
|
||||
return static_cast<ZPageAge>(age + 1);
|
||||
return to_zpageage(age + 1);
|
||||
}
|
||||
|
||||
class ZFlipAgePagesTask : public ZTask {
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "gc/z/zArray.inline.hpp"
|
||||
#include "gc/z/zForwarding.inline.hpp"
|
||||
#include "gc/z/zPage.inline.hpp"
|
||||
#include "gc/z/zPageAge.inline.hpp"
|
||||
#include "gc/z/zRelocationSetSelector.inline.hpp"
|
||||
#include "jfr/jfrEvents.hpp"
|
||||
#include "logging/log.hpp"
|
||||
@ -117,8 +118,8 @@ void ZRelocationSetSelectorGroup::select_inner() {
|
||||
const int npages = _live_pages.length();
|
||||
int selected_from = 0;
|
||||
int selected_to = 0;
|
||||
size_t npages_selected[ZPageAgeMax + 1] = { 0 };
|
||||
size_t selected_live_bytes[ZPageAgeMax + 1] = { 0 };
|
||||
size_t npages_selected[ZPageAgeCount] = { 0 };
|
||||
size_t selected_live_bytes[ZPageAgeCount] = { 0 };
|
||||
size_t selected_forwarding_entries = 0;
|
||||
|
||||
size_t from_live_bytes = 0;
|
||||
@ -149,8 +150,8 @@ void ZRelocationSetSelectorGroup::select_inner() {
|
||||
if (diff_reclaimable > _fragmentation_limit) {
|
||||
selected_from = from;
|
||||
selected_to = to;
|
||||
selected_live_bytes[static_cast<uint>(page->age())] += page_live_bytes;
|
||||
npages_selected[static_cast<uint>(page->age())] += 1;
|
||||
selected_live_bytes[untype(page->age())] += page_live_bytes;
|
||||
npages_selected[untype(page->age())] += 1;
|
||||
selected_forwarding_entries = from_forwarding_entries;
|
||||
}
|
||||
|
||||
@ -172,7 +173,7 @@ void ZRelocationSetSelectorGroup::select_inner() {
|
||||
_forwarding_entries = selected_forwarding_entries;
|
||||
|
||||
// Update statistics
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
for (uint i = 0; i < ZPageAgeCount; ++i) {
|
||||
_stats[i]._relocate = selected_live_bytes[i];
|
||||
_stats[i]._npages_selected = npages_selected[i];
|
||||
}
|
||||
@ -200,7 +201,7 @@ void ZRelocationSetSelectorGroup::select() {
|
||||
}
|
||||
|
||||
ZRelocationSetSelectorGroupStats s{};
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
for (uint i = 0; i < ZPageAgeCount; ++i) {
|
||||
s._npages_candidates += _stats[i].npages_candidates();
|
||||
s._total += _stats[i].total();
|
||||
s._empty += _stats[i].empty();
|
||||
@ -239,8 +240,8 @@ void ZRelocationSetSelector::select() {
|
||||
ZRelocationSetSelectorStats ZRelocationSetSelector::stats() const {
|
||||
ZRelocationSetSelectorStats stats;
|
||||
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
const ZPageAge age = static_cast<ZPageAge>(i);
|
||||
for (ZPageAge age : ZPageAgeRange()) {
|
||||
const uint i = untype(age);
|
||||
stats._small[i] = _small.stats(age);
|
||||
stats._medium[i] = _medium.stats(age);
|
||||
stats._large[i] = _large.stats(age);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -62,9 +62,9 @@ class ZRelocationSetSelectorStats {
|
||||
friend class ZRelocationSetSelector;
|
||||
|
||||
private:
|
||||
ZRelocationSetSelectorGroupStats _small[ZPageAgeMax + 1];
|
||||
ZRelocationSetSelectorGroupStats _medium[ZPageAgeMax + 1];
|
||||
ZRelocationSetSelectorGroupStats _large[ZPageAgeMax + 1];
|
||||
ZRelocationSetSelectorGroupStats _small[ZPageAgeCount];
|
||||
ZRelocationSetSelectorGroupStats _medium[ZPageAgeCount];
|
||||
ZRelocationSetSelectorGroupStats _large[ZPageAgeCount];
|
||||
|
||||
size_t _has_relocatable_pages;
|
||||
|
||||
@ -90,7 +90,7 @@ private:
|
||||
ZArray<ZPage*> _live_pages;
|
||||
ZArray<ZPage*> _not_selected_pages;
|
||||
size_t _forwarding_entries;
|
||||
ZRelocationSetSelectorGroupStats _stats[ZPageAgeMax + 1];
|
||||
ZRelocationSetSelectorGroupStats _stats[ZPageAgeCount];
|
||||
|
||||
bool is_disabled();
|
||||
bool is_selectable();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -29,6 +29,7 @@
|
||||
#include "gc/z/zArray.inline.hpp"
|
||||
#include "gc/z/zGlobals.hpp"
|
||||
#include "gc/z/zPage.inline.hpp"
|
||||
#include "gc/z/zPageAge.inline.hpp"
|
||||
#include "utilities/powerOfTwo.hpp"
|
||||
|
||||
inline size_t ZRelocationSetSelectorGroupStats::npages_candidates() const {
|
||||
@ -60,15 +61,15 @@ inline bool ZRelocationSetSelectorStats::has_relocatable_pages() const {
|
||||
}
|
||||
|
||||
inline const ZRelocationSetSelectorGroupStats& ZRelocationSetSelectorStats::small(ZPageAge age) const {
|
||||
return _small[static_cast<uint>(age)];
|
||||
return _small[untype(age)];
|
||||
}
|
||||
|
||||
inline const ZRelocationSetSelectorGroupStats& ZRelocationSetSelectorStats::medium(ZPageAge age) const {
|
||||
return _medium[static_cast<uint>(age)];
|
||||
return _medium[untype(age)];
|
||||
}
|
||||
|
||||
inline const ZRelocationSetSelectorGroupStats& ZRelocationSetSelectorStats::large(ZPageAge age) const {
|
||||
return _large[static_cast<uint>(age)];
|
||||
return _large[untype(age)];
|
||||
}
|
||||
|
||||
inline bool ZRelocationSetSelectorGroup::pre_filter_page(const ZPage* page, size_t live_bytes) const {
|
||||
@ -113,7 +114,7 @@ inline void ZRelocationSetSelectorGroup::register_live_page(ZPage* page) {
|
||||
}
|
||||
|
||||
const size_t size = page->size();
|
||||
const uint age = static_cast<uint>(page->age());
|
||||
const uint age = untype(page->age());
|
||||
_stats[age]._npages_candidates++;
|
||||
_stats[age]._total += size;
|
||||
_stats[age]._live += live;
|
||||
@ -122,7 +123,7 @@ inline void ZRelocationSetSelectorGroup::register_live_page(ZPage* page) {
|
||||
inline void ZRelocationSetSelectorGroup::register_empty_page(ZPage* page) {
|
||||
const size_t size = page->size();
|
||||
|
||||
const uint age = static_cast<uint>(page->age());
|
||||
const uint age = untype(page->age());
|
||||
_stats[age]._npages_candidates++;
|
||||
_stats[age]._total += size;
|
||||
_stats[age]._empty += size;
|
||||
@ -141,7 +142,7 @@ inline size_t ZRelocationSetSelectorGroup::forwarding_entries() const {
|
||||
}
|
||||
|
||||
inline const ZRelocationSetSelectorGroupStats& ZRelocationSetSelectorGroup::stats(ZPageAge age) const {
|
||||
return _stats[static_cast<uint>(age)];
|
||||
return _stats[untype(age)];
|
||||
}
|
||||
|
||||
inline void ZRelocationSetSelector::register_live_page(ZPage* page) {
|
||||
@ -188,8 +189,7 @@ inline void ZRelocationSetSelector::clear_empty_pages() {
|
||||
|
||||
inline size_t ZRelocationSetSelector::total() const {
|
||||
size_t sum = 0;
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
const ZPageAge age = static_cast<ZPageAge>(i);
|
||||
for (ZPageAge age : ZPageAgeRange()) {
|
||||
sum += _small.stats(age).total() + _medium.stats(age).total() + _large.stats(age).total();
|
||||
}
|
||||
return sum;
|
||||
@ -197,8 +197,7 @@ inline size_t ZRelocationSetSelector::total() const {
|
||||
|
||||
inline size_t ZRelocationSetSelector::empty() const {
|
||||
size_t sum = 0;
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
const ZPageAge age = static_cast<ZPageAge>(i);
|
||||
for (ZPageAge age : ZPageAgeRange()) {
|
||||
sum += _small.stats(age).empty() + _medium.stats(age).empty() + _large.stats(age).empty();
|
||||
}
|
||||
return sum;
|
||||
@ -206,8 +205,7 @@ inline size_t ZRelocationSetSelector::empty() const {
|
||||
|
||||
inline size_t ZRelocationSetSelector::relocate() const {
|
||||
size_t sum = 0;
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
const ZPageAge age = static_cast<ZPageAge>(i);
|
||||
for (ZPageAge age : ZPageAgeRange()) {
|
||||
sum += _small.stats(age).relocate() + _medium.stats(age).relocate() + _large.stats(age).relocate();
|
||||
}
|
||||
return sum;
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "gc/z/zGeneration.inline.hpp"
|
||||
#include "gc/z/zGlobals.hpp"
|
||||
#include "gc/z/zNMethodTable.hpp"
|
||||
#include "gc/z/zPageAge.inline.hpp"
|
||||
#include "gc/z/zPageAllocator.inline.hpp"
|
||||
#include "gc/z/zRelocationSetSelector.inline.hpp"
|
||||
#include "gc/z/zStat.hpp"
|
||||
@ -1499,9 +1500,7 @@ void ZStatRelocation::print_page_summary() {
|
||||
summary.relocate += stats.relocate();
|
||||
};
|
||||
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
const ZPageAge age = static_cast<ZPageAge>(i);
|
||||
|
||||
for (ZPageAge age : ZPageAgeRange()) {
|
||||
account_page_size(small_summary, _selector_stats.small(age));
|
||||
account_page_size(medium_summary, _selector_stats.medium(age));
|
||||
account_page_size(large_summary, _selector_stats.large(age));
|
||||
@ -1557,13 +1556,13 @@ void ZStatRelocation::print_age_table() {
|
||||
.center("Large")
|
||||
.end());
|
||||
|
||||
size_t live[ZPageAgeMax + 1] = {};
|
||||
size_t total[ZPageAgeMax + 1] = {};
|
||||
size_t live[ZPageAgeCount] = {};
|
||||
size_t total[ZPageAgeCount] = {};
|
||||
|
||||
uint oldest_none_empty_age = 0;
|
||||
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
ZPageAge age = static_cast<ZPageAge>(i);
|
||||
for (ZPageAge age : ZPageAgeRange()) {
|
||||
uint i = untype(age);
|
||||
auto summarize_pages = [&](const ZRelocationSetSelectorGroupStats& stats) {
|
||||
live[i] += stats.live();
|
||||
total[i] += stats.total();
|
||||
@ -1579,7 +1578,7 @@ void ZStatRelocation::print_age_table() {
|
||||
}
|
||||
|
||||
for (uint i = 0; i <= oldest_none_empty_age; ++i) {
|
||||
ZPageAge age = static_cast<ZPageAge>(i);
|
||||
ZPageAge age = to_zpageage(i);
|
||||
|
||||
FormatBuffer<> age_str("");
|
||||
if (age == ZPageAge::eden) {
|
||||
@ -1791,8 +1790,7 @@ void ZStatHeap::at_select_relocation_set(const ZRelocationSetSelectorStats& stat
|
||||
ZLocker<ZLock> locker(&_stat_lock);
|
||||
|
||||
size_t live = 0;
|
||||
for (uint i = 0; i <= ZPageAgeMax; ++i) {
|
||||
const ZPageAge age = static_cast<ZPageAge>(i);
|
||||
for (ZPageAge age : ZPageAgeRange()) {
|
||||
live += stats.small(age).live() + stats.medium(age).live() + stats.large(age).live();
|
||||
}
|
||||
_at_mark_end.live = live;
|
||||
|
@ -113,7 +113,7 @@
|
||||
\
|
||||
product(int, ZTenuringThreshold, -1, DIAGNOSTIC, \
|
||||
"Young generation tenuring threshold, -1 for dynamic computation")\
|
||||
range(-1, static_cast<int>(ZPageAgeMax)) \
|
||||
range(-1, static_cast<int>(ZPageAgeCount) - 1) \
|
||||
\
|
||||
develop(bool, ZVerifyOops, false, \
|
||||
"Verify accessed oops") \
|
||||
|
@ -1463,8 +1463,7 @@ JVMCIPrimitiveArray JVMCIEnv::new_byteArray(int length, JVMCI_TRAPS) {
|
||||
JVMCIObjectArray JVMCIEnv::new_byte_array_array(int length, JVMCI_TRAPS) {
|
||||
JavaThread* THREAD = JavaThread::current(); // For exception macros.
|
||||
if (is_hotspot()) {
|
||||
Klass* byteArrayArrayKlass = TypeArrayKlass::cast(Universe::byteArrayKlass())->array_klass(CHECK_(JVMCIObject()));
|
||||
objArrayOop result = ObjArrayKlass::cast(byteArrayArrayKlass) ->allocate(length, CHECK_(JVMCIObject()));
|
||||
objArrayOop result = oopFactory::new_objArray(Universe::byteArrayKlass(), length, CHECK_(JVMCIObject()));
|
||||
return wrap(result);
|
||||
} else {
|
||||
JNIAccessMark jni(this, THREAD);
|
||||
|
@ -216,6 +216,7 @@ class outputStream;
|
||||
LOG_TAG(valuebasedclasses) \
|
||||
LOG_TAG(verification) \
|
||||
LOG_TAG(verify) \
|
||||
LOG_TAG(vmatree) \
|
||||
LOG_TAG(vmmutex) \
|
||||
LOG_TAG(vmoperation) \
|
||||
LOG_TAG(vmthread) \
|
||||
|
@ -189,14 +189,6 @@ static size_t align_to_page_size(size_t size) {
|
||||
}
|
||||
|
||||
|
||||
void CodeHeap::on_code_mapping(char* base, size_t size) {
|
||||
#ifdef LINUX
|
||||
extern void linux_wrap_code(char* base, size_t size);
|
||||
linux_wrap_code(base, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
bool CodeHeap::reserve(ReservedSpace rs, size_t committed_size, size_t segment_size) {
|
||||
assert(rs.size() >= committed_size, "reserved < committed");
|
||||
assert(is_aligned(committed_size, rs.page_size()), "must be page aligned");
|
||||
@ -213,7 +205,6 @@ bool CodeHeap::reserve(ReservedSpace rs, size_t committed_size, size_t segment_s
|
||||
return false;
|
||||
}
|
||||
|
||||
on_code_mapping(_memory.low(), _memory.committed_size());
|
||||
_number_of_committed_segments = size_to_segments(_memory.committed_size());
|
||||
_number_of_reserved_segments = size_to_segments(_memory.reserved_size());
|
||||
assert(_number_of_reserved_segments >= _number_of_committed_segments, "just checking");
|
||||
@ -250,7 +241,6 @@ bool CodeHeap::expand_by(size_t size) {
|
||||
}
|
||||
char* base = _memory.low() + _memory.committed_size();
|
||||
if (!_memory.expand_by(dm)) return false;
|
||||
on_code_mapping(base, dm);
|
||||
size_t i = _number_of_committed_segments;
|
||||
_number_of_committed_segments = size_to_segments(_memory.committed_size());
|
||||
assert(_number_of_reserved_segments == size_to_segments(_memory.reserved_size()), "number of reserved segments should not change");
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -146,9 +146,6 @@ class CodeHeap : public CHeapObj<mtCode> {
|
||||
void* next_used(HeapBlock* b) const;
|
||||
HeapBlock* block_start(void* p) const;
|
||||
|
||||
// to perform additional actions on creation of executable code
|
||||
void on_code_mapping(char* base, size_t size);
|
||||
|
||||
public:
|
||||
CodeHeap(const char* name, const CodeBlobType code_blob_type);
|
||||
|
||||
|
@ -40,41 +40,41 @@
|
||||
#include "utilities/utf8.hpp"
|
||||
|
||||
typeArrayOop oopFactory::new_boolArray(int length, TRAPS) {
|
||||
return Universe::boolArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::boolArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
typeArrayOop oopFactory::new_charArray(int length, TRAPS) {
|
||||
return Universe::charArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::charArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
typeArrayOop oopFactory::new_floatArray(int length, TRAPS) {
|
||||
return Universe::floatArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::floatArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
typeArrayOop oopFactory::new_doubleArray(int length, TRAPS) {
|
||||
return Universe::doubleArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::doubleArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
typeArrayOop oopFactory::new_byteArray(int length, TRAPS) {
|
||||
return Universe::byteArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::byteArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
typeArrayOop oopFactory::new_shortArray(int length, TRAPS) {
|
||||
return Universe::shortArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::shortArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
typeArrayOop oopFactory::new_intArray(int length, TRAPS) {
|
||||
return Universe::intArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::intArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
typeArrayOop oopFactory::new_longArray(int length, TRAPS) {
|
||||
return Universe::longArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::longArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
// create java.lang.Object[]
|
||||
objArrayOop oopFactory::new_objectArray(int length, TRAPS) {
|
||||
assert(Universe::objectArrayKlass() != nullptr, "Too early?");
|
||||
return Universe::objectArrayKlass()->allocate(length, THREAD);
|
||||
return Universe::objectArrayKlass()->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
typeArrayOop oopFactory::new_charArray(const char* utf8_str, TRAPS) {
|
||||
@ -88,7 +88,7 @@ typeArrayOop oopFactory::new_charArray(const char* utf8_str, TRAPS) {
|
||||
|
||||
typeArrayOop oopFactory::new_typeArray(BasicType type, int length, TRAPS) {
|
||||
TypeArrayKlass* klass = Universe::typeArrayKlass(type);
|
||||
return klass->allocate(length, THREAD);
|
||||
return klass->allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
// Create a Java array that points to Symbol.
|
||||
|
@ -91,7 +91,7 @@ void MemoryFileTracker::print_report_on(const MemoryFile* file, outputStream* st
|
||||
NMTUtil::tag_to_name(prev->val().out.mem_tag()));
|
||||
{
|
||||
StreamIndentor si(stream, 4);
|
||||
_stack_storage.get(prev->val().out.stack()).print_on(stream);
|
||||
_stack_storage.get(prev->val().out.reserved_stack()).print_on(stream);
|
||||
}
|
||||
stream->cr();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -43,11 +43,8 @@
|
||||
class NativeCallStackStorage : public CHeapObjBase {
|
||||
public:
|
||||
using StackIndex = int;
|
||||
|
||||
private:
|
||||
constexpr static const StackIndex invalid = std::numeric_limits<StackIndex>::max() - 1;
|
||||
|
||||
public:
|
||||
static bool equals(const StackIndex a, const StackIndex b) {
|
||||
return a == b;
|
||||
}
|
||||
|
@ -28,21 +28,228 @@
|
||||
#include "utilities/globalDefinitions.hpp"
|
||||
#include "utilities/growableArray.hpp"
|
||||
|
||||
const VMATree::RegionData VMATree::empty_regiondata{NativeCallStackStorage::StackIndex{}, mtNone};
|
||||
|
||||
// Semantics
|
||||
// This tree is used to store and track the state of virtual memory regions.
|
||||
// The nodes in the tree are key-value pairs where the key is the memory address and the value is the State of the memory regions.
|
||||
// The State of a region describes whether the region is released, reserved or committed, which MemTag it has and where in
|
||||
// Hotspot (using call-stacks) it is reserved or committed.
|
||||
// Each node holds the State of the regions to its left and right. Each memory region is described by two
|
||||
// memory addresses for its start and end.
|
||||
// For example, to describe the region that starts at memory address 0xA000 with size 0x1000, there will be two nodes
|
||||
// with the keys 0xA000 (node A) and 0xB000 (node B) in the tree. The value of the key-value pairs of node A and
|
||||
// node B describe the region's State, using right of A and left of B (<--left--A--right-->.....<--left--B--right-->...).
|
||||
//
|
||||
// Virtual memory can be reserved, committed, uncommitted and released. For each operation a request
|
||||
// (<from-address, to-address, operation, tag, call-stack, which-tag-to-use >) is sent to the tree to handle.
|
||||
//
|
||||
// The expected changes are described here for each operation:
|
||||
//
|
||||
// ### Reserve a region
|
||||
// When a region is reserved, all the overlapping regions in the tree should:
|
||||
// - be marked as Reserved
|
||||
// - take MemTag of the operation
|
||||
// - store call-stack of the request to the reserve call-stack
|
||||
// - clear commit call-stack
|
||||
//
|
||||
// ### Commit a region
|
||||
// When a region is committed, all the overlapping regions in the tree should:
|
||||
// - be marked as Committed
|
||||
// - take MemTag of the operation or MemTag of the existing region, depends on which-tag-to-use in the request
|
||||
// - if the region is in Released state
|
||||
// - mark the region as both Reserved and Committed
|
||||
// - store the call-stack of the request to the reserve call-stack
|
||||
// - store the call-stack of the request to the commit call-stack
|
||||
//
|
||||
// ### Uncommit a region
|
||||
// When a region is uncommitted, all the overlapping regions in the tree should:
|
||||
// - be ignored if the region is in Released state
|
||||
// - be marked as Reserved
|
||||
// - not change the MemTag
|
||||
// - not change the reserve call-stack
|
||||
// - clear commit call-stack
|
||||
//
|
||||
// ### Release a region
|
||||
// When a region is released, all the overlapping regions in the tree should:
|
||||
// - be marked as Released
|
||||
// - set the MemTag to mtNone
|
||||
// - clear both reserve and commit call-stack
|
||||
//
|
||||
// --- Accounting
|
||||
// After each operation, the tree should be able to report how much memory is reserved or committed per MemTag.
|
||||
// So for each region that changes to a new State, the report should contain (separately for each tag) the amount
|
||||
// of reserve and commit that are changed (increased or decreased) due to the operation.
|
||||
|
||||
const VMATree::RegionData VMATree::empty_regiondata{NativeCallStackStorage::invalid, mtNone};
|
||||
|
||||
const char* VMATree::statetype_strings[3] = {
|
||||
"reserved", "committed", "released",
|
||||
"released", "reserved", "committed"
|
||||
};
|
||||
|
||||
VMATree::SummaryDiff VMATree::register_mapping(position A, position B, StateType state,
|
||||
VMATree::SIndex VMATree::get_new_reserve_callstack(const SIndex es, const StateType ex, const RequestInfo& req) const {
|
||||
const SIndex ES = NativeCallStackStorage::invalid; // Empty Stack
|
||||
const SIndex rq = req.callstack;
|
||||
const int op = req.op_to_index();
|
||||
const Operation oper = req.op();
|
||||
assert(op >= 0 && op < 4, "should be");
|
||||
assert(op >= 0 && op < 4, "should be");
|
||||
// existing state
|
||||
SIndex result[4][3] = {// Rl Rs C
|
||||
{ES, ES, ES}, // op == Release
|
||||
{rq, rq, rq}, // op == Reserve
|
||||
{es, es, es}, // op == Commit
|
||||
{es, es, es} // op == Uncommit
|
||||
};
|
||||
// When committing a Released region, the reserve-call-stack of the region should also be as what is in the request
|
||||
if (oper == Operation::Commit && ex == StateType::Released) {
|
||||
return rq;
|
||||
} else {
|
||||
return result[op][state_to_index(ex)];
|
||||
}
|
||||
}
|
||||
|
||||
VMATree::SIndex VMATree::get_new_commit_callstack(const SIndex es, const StateType ex, const RequestInfo& req) const {
|
||||
const SIndex ES = NativeCallStackStorage::invalid; // Empty Stack
|
||||
const SIndex rq = req.callstack;
|
||||
const int op_index = req.op_to_index();
|
||||
const Operation op = req.op();
|
||||
assert(op_index >= 0 && op_index < 4, "should be");
|
||||
// existing state
|
||||
SIndex result[4][3] = {// Rl Rs C
|
||||
{ES, ES, ES}, // op == Release
|
||||
{ES, ES, ES}, // op == Reserve
|
||||
{rq, rq, rq}, // op == Commit
|
||||
{ES, ES, ES} // op == Uncommit
|
||||
};
|
||||
return result[op_index][state_to_index(ex)];
|
||||
}
|
||||
|
||||
VMATree::StateType VMATree::get_new_state(const StateType ex, const RequestInfo& req) const {
|
||||
const StateType Rl = StateType::Released;
|
||||
const StateType Rs = StateType::Reserved;
|
||||
const StateType C = StateType::Committed;
|
||||
const int op = req.op_to_index();
|
||||
assert(op >= 0 && op < 4, "should be");
|
||||
// existing state
|
||||
StateType result[4][3] = {// Rl Rs C
|
||||
{Rl, Rl, Rl}, // op == Release
|
||||
{Rs, Rs, Rs}, // op == Reserve
|
||||
{ C, C, C}, // op == Commit
|
||||
{Rl, Rs, Rs} // op == Uncommit
|
||||
};
|
||||
return result[op][state_to_index(ex)];
|
||||
}
|
||||
|
||||
MemTag VMATree::get_new_tag(const MemTag ex, const RequestInfo& req) const {
|
||||
switch(req.op()) {
|
||||
case Operation::Release:
|
||||
return mtNone;
|
||||
case Operation::Reserve:
|
||||
return req.tag;
|
||||
case Operation::Commit:
|
||||
return req.use_tag_inplace ? ex : req.tag;
|
||||
case Operation::Uncommit:
|
||||
return ex;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return mtNone;
|
||||
}
|
||||
|
||||
void VMATree::compute_summary_diff(const SingleDiff::delta region_size,
|
||||
const MemTag current_tag,
|
||||
const StateType& ex,
|
||||
const RequestInfo& req,
|
||||
const MemTag operation_tag,
|
||||
SummaryDiff& diff) const {
|
||||
const StateType Rl = StateType::Released;
|
||||
const StateType Rs = StateType::Reserved;
|
||||
const StateType C = StateType::Committed;
|
||||
const int op = req.op_to_index();
|
||||
const Operation oper = req.op();
|
||||
assert(op >= 0 && op < 4, "should be");
|
||||
|
||||
SingleDiff::delta a = region_size;
|
||||
// A region with size `a` has a state as <column> and an operation is requested as in <row>
|
||||
// The region has tag `current_tag` and the operation has tag `operation_tag`.
|
||||
// For each state, we decide how much to be added/subtracted from current_tag to operation_tag. Two tables for reserve and commit.
|
||||
// Each pair of <x,y> in the table means add `x` to current_tag and add `y` to operation_tag. There are 3 pairs in each row for 3 states.
|
||||
// For example, `reserve[1][4,5]` says `-a,a` means:
|
||||
// - we are reserving with operation_tag a region which is already commited with current_tag
|
||||
// - since we are reserving, then `a` will be added to operation_tag. (`y` is `a`)
|
||||
// - since we uncommitting (by reserving) then `a` is to be subtracted from current_tag. (`x` is `-a`).
|
||||
// - amount of uncommitted size is in table `commit[1][4,5]` which is `-a,0` that means subtract `a` from current_tag.
|
||||
// existing state
|
||||
SingleDiff::delta reserve[4][3*2] = {// Rl Rs C
|
||||
{0,0, -a,0, -a,0 }, // op == Release
|
||||
{0,a, -a,a, -a,a }, // op == Reserve
|
||||
{0,a, -a,a, -a,a }, // op == Commit
|
||||
{0,0, 0,0, 0,0 } // op == Uncommit
|
||||
};
|
||||
SingleDiff::delta commit[4][3*2] = {// Rl Rs C
|
||||
{0,0, 0,0, -a,0 }, // op == Release
|
||||
{0,0, 0,0, -a,0 }, // op == Reserve
|
||||
{0,a, 0,a, -a,a }, // op == Commit
|
||||
{0,0, 0,0, -a,0 } // op == Uncommit
|
||||
};
|
||||
SingleDiff& from_rescom = diff.tag[NMTUtil::tag_to_index(current_tag)];
|
||||
SingleDiff& to_rescom = diff.tag[NMTUtil::tag_to_index(operation_tag)];
|
||||
int st = state_to_index(ex);
|
||||
from_rescom.reserve += reserve[op][st * 2 ];
|
||||
to_rescom.reserve += reserve[op][st * 2 + 1];
|
||||
from_rescom.commit += commit[op][st * 2 ];
|
||||
to_rescom.commit += commit[op][st * 2 + 1];
|
||||
|
||||
}
|
||||
// update the region state between n1 and n2. Since n1 and n2 are pointers, any update of them will be visible from tree.
|
||||
// If n1 is noop, it can be removed because its left region (n1->val().in) is already decided and its right state (n1->val().out) is decided here.
|
||||
// The state of right of n2 (n2->val().out) cannot be decided here yet.
|
||||
void VMATree::update_region(TreapNode* n1, TreapNode* n2, const RequestInfo& req, SummaryDiff& diff) {
|
||||
assert(n1 != nullptr,"sanity");
|
||||
assert(n2 != nullptr,"sanity");
|
||||
//.........n1......n2......
|
||||
// ^------^
|
||||
// |
|
||||
IntervalState exSt = n1->val().out; // existing state info
|
||||
|
||||
|
||||
StateType existing_state = exSt.type();
|
||||
MemTag existing_tag = exSt.mem_tag();
|
||||
SIndex existing_reserve_callstack = exSt.reserved_stack();
|
||||
SIndex existing_commit_callstack = exSt.committed_stack();
|
||||
|
||||
StateType new_state = get_new_state(existing_state, req);
|
||||
MemTag new_tag = get_new_tag(n1->val().out.mem_tag(), req);
|
||||
SIndex new_reserve_callstack = get_new_reserve_callstack(existing_reserve_callstack, existing_state, req);
|
||||
SIndex new_commit_callstack = get_new_commit_callstack(existing_commit_callstack, existing_state, req);
|
||||
|
||||
// n1........n2
|
||||
// out-->
|
||||
n1->val().out.set_tag(new_tag);
|
||||
n1->val().out.set_type(new_state);
|
||||
n1->val().out.set_reserve_stack(new_reserve_callstack);
|
||||
n1->val().out.set_commit_stack(new_commit_callstack);
|
||||
|
||||
// n1........n2
|
||||
// <--in
|
||||
n2->val().in.set_tag(new_tag);
|
||||
n2->val().in.set_type(new_state);
|
||||
n2->val().in.set_reserve_stack(new_reserve_callstack);
|
||||
n2->val().in.set_commit_stack(new_commit_callstack);
|
||||
|
||||
SingleDiff::delta region_size = n2->key() - n1->key();
|
||||
compute_summary_diff(region_size, existing_tag, existing_state, req, new_tag, diff);
|
||||
}
|
||||
|
||||
VMATree::SummaryDiff VMATree::register_mapping(position _A, position _B, StateType state,
|
||||
const RegionData& metadata, bool use_tag_inplace) {
|
||||
assert(!use_tag_inplace || metadata.mem_tag == mtNone,
|
||||
"If using use_tag_inplace, then the supplied tag should be mtNone, was instead: %s", NMTUtil::tag_to_name(metadata.mem_tag));
|
||||
if (A == B) {
|
||||
// A 0-sized mapping isn't worth recording.
|
||||
|
||||
if (_A == _B) {
|
||||
return SummaryDiff();
|
||||
}
|
||||
|
||||
assert(_A < _B, "should be");
|
||||
SummaryDiff diff;
|
||||
RequestInfo req{_A, _B, state, metadata.mem_tag, metadata.stack_idx, use_tag_inplace};
|
||||
IntervalChange stA{
|
||||
IntervalState{StateType::Released, empty_regiondata},
|
||||
IntervalState{ state, metadata}
|
||||
@ -51,176 +258,400 @@ VMATree::SummaryDiff VMATree::register_mapping(position A, position B, StateType
|
||||
IntervalState{ state, metadata},
|
||||
IntervalState{StateType::Released, empty_regiondata}
|
||||
};
|
||||
stA.out.set_commit_stack(NativeCallStackStorage::invalid);
|
||||
stB.in.set_commit_stack(NativeCallStackStorage::invalid);
|
||||
VMATreap::Range rA = _tree.find_enclosing_range(_A);
|
||||
VMATreap::Range rB = _tree.find_enclosing_range(_B);
|
||||
|
||||
// First handle A.
|
||||
// Find closest node that is LEQ A
|
||||
bool LEQ_A_found = false;
|
||||
AddressState LEQ_A;
|
||||
TreapNode* leqA_n = _tree.closest_leq(A);
|
||||
if (leqA_n == nullptr) {
|
||||
assert(!use_tag_inplace, "Cannot use the tag inplace if no pre-existing tag exists. From: " PTR_FORMAT " To: " PTR_FORMAT, A, B);
|
||||
if (use_tag_inplace) {
|
||||
log_debug(nmt)("Cannot use the tag inplace if no pre-existing tag exists. From: " PTR_FORMAT " To: " PTR_FORMAT, A, B);
|
||||
// nodes: .....X.......Y...Z......W........U
|
||||
// request: A------------------B
|
||||
// X,Y = enclosing_nodes(A)
|
||||
// W,U = enclosing_nodes(B)
|
||||
// The cases are whether or not X and Y exists and X == A. (A == Y doesn't happen since it is searched by 'lt' predicate)
|
||||
// The cases are whether or not W and U exists and W == B. (B == U doesn't happen since it is searched by 'lt' predicate)
|
||||
|
||||
// We update regions in 3 sections: 1) X..A..Y, 2) Y....W, 3) W..B..U
|
||||
// Y: is the closest node greater than A, but less than B
|
||||
// W: is the closest node less than B, but greater than A
|
||||
// The regions in [Y,W) are updated in a loop. We update X..A..Y before the loop and W..B..U after the loop.
|
||||
// The table below summarizes the overlap cases. The overlapping case depends on whether X, Y, W and U exist or not,
|
||||
// and if they exist whether they are the same or not.
|
||||
// In the notations here, when there is not dot ('.') between two nodes it meaans that they are the same. For example,
|
||||
// ...XA....Y.... means X == A.
|
||||
|
||||
|
||||
// row 0: .........A..................B.....
|
||||
// row 1: .........A...YW.............B..... // it is impossible, since it means only one node exists in the tree.
|
||||
// row 2: .........A...Y..........W...B.....
|
||||
// row 3: .........A...Y.............WB.....
|
||||
|
||||
// row 4: .....X...A..................B.....
|
||||
// row 5: .....X...A...YW.............B.....
|
||||
// row 6: .....X...A...Y..........W...B.....
|
||||
// row 7: .....X...A...Y.............WB.....
|
||||
|
||||
// row 8: ........XA..................B.....
|
||||
// row 9: ........XA...YW.............B.....
|
||||
// row 10: ........XA...Y..........W...B.....
|
||||
// row 11: ........XA...Y.............WB.....
|
||||
|
||||
// row 12: .........A..................B....U
|
||||
// row 13: .........A...YW.............B....U
|
||||
// row 14: .........A...Y..........W...B....U
|
||||
// row 15: .........A...Y.............WB....U
|
||||
|
||||
// row 16: .....X...A..................B....U
|
||||
// row 17: .....X...A...YW.............B....U
|
||||
// row 18: .....X...A...Y..........W...B....U
|
||||
// row 19: .....X...A...Y.............WB....U
|
||||
|
||||
// row 20: ........XA..................B....U
|
||||
// row 21: ........XA...YW.............B....U
|
||||
// row 22: ........XA...Y..........W...B....U
|
||||
// row 23: ........XA...Y.............WB....U
|
||||
|
||||
|
||||
// We intentionally did not summarize/compress the cases to keep them as separate.
|
||||
// This expanded way of describing the cases helps us to understand/analyze/verify/debug/maintain
|
||||
// the corresponding code more easily.
|
||||
// Mapping of table to row, row to switch-case should be consistent. If one changes, the others have
|
||||
// to be updated accordingly. The sequence of dependecies is: table -> row no -> switch(row)-case -> code.
|
||||
// Meaning that whenever any of one item in this sequence is changed, the rest of the consequent items to
|
||||
// be checked/changed.
|
||||
|
||||
TreapNode* X = rA.start;
|
||||
TreapNode* Y = rA.end;
|
||||
TreapNode* W = rB.start;
|
||||
TreapNode* U = rB.end;
|
||||
TreapNode nA{_A, stA, 0}; // the node that represents A
|
||||
TreapNode nB{_B, stB, 0}; // the node that represents B
|
||||
TreapNode* A = &nA;
|
||||
TreapNode* B = &nB;
|
||||
auto upsert_if= [&](TreapNode* node) {
|
||||
if (!node->val().is_noop()) {
|
||||
_tree.upsert(node->key(), node->val());
|
||||
}
|
||||
// No match. We add the A node directly, unless it would have no effect.
|
||||
if (!stA.is_noop()) {
|
||||
_tree.upsert(A, stA);
|
||||
};
|
||||
// update region between n1 and n2
|
||||
auto update = [&](TreapNode* n1, TreapNode* n2) {
|
||||
update_region(n1, n2, req, diff);
|
||||
};
|
||||
auto remove_if = [&](TreapNode* node) -> bool{
|
||||
if (node->val().is_noop()) {
|
||||
_tree.remove(node->key());
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
LEQ_A_found = true;
|
||||
LEQ_A = AddressState{leqA_n->key(), leqA_n->val()};
|
||||
StateType leqA_state = leqA_n->val().out.type();
|
||||
StateType new_state = stA.out.type();
|
||||
// If we specify use_tag_inplace then the new region takes over the current tag instead of the tag in metadata.
|
||||
// This is important because the VirtualMemoryTracker API doesn't require supplying the tag for some operations.
|
||||
if (use_tag_inplace) {
|
||||
assert(leqA_n->val().out.type() != StateType::Released, "Should not use inplace the tag of a released region");
|
||||
MemTag tag = leqA_n->val().out.mem_tag();
|
||||
stA.out.set_tag(tag);
|
||||
stB.in.set_tag(tag);
|
||||
}
|
||||
|
||||
// Unless we know better, let B's outgoing state be the outgoing state of the node at or preceding A.
|
||||
// Consider the case where the found node is the start of a region enclosing [A,B)
|
||||
stB.out = out_state(leqA_n);
|
||||
|
||||
// Direct address match.
|
||||
if (leqA_n->key() == A) {
|
||||
// Take over in state from old address.
|
||||
stA.in = in_state(leqA_n);
|
||||
|
||||
// We may now be able to merge two regions:
|
||||
// If the node's old state matches the new, it becomes a noop. That happens, for example,
|
||||
// when expanding a committed area: commit [x1, A); ... commit [A, x3)
|
||||
// and the result should be a larger area, [x1, x3). In that case, the middle node (A and le_n)
|
||||
// is not needed anymore. So we just remove the old node.
|
||||
stB.in = stA.out;
|
||||
if (stA.is_noop()) {
|
||||
// invalidates leqA_n
|
||||
_tree.remove(leqA_n->key());
|
||||
} else {
|
||||
// If the state is not matching then we have different operations, such as:
|
||||
// reserve [x1, A); ... commit [A, x2); or
|
||||
// reserve [x1, A), mem_tag1; ... reserve [A, x2), mem_tag2; or
|
||||
// reserve [A, x1), mem_tag1; ... reserve [A, x2), mem_tag2;
|
||||
// then we re-use the existing out node, overwriting its old metadata.
|
||||
leqA_n->val() = stA;
|
||||
return false;
|
||||
};
|
||||
GrowableArrayCHeap<position, mtNMT> to_be_removed;
|
||||
// update regions in [Y,W)
|
||||
auto update_loop = [&]() {
|
||||
TreapNode* prev = nullptr;
|
||||
_tree.visit_range_in_order(_A + 1, _B + 1, [&](TreapNode* curr) {
|
||||
if (prev != nullptr) {
|
||||
update_region(prev, curr, req, diff);
|
||||
// during visit, structure of the tree should not be changed
|
||||
// keep the keys to be removed, and remove them later
|
||||
if (prev->val().is_noop()) {
|
||||
to_be_removed.push(prev->key());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The address must be smaller.
|
||||
assert(A > leqA_n->key(), "must be");
|
||||
prev = curr;
|
||||
});
|
||||
};
|
||||
// update region of [A,T)
|
||||
auto update_A = [&](TreapNode* T) {
|
||||
A->val().out = A->val().in;
|
||||
update(A, T);
|
||||
};
|
||||
bool X_exists = X != nullptr;
|
||||
bool Y_exists = Y != nullptr && Y->key() <= _B;
|
||||
bool W_exists = W != nullptr && W->key() > _A;
|
||||
bool U_exists = U != nullptr;
|
||||
bool X_eq_A = X_exists && X->key() == _A;
|
||||
bool W_eq_B = W_exists && W->key() == _B;
|
||||
bool Y_eq_W = Y_exists && W_exists && W->key() == Y->key();
|
||||
int row = -1;
|
||||
#ifdef ASSERT
|
||||
auto print_case = [&]() {
|
||||
log_trace(vmatree)(" req: %4d---%4d", (int)_A, (int)_B);
|
||||
log_trace(vmatree)(" row: %2d", row);
|
||||
log_trace(vmatree)(" X: %4ld", X_exists ? (long)X->key() : -1);
|
||||
log_trace(vmatree)(" Y: %4ld", Y_exists ? (long)Y->key() : -1);
|
||||
log_trace(vmatree)(" W: %4ld", W_exists ? (long)W->key() : -1);
|
||||
log_trace(vmatree)(" U: %4ld", U_exists ? (long)U->key() : -1);
|
||||
};
|
||||
#endif
|
||||
// Order of the nodes if they exist are as: X <= A < Y <= W <= B < U
|
||||
// A---------------------------B
|
||||
// X Y YW WB U
|
||||
// XA Y YW WB U
|
||||
if (!X_exists && !Y_exists && !U_exists) { row = 0; }
|
||||
if (!X_exists && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 1; }
|
||||
if (!X_exists && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 2; }
|
||||
if (!X_exists && Y_exists && W_eq_B && !U_exists) { row = 3; }
|
||||
|
||||
// We add a new node, but only if there would be a state change. If there would not be a
|
||||
// state change, we just omit the node.
|
||||
// That happens, for example, when reserving within an already reserved region with identical metadata.
|
||||
stA.in = out_state(leqA_n); // .. and the region's prior state is the incoming state
|
||||
if (stA.is_noop()) {
|
||||
// Nothing to do.
|
||||
} else {
|
||||
// Add new node.
|
||||
_tree.upsert(A, stA);
|
||||
}
|
||||
if ( X_exists && !Y_exists && !U_exists) { row = 4; }
|
||||
if ( X_exists && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 5; }
|
||||
if ( X_exists && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 6; }
|
||||
if ( X_exists && Y_exists && W_eq_B && !U_exists) { row = 7; }
|
||||
|
||||
if ( X_eq_A && !Y_exists && !U_exists) { row = 8; }
|
||||
if ( X_eq_A && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 9; }
|
||||
if ( X_eq_A && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 10; }
|
||||
if ( X_eq_A && Y_exists && W_eq_B && !U_exists) { row = 11; }
|
||||
|
||||
if (!X_exists && !Y_exists && U_exists) { row = 12; }
|
||||
if (!X_exists && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 13; }
|
||||
if (!X_exists && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 14; }
|
||||
if (!X_exists && Y_exists && W_eq_B && U_exists) { row = 15; }
|
||||
|
||||
if ( X_exists && !Y_exists && U_exists) { row = 16; }
|
||||
if ( X_exists && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 17; }
|
||||
if ( X_exists && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 18; }
|
||||
if ( X_exists && Y_exists && W_eq_B && U_exists) { row = 19; }
|
||||
|
||||
if ( X_eq_A && !Y_exists && U_exists) { row = 20; }
|
||||
if ( X_eq_A && Y_exists && Y_eq_W && !W_eq_B && U_exists) { row = 21; }
|
||||
if ( X_eq_A && Y_exists && !Y_eq_W && !W_eq_B && U_exists) { row = 22; }
|
||||
if ( X_eq_A && Y_exists && W_eq_B && U_exists) { row = 23; }
|
||||
|
||||
DEBUG_ONLY(print_case();)
|
||||
switch(row) {
|
||||
// row 0: .........A..................B.....
|
||||
case 0: {
|
||||
update_A(B);
|
||||
upsert_if(A);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we handle B.
|
||||
// We first search all nodes that are (A, B]. All of these nodes
|
||||
// need to be deleted and summary accounted for. The last node before B determines B's outgoing state.
|
||||
// If there is no node between A and B, its A's incoming state.
|
||||
GrowableArrayCHeap<AddressState, mtNMT> to_be_deleted_inbetween_a_b;
|
||||
bool B_needs_insert = true;
|
||||
|
||||
// Find all nodes between (A, B] and record their addresses and values. Also update B's
|
||||
// outgoing state.
|
||||
_tree.visit_range_in_order(A + 1, B + 1, [&](TreapNode* head) {
|
||||
int cmp_B = PositionComparator::cmp(head->key(), B);
|
||||
stB.out = out_state(head);
|
||||
if (cmp_B < 0) {
|
||||
// Record all nodes preceding B.
|
||||
to_be_deleted_inbetween_a_b.push({head->key(), head->val()});
|
||||
} else if (cmp_B == 0) {
|
||||
// Re-purpose B node, unless it would result in a noop node, in
|
||||
// which case record old node at B for deletion and summary accounting.
|
||||
if (stB.is_noop()) {
|
||||
to_be_deleted_inbetween_a_b.push(AddressState{B, head->val()});
|
||||
} else {
|
||||
head->val() = stB;
|
||||
}
|
||||
B_needs_insert = false;
|
||||
// row 1: .........A...YW.............B.....
|
||||
case 1: {
|
||||
ShouldNotReachHere();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Insert B node if needed
|
||||
if (B_needs_insert && // Was not already inserted
|
||||
!stB.is_noop()) // The operation is differing
|
||||
{
|
||||
_tree.upsert(B, stB);
|
||||
}
|
||||
|
||||
// We now need to:
|
||||
// a) Delete all nodes between (A, B]. Including B in the case of a noop.
|
||||
// b) Perform summary accounting
|
||||
SummaryDiff diff;
|
||||
|
||||
if (to_be_deleted_inbetween_a_b.length() == 0 && LEQ_A_found) {
|
||||
// We must have smashed a hole in an existing region (or replaced it entirely).
|
||||
// LEQ_A < A < B <= C
|
||||
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(LEQ_A.out().mem_tag())];
|
||||
if (LEQ_A.out().type() == StateType::Reserved) {
|
||||
rescom.reserve -= B - A;
|
||||
} else if (LEQ_A.out().type() == StateType::Committed) {
|
||||
rescom.commit -= B - A;
|
||||
rescom.reserve -= B - A;
|
||||
// row 2: .........A...Y..........W...B.....
|
||||
case 2: {
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update_loop();
|
||||
remove_if(Y);
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Track the previous node.
|
||||
AddressState prev{A, stA};
|
||||
for (int i = 0; i < to_be_deleted_inbetween_a_b.length(); i++) {
|
||||
const AddressState delete_me = to_be_deleted_inbetween_a_b.at(i);
|
||||
_tree.remove(delete_me.address);
|
||||
|
||||
// Perform summary accounting
|
||||
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(delete_me.in().mem_tag())];
|
||||
if (delete_me.in().type() == StateType::Reserved) {
|
||||
rescom.reserve -= delete_me.address - prev.address;
|
||||
} else if (delete_me.in().type() == StateType::Committed) {
|
||||
rescom.commit -= delete_me.address - prev.address;
|
||||
rescom.reserve -= delete_me.address - prev.address;
|
||||
// row 3: .........A...Y.............WB.....
|
||||
case 3: {
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update_loop();
|
||||
remove_if(W);
|
||||
break;
|
||||
}
|
||||
prev = delete_me;
|
||||
}
|
||||
|
||||
if (prev.address != A && prev.out().type() != StateType::Released) {
|
||||
// The last node wasn't released, so it must be connected to a node outside of (A, B)
|
||||
// A - prev - B - (some node >= B)
|
||||
// It might be that prev.address == B == (some node >= B), this is fine.
|
||||
if (prev.out().type() == StateType::Reserved) {
|
||||
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(prev.out().mem_tag())];
|
||||
rescom.reserve -= B - prev.address;
|
||||
} else if (prev.out().type() == StateType::Committed) {
|
||||
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(prev.out().mem_tag())];
|
||||
rescom.commit -= B - prev.address;
|
||||
rescom.reserve -= B - prev.address;
|
||||
// row 4: .....X...A..................B.....
|
||||
case 4: {
|
||||
A->val().in = X->val().out;
|
||||
update_A(B);
|
||||
upsert_if(A);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 5: .....X...A...YW.............B.....
|
||||
case 5: {
|
||||
A->val().in = X->val().out;
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update(Y, B);
|
||||
remove_if(Y);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 6: .....X...A...Y..........W...B.....
|
||||
case 6: {
|
||||
A->val().in = X->val().out;
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update_loop();
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 7: .....X...A...Y.............WB.....
|
||||
case 7: {
|
||||
A->val().in = X->val().out;
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update_loop();
|
||||
remove_if(W);
|
||||
break;
|
||||
}
|
||||
// row 8: ........XA..................B.....
|
||||
case 8: {
|
||||
update(X, B);
|
||||
remove_if(X);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 9: ........XA...YW.............B.....
|
||||
case 9: {
|
||||
update(X, Y);
|
||||
remove_if(X);
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 10: ........XA...Y..........W...B.....
|
||||
case 10: {
|
||||
update(X, Y);
|
||||
remove_if(X);
|
||||
update_loop();
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 11: ........XA...Y.............WB.....
|
||||
case 11: {
|
||||
update(X, Y);
|
||||
remove_if(X);
|
||||
update_loop();
|
||||
remove_if(W);
|
||||
break;
|
||||
}
|
||||
// row 12: .........A..................B....U
|
||||
case 12: {
|
||||
update_A(B);
|
||||
upsert_if(A);
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 13: .........A...YW.............B....U
|
||||
case 13: {
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
B->val().out = U->val().in;
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 14: .........A...Y..........W...B....U
|
||||
case 14: {
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update_loop();
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
B->val().out = U->val().in;
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 15: .........A...Y.............WB....U
|
||||
case 15: {
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update_loop();
|
||||
remove_if(W);
|
||||
break;
|
||||
}
|
||||
// row 16: .....X...A..................B....U
|
||||
case 16: {
|
||||
A->val().in = X->val().out;
|
||||
update_A(B);
|
||||
upsert_if(A);
|
||||
B->val().out = U->val().in;
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 17: .....X...A...YW.............B....U
|
||||
case 17: {
|
||||
A->val().in = X->val().out;
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
B->val().out = U->val().in;
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 18: .....X...A...Y..........W...B....U
|
||||
case 18: {
|
||||
A->val().in = X->val().out;
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update_loop();
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
B->val().out = U->val().in;
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 19: .....X...A...Y.............WB....U
|
||||
case 19: {
|
||||
A->val().in = X->val().out;
|
||||
update_A(Y);
|
||||
upsert_if(A);
|
||||
update_loop();
|
||||
remove_if(W);
|
||||
break;
|
||||
}
|
||||
// row 20: ........XA..................B....U
|
||||
case 20: {
|
||||
update(X, B);
|
||||
remove_if(X);
|
||||
B->val().out = U->val().in;
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 21: ........XA...YW.............B....U
|
||||
case 21: {
|
||||
update(X, Y);
|
||||
remove_if(X);
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
B->val().out = U->val().in;
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 22: ........XA...Y..........W...B....U
|
||||
case 22: {
|
||||
update(X, Y);
|
||||
remove_if(X);
|
||||
update_loop();
|
||||
update(W, B);
|
||||
remove_if(W);
|
||||
B->val().out = U->val().in;
|
||||
upsert_if(B);
|
||||
break;
|
||||
}
|
||||
// row 23: ........XA...Y.............WB....U
|
||||
case 23: {
|
||||
update(X, Y);
|
||||
remove_if(X);
|
||||
update_loop();
|
||||
remove_if(W);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ShouldNotReachHere();
|
||||
}
|
||||
|
||||
// Finally, we can register the new region [A, B)'s summary data.
|
||||
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(stA.out.mem_tag())];
|
||||
if (state == StateType::Reserved) {
|
||||
rescom.reserve += B - A;
|
||||
} else if (state == StateType::Committed) {
|
||||
rescom.commit += B - A;
|
||||
rescom.reserve += B - A;
|
||||
// Remove the 'noop' nodes that found inside the loop
|
||||
while(to_be_removed.length() != 0) {
|
||||
_tree.remove(to_be_removed.pop());
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
void VMATree::print_on(outputStream* out) {
|
||||
visit_in_order([&](TreapNode* current) {
|
||||
out->print("%zu (%s) - %s - ", current->key(), NMTUtil::tag_to_name(out_state(current).mem_tag()),
|
||||
statetype_to_string(out_state(current).type()));
|
||||
out->print("%zu (%s) - %s [%d, %d]-> ", current->key(), NMTUtil::tag_to_name(out_state(current).mem_tag()),
|
||||
statetype_to_string(out_state(current).type()), current->val().out.reserved_stack(), current->val().out.committed_stack());
|
||||
});
|
||||
out->cr();
|
||||
}
|
||||
@ -268,7 +699,7 @@ VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, con
|
||||
SummaryDiff diff;
|
||||
// Ignore any released ranges, these must be mtNone and have no stack
|
||||
if (type != StateType::Released) {
|
||||
RegionData new_data = RegionData(out.stack(), tag);
|
||||
RegionData new_data = RegionData(out.reserved_stack(), tag);
|
||||
SummaryDiff result = register_mapping(from, end, type, new_data);
|
||||
diff.add(result);
|
||||
}
|
||||
@ -289,7 +720,7 @@ VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, con
|
||||
StateType type = out.type();
|
||||
|
||||
if (type != StateType::Released) {
|
||||
RegionData new_data = RegionData(out.stack(), tag);
|
||||
RegionData new_data = RegionData(out.reserved_stack(), tag);
|
||||
SummaryDiff result = register_mapping(from, end, type, new_data);
|
||||
diff.add(result);
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ class VMATree {
|
||||
public:
|
||||
using position = size_t;
|
||||
using size = size_t;
|
||||
using SIndex = NativeCallStackStorage::StackIndex;
|
||||
|
||||
class PositionComparator {
|
||||
public:
|
||||
@ -55,7 +56,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
enum class StateType : uint8_t { Reserved, Committed, Released, LAST };
|
||||
enum class StateType : uint8_t { Released, Reserved, Committed, LAST };
|
||||
|
||||
private:
|
||||
static const char* statetype_strings[static_cast<uint8_t>(StateType::LAST)];
|
||||
@ -70,12 +71,12 @@ public:
|
||||
|
||||
// Each point has some stack and a tag associated with it.
|
||||
struct RegionData {
|
||||
const NativeCallStackStorage::StackIndex stack_idx;
|
||||
const SIndex stack_idx;
|
||||
const MemTag mem_tag;
|
||||
|
||||
RegionData() : stack_idx(), mem_tag(mtNone) {}
|
||||
|
||||
RegionData(NativeCallStackStorage::StackIndex stack_idx, MemTag mem_tag)
|
||||
RegionData(SIndex stack_idx, MemTag mem_tag)
|
||||
: stack_idx(stack_idx), mem_tag(mem_tag) {}
|
||||
|
||||
static bool equals(const RegionData& a, const RegionData& b) {
|
||||
@ -91,15 +92,27 @@ private:
|
||||
private:
|
||||
// Store the type and mem_tag as two bytes
|
||||
uint8_t type_tag[2];
|
||||
NativeCallStackStorage::StackIndex sidx;
|
||||
NativeCallStackStorage::StackIndex _reserved_stack;
|
||||
NativeCallStackStorage::StackIndex _committed_stack;
|
||||
|
||||
public:
|
||||
IntervalState() : type_tag{0,0}, sidx() {}
|
||||
IntervalState() : type_tag{0,0}, _reserved_stack(NativeCallStackStorage::invalid), _committed_stack(NativeCallStackStorage::invalid) {}
|
||||
IntervalState(const StateType type,
|
||||
const MemTag mt,
|
||||
const NativeCallStackStorage::StackIndex res_stack,
|
||||
const NativeCallStackStorage::StackIndex com_stack) {
|
||||
assert(!(type == StateType::Released) || mt == mtNone, "Released state-type must have memory tag mtNone");
|
||||
type_tag[0] = static_cast<uint8_t>(type);
|
||||
type_tag[1] = static_cast<uint8_t>(mt);
|
||||
_reserved_stack = res_stack;
|
||||
_committed_stack = com_stack;
|
||||
}
|
||||
IntervalState(const StateType type, const RegionData data) {
|
||||
assert(!(type == StateType::Released) || data.mem_tag == mtNone, "Released state-type must have memory tag mtNone");
|
||||
type_tag[0] = static_cast<uint8_t>(type);
|
||||
type_tag[1] = static_cast<uint8_t>(data.mem_tag);
|
||||
sidx = data.stack_idx;
|
||||
_reserved_stack = data.stack_idx;
|
||||
_committed_stack = NativeCallStackStorage::invalid;
|
||||
}
|
||||
|
||||
StateType type() const {
|
||||
@ -110,16 +123,50 @@ private:
|
||||
return static_cast<MemTag>(type_tag[1]);
|
||||
}
|
||||
|
||||
RegionData regiondata() const {
|
||||
return RegionData{sidx, mem_tag()};
|
||||
RegionData reserved_regiondata() const {
|
||||
return RegionData{_reserved_stack, mem_tag()};
|
||||
}
|
||||
RegionData committed_regiondata() const {
|
||||
return RegionData{_committed_stack, mem_tag()};
|
||||
}
|
||||
|
||||
void set_tag(MemTag tag) {
|
||||
type_tag[1] = static_cast<uint8_t>(tag);
|
||||
}
|
||||
|
||||
NativeCallStackStorage::StackIndex stack() const {
|
||||
return sidx;
|
||||
NativeCallStackStorage::StackIndex reserved_stack() const {
|
||||
return _reserved_stack;
|
||||
}
|
||||
|
||||
NativeCallStackStorage::StackIndex committed_stack() const {
|
||||
return _committed_stack;
|
||||
}
|
||||
|
||||
void set_reserve_stack(NativeCallStackStorage::StackIndex idx) {
|
||||
_reserved_stack = idx;
|
||||
}
|
||||
|
||||
void set_commit_stack(NativeCallStackStorage::StackIndex idx) {
|
||||
_committed_stack = idx;
|
||||
}
|
||||
|
||||
bool has_reserved_stack() {
|
||||
return _reserved_stack != NativeCallStackStorage::invalid;
|
||||
}
|
||||
|
||||
bool has_committed_stack() {
|
||||
return _committed_stack != NativeCallStackStorage::invalid;
|
||||
}
|
||||
|
||||
void set_type(StateType t) {
|
||||
type_tag[0] = static_cast<uint8_t>(t);
|
||||
}
|
||||
|
||||
bool equals(const IntervalState& other) const {
|
||||
return mem_tag() == other.mem_tag() &&
|
||||
type() == other.type() &&
|
||||
reserved_stack() == other.reserved_stack() &&
|
||||
committed_stack() == other.committed_stack();
|
||||
}
|
||||
};
|
||||
|
||||
@ -130,8 +177,14 @@ private:
|
||||
IntervalState out;
|
||||
|
||||
bool is_noop() {
|
||||
if (in.type() == StateType::Released &&
|
||||
in.type() == out.type() &&
|
||||
in.mem_tag() == out.mem_tag()) {
|
||||
return true;
|
||||
}
|
||||
return in.type() == out.type() &&
|
||||
RegionData::equals(in.regiondata(), out.regiondata());
|
||||
RegionData::equals(in.reserved_regiondata(), out.reserved_regiondata()) &&
|
||||
RegionData::equals(in.committed_regiondata(), out.committed_regiondata());
|
||||
}
|
||||
};
|
||||
|
||||
@ -193,8 +246,44 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
enum Operation {Release, Reserve, Commit, Uncommit};
|
||||
struct RequestInfo {
|
||||
position A, B;
|
||||
StateType _op;
|
||||
MemTag tag;
|
||||
SIndex callstack;
|
||||
bool use_tag_inplace;
|
||||
Operation op() const {
|
||||
return
|
||||
_op == StateType::Reserved && !use_tag_inplace ? Operation::Reserve :
|
||||
_op == StateType::Committed ? Operation::Commit :
|
||||
_op == StateType::Reserved && use_tag_inplace ? Operation::Uncommit :
|
||||
Operation::Release;
|
||||
}
|
||||
|
||||
int op_to_index() const {
|
||||
return
|
||||
_op == StateType::Reserved && !use_tag_inplace ? 1 :
|
||||
_op == StateType::Committed ? 2 :
|
||||
_op == StateType::Reserved && use_tag_inplace ? 3 :
|
||||
0;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
SummaryDiff register_mapping(position A, position B, StateType state, const RegionData& metadata, bool use_tag_inplace = false);
|
||||
StateType get_new_state(const StateType existinting_state, const RequestInfo& req) const;
|
||||
MemTag get_new_tag(const MemTag existinting_tag, const RequestInfo& req) const;
|
||||
SIndex get_new_reserve_callstack(const SIndex existinting_stack, const StateType ex, const RequestInfo& req) const;
|
||||
SIndex get_new_commit_callstack(const SIndex existinting_stack, const StateType ex, const RequestInfo& req) const;
|
||||
void compute_summary_diff(const SingleDiff::delta region_size, const MemTag t1, const StateType& ex, const RequestInfo& req, const MemTag new_tag, SummaryDiff& diff) const;
|
||||
void update_region(TreapNode* n1, TreapNode* n2, const RequestInfo& req, SummaryDiff& diff);
|
||||
int state_to_index(const StateType st) const {
|
||||
return
|
||||
st == StateType::Released ? 0 :
|
||||
st == StateType::Reserved ? 1 :
|
||||
st == StateType::Committed ? 2 : -1;
|
||||
}
|
||||
|
||||
public:
|
||||
SummaryDiff reserve_mapping(position from, size size, const RegionData& metadata) {
|
||||
|
@ -22,8 +22,11 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "memory/resourceArea.hpp"
|
||||
#include "cds/cdsConfig.hpp"
|
||||
#include "oops/fieldInfo.inline.hpp"
|
||||
#include "runtime/atomic.hpp"
|
||||
#include "utilities/packedTable.hpp"
|
||||
|
||||
void FieldInfo::print(outputStream* os, ConstantPool* cp) {
|
||||
os->print_cr("index=%d name_index=%d name=%s signature_index=%d signature=%s offset=%d "
|
||||
@ -37,8 +40,10 @@ void FieldInfo::print(outputStream* os, ConstantPool* cp) {
|
||||
field_flags().as_uint(),
|
||||
initializer_index(),
|
||||
generic_signature_index(),
|
||||
_field_flags.is_injected() ? lookup_symbol(generic_signature_index())->as_utf8() : cp->symbol_at(generic_signature_index())->as_utf8(),
|
||||
contended_group());
|
||||
_field_flags.is_generic() ? (_field_flags.is_injected() ?
|
||||
lookup_symbol(generic_signature_index())->as_utf8() : cp->symbol_at(generic_signature_index())->as_utf8()
|
||||
) : "",
|
||||
is_contended() ? contended_group() : 0);
|
||||
}
|
||||
|
||||
void FieldInfo::print_from_growable_array(outputStream* os, GrowableArray<FieldInfo>* array, ConstantPool* cp) {
|
||||
@ -62,13 +67,17 @@ Array<u1>* FieldInfoStream::create_FieldInfoStream(GrowableArray<FieldInfo>* fie
|
||||
StreamSizer s;
|
||||
StreamFieldSizer sizer(&s);
|
||||
|
||||
assert(fields->length() == java_fields + injected_fields, "must be");
|
||||
|
||||
sizer.consumer()->accept_uint(java_fields);
|
||||
sizer.consumer()->accept_uint(injected_fields);
|
||||
for (int i = 0; i < fields->length(); i++) {
|
||||
FieldInfo* fi = fields->adr_at(i);
|
||||
sizer.map_field_info(*fi);
|
||||
}
|
||||
int storage_size = sizer.consumer()->position() + 1;
|
||||
// Originally there was an extra byte with 0 terminating the reading;
|
||||
// now we check limits instead.
|
||||
int storage_size = sizer.consumer()->position();
|
||||
Array<u1>* const fis = MetadataFactory::new_array<u1>(loader_data, storage_size, CHECK_NULL);
|
||||
|
||||
using StreamWriter = UNSIGNED5::Writer<Array<u1>*, int, ArrayHelper<Array<u1>*, int>>;
|
||||
@ -79,15 +88,14 @@ Array<u1>* FieldInfoStream::create_FieldInfoStream(GrowableArray<FieldInfo>* fie
|
||||
writer.consumer()->accept_uint(java_fields);
|
||||
writer.consumer()->accept_uint(injected_fields);
|
||||
for (int i = 0; i < fields->length(); i++) {
|
||||
FieldInfo* fi = fields->adr_at(i);
|
||||
writer.map_field_info(*fi);
|
||||
writer.map_field_info(fields->at(i));
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
FieldInfoReader r(fis);
|
||||
int jfc = r.next_uint();
|
||||
int jfc, ifc;
|
||||
r.read_field_counts(&jfc, &ifc);
|
||||
assert(jfc == java_fields, "Must be");
|
||||
int ifc = r.next_uint();
|
||||
assert(ifc == injected_fields, "Must be");
|
||||
for (int i = 0; i < jfc + ifc; i++) {
|
||||
FieldInfo fi;
|
||||
@ -113,30 +121,221 @@ Array<u1>* FieldInfoStream::create_FieldInfoStream(GrowableArray<FieldInfo>* fie
|
||||
return fis;
|
||||
}
|
||||
|
||||
GrowableArray<FieldInfo>* FieldInfoStream::create_FieldInfoArray(const Array<u1>* fis, int* java_fields_count, int* injected_fields_count) {
|
||||
int length = FieldInfoStream::num_total_fields(fis);
|
||||
GrowableArray<FieldInfo>* array = new GrowableArray<FieldInfo>(length);
|
||||
int FieldInfoStream::compare_name_and_sig(const Symbol* n1, const Symbol* s1, const Symbol* n2, const Symbol* s2) {
|
||||
int cmp = n1->fast_compare(n2);
|
||||
return cmp != 0 ? cmp : s1->fast_compare(s2);
|
||||
}
|
||||
|
||||
|
||||
// We use both name and signature during the comparison; while JLS require unique
|
||||
// names for fields, JVMS requires only unique name + signature combination.
|
||||
struct field_pos {
|
||||
Symbol* _name;
|
||||
Symbol* _signature;
|
||||
int _index;
|
||||
int _position;
|
||||
};
|
||||
|
||||
class FieldInfoSupplier: public PackedTableBuilder::Supplier {
|
||||
const field_pos* _positions;
|
||||
size_t _elements;
|
||||
|
||||
public:
|
||||
FieldInfoSupplier(const field_pos* positions, size_t elements): _positions(positions), _elements(elements) {}
|
||||
|
||||
bool next(uint32_t* key, uint32_t* value) override {
|
||||
if (_elements == 0) {
|
||||
return false;
|
||||
}
|
||||
*key = _positions->_position;
|
||||
*value = _positions->_index;
|
||||
++_positions;
|
||||
--_elements;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
Array<u1>* FieldInfoStream::create_search_table(ConstantPool* cp, const Array<u1>* fis, ClassLoaderData* loader_data, TRAPS) {
|
||||
if (CDSConfig::is_dumping_dynamic_archive()) {
|
||||
// We cannot use search table; in case of dynamic archives it should be sorted by "requested" addresses,
|
||||
// but Symbol* addresses are coming from _constants, which has "buffered" addresses.
|
||||
// For background, see new comments inside allocate_node_impl in symbolTable.cpp
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FieldInfoReader r(fis);
|
||||
*java_fields_count = r.next_uint();
|
||||
*injected_fields_count = r.next_uint();
|
||||
int java_fields;
|
||||
int injected_fields;
|
||||
r.read_field_counts(&java_fields, &injected_fields);
|
||||
assert(java_fields >= 0, "must be");
|
||||
if (java_fields == 0 || fis->length() == 0 || static_cast<uint>(java_fields) < BinarySearchThreshold) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ResourceMark rm;
|
||||
field_pos* positions = NEW_RESOURCE_ARRAY(field_pos, java_fields);
|
||||
for (int i = 0; i < java_fields; ++i) {
|
||||
assert(r.has_next(), "number of fields must match");
|
||||
|
||||
positions[i]._position = r.position();
|
||||
FieldInfo fi;
|
||||
r.read_field_info(fi);
|
||||
|
||||
positions[i]._name = fi.name(cp);
|
||||
positions[i]._signature = fi.signature(cp);
|
||||
positions[i]._index = i;
|
||||
}
|
||||
auto compare_pair = [](const void* v1, const void* v2) {
|
||||
const field_pos* p1 = reinterpret_cast<const field_pos*>(v1);
|
||||
const field_pos* p2 = reinterpret_cast<const field_pos*>(v2);
|
||||
return compare_name_and_sig(p1->_name, p1->_signature, p2->_name, p2->_signature);
|
||||
};
|
||||
qsort(positions, java_fields, sizeof(field_pos), compare_pair);
|
||||
|
||||
PackedTableBuilder builder(fis->length() - 1, java_fields - 1);
|
||||
Array<u1>* table = MetadataFactory::new_array<u1>(loader_data, java_fields * builder.element_bytes(), CHECK_NULL);
|
||||
FieldInfoSupplier supplier(positions, java_fields);
|
||||
builder.fill(table->data(), static_cast<size_t>(table->length()), supplier);
|
||||
return table;
|
||||
}
|
||||
|
||||
GrowableArray<FieldInfo>* FieldInfoStream::create_FieldInfoArray(const Array<u1>* fis, int* java_fields_count, int* injected_fields_count) {
|
||||
FieldInfoReader r(fis);
|
||||
r.read_field_counts(java_fields_count, injected_fields_count);
|
||||
int length = *java_fields_count + *injected_fields_count;
|
||||
|
||||
GrowableArray<FieldInfo>* array = new GrowableArray<FieldInfo>(length);
|
||||
while (r.has_next()) {
|
||||
FieldInfo fi;
|
||||
r.read_field_info(fi);
|
||||
array->append(fi);
|
||||
}
|
||||
assert(array->length() == length, "Must be");
|
||||
assert(array->length() == *java_fields_count + *injected_fields_count, "Must be");
|
||||
return array;
|
||||
}
|
||||
|
||||
void FieldInfoStream::print_from_fieldinfo_stream(Array<u1>* fis, outputStream* os, ConstantPool* cp) {
|
||||
int length = FieldInfoStream::num_total_fields(fis);
|
||||
FieldInfoReader r(fis);
|
||||
int java_field_count = r.next_uint();
|
||||
int injected_fields_count = r.next_uint();
|
||||
int java_fields_count;
|
||||
int injected_fields_count;
|
||||
r.read_field_counts(&java_fields_count, &injected_fields_count);
|
||||
while (r.has_next()) {
|
||||
FieldInfo fi;
|
||||
r.read_field_info(fi);
|
||||
fi.print(os, cp);
|
||||
}
|
||||
}
|
||||
|
||||
class FieldInfoComparator: public PackedTableLookup::Comparator {
|
||||
const FieldInfoReader* _reader;
|
||||
ConstantPool* _cp;
|
||||
const Symbol* _name;
|
||||
const Symbol* _signature;
|
||||
|
||||
public:
|
||||
FieldInfoComparator(const FieldInfoReader* reader, ConstantPool* cp, const Symbol* name, const Symbol* signature):
|
||||
_reader(reader), _cp(cp), _name(name), _signature(signature) {}
|
||||
|
||||
int compare_to(uint32_t position) override {
|
||||
FieldInfoReader r2(*_reader);
|
||||
r2.set_position_and_next_index(position, -1);
|
||||
u2 name_index, sig_index;
|
||||
r2.read_name_and_signature(&name_index, &sig_index);
|
||||
Symbol* mid_name = _cp->symbol_at(name_index);
|
||||
Symbol* mid_sig = _cp->symbol_at(sig_index);
|
||||
|
||||
return FieldInfoStream::compare_name_and_sig(_name, _signature, mid_name, mid_sig);
|
||||
}
|
||||
|
||||
#ifdef ASSERT
|
||||
void reset(uint32_t position) override {
|
||||
FieldInfoReader r2(*_reader);
|
||||
r2.set_position_and_next_index(position, -1);
|
||||
u2 name_index, signature_index;
|
||||
r2.read_name_and_signature(&name_index, &signature_index);
|
||||
_name = _cp->symbol_at(name_index);
|
||||
_signature = _cp->symbol_at(signature_index);
|
||||
}
|
||||
#endif // ASSERT
|
||||
};
|
||||
|
||||
#ifdef ASSERT
|
||||
void FieldInfoStream::validate_search_table(ConstantPool* cp, const Array<u1>* fis, const Array<u1>* search_table) {
|
||||
if (search_table == nullptr) {
|
||||
return;
|
||||
}
|
||||
FieldInfoReader reader(fis);
|
||||
int java_fields, injected_fields;
|
||||
reader.read_field_counts(&java_fields, &injected_fields);
|
||||
assert(java_fields > 0, "must be");
|
||||
|
||||
PackedTableLookup lookup(fis->length() - 1, java_fields - 1, search_table);
|
||||
assert(lookup.element_bytes() * java_fields == static_cast<unsigned int>(search_table->length()), "size does not match");
|
||||
|
||||
FieldInfoComparator comparator(&reader, cp, nullptr, nullptr);
|
||||
// Check 1: assert that elements have the correct order based on the comparison function
|
||||
lookup.validate_order(comparator);
|
||||
|
||||
// Check 2: Iterate through the original stream (not just search_table) and try if lookup works as expected
|
||||
reader.set_position_and_next_index(0, 0);
|
||||
reader.read_field_counts(&java_fields, &injected_fields);
|
||||
while (reader.has_next()) {
|
||||
int field_start = reader.position();
|
||||
FieldInfo fi;
|
||||
reader.read_field_info(fi);
|
||||
if (fi.field_flags().is_injected()) {
|
||||
// checking only java fields that precede injected ones
|
||||
break;
|
||||
}
|
||||
|
||||
FieldInfoReader r2(fis);
|
||||
int index = r2.search_table_lookup(search_table, fi.name(cp), fi.signature(cp), cp, java_fields);
|
||||
assert(index == static_cast<int>(fi.index()), "wrong index: %d != %u", index, fi.index());
|
||||
assert(index == r2.next_index(), "index should match");
|
||||
assert(field_start == r2.position(), "must find the same position");
|
||||
}
|
||||
}
|
||||
#endif // ASSERT
|
||||
|
||||
void FieldInfoStream::print_search_table(outputStream* st, ConstantPool* cp, const Array<u1>* fis, const Array<u1>* search_table) {
|
||||
if (search_table == nullptr) {
|
||||
return;
|
||||
}
|
||||
FieldInfoReader reader(fis);
|
||||
int java_fields, injected_fields;
|
||||
reader.read_field_counts(&java_fields, &injected_fields);
|
||||
assert(java_fields > 0, "must be");
|
||||
PackedTableLookup lookup(fis->length() - 1, java_fields - 1, search_table);
|
||||
auto printer = [&] (size_t offset, uint32_t position, uint32_t index) {
|
||||
reader.set_position_and_next_index(position, -1);
|
||||
u2 name_index, sig_index;
|
||||
reader.read_name_and_signature(&name_index, &sig_index);
|
||||
Symbol* name = cp->symbol_at(name_index);
|
||||
Symbol* sig = cp->symbol_at(sig_index);
|
||||
st->print(" [%zu] #%d,#%d = ", offset, name_index, sig_index);
|
||||
name->print_symbol_on(st);
|
||||
st->print(":");
|
||||
sig->print_symbol_on(st);
|
||||
st->print(" @ %p,%p", name, sig);
|
||||
st->cr();
|
||||
};
|
||||
|
||||
lookup.iterate(printer);
|
||||
}
|
||||
|
||||
int FieldInfoReader::search_table_lookup(const Array<u1>* search_table, const Symbol* name, const Symbol* signature, ConstantPool* cp, int java_fields) {
|
||||
assert(java_fields >= 0, "must be");
|
||||
if (java_fields == 0) {
|
||||
return -1;
|
||||
}
|
||||
FieldInfoComparator comp(this, cp, name, signature);
|
||||
PackedTableLookup lookup(_r.limit() - 1, java_fields - 1, search_table);
|
||||
uint32_t position;
|
||||
static_assert(sizeof(uint32_t) == sizeof(_next_index), "field size assert");
|
||||
if (lookup.search(comp, &position, reinterpret_cast<uint32_t*>(&_next_index))) {
|
||||
_r.set_position(static_cast<int>(position));
|
||||
return _next_index;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@ -222,29 +222,28 @@ public:
|
||||
void map_field_info(const FieldInfo& fi);
|
||||
};
|
||||
|
||||
|
||||
// Gadget for decoding and reading the stream of field records.
|
||||
class FieldInfoReader {
|
||||
friend class FieldInfoStream;
|
||||
friend class ClassFileParser;
|
||||
friend class FieldStreamBase;
|
||||
friend class FieldInfo;
|
||||
|
||||
UNSIGNED5::Reader<const u1*, int> _r;
|
||||
int _next_index;
|
||||
|
||||
public:
|
||||
public:
|
||||
FieldInfoReader(const Array<u1>* fi);
|
||||
|
||||
private:
|
||||
uint32_t next_uint() { return _r.next_uint(); }
|
||||
private:
|
||||
inline uint32_t next_uint() { return _r.next_uint(); }
|
||||
void skip(int n) { int s = _r.try_skip(n); assert(s == n,""); }
|
||||
|
||||
public:
|
||||
int has_next() { return _r.has_next(); }
|
||||
int position() { return _r.position(); }
|
||||
int next_index() { return _next_index; }
|
||||
void read_field_counts(int* java_fields, int* injected_fields);
|
||||
int has_next() const { return _r.position() < _r.limit(); }
|
||||
int position() const { return _r.position(); }
|
||||
int next_index() const { return _next_index; }
|
||||
void read_name_and_signature(u2* name_index, u2* signature_index);
|
||||
void read_field_info(FieldInfo& fi);
|
||||
|
||||
int search_table_lookup(const Array<u1>* search_table, const Symbol* name, const Symbol* signature, ConstantPool* cp, int java_fields);
|
||||
|
||||
// skip a whole field record, both required and optional bits
|
||||
FieldInfoReader& skip_field_info();
|
||||
|
||||
@ -271,6 +270,11 @@ class FieldInfoStream : AllStatic {
|
||||
friend class JavaFieldStream;
|
||||
friend class FieldStreamBase;
|
||||
friend class ClassFileParser;
|
||||
friend class FieldInfoReader;
|
||||
friend class FieldInfoComparator;
|
||||
|
||||
private:
|
||||
static int compare_name_and_sig(const Symbol* n1, const Symbol* s1, const Symbol* n2, const Symbol* s2);
|
||||
|
||||
public:
|
||||
static int num_java_fields(const Array<u1>* fis);
|
||||
@ -278,9 +282,14 @@ class FieldInfoStream : AllStatic {
|
||||
static int num_total_fields(const Array<u1>* fis);
|
||||
|
||||
static Array<u1>* create_FieldInfoStream(GrowableArray<FieldInfo>* fields, int java_fields, int injected_fields,
|
||||
ClassLoaderData* loader_data, TRAPS);
|
||||
ClassLoaderData* loader_data, TRAPS);
|
||||
static Array<u1>* create_search_table(ConstantPool* cp, const Array<u1>* fis, ClassLoaderData* loader_data, TRAPS);
|
||||
static GrowableArray<FieldInfo>* create_FieldInfoArray(const Array<u1>* fis, int* java_fields_count, int* injected_fields_count);
|
||||
static void print_from_fieldinfo_stream(Array<u1>* fis, outputStream* os, ConstantPool* cp);
|
||||
|
||||
DEBUG_ONLY(static void validate_search_table(ConstantPool* cp, const Array<u1>* fis, const Array<u1>* search_table);)
|
||||
|
||||
static void print_search_table(outputStream* st, ConstantPool* cp, const Array<u1>* fis, const Array<u1>* search_table);
|
||||
};
|
||||
|
||||
class FieldStatus {
|
||||
|
@ -56,16 +56,27 @@ inline Symbol* FieldInfo::lookup_symbol(int symbol_index) const {
|
||||
|
||||
inline int FieldInfoStream::num_injected_java_fields(const Array<u1>* fis) {
|
||||
FieldInfoReader fir(fis);
|
||||
fir.skip(1);
|
||||
return fir.next_uint();
|
||||
int java_fields_count;
|
||||
int injected_fields_count;
|
||||
fir.read_field_counts(&java_fields_count, &injected_fields_count);
|
||||
return injected_fields_count;
|
||||
}
|
||||
|
||||
inline int FieldInfoStream::num_total_fields(const Array<u1>* fis) {
|
||||
FieldInfoReader fir(fis);
|
||||
return fir.next_uint() + fir.next_uint();
|
||||
int java_fields_count;
|
||||
int injected_fields_count;
|
||||
fir.read_field_counts(&java_fields_count, &injected_fields_count);
|
||||
return java_fields_count + injected_fields_count;
|
||||
}
|
||||
|
||||
inline int FieldInfoStream::num_java_fields(const Array<u1>* fis) { return FieldInfoReader(fis).next_uint(); }
|
||||
inline int FieldInfoStream::num_java_fields(const Array<u1>* fis) {
|
||||
FieldInfoReader fir(fis);
|
||||
int java_fields_count;
|
||||
int injected_fields_count;
|
||||
fir.read_field_counts(&java_fields_count, &injected_fields_count);
|
||||
return java_fields_count;
|
||||
}
|
||||
|
||||
template<typename CON>
|
||||
inline void Mapper<CON>::map_field_info(const FieldInfo& fi) {
|
||||
@ -94,13 +105,22 @@ inline void Mapper<CON>::map_field_info(const FieldInfo& fi) {
|
||||
|
||||
|
||||
inline FieldInfoReader::FieldInfoReader(const Array<u1>* fi)
|
||||
: _r(fi->data(), 0),
|
||||
: _r(fi->data(), fi->length()),
|
||||
_next_index(0) { }
|
||||
|
||||
inline void FieldInfoReader::read_field_counts(int* java_fields, int* injected_fields) {
|
||||
*java_fields = next_uint();
|
||||
*injected_fields = next_uint();
|
||||
}
|
||||
|
||||
inline void FieldInfoReader::read_name_and_signature(u2* name_index, u2* signature_index) {
|
||||
*name_index = checked_cast<u2>(next_uint());
|
||||
*signature_index = checked_cast<u2>(next_uint());
|
||||
}
|
||||
|
||||
inline void FieldInfoReader::read_field_info(FieldInfo& fi) {
|
||||
fi._index = _next_index++;
|
||||
fi._name_index = checked_cast<u2>(next_uint());
|
||||
fi._signature_index = checked_cast<u2>(next_uint());
|
||||
read_name_and_signature(&fi._name_index, &fi._signature_index);
|
||||
fi._offset = next_uint();
|
||||
fi._access_flags = AccessFlags(checked_cast<u2>(next_uint()));
|
||||
fi._field_flags = FieldInfo::FieldFlags(next_uint());
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -56,17 +56,23 @@ class FieldStreamBase : public StackObj {
|
||||
|
||||
inline FieldStreamBase(const Array<u1>* fieldinfo_stream, ConstantPool* constants, int start, int limit);
|
||||
|
||||
inline FieldStreamBase(Array<u1>* fieldinfo_stream, ConstantPool* constants);
|
||||
inline FieldStreamBase(const Array<u1>* fieldinfo_stream, ConstantPool* constants);
|
||||
|
||||
private:
|
||||
private:
|
||||
void initialize() {
|
||||
int java_fields_count = _reader.next_uint();
|
||||
int injected_fields_count = _reader.next_uint();
|
||||
assert( _limit <= java_fields_count + injected_fields_count, "Safety check");
|
||||
int java_fields_count;
|
||||
int injected_fields_count;
|
||||
_reader.read_field_counts(&java_fields_count, &injected_fields_count);
|
||||
if (_limit < _index) {
|
||||
_limit = java_fields_count + injected_fields_count;
|
||||
} else {
|
||||
assert( _limit <= java_fields_count + injected_fields_count, "Safety check");
|
||||
}
|
||||
if (_limit != 0) {
|
||||
_reader.read_field_info(_fi_buf);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
inline FieldStreamBase(InstanceKlass* klass);
|
||||
|
||||
@ -138,8 +144,11 @@ class FieldStreamBase : public StackObj {
|
||||
|
||||
// Iterate over only the Java fields
|
||||
class JavaFieldStream : public FieldStreamBase {
|
||||
Array<u1>* _search_table;
|
||||
|
||||
public:
|
||||
JavaFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants(), 0, k->java_fields_count()) {}
|
||||
JavaFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants(), 0, k->java_fields_count()),
|
||||
_search_table(k->fieldinfo_search_table()) {}
|
||||
|
||||
u2 name_index() const {
|
||||
assert(!field()->field_flags().is_injected(), "regular only");
|
||||
@ -149,7 +158,6 @@ class JavaFieldStream : public FieldStreamBase {
|
||||
u2 signature_index() const {
|
||||
assert(!field()->field_flags().is_injected(), "regular only");
|
||||
return field()->signature_index();
|
||||
return -1;
|
||||
}
|
||||
|
||||
u2 generic_signature_index() const {
|
||||
@ -164,6 +172,10 @@ class JavaFieldStream : public FieldStreamBase {
|
||||
assert(!field()->field_flags().is_injected(), "regular only");
|
||||
return field()->initializer_index();
|
||||
}
|
||||
|
||||
// Performs either a linear search or binary search through the stream
|
||||
// looking for a matching name/signature combo
|
||||
bool lookup(const Symbol* name, const Symbol* signature);
|
||||
};
|
||||
|
||||
|
||||
@ -176,7 +188,6 @@ class InternalFieldStream : public FieldStreamBase {
|
||||
|
||||
class AllFieldStream : public FieldStreamBase {
|
||||
public:
|
||||
AllFieldStream(Array<u1>* fieldinfo, ConstantPool* constants): FieldStreamBase(fieldinfo, constants) {}
|
||||
AllFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants()) {}
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -33,22 +33,18 @@
|
||||
FieldStreamBase::FieldStreamBase(const Array<u1>* fieldinfo_stream, ConstantPool* constants, int start, int limit) :
|
||||
_fieldinfo_stream(fieldinfo_stream),
|
||||
_reader(FieldInfoReader(_fieldinfo_stream)),
|
||||
_constants(constantPoolHandle(Thread::current(), constants)), _index(start) {
|
||||
_index = start;
|
||||
if (limit < start) {
|
||||
_limit = FieldInfoStream::num_total_fields(_fieldinfo_stream);
|
||||
} else {
|
||||
_limit = limit;
|
||||
}
|
||||
_constants(constantPoolHandle(Thread::current(), constants)),
|
||||
_index(start),
|
||||
_limit(limit) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
FieldStreamBase::FieldStreamBase(Array<u1>* fieldinfo_stream, ConstantPool* constants) :
|
||||
FieldStreamBase::FieldStreamBase(const Array<u1>* fieldinfo_stream, ConstantPool* constants) :
|
||||
_fieldinfo_stream(fieldinfo_stream),
|
||||
_reader(FieldInfoReader(_fieldinfo_stream)),
|
||||
_constants(constantPoolHandle(Thread::current(), constants)),
|
||||
_index(0),
|
||||
_limit(FieldInfoStream::num_total_fields(_fieldinfo_stream)) {
|
||||
_limit(-1) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
@ -57,9 +53,28 @@ FieldStreamBase::FieldStreamBase(InstanceKlass* klass) :
|
||||
_reader(FieldInfoReader(_fieldinfo_stream)),
|
||||
_constants(constantPoolHandle(Thread::current(), klass->constants())),
|
||||
_index(0),
|
||||
_limit(FieldInfoStream::num_total_fields(_fieldinfo_stream)) {
|
||||
_limit(-1) {
|
||||
assert(klass == field_holder(), "");
|
||||
initialize();
|
||||
}
|
||||
|
||||
inline bool JavaFieldStream::lookup(const Symbol* name, const Symbol* signature) {
|
||||
if (_search_table != nullptr) {
|
||||
int index = _reader.search_table_lookup(_search_table, name, signature, _constants(), _limit);
|
||||
if (index >= 0) {
|
||||
assert(index < _limit, "must be");
|
||||
_index = index;
|
||||
_reader.read_field_info(_fi_buf);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (; !done(); next()) {
|
||||
if (this->name() == name && this->signature() == signature) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // SHARE_OOPS_FIELDSTREAMS_INLINE_HPP
|
||||
|
@ -686,6 +686,11 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) {
|
||||
}
|
||||
set_fieldinfo_stream(nullptr);
|
||||
|
||||
if (fieldinfo_search_table() != nullptr && !fieldinfo_search_table()->is_shared()) {
|
||||
MetadataFactory::free_array<u1>(loader_data, fieldinfo_search_table());
|
||||
}
|
||||
set_fieldinfo_search_table(nullptr);
|
||||
|
||||
if (fields_status() != nullptr && !fields_status()->is_shared()) {
|
||||
MetadataFactory::free_array<FieldStatus>(loader_data, fields_status());
|
||||
}
|
||||
@ -1786,13 +1791,12 @@ FieldInfo InstanceKlass::field(int index) const {
|
||||
}
|
||||
|
||||
bool InstanceKlass::find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const {
|
||||
for (JavaFieldStream fs(this); !fs.done(); fs.next()) {
|
||||
Symbol* f_name = fs.name();
|
||||
Symbol* f_sig = fs.signature();
|
||||
if (f_name == name && f_sig == sig) {
|
||||
fd->reinitialize(const_cast<InstanceKlass*>(this), fs.to_FieldInfo());
|
||||
return true;
|
||||
}
|
||||
JavaFieldStream fs(this);
|
||||
if (fs.lookup(name, sig)) {
|
||||
assert(fs.name() == name, "name must match");
|
||||
assert(fs.signature() == sig, "signature must match");
|
||||
fd->reinitialize(const_cast<InstanceKlass*>(this), fs.to_FieldInfo());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -2610,6 +2614,7 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) {
|
||||
}
|
||||
|
||||
it->push(&_fieldinfo_stream);
|
||||
it->push(&_fieldinfo_search_table);
|
||||
// _fields_status might be written into by Rewriter::scan_method() -> fd.set_has_initialized_final_update()
|
||||
it->push(&_fields_status, MetaspaceClosure::_writable);
|
||||
|
||||
@ -2710,6 +2715,8 @@ void InstanceKlass::remove_unshareable_info() {
|
||||
DEBUG_ONLY(_shared_class_load_count = 0);
|
||||
|
||||
remove_unshareable_flags();
|
||||
|
||||
DEBUG_ONLY(FieldInfoStream::validate_search_table(_constants, _fieldinfo_stream, _fieldinfo_search_table));
|
||||
}
|
||||
|
||||
void InstanceKlass::remove_unshareable_flags() {
|
||||
@ -2816,6 +2823,8 @@ void InstanceKlass::restore_unshareable_info(ClassLoaderData* loader_data, Handl
|
||||
if (DiagnoseSyncOnValueBasedClasses && has_value_based_class_annotation() && !is_value_based()) {
|
||||
set_is_value_based();
|
||||
}
|
||||
|
||||
DEBUG_ONLY(FieldInfoStream::validate_search_table(_constants, _fieldinfo_stream, _fieldinfo_search_table));
|
||||
}
|
||||
|
||||
// Check if a class or any of its supertypes has a version older than 50.
|
||||
@ -3760,6 +3769,11 @@ void InstanceKlass::print_on(outputStream* st) const {
|
||||
map++;
|
||||
}
|
||||
st->cr();
|
||||
|
||||
if (fieldinfo_search_table() != nullptr) {
|
||||
st->print_cr(BULLET"---- field info search table:");
|
||||
FieldInfoStream::print_search_table(st, _constants, _fieldinfo_stream, _fieldinfo_search_table);
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceKlass::print_value_on(outputStream* st) const {
|
||||
|
@ -276,6 +276,7 @@ class InstanceKlass: public Klass {
|
||||
|
||||
// Fields information is stored in an UNSIGNED5 encoded stream (see fieldInfo.hpp)
|
||||
Array<u1>* _fieldinfo_stream;
|
||||
Array<u1>* _fieldinfo_search_table;
|
||||
Array<FieldStatus>* _fields_status;
|
||||
|
||||
// embedded Java vtable follows here
|
||||
@ -398,6 +399,9 @@ class InstanceKlass: public Klass {
|
||||
Array<u1>* fieldinfo_stream() const { return _fieldinfo_stream; }
|
||||
void set_fieldinfo_stream(Array<u1>* fis) { _fieldinfo_stream = fis; }
|
||||
|
||||
Array<u1>* fieldinfo_search_table() const { return _fieldinfo_search_table; }
|
||||
void set_fieldinfo_search_table(Array<u1>* table) { _fieldinfo_search_table = table; }
|
||||
|
||||
Array<FieldStatus>* fields_status() const {return _fields_status; }
|
||||
void set_fields_status(Array<FieldStatus>* array) { _fields_status = array; }
|
||||
|
||||
|
@ -44,7 +44,7 @@
|
||||
#include "runtime/mutexLocker.hpp"
|
||||
#include "utilities/macros.hpp"
|
||||
|
||||
ObjArrayKlass* ObjArrayKlass::allocate(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS) {
|
||||
ObjArrayKlass* ObjArrayKlass::allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS) {
|
||||
assert(ObjArrayKlass::header_size() <= InstanceKlass::header_size(),
|
||||
"array klasses must be same size as InstanceKlass");
|
||||
|
||||
@ -100,7 +100,7 @@ ObjArrayKlass* ObjArrayKlass::allocate_objArray_klass(ClassLoaderData* loader_da
|
||||
}
|
||||
|
||||
// Initialize instance variables
|
||||
ObjArrayKlass* oak = ObjArrayKlass::allocate(loader_data, n, element_klass, name, CHECK_NULL);
|
||||
ObjArrayKlass* oak = ObjArrayKlass::allocate_klass(loader_data, n, element_klass, name, CHECK_NULL);
|
||||
|
||||
ModuleEntry* module = oak->module();
|
||||
assert(module != nullptr, "No module entry for array");
|
||||
@ -149,7 +149,7 @@ size_t ObjArrayKlass::oop_size(oop obj) const {
|
||||
return objArrayOop(obj)->object_size();
|
||||
}
|
||||
|
||||
objArrayOop ObjArrayKlass::allocate(int length, TRAPS) {
|
||||
objArrayOop ObjArrayKlass::allocate_instance(int length, TRAPS) {
|
||||
check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL);
|
||||
size_t size = objArrayOopDesc::object_size(length);
|
||||
return (objArrayOop)Universe::heap()->array_allocate(this, size, length,
|
||||
@ -160,7 +160,7 @@ oop ObjArrayKlass::multi_allocate(int rank, jint* sizes, TRAPS) {
|
||||
int length = *sizes;
|
||||
ArrayKlass* ld_klass = lower_dimension();
|
||||
// If length < 0 allocate will throw an exception.
|
||||
objArrayOop array = allocate(length, CHECK_NULL);
|
||||
objArrayOop array = allocate_instance(length, CHECK_NULL);
|
||||
objArrayHandle h_array (THREAD, array);
|
||||
if (rank > 1) {
|
||||
if (length != 0) {
|
||||
|
@ -33,8 +33,10 @@ class ClassLoaderData;
|
||||
// ObjArrayKlass is the klass for objArrays
|
||||
|
||||
class ObjArrayKlass : public ArrayKlass {
|
||||
friend class VMStructs;
|
||||
friend class Deoptimization;
|
||||
friend class JVMCIVMStructs;
|
||||
friend class oopFactory;
|
||||
friend class VMStructs;
|
||||
|
||||
public:
|
||||
static const KlassKind Kind = ObjArrayKlassKind;
|
||||
@ -47,7 +49,9 @@ class ObjArrayKlass : public ArrayKlass {
|
||||
|
||||
// Constructor
|
||||
ObjArrayKlass(int n, Klass* element_klass, Symbol* name);
|
||||
static ObjArrayKlass* allocate(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS);
|
||||
static ObjArrayKlass* allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS);
|
||||
|
||||
objArrayOop allocate_instance(int length, TRAPS);
|
||||
public:
|
||||
// For dummy objects
|
||||
ObjArrayKlass() {}
|
||||
@ -78,7 +82,6 @@ class ObjArrayKlass : public ArrayKlass {
|
||||
static ObjArrayKlass* allocate_objArray_klass(ClassLoaderData* loader_data,
|
||||
int n, Klass* element_klass, TRAPS);
|
||||
|
||||
objArrayOop allocate(int length, TRAPS);
|
||||
oop multi_allocate(int rank, jint* sizes, TRAPS);
|
||||
|
||||
// Copying
|
||||
|
@ -50,7 +50,7 @@ TypeArrayKlass* TypeArrayKlass::create_klass(BasicType type,
|
||||
|
||||
ClassLoaderData* null_loader_data = ClassLoaderData::the_null_class_loader_data();
|
||||
|
||||
TypeArrayKlass* ak = TypeArrayKlass::allocate(null_loader_data, type, sym, CHECK_NULL);
|
||||
TypeArrayKlass* ak = TypeArrayKlass::allocate_klass(null_loader_data, type, sym, CHECK_NULL);
|
||||
|
||||
// Call complete_create_array_klass after all instance variables have been initialized.
|
||||
complete_create_array_klass(ak, ak->super(), ModuleEntryTable::javabase_moduleEntry(), CHECK_NULL);
|
||||
@ -65,7 +65,7 @@ TypeArrayKlass* TypeArrayKlass::create_klass(BasicType type,
|
||||
return ak;
|
||||
}
|
||||
|
||||
TypeArrayKlass* TypeArrayKlass::allocate(ClassLoaderData* loader_data, BasicType type, Symbol* name, TRAPS) {
|
||||
TypeArrayKlass* TypeArrayKlass::allocate_klass(ClassLoaderData* loader_data, BasicType type, Symbol* name, TRAPS) {
|
||||
assert(TypeArrayKlass::header_size() <= InstanceKlass::header_size(),
|
||||
"array klasses must be same size as InstanceKlass");
|
||||
|
||||
@ -101,7 +101,7 @@ oop TypeArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) {
|
||||
// For typeArrays this is only called for the last dimension
|
||||
assert(rank == 1, "just checking");
|
||||
int length = *last_size;
|
||||
return allocate(length, THREAD);
|
||||
return allocate_instance(length, THREAD);
|
||||
}
|
||||
|
||||
|
||||
|
@ -33,6 +33,8 @@ class ClassLoaderData;
|
||||
// It contains the type and size of the elements
|
||||
|
||||
class TypeArrayKlass : public ArrayKlass {
|
||||
friend class Deoptimization;
|
||||
friend class oopFactory;
|
||||
friend class VMStructs;
|
||||
|
||||
public:
|
||||
@ -43,7 +45,10 @@ class TypeArrayKlass : public ArrayKlass {
|
||||
|
||||
// Constructor
|
||||
TypeArrayKlass(BasicType type, Symbol* name);
|
||||
static TypeArrayKlass* allocate(ClassLoaderData* loader_data, BasicType type, Symbol* name, TRAPS);
|
||||
static TypeArrayKlass* allocate_klass(ClassLoaderData* loader_data, BasicType type, Symbol* name, TRAPS);
|
||||
|
||||
typeArrayOop allocate_common(int length, bool do_zero, TRAPS);
|
||||
typeArrayOop allocate_instance(int length, TRAPS) { return allocate_common(length, true, THREAD); }
|
||||
public:
|
||||
TypeArrayKlass() {} // For dummy objects.
|
||||
|
||||
@ -66,8 +71,6 @@ class TypeArrayKlass : public ArrayKlass {
|
||||
size_t oop_size(oop obj) const;
|
||||
|
||||
// Allocation
|
||||
typeArrayOop allocate_common(int length, bool do_zero, TRAPS);
|
||||
typeArrayOop allocate(int length, TRAPS) { return allocate_common(length, true, THREAD); }
|
||||
oop multi_allocate(int rank, jint* sizes, TRAPS);
|
||||
|
||||
oop protection_domain() const { return nullptr; }
|
||||
|
@ -464,6 +464,14 @@ class PhaseCFG : public Phase {
|
||||
Node* catch_cleanup_find_cloned_def(Block* use_blk, Node* def, Block* def_blk, int n_clone_idx);
|
||||
void catch_cleanup_inter_block(Node *use, Block *use_blk, Node *def, Block *def_blk, int n_clone_idx);
|
||||
|
||||
// Ensure that n happens at b or above, i.e. at a block that dominates b.
|
||||
// We expect n to be an orphan node without further inputs.
|
||||
void ensure_node_is_at_block_or_above(Node* n, Block* b);
|
||||
|
||||
// Move node n from its current placement into the end of block b.
|
||||
// Move also outgoing Mach projections.
|
||||
void move_node_and_its_projections_to_block(Node* n, Block* b);
|
||||
|
||||
// Detect implicit-null-check opportunities. Basically, find null checks
|
||||
// with suitable memory ops nearby. Use the memory op to do the null check.
|
||||
// I can generate a memory op if there is not one nearby.
|
||||
|
@ -678,10 +678,12 @@
|
||||
"Print progress during Iterative Global Value Numbering") \
|
||||
\
|
||||
develop(uint, VerifyIterativeGVN, 0, \
|
||||
"Verify Iterative Global Value Numbering" \
|
||||
"=XY, with Y: verify Def-Use modifications during IGVN" \
|
||||
" X: verify that type(n) == n->Value() after IGVN" \
|
||||
"X and Y in 0=off; 1=on") \
|
||||
"Verify Iterative Global Value Numbering =DCBA, with:" \
|
||||
" D: verify Node::Identity did not miss opportunities" \
|
||||
" C: verify Node::Ideal did not miss opportunities" \
|
||||
" B: verify that type(n) == n->Value() after IGVN" \
|
||||
" A: verify Def-Use modifications during IGVN" \
|
||||
"Each can be 0=off or 1=on") \
|
||||
constraint(VerifyIterativeGVNConstraintFunc, AtParse) \
|
||||
\
|
||||
develop(bool, TraceCISCSpill, false, \
|
||||
|
@ -155,7 +155,6 @@ void IdealGraphPrinter::init(const char* file_name, bool use_multiple_files, boo
|
||||
// in the mach where kill projections have no users but should
|
||||
// appear in the dump.
|
||||
_traverse_outs = true;
|
||||
_should_send_method = true;
|
||||
_output = nullptr;
|
||||
buffer[0] = 0;
|
||||
_depth = 0;
|
||||
@ -300,13 +299,11 @@ void IdealGraphPrinter::print_inline_tree(InlineTree *tree) {
|
||||
void IdealGraphPrinter::print_inlining() {
|
||||
|
||||
// Print inline tree
|
||||
if (_should_send_method) {
|
||||
InlineTree *inlineTree = C->ilt();
|
||||
if (inlineTree != nullptr) {
|
||||
print_inline_tree(inlineTree);
|
||||
} else {
|
||||
// print this method only
|
||||
}
|
||||
InlineTree *inlineTree = C->ilt();
|
||||
if (inlineTree != nullptr) {
|
||||
print_inline_tree(inlineTree);
|
||||
} else {
|
||||
// print this method only
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,7 +379,6 @@ void IdealGraphPrinter::begin_method() {
|
||||
|
||||
tail(PROPERTIES_ELEMENT);
|
||||
|
||||
_should_send_method = true;
|
||||
this->_current_method = method;
|
||||
|
||||
_xml->flush();
|
||||
@ -975,7 +971,7 @@ void IdealGraphPrinter::print_graph(const char* name, const frame* fr) {
|
||||
// Print current ideal graph
|
||||
void IdealGraphPrinter::print(const char* name, Node* node, GrowableArray<const Node*>& visible_nodes, const frame* fr) {
|
||||
|
||||
if (!_current_method || !_should_send_method || node == nullptr) return;
|
||||
if (!_current_method || node == nullptr) return;
|
||||
|
||||
if (name == nullptr) {
|
||||
stringStream graph_name;
|
||||
|
@ -110,7 +110,6 @@ class IdealGraphPrinter : public CHeapObj<mtCompiler> {
|
||||
ciMethod *_current_method;
|
||||
int _depth;
|
||||
char buffer[2048];
|
||||
bool _should_send_method;
|
||||
PhaseChaitin* _chaitin;
|
||||
bool _traverse_outs;
|
||||
Compile *C;
|
||||
|
@ -76,6 +76,36 @@ static bool needs_explicit_null_check_for_read(Node *val) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PhaseCFG::move_node_and_its_projections_to_block(Node* n, Block* b) {
|
||||
assert(!is_CFG(n), "cannot move CFG node");
|
||||
Block* old = get_block_for_node(n);
|
||||
old->find_remove(n);
|
||||
b->add_inst(n);
|
||||
map_node_to_block(n, b);
|
||||
// Check for Mach projections that also need to be moved.
|
||||
for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) {
|
||||
Node* out = n->fast_out(i);
|
||||
if (!out->is_MachProj()) {
|
||||
continue;
|
||||
}
|
||||
assert(!n->is_MachProj(), "nested projections are not allowed");
|
||||
move_node_and_its_projections_to_block(out, b);
|
||||
}
|
||||
}
|
||||
|
||||
void PhaseCFG::ensure_node_is_at_block_or_above(Node* n, Block* b) {
|
||||
assert(!is_CFG(n), "cannot move CFG node");
|
||||
Block* current = get_block_for_node(n);
|
||||
if (current->dominates(b)) {
|
||||
return; // n is already placed above b, do nothing.
|
||||
}
|
||||
// We only expect nodes without further inputs, like MachTemp or load Base.
|
||||
assert(n->req() == 0 || (n->req() == 1 && n->in(0) == (Node*)C->root()),
|
||||
"need for recursive hoisting not expected");
|
||||
assert(b->dominates(current), "precondition: can only move n to b if b dominates n");
|
||||
move_node_and_its_projections_to_block(n, b);
|
||||
}
|
||||
|
||||
//------------------------------implicit_null_check----------------------------
|
||||
// Detect implicit-null-check opportunities. Basically, find null checks
|
||||
// with suitable memory ops nearby. Use the memory op to do the null check.
|
||||
@ -160,12 +190,14 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo
|
||||
Node *m = val->out(i);
|
||||
if( !m->is_Mach() ) continue;
|
||||
MachNode *mach = m->as_Mach();
|
||||
if (mach->barrier_data() != 0) {
|
||||
if (mach->barrier_data() != 0 &&
|
||||
!mach->is_late_expanded_null_check_candidate()) {
|
||||
// Using memory accesses with barriers to perform implicit null checks is
|
||||
// not supported. These operations might expand into multiple assembly
|
||||
// instructions during code emission, including new memory accesses (e.g.
|
||||
// in G1's pre-barrier), which would invalidate the implicit null
|
||||
// exception table.
|
||||
// only supported if these are explicit marked as emitting a candidate
|
||||
// memory access instruction at their initial address. If not marked as
|
||||
// such, barrier-tagged operations might expand into one or several memory
|
||||
// access instructions located at arbitrary offsets from the initial
|
||||
// address, which would invalidate the implicit null exception table.
|
||||
continue;
|
||||
}
|
||||
was_store = false;
|
||||
@ -321,6 +353,14 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo
|
||||
// Ignore DecodeN val which could be hoisted to where needed.
|
||||
if( is_decoden ) continue;
|
||||
}
|
||||
if (mach->in(j)->is_MachTemp()) {
|
||||
assert(mach->in(j)->outcnt() == 1, "MachTemp nodes should not be shared");
|
||||
// Ignore MachTemp inputs, they can be safely hoisted with the candidate.
|
||||
// MachTemp nodes have no inputs themselves and are only used to reserve
|
||||
// a scratch register for the implementation of the node (e.g. in
|
||||
// late-expanded GC barriers).
|
||||
continue;
|
||||
}
|
||||
// Block of memory-op input
|
||||
Block *inb = get_block_for_node(mach->in(j));
|
||||
Block *b = block; // Start from nul check
|
||||
@ -388,38 +428,24 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo
|
||||
// Hoist it up to the end of the test block together with its inputs if they exist.
|
||||
for (uint i = 2; i < val->req(); i++) {
|
||||
// DecodeN has 2 regular inputs + optional MachTemp or load Base inputs.
|
||||
Node *temp = val->in(i);
|
||||
Block *tempb = get_block_for_node(temp);
|
||||
if (!tempb->dominates(block)) {
|
||||
assert(block->dominates(tempb), "sanity check: temp node placement");
|
||||
// We only expect nodes without further inputs, like MachTemp or load Base.
|
||||
assert(temp->req() == 0 || (temp->req() == 1 && temp->in(0) == (Node*)C->root()),
|
||||
"need for recursive hoisting not expected");
|
||||
tempb->find_remove(temp);
|
||||
block->add_inst(temp);
|
||||
map_node_to_block(temp, block);
|
||||
}
|
||||
}
|
||||
valb->find_remove(val);
|
||||
block->add_inst(val);
|
||||
map_node_to_block(val, block);
|
||||
// DecodeN on x86 may kill flags. Check for flag-killing projections
|
||||
// that also need to be hoisted.
|
||||
for (DUIterator_Fast jmax, j = val->fast_outs(jmax); j < jmax; j++) {
|
||||
Node* n = val->fast_out(j);
|
||||
if( n->is_MachProj() ) {
|
||||
get_block_for_node(n)->find_remove(n);
|
||||
block->add_inst(n);
|
||||
map_node_to_block(n, block);
|
||||
}
|
||||
// Inputs of val may already be early enough, but if not move them together with val.
|
||||
ensure_node_is_at_block_or_above(val->in(i), block);
|
||||
}
|
||||
move_node_and_its_projections_to_block(val, block);
|
||||
}
|
||||
}
|
||||
|
||||
// Move any MachTemp inputs to the end of the test block.
|
||||
for (uint i = 0; i < best->req(); i++) {
|
||||
Node* n = best->in(i);
|
||||
if (n == nullptr || !n->is_MachTemp()) {
|
||||
continue;
|
||||
}
|
||||
ensure_node_is_at_block_or_above(n, block);
|
||||
}
|
||||
|
||||
// Hoist the memory candidate up to the end of the test block.
|
||||
Block *old_block = get_block_for_node(best);
|
||||
old_block->find_remove(best);
|
||||
block->add_inst(best);
|
||||
map_node_to_block(best, block);
|
||||
move_node_and_its_projections_to_block(best, block);
|
||||
|
||||
// Move the control dependence if it is pinned to not-null block.
|
||||
// Don't change it in other cases: null or dominating control.
|
||||
@ -429,17 +455,6 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo
|
||||
best->set_req(0, proj->in(0)->in(0));
|
||||
}
|
||||
|
||||
// Check for flag-killing projections that also need to be hoisted
|
||||
// Should be DU safe because no edge updates.
|
||||
for (DUIterator_Fast jmax, j = best->fast_outs(jmax); j < jmax; j++) {
|
||||
Node* n = best->fast_out(j);
|
||||
if( n->is_MachProj() ) {
|
||||
get_block_for_node(n)->find_remove(n);
|
||||
block->add_inst(n);
|
||||
map_node_to_block(n, block);
|
||||
}
|
||||
}
|
||||
|
||||
// proj==Op_True --> ne test; proj==Op_False --> eq test.
|
||||
// One of two graph shapes got matched:
|
||||
// (IfTrue (If (Bool NE (CmpP ptr null))))
|
||||
|
@ -6580,6 +6580,10 @@ bool LibraryCallKit::inline_vectorizedMismatch() {
|
||||
memory_phi = _gvn.transform(memory_phi);
|
||||
result_phi = _gvn.transform(result_phi);
|
||||
|
||||
record_for_igvn(exit_block);
|
||||
record_for_igvn(memory_phi);
|
||||
record_for_igvn(result_phi);
|
||||
|
||||
set_control(exit_block);
|
||||
set_all_memory(memory_phi);
|
||||
set_result(result_phi);
|
||||
|
@ -1676,6 +1676,10 @@ bool PhaseIdealLoop::safe_for_if_replacement(const Node* dom) const {
|
||||
// like various versions of induction variable+offset. Clone the
|
||||
// computation per usage to allow it to sink out of the loop.
|
||||
void PhaseIdealLoop::try_sink_out_of_loop(Node* n) {
|
||||
bool is_raw_to_oop_cast = n->is_ConstraintCast() &&
|
||||
n->in(1)->bottom_type()->isa_rawptr() &&
|
||||
!n->bottom_type()->isa_rawptr();
|
||||
|
||||
if (has_ctrl(n) &&
|
||||
!n->is_Phi() &&
|
||||
!n->is_Bool() &&
|
||||
@ -1685,7 +1689,9 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) {
|
||||
!n->is_OpaqueNotNull() &&
|
||||
!n->is_OpaqueInitializedAssertionPredicate() &&
|
||||
!n->is_OpaqueTemplateAssertionPredicate() &&
|
||||
!n->is_Type()) {
|
||||
!is_raw_to_oop_cast && // don't extend live ranges of raw oops
|
||||
(KillPathsReachableByDeadTypeNode || !n->is_Type())
|
||||
) {
|
||||
Node *n_ctrl = get_ctrl(n);
|
||||
IdealLoopTree *n_loop = get_loop(n_ctrl);
|
||||
|
||||
|
@ -386,6 +386,13 @@ public:
|
||||
|
||||
// Returns true if this node is a check that can be implemented with a trap.
|
||||
virtual bool is_TrapBasedCheckNode() const { return false; }
|
||||
|
||||
// Whether this node is expanded during code emission into a sequence of
|
||||
// instructions and the first instruction can perform an implicit null check.
|
||||
virtual bool is_late_expanded_null_check_candidate() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_removed() { add_flag(Flag_is_removed_by_peephole); }
|
||||
bool get_removed() { return (flags() & Flag_is_removed_by_peephole) != 0; }
|
||||
|
||||
|
@ -2407,7 +2407,6 @@ void PhaseMacroExpand::eliminate_macro_nodes() {
|
||||
}
|
||||
}
|
||||
// Next, attempt to eliminate allocations
|
||||
_has_locks = false;
|
||||
progress = true;
|
||||
while (progress) {
|
||||
progress = false;
|
||||
@ -2431,7 +2430,6 @@ void PhaseMacroExpand::eliminate_macro_nodes() {
|
||||
case Node::Class_Lock:
|
||||
case Node::Class_Unlock:
|
||||
assert(!n->as_AbstractLock()->is_eliminated(), "sanity");
|
||||
_has_locks = true;
|
||||
break;
|
||||
case Node::Class_ArrayCopy:
|
||||
break;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -83,9 +83,6 @@ private:
|
||||
// projections extracted from a call node
|
||||
CallProjections _callprojs;
|
||||
|
||||
// Additional data collected during macro expansion
|
||||
bool _has_locks;
|
||||
|
||||
void expand_allocate(AllocateNode *alloc);
|
||||
void expand_allocate_array(AllocateArrayNode *alloc);
|
||||
void expand_allocate_common(AllocateNode* alloc,
|
||||
@ -199,7 +196,7 @@ private:
|
||||
Node* make_arraycopy_load(ArrayCopyNode* ac, intptr_t offset, Node* ctl, Node* mem, BasicType ft, const Type *ftype, AllocateNode *alloc);
|
||||
|
||||
public:
|
||||
PhaseMacroExpand(PhaseIterGVN &igvn) : Phase(Macro_Expand), _igvn(igvn), _has_locks(false) {
|
||||
PhaseMacroExpand(PhaseIterGVN &igvn) : Phase(Macro_Expand), _igvn(igvn) {
|
||||
_igvn.set_delay_transform(true);
|
||||
}
|
||||
|
||||
|
@ -2015,8 +2015,10 @@ void PhaseOutput::FillExceptionTables(uint cnt, uint *call_returns, uint *inct_s
|
||||
|
||||
// Handle implicit null exception table updates
|
||||
if (n->is_MachNullCheck()) {
|
||||
assert(n->in(1)->as_Mach()->barrier_data() == 0,
|
||||
"Implicit null checks on memory accesses with barriers are not yet supported");
|
||||
MachNode* access = n->in(1)->as_Mach();
|
||||
assert(access->barrier_data() == 0 ||
|
||||
access->is_late_expanded_null_check_candidate(),
|
||||
"Implicit null checks on memory accesses with barriers are only supported on nodes explicitly marked as null-check candidates");
|
||||
uint block_num = block->non_connector_successor(0)->_pre_order;
|
||||
_inc_table.append(inct_starts[inct_cnt++], blk_labels[block_num].loc_pos());
|
||||
continue;
|
||||
|
@ -1072,7 +1072,11 @@ void PhaseIterGVN::optimize() {
|
||||
|
||||
#ifdef ASSERT
|
||||
void PhaseIterGVN::verify_optimize() {
|
||||
if (is_verify_Value()) {
|
||||
assert(_worklist.size() == 0, "igvn worklist must be empty before verify");
|
||||
|
||||
if (is_verify_Value() ||
|
||||
is_verify_Ideal() ||
|
||||
is_verify_Identity()) {
|
||||
ResourceMark rm;
|
||||
Unique_Node_List worklist;
|
||||
bool failure = false;
|
||||
@ -1080,7 +1084,10 @@ void PhaseIterGVN::verify_optimize() {
|
||||
worklist.push(C->root());
|
||||
for (uint j = 0; j < worklist.size(); ++j) {
|
||||
Node* n = worklist.at(j);
|
||||
failure |= verify_node_value(n);
|
||||
if (is_verify_Value()) { failure |= verify_Value_for(n); }
|
||||
if (is_verify_Ideal()) { failure |= verify_Ideal_for(n, false); }
|
||||
if (is_verify_Ideal()) { failure |= verify_Ideal_for(n, true); }
|
||||
if (is_verify_Identity()) { failure |= verify_Identity_for(n); }
|
||||
// traverse all inputs and outputs
|
||||
for (uint i = 0; i < n->req(); i++) {
|
||||
if (n->in(i) != nullptr) {
|
||||
@ -1097,6 +1104,27 @@ void PhaseIterGVN::verify_optimize() {
|
||||
// in the verification code above if that is not possible for some reason (like Load nodes).
|
||||
assert(!failure, "Missed optimization opportunity in PhaseIterGVN");
|
||||
}
|
||||
|
||||
verify_empty_worklist(nullptr);
|
||||
}
|
||||
|
||||
void PhaseIterGVN::verify_empty_worklist(Node* node) {
|
||||
// Verify that the igvn worklist is empty. If no optimization happened, then
|
||||
// nothing needs to be on the worklist.
|
||||
if (_worklist.size() == 0) { return; }
|
||||
|
||||
stringStream ss; // Print as a block without tty lock.
|
||||
for (uint j = 0; j < _worklist.size(); j++) {
|
||||
Node* n = _worklist.at(j);
|
||||
ss.print("igvn.worklist[%d] ", j);
|
||||
n->dump("\n", false, &ss);
|
||||
}
|
||||
if (_worklist.size() != 0 && node != nullptr) {
|
||||
ss.print_cr("Previously optimized:");
|
||||
node->dump("\n", false, &ss);
|
||||
}
|
||||
tty->print_cr("%s", ss.as_string());
|
||||
assert(false, "igvn worklist must still be empty after verify");
|
||||
}
|
||||
|
||||
// Check that type(n) == n->Value(), return true if we have a failure.
|
||||
@ -1104,7 +1132,7 @@ void PhaseIterGVN::verify_optimize() {
|
||||
// (1) Integer "widen" changes, but the range is the same.
|
||||
// (2) LoadNode performs deep traversals. Load is not notified for changes far away.
|
||||
// (3) CmpPNode performs deep traversals if it compares oopptr. CmpP is not notified for changes far away.
|
||||
bool PhaseIterGVN::verify_node_value(Node* n) {
|
||||
bool PhaseIterGVN::verify_Value_for(Node* n) {
|
||||
// If we assert inside type(n), because the type is still a null, then maybe
|
||||
// the node never went through gvn.transform, which would be a bug.
|
||||
const Type* told = type(n);
|
||||
@ -1149,15 +1177,873 @@ bool PhaseIterGVN::verify_node_value(Node* n) {
|
||||
// after loop-opts, so that should take care of many of these cases.
|
||||
return false;
|
||||
}
|
||||
tty->cr();
|
||||
tty->print_cr("Missed Value optimization:");
|
||||
n->dump_bfs(1, nullptr, "");
|
||||
tty->print_cr("Current type:");
|
||||
told->dump_on(tty);
|
||||
tty->cr();
|
||||
tty->print_cr("Optimized type:");
|
||||
tnew->dump_on(tty);
|
||||
tty->cr();
|
||||
|
||||
stringStream ss; // Print as a block without tty lock.
|
||||
ss.cr();
|
||||
ss.print_cr("Missed Value optimization:");
|
||||
n->dump_bfs(1, nullptr, "", &ss);
|
||||
ss.print_cr("Current type:");
|
||||
told->dump_on(&ss);
|
||||
ss.cr();
|
||||
ss.print_cr("Optimized type:");
|
||||
tnew->dump_on(&ss);
|
||||
ss.cr();
|
||||
tty->print_cr("%s", ss.as_string());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that all Ideal optimizations that could be done were done.
|
||||
// Returns true if it found missed optimization opportunities and
|
||||
// false otherwise (no missed optimization, or skipped verification).
|
||||
bool PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape) {
|
||||
// First, we check a list of exceptions, where we skip verification,
|
||||
// because there are known cases where Ideal can optimize after IGVN.
|
||||
// Some may be expected and cannot be fixed, and others should be fixed.
|
||||
switch (n->Opcode()) {
|
||||
// RangeCheckNode::Ideal looks up the chain for about 999 nodes
|
||||
// (see "Range-Check scan limit"). So, it is possible that something
|
||||
// is optimized in that input subgraph, and the RangeCheck was not
|
||||
// added to the worklist because it would be too expensive to walk
|
||||
// down the graph for 1000 nodes and put all on the worklist.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xbatch --version
|
||||
case Op_RangeCheck:
|
||||
return false;
|
||||
|
||||
// IfNode::Ideal does:
|
||||
// Node* prev_dom = search_identical(dist, igvn);
|
||||
// which means we seach up the CFG, traversing at most up to a distance.
|
||||
// If anything happens rather far away from the If, we may not put the If
|
||||
// back on the worklist.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
case Op_If:
|
||||
return false;
|
||||
|
||||
// IfNode::simple_subsuming
|
||||
// Looks for dominating test that subsumes the current test.
|
||||
// Notification could be difficult because of larger distance.
|
||||
//
|
||||
// Found with:
|
||||
// runtime/exceptionMsgs/ArrayIndexOutOfBoundsException/ArrayIndexOutOfBoundsExceptionTest.java#id1
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_CountedLoopEnd:
|
||||
return false;
|
||||
|
||||
// LongCountedLoopEndNode::Ideal
|
||||
// Probably same issue as above.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/predicates/assertion/TestAssertionPredicates.java#NoLoopPredicationXbatch
|
||||
// -XX:StressLongCountedLoop=2000000 -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
case Op_LongCountedLoopEnd:
|
||||
return false;
|
||||
|
||||
// RegionNode::Ideal does "Skip around the useless IF diamond".
|
||||
// 245 IfTrue === 244
|
||||
// 258 If === 245 257
|
||||
// 259 IfTrue === 258 [[ 263 ]]
|
||||
// 260 IfFalse === 258 [[ 263 ]]
|
||||
// 263 Region === 263 260 259 [[ 263 268 ]]
|
||||
// to
|
||||
// 245 IfTrue === 244
|
||||
// 263 Region === 263 245 _ [[ 263 268 ]]
|
||||
//
|
||||
// "Useless" means that there is no code in either branch of the If.
|
||||
// I found a case where this was not done yet during IGVN.
|
||||
// Why does the Region not get added to IGVN worklist when the If diamond becomes useless?
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
case Op_Region:
|
||||
return false;
|
||||
|
||||
// In AddNode::Ideal, we call "commute", which swaps the inputs so
|
||||
// that smaller idx are first. Tracking it back, it led me to
|
||||
// PhaseIdealLoop::remix_address_expressions which swapped the edges.
|
||||
//
|
||||
// Example:
|
||||
// Before PhaseIdealLoop::remix_address_expressions
|
||||
// 154 AddI === _ 12 144
|
||||
// After PhaseIdealLoop::remix_address_expressions
|
||||
// 154 AddI === _ 144 12
|
||||
// After AddNode::Ideal
|
||||
// 154 AddI === _ 12 144
|
||||
//
|
||||
// I suspect that the node should be added to the IGVN worklist after
|
||||
// PhaseIdealLoop::remix_address_expressions
|
||||
//
|
||||
// This is the only case I looked at, there may be others. Found like this:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xbatch --version
|
||||
//
|
||||
// The following hit the same logic in PhaseIdealLoop::remix_address_expressions.
|
||||
//
|
||||
// Note: currently all of these fail also for other reasons, for example
|
||||
// because of "commute" doing the reordering with the phi below. Once
|
||||
// that is resolved, we can come back to this issue here.
|
||||
//
|
||||
// case Op_AddD:
|
||||
// case Op_AddI:
|
||||
// case Op_AddL:
|
||||
// case Op_AddF:
|
||||
// case Op_MulI:
|
||||
// case Op_MulL:
|
||||
// case Op_MulF:
|
||||
// case Op_MulD:
|
||||
// if (n->in(1)->_idx > n->in(2)->_idx) {
|
||||
// // Expect "commute" to revert this case.
|
||||
// return false;
|
||||
// }
|
||||
// break; // keep verifying
|
||||
|
||||
// AddFNode::Ideal calls "commute", which can reorder the inputs for this:
|
||||
// Check for tight loop increments: Loop-phi of Add of loop-phi
|
||||
// It wants to take the phi into in(1):
|
||||
// 471 Phi === 435 38 390
|
||||
// 390 AddF === _ 471 391
|
||||
//
|
||||
// Other Associative operators are also affected equally.
|
||||
//
|
||||
// Investigate why this does not happen earlier during IGVN.
|
||||
//
|
||||
// Found with:
|
||||
// test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_AddD:
|
||||
//case Op_AddI: // Also affected for other reasons, see case further down.
|
||||
//case Op_AddL: // Also affected for other reasons, see case further down.
|
||||
case Op_AddF:
|
||||
case Op_MulI:
|
||||
case Op_MulL:
|
||||
case Op_MulF:
|
||||
case Op_MulD:
|
||||
case Op_MinF:
|
||||
case Op_MinD:
|
||||
case Op_MaxF:
|
||||
case Op_MaxD:
|
||||
// XorINode::Ideal
|
||||
// Found with:
|
||||
// compiler/intrinsics/chacha/TestChaCha20.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_XorI:
|
||||
case Op_XorL:
|
||||
// It seems we may have similar issues with the HF cases.
|
||||
// Found with aarch64:
|
||||
// compiler/vectorization/TestFloat16VectorOperations.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_AddHF:
|
||||
case Op_MulHF:
|
||||
case Op_MaxHF:
|
||||
case Op_MinHF:
|
||||
return false;
|
||||
|
||||
// In MulNode::Ideal the edges can be swapped to help value numbering:
|
||||
//
|
||||
// // We are OK if right is a constant, or right is a load and
|
||||
// // left is a non-constant.
|
||||
// if( !(t2->singleton() ||
|
||||
// (in(2)->is_Load() && !(t1->singleton() || in(1)->is_Load())) ) ) {
|
||||
// if( t1->singleton() || // Left input is a constant?
|
||||
// // Otherwise, sort inputs (commutativity) to help value numbering.
|
||||
// (in(1)->_idx > in(2)->_idx) ) {
|
||||
// swap_edges(1, 2);
|
||||
//
|
||||
// Why was this not done earlier during IGVN?
|
||||
//
|
||||
// Found with:
|
||||
// test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithG1.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_AndI:
|
||||
// Same for AndL.
|
||||
// Found with:
|
||||
// compiler/intrinsics/bigInteger/MontgomeryMultiplyTest.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_AndL:
|
||||
return false;
|
||||
|
||||
// SubLNode::Ideal does transform like:
|
||||
// Convert "c1 - (y+c0)" into "(c1-c0) - y"
|
||||
//
|
||||
// In IGVN before verification:
|
||||
// 8423 ConvI2L === _ 3519 [[ 8424 ]] #long:-2
|
||||
// 8422 ConvI2L === _ 8399 [[ 8424 ]] #long:3..256:www
|
||||
// 8424 AddL === _ 8422 8423 [[ 8383 ]] !orig=[8382]
|
||||
// 8016 ConL === 0 [[ 8383 ]] #long:0
|
||||
// 8383 SubL === _ 8016 8424 [[ 8156 ]] !orig=[8154]
|
||||
//
|
||||
// And then in verification:
|
||||
// 8338 ConL === 0 [[ 8339 8424 ]] #long:-2 <----- Was constant folded.
|
||||
// 8422 ConvI2L === _ 8399 [[ 8424 ]] #long:3..256:www
|
||||
// 8424 AddL === _ 8422 8338 [[ 8383 ]] !orig=[8382]
|
||||
// 8016 ConL === 0 [[ 8383 ]] #long:0
|
||||
// 8383 SubL === _ 8016 8424 [[ 8156 ]] !orig=[8154]
|
||||
//
|
||||
// So the form changed from:
|
||||
// c1 - (y + [8423 ConvI2L])
|
||||
// to
|
||||
// c1 - (y + -2)
|
||||
// but the SubL was not added to the IGVN worklist. Investigate why.
|
||||
// There could be other issues too.
|
||||
//
|
||||
// There seems to be a related AddL IGVN optimization that triggers
|
||||
// the same SubL optimization, so investigate that too.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
case Op_SubL:
|
||||
return false;
|
||||
|
||||
// SubINode::Ideal does
|
||||
// Convert "x - (y+c0)" into "(x-y) - c0" AND
|
||||
// Convert "c1 - (y+c0)" into "(c1-c0) - y"
|
||||
//
|
||||
// Investigate why this does not yet happen during IGVN.
|
||||
//
|
||||
// Found with:
|
||||
// test/hotspot/jtreg/compiler/c2/IVTest.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_SubI:
|
||||
return false;
|
||||
|
||||
// AddNode::IdealIL does transform like:
|
||||
// Convert x + (con - y) into "(x - y) + con"
|
||||
//
|
||||
// In IGVN before verification:
|
||||
// 8382 ConvI2L
|
||||
// 8381 ConvI2L === _ 791 [[ 8383 ]] #long:0
|
||||
// 8383 SubL === _ 8381 8382
|
||||
// 8168 ConvI2L
|
||||
// 8156 AddL === _ 8168 8383 [[ 8158 ]]
|
||||
//
|
||||
// And then in verification:
|
||||
// 8424 AddL
|
||||
// 8016 ConL === 0 [[ 8383 ]] #long:0 <--- Was constant folded.
|
||||
// 8383 SubL === _ 8016 8424
|
||||
// 8168 ConvI2L
|
||||
// 8156 AddL === _ 8168 8383 [[ 8158 ]]
|
||||
//
|
||||
// So the form changed from:
|
||||
// x + (ConvI2L(0) - [8382 ConvI2L])
|
||||
// to
|
||||
// x + (0 - [8424 AddL])
|
||||
// but the AddL was not added to the IGVN worklist. Investigate why.
|
||||
// There could be other issues, too. For example with "commute", see above.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
case Op_AddL:
|
||||
return false;
|
||||
|
||||
// SubTypeCheckNode::Ideal calls SubTypeCheckNode::verify_helper, which does
|
||||
// Node* cmp = phase->transform(new CmpPNode(subklass, in(SuperKlass)));
|
||||
// record_for_cleanup(cmp, phase);
|
||||
// This verification code in the Ideal code creates new nodes, and checks
|
||||
// if they fold in unexpected ways. This means some nodes are created and
|
||||
// added to the worklist, even if the SubTypeCheck is not optimized. This
|
||||
// goes agains the assumption of the verification here, which assumes that
|
||||
// if the node is not optimized, then no new nodes should be created, and
|
||||
// also no nodes should be added to the worklist.
|
||||
// I see two options:
|
||||
// 1) forbid what verify_helper does, because for each Ideal call it
|
||||
// uses memory and that is suboptimal. But it is not clear how that
|
||||
// verification can be done otherwise.
|
||||
// 2) Special case the verification here. Probably the new nodes that
|
||||
// were just created are dead, i.e. they are not connected down to
|
||||
// root. We could verify that, and remove those nodes from the graph
|
||||
// by setting all their inputs to nullptr. And of course we would
|
||||
// have to remove those nodes from the worklist.
|
||||
// Maybe there are other options too, I did not dig much deeper yet.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xbatch --version
|
||||
case Op_SubTypeCheck:
|
||||
return false;
|
||||
|
||||
// LoopLimitNode::Ideal when stride is constant power-of-2, we can do a lowering
|
||||
// to other nodes: Conv, Add, Sub, Mul, And ...
|
||||
//
|
||||
// 107 ConI === 0 [[ ... ]] #int:2
|
||||
// 84 LoadRange === _ 7 83
|
||||
// 50 ConI === 0 [[ ... ]] #int:0
|
||||
// 549 LoopLimit === _ 50 84 107
|
||||
//
|
||||
// I stepped backward, to see how the node was generated, and I found that it was
|
||||
// created in PhaseIdealLoop::exact_limit and not changed since. It is added to the
|
||||
// IGVN worklist. I quickly checked when it goes into LoopLimitNode::Ideal after
|
||||
// that, and it seems we want to skip lowering it until after loop-opts, but never
|
||||
// add call record_for_post_loop_opts_igvn. This would be an easy fix, but there
|
||||
// could be other issues too.
|
||||
//
|
||||
// Fond with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
case Op_LoopLimit:
|
||||
return false;
|
||||
|
||||
// PhiNode::Ideal calls split_flow_path, which tries to do this:
|
||||
// "This optimization tries to find two or more inputs of phi with the same constant
|
||||
// value. It then splits them into a separate Phi, and according Region."
|
||||
//
|
||||
// Example:
|
||||
// 130 DecodeN === _ 129
|
||||
// 50 ConP === 0 [[ 18 91 99 18 ]] #null
|
||||
// 18 Phi === 14 50 130 50 [[ 133 ]] #java/lang/Object * Oop:java/lang/Object *
|
||||
//
|
||||
// turns into:
|
||||
//
|
||||
// 50 ConP === 0 [[ 99 91 18 ]] #null
|
||||
// 130 DecodeN === _ 129 [[ 18 ]]
|
||||
// 18 Phi === 14 130 50 [[ 133 ]] #java/lang/Object * Oop:java/lang/Object *
|
||||
//
|
||||
// We would have to investigate why this optimization does not happen during IGVN.
|
||||
// There could also be other issues - I did not investigate further yet.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
case Op_Phi:
|
||||
return false;
|
||||
|
||||
// MemBarNode::Ideal does "Eliminate volatile MemBars for scalar replaced objects".
|
||||
// For examle "The allocated object does not escape".
|
||||
//
|
||||
// It seems the difference to earlier calls to MemBarNode::Ideal, is that there
|
||||
// alloc->as_Allocate()->does_not_escape_thread() returned false, but in verification
|
||||
// it returned true. Why does the MemBarStoreStore not get added to the IGVN
|
||||
// worklist when this change happens?
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
case Op_MemBarStoreStore:
|
||||
return false;
|
||||
|
||||
// ConvI2LNode::Ideal converts
|
||||
// 648 AddI === _ 583 645 [[ 661 ]]
|
||||
// 661 ConvI2L === _ 648 [[ 664 ]] #long:0..maxint-1:www
|
||||
// into
|
||||
// 772 ConvI2L === _ 645 [[ 773 ]] #long:-120..maxint-61:www
|
||||
// 771 ConvI2L === _ 583 [[ 773 ]] #long:60..120:www
|
||||
// 773 AddL === _ 771 772 [[ ]]
|
||||
//
|
||||
// We have to investigate why this does not happen during IGVN in this case.
|
||||
// There could also be other issues - I did not investigate further yet.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
case Op_ConvI2L:
|
||||
return false;
|
||||
|
||||
// AddNode::IdealIL can do this transform (and similar other ones):
|
||||
// Convert "a*b+a*c into a*(b+c)
|
||||
// The example had AddI(MulI(a, b), MulI(a, c)). Why did this not happen
|
||||
// during IGVN? There was a mutation for one of the MulI, and only
|
||||
// after that the pattern was as needed for the optimization. The MulI
|
||||
// was added to the IGVN worklist, but not the AddI. This probably
|
||||
// can be fixed by adding the correct pattern in add_users_of_use_to_worklist.
|
||||
//
|
||||
// Found with:
|
||||
// test/hotspot/jtreg/compiler/loopopts/superword/ReductionPerf.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_AddI:
|
||||
return false;
|
||||
|
||||
// ArrayCopyNode::Ideal
|
||||
// calls ArrayCopyNode::prepare_array_copy
|
||||
// calls Compile::conv_I2X_index -> is called with sizetype = intcon(0), I think that
|
||||
// is not expected, and we create a range int:0..-1
|
||||
// calls Compile::constrained_convI2L -> creates ConvI2L(intcon(1), int:0..-1)
|
||||
// note: the type is already empty!
|
||||
// calls PhaseIterGVN::transform
|
||||
// calls PhaseIterGVN::transform_old
|
||||
// calls PhaseIterGVN::subsume_node -> subsume ConvI2L with TOP
|
||||
// calls Unique_Node_List::push -> pushes TOP to worklist
|
||||
//
|
||||
// Once we get back to ArrayCopyNode::prepare_array_copy, we get back TOP, and
|
||||
// return false. This means we eventually return nullptr from ArrayCopyNode::Ideal.
|
||||
//
|
||||
// Question: is it ok to push anything to the worklist during ::Ideal, if we will
|
||||
// return nullptr, indicating nothing happened?
|
||||
// Is it smart to do transform in Compile::constrained_convI2L, and then
|
||||
// check for TOP in calls ArrayCopyNode::prepare_array_copy?
|
||||
// Should we just allow TOP to land on the worklist, as an exception?
|
||||
//
|
||||
// Found with:
|
||||
// compiler/arraycopy/TestArrayCopyAsLoadsStores.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_ArrayCopy:
|
||||
return false;
|
||||
|
||||
// CastLLNode::Ideal
|
||||
// calls ConstraintCastNode::optimize_integer_cast -> pushes CastLL through SubL
|
||||
//
|
||||
// Could be a notification issue, where updates inputs of CastLL do not notify
|
||||
// down through SubL to CastLL.
|
||||
//
|
||||
// Found With:
|
||||
// compiler/c2/TestMergeStoresMemorySegment.java#byte-array
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_CastLL:
|
||||
return false;
|
||||
|
||||
// Similar case happens to CastII
|
||||
//
|
||||
// Found With:
|
||||
// compiler/c2/TestScalarReplacementMaxLiveNodes.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_CastII:
|
||||
return false;
|
||||
|
||||
// MaxLNode::Ideal
|
||||
// calls AddNode::Ideal
|
||||
// calls commute -> decides to swap edges
|
||||
//
|
||||
// Another notification issue, because we check inputs of inputs?
|
||||
// MaxL -> Phi -> Loop
|
||||
// MaxL -> Phi -> MaxL
|
||||
//
|
||||
// Found with:
|
||||
// compiler/c2/irTests/TestIfMinMax.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_MaxL:
|
||||
case Op_MinL:
|
||||
return false;
|
||||
|
||||
// OrINode::Ideal
|
||||
// calls AddNode::Ideal
|
||||
// calls commute -> left is Load, right not -> commute.
|
||||
//
|
||||
// Not sure why notification does not work here, seems like
|
||||
// the depth is only 1, so it should work. Needs investigation.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/codegen/TestCharVect2.java#id0
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_OrI:
|
||||
case Op_OrL:
|
||||
return false;
|
||||
|
||||
// Bool -> constant folded to 1.
|
||||
// Issue with notification?
|
||||
//
|
||||
// Found with:
|
||||
// compiler/c2/irTests/TestVectorizationMismatchedAccess.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_Bool:
|
||||
return false;
|
||||
|
||||
// LShiftLNode::Ideal
|
||||
// Looks at pattern: "(x + x) << c0", converts it to "x << (c0 + 1)"
|
||||
// Probably a notification issue.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/conversions/TestMoveConvI2LOrCastIIThruAddIs.java
|
||||
// -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
case Op_LShiftL:
|
||||
return false;
|
||||
|
||||
// LShiftINode::Ideal
|
||||
// pattern: ((x + con1) << con2) -> x << con2 + con1 << con2
|
||||
// Could be issue with notification of inputs of inputs
|
||||
//
|
||||
// Side-note: should cases like these not be shared between
|
||||
// LShiftI and LShiftL?
|
||||
//
|
||||
// Found with:
|
||||
// compiler/escapeAnalysis/Test6689060.java
|
||||
// -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
case Op_LShiftI:
|
||||
return false;
|
||||
|
||||
// AddPNode::Ideal seems to do set_req without removing lock first.
|
||||
// Found with various vector tests tier1-tier3.
|
||||
case Op_AddP:
|
||||
return false;
|
||||
|
||||
// StrIndexOfNode::Ideal
|
||||
// Found in tier1-3.
|
||||
case Op_StrIndexOf:
|
||||
case Op_StrIndexOfChar:
|
||||
return false;
|
||||
|
||||
// StrEqualsNode::Identity
|
||||
//
|
||||
// Found (linux x64 only?) with:
|
||||
// serviceability/sa/ClhsdbThreadContext.java
|
||||
// -XX:+UnlockExperimentalVMOptions -XX:LockingMode=1 -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
case Op_StrEquals:
|
||||
return false;
|
||||
|
||||
// AryEqNode::Ideal
|
||||
// Not investigated. Reshapes itself and adds lots of nodes to the worklist.
|
||||
//
|
||||
// Found with:
|
||||
// vmTestbase/vm/mlvm/meth/stress/compiler/i2c_c2i/Test.java
|
||||
// -XX:+UnlockDiagnosticVMOptions -XX:-TieredCompilation -XX:+StressUnstableIfTraps -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
case Op_AryEq:
|
||||
return false;
|
||||
|
||||
// MergeMemNode::Ideal
|
||||
// Found in tier1-3. Did not investigate further yet.
|
||||
case Op_MergeMem:
|
||||
return false;
|
||||
|
||||
// URShiftINode::Ideal
|
||||
// Found in tier1-3. Did not investigate further yet.
|
||||
case Op_URShiftI:
|
||||
return false;
|
||||
|
||||
// CMoveINode::Ideal
|
||||
// Found in tier1-3. Did not investigate further yet.
|
||||
case Op_CMoveI:
|
||||
return false;
|
||||
|
||||
// CmpPNode::Ideal calls isa_const_java_mirror
|
||||
// and generates new constant nodes, even if no progress is made.
|
||||
// We can probably rewrite this so that only types are generated.
|
||||
// It seems that object types are not hashed, we could investigate
|
||||
// if that is an option as well.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=1110 -Xcomp --version
|
||||
case Op_CmpP:
|
||||
return false;
|
||||
|
||||
// MinINode::Ideal
|
||||
// Did not investigate, but there are some patterns that might
|
||||
// need more notification.
|
||||
case Op_MinI:
|
||||
case Op_MaxI: // preemptively removed it as well.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n->is_Load()) {
|
||||
// LoadNode::Ideal uses tries to find an earlier memory state, and
|
||||
// checks can_see_stored_value for it.
|
||||
//
|
||||
// Investigate why this was not already done during IGVN.
|
||||
// A similar issue happens with Identity.
|
||||
//
|
||||
// There seem to be other cases where loads go up some steps, like
|
||||
// LoadNode::Ideal going up 10x steps to find dominating load.
|
||||
//
|
||||
// Found with:
|
||||
// test/hotspot/jtreg/compiler/arraycopy/TestCloneAccess.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n->is_Store()) {
|
||||
// StoreNode::Ideal can do this:
|
||||
// // Capture an unaliased, unconditional, simple store into an initializer.
|
||||
// // Or, if it is independent of the allocation, hoist it above the allocation.
|
||||
// That replaces the Store with a MergeMem.
|
||||
//
|
||||
// We have to investigate why this does not happen during IGVN in this case.
|
||||
// There could also be other issues - I did not investigate further yet.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=0100 -Xcomp --version
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n->is_Vector()) {
|
||||
// VectorNode::Ideal swaps edges, but only for ops
|
||||
// that are deemed commutable. But swap_edges
|
||||
// requires the hash to be invariant when the edges
|
||||
// are swapped, which is not implemented for these
|
||||
// vector nodes. This seems not to create any trouble
|
||||
// usually, but we can also get graphs where in the
|
||||
// end the nodes are not all commuted, so there is
|
||||
// definitively an issue here.
|
||||
//
|
||||
// Probably we have two options: kill the hash, or
|
||||
// properly make the hash commutation friendly.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/vectorapi/TestMaskedMacroLogicVector.java
|
||||
// -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 -XX:+UseParallelGC -XX:+UseNUMA
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n->is_Region()) {
|
||||
// LoopNode::Ideal calls RegionNode::Ideal.
|
||||
// CountedLoopNode::Ideal calls RegionNode::Ideal too.
|
||||
// But I got an issue because RegionNode::optimize_trichotomy
|
||||
// then modifies another node, and pushes nodes to the worklist
|
||||
// Not sure if this is ok, modifying another node like that.
|
||||
// Maybe it is, then we need to look into what to do with
|
||||
// the nodes that are now on the worklist, maybe just clear
|
||||
// them out again. But maybe modifying other nodes like that
|
||||
// is also bad design. In the end, we return nullptr for
|
||||
// the current CountedLoop. But the extra nodes on the worklist
|
||||
// trip the asserts later on.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/eliminateAutobox/TestShortBoxing.java
|
||||
// -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n->is_CallJava()) {
|
||||
// CallStaticJavaNode::Ideal
|
||||
// Led to a crash:
|
||||
// assert((is_CallStaticJava() && cg->is_mh_late_inline()) || (is_CallDynamicJava() && cg->is_virtual_late_inline())) failed: mismatch
|
||||
//
|
||||
// Did not investigate yet, could be a bug.
|
||||
// Or maybe it does not expect to be called during verification.
|
||||
//
|
||||
// Found with:
|
||||
// test/jdk/jdk/incubator/vector/VectorRuns.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
|
||||
// CallDynamicJavaNode::Ideal, and I think also for CallStaticJavaNode::Ideal
|
||||
// and possibly their subclasses.
|
||||
// During late inlining it can call CallJavaNode::register_for_late_inline
|
||||
// That means we do more rounds of late inlining, but might fail.
|
||||
// Then we do IGVN again, and register the node again for late inlining.
|
||||
// This creates an endless cycle. Everytime we try late inlining, we
|
||||
// are also creating more nodes, especially SafePoint and MergeMem.
|
||||
// These nodes are immediately rejected when the inlining fails in the
|
||||
// do_late_inline_check, but they still grow the memory, until we hit
|
||||
// the MemLimit and crash.
|
||||
// The assumption here seems that CallDynamicJavaNode::Ideal does not get
|
||||
// called repeatedly, and eventually we terminate. I fear this is not
|
||||
// a great assumption to make. We should investigate more.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/loopopts/superword/TestDependencyOffsets.java#vanilla-U
|
||||
// -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
return false;
|
||||
}
|
||||
|
||||
// The number of nodes shoud not increase.
|
||||
uint old_unique = C->unique();
|
||||
|
||||
Node* i = n->Ideal(this, can_reshape);
|
||||
// If there was no new Idealization, we are probably happy.
|
||||
if (i == nullptr) {
|
||||
if (old_unique < C->unique()) {
|
||||
stringStream ss; // Print as a block without tty lock.
|
||||
ss.cr();
|
||||
ss.print_cr("Ideal optimization did not make progress but created new unused nodes.");
|
||||
ss.print_cr(" old_unique = %d, unique = %d", old_unique, C->unique());
|
||||
n->dump_bfs(1, nullptr, "", &ss);
|
||||
tty->print_cr("%s", ss.as_string());
|
||||
return true;
|
||||
}
|
||||
|
||||
verify_empty_worklist(n);
|
||||
|
||||
// Everything is good.
|
||||
return false;
|
||||
}
|
||||
|
||||
// We just saw a new Idealization which was not done during IGVN.
|
||||
stringStream ss; // Print as a block without tty lock.
|
||||
ss.cr();
|
||||
ss.print_cr("Missed Ideal optimization (can_reshape=%s):", can_reshape ? "true": "false");
|
||||
if (i == n) {
|
||||
ss.print_cr("The node was reshaped by Ideal.");
|
||||
} else {
|
||||
ss.print_cr("The node was replaced by Ideal.");
|
||||
ss.print_cr("Old node:");
|
||||
n->dump_bfs(1, nullptr, "", &ss);
|
||||
}
|
||||
ss.print_cr("The result after Ideal:");
|
||||
i->dump_bfs(1, nullptr, "", &ss);
|
||||
tty->print_cr("%s", ss.as_string());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that all Identity optimizations that could be done were done.
|
||||
// Returns true if it found missed optimization opportunities and
|
||||
// false otherwise (no missed optimization, or skipped verification).
|
||||
bool PhaseIterGVN::verify_Identity_for(Node* n) {
|
||||
// First, we check a list of exceptions, where we skip verification,
|
||||
// because there are known cases where Ideal can optimize after IGVN.
|
||||
// Some may be expected and cannot be fixed, and others should be fixed.
|
||||
switch (n->Opcode()) {
|
||||
// SafePointNode::Identity can remove SafePoints, but wants to wait until
|
||||
// after loopopts:
|
||||
// // Transforming long counted loops requires a safepoint node. Do not
|
||||
// // eliminate a safepoint until loop opts are over.
|
||||
// if (in(0)->is_Proj() && !phase->C->major_progress()) {
|
||||
//
|
||||
// I think the check for major_progress does delay it until after loopopts
|
||||
// but it does not ensure that the node is on the IGVN worklist after
|
||||
// loopopts. I think we should try to instead check for
|
||||
// phase->C->post_loop_opts_phase() and call record_for_post_loop_opts_igvn.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=1000 -Xcomp --version
|
||||
case Op_SafePoint:
|
||||
return false;
|
||||
|
||||
// MergeMemNode::Identity replaces the MergeMem with its base_memory if it
|
||||
// does not record any other memory splits.
|
||||
//
|
||||
// I did not deeply investigate, but it looks like MergeMemNode::Identity
|
||||
// never got called during IGVN for this node, investigate why.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=1000 -Xcomp --version
|
||||
case Op_MergeMem:
|
||||
return false;
|
||||
|
||||
// ConstraintCastNode::Identity finds casts that are the same, except that
|
||||
// the control is "higher up", i.e. dominates. The call goes via
|
||||
// ConstraintCastNode::dominating_cast to PhaseGVN::is_dominator_helper,
|
||||
// which traverses up to 100 idom steps. If anything gets optimized somewhere
|
||||
// away from the cast, but within 100 idom steps, the cast may not be
|
||||
// put on the IGVN worklist any more.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=1000 -Xcomp --version
|
||||
case Op_CastPP:
|
||||
case Op_CastII:
|
||||
case Op_CastLL:
|
||||
return false;
|
||||
|
||||
// Same issue for CheckCastPP, uses ConstraintCastNode::Identity and
|
||||
// checks dominator, which may be changed, but too far up for notification
|
||||
// to work.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/c2/irTests/TestSkeletonPredicates.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_CheckCastPP:
|
||||
return false;
|
||||
|
||||
// In SubNode::Identity, we do:
|
||||
// Convert "(X+Y) - Y" into X and "(X+Y) - X" into Y
|
||||
// In the example, the AddI had an input replaced, the AddI is
|
||||
// added to the IGVN worklist, but the SubI is one link further
|
||||
// down and is not added. I checked add_users_of_use_to_worklist
|
||||
// where I would expect the SubI would be added, and I cannot
|
||||
// find the pattern, only this one:
|
||||
// If changed AddI/SubI inputs, check CmpU for range check optimization.
|
||||
//
|
||||
// Fix this "notification" issue and check if there are any other
|
||||
// issues.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=1000 -Xcomp --version
|
||||
case Op_SubI:
|
||||
case Op_SubL:
|
||||
return false;
|
||||
|
||||
// PhiNode::Identity checks for patterns like:
|
||||
// r = (x != con) ? x : con;
|
||||
// that can be constant folded to "x".
|
||||
//
|
||||
// Call goes through PhiNode::is_cmove_id and CMoveNode::is_cmove_id.
|
||||
// I suspect there was some earlier change to one of the inputs, but
|
||||
// not all relevant outputs were put on the IGVN worklist.
|
||||
//
|
||||
// Found with:
|
||||
// test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithG1.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_Phi:
|
||||
return false;
|
||||
|
||||
// ConvI2LNode::Identity does
|
||||
// convert I2L(L2I(x)) => x
|
||||
//
|
||||
// Investigate why this did not already happen during IGVN.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/loopopts/superword/TestDependencyOffsets.java#vanilla-A
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_ConvI2L:
|
||||
return false;
|
||||
|
||||
// MaxNode::find_identity_operation
|
||||
// Finds patterns like Max(A, Max(A, B)) -> Max(A, B)
|
||||
// This can be a 2-hop search, so maybe notification is not
|
||||
// good enough.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/codegen/TestBooleanVect.java
|
||||
// -XX:VerifyIterativeGVN=1110
|
||||
case Op_MaxL:
|
||||
case Op_MinL:
|
||||
case Op_MaxI:
|
||||
case Op_MinI:
|
||||
case Op_MaxF:
|
||||
case Op_MinF:
|
||||
case Op_MaxHF:
|
||||
case Op_MinHF:
|
||||
case Op_MaxD:
|
||||
case Op_MinD:
|
||||
return false;
|
||||
|
||||
|
||||
// AddINode::Identity
|
||||
// Converts (x-y)+y to x
|
||||
// Could be issue with notification
|
||||
//
|
||||
// Turns out AddL does the same.
|
||||
//
|
||||
// Found with:
|
||||
// compiler/c2/Test6792161.java
|
||||
// -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
case Op_AddI:
|
||||
case Op_AddL:
|
||||
return false;
|
||||
|
||||
// AbsINode::Identity
|
||||
// Not investigated yet.
|
||||
case Op_AbsI:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n->is_Load()) {
|
||||
// LoadNode::Identity tries to look for an earlier store value via
|
||||
// can_see_stored_value. I found an example where this led to
|
||||
// an Allocation, where we could assume the value was still zero.
|
||||
// So the LoadN can be replaced with a zerocon.
|
||||
//
|
||||
// Investigate why this was not already done during IGVN.
|
||||
// A similar issue happens with Ideal.
|
||||
//
|
||||
// Found with:
|
||||
// java -XX:VerifyIterativeGVN=1000 -Xcomp --version
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n->is_Store()) {
|
||||
// StoreNode::Identity
|
||||
// Not investigated, but found missing optimization for StoreI.
|
||||
// Looks like a StoreI is replaced with an InitializeNode.
|
||||
//
|
||||
// Found with:
|
||||
// applications/ctw/modules/java_base_2.java
|
||||
// -ea -esa -XX:CompileThreshold=100 -XX:+UnlockExperimentalVMOptions -server -XX:-TieredCompilation -Djava.awt.headless=true -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110
|
||||
return false;
|
||||
}
|
||||
|
||||
if (n->is_Vector()) {
|
||||
// Found with tier1-3. Not investigated yet.
|
||||
// The observed issue was with AndVNode::Identity
|
||||
return false;
|
||||
}
|
||||
|
||||
Node* i = n->Identity(this);
|
||||
// If we cannot find any other Identity, we are happy.
|
||||
if (i == n) {
|
||||
verify_empty_worklist(n);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The verification just found a new Identity that was not found during IGVN.
|
||||
stringStream ss; // Print as a block without tty lock.
|
||||
ss.cr();
|
||||
ss.print_cr("Missed Identity optimization:");
|
||||
ss.print_cr("Old node:");
|
||||
n->dump_bfs(1, nullptr, "", &ss);
|
||||
ss.print_cr("New node:");
|
||||
i->dump_bfs(1, nullptr, "", &ss);
|
||||
tty->print_cr("%s", ss.as_string());
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@ -1890,12 +2776,12 @@ void PhaseCCP::analyze() {
|
||||
|
||||
#ifdef ASSERT
|
||||
// For every node n on verify list, check if type(n) == n->Value()
|
||||
// We have a list of exceptions, see comments in verify_node_value.
|
||||
// We have a list of exceptions, see comments in verify_Value_for.
|
||||
void PhaseCCP::verify_analyze(Unique_Node_List& worklist_verify) {
|
||||
bool failure = false;
|
||||
while (worklist_verify.size()) {
|
||||
Node* n = worklist_verify.pop();
|
||||
failure |= verify_node_value(n);
|
||||
failure |= verify_Value_for(n);
|
||||
}
|
||||
// If we get this assert, check why the reported nodes were not processed again in CCP.
|
||||
// We should either make sure that these nodes are properly added back to the CCP worklist
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -494,7 +494,10 @@ public:
|
||||
void optimize();
|
||||
#ifdef ASSERT
|
||||
void verify_optimize();
|
||||
bool verify_node_value(Node* n);
|
||||
bool verify_Value_for(Node* n);
|
||||
bool verify_Ideal_for(Node* n, bool can_reshape);
|
||||
bool verify_Identity_for(Node* n);
|
||||
void verify_empty_worklist(Node* n);
|
||||
#endif
|
||||
|
||||
#ifndef PRODUCT
|
||||
@ -593,6 +596,14 @@ public:
|
||||
// '-XX:VerifyIterativeGVN=10'
|
||||
return ((VerifyIterativeGVN % 100) / 10) == 1;
|
||||
}
|
||||
static bool is_verify_Ideal() {
|
||||
// '-XX:VerifyIterativeGVN=100'
|
||||
return ((VerifyIterativeGVN % 1000) / 100) == 1;
|
||||
}
|
||||
static bool is_verify_Identity() {
|
||||
// '-XX:VerifyIterativeGVN=1000'
|
||||
return ((VerifyIterativeGVN % 10000) / 1000) == 1;
|
||||
}
|
||||
protected:
|
||||
// Sub-quadratic implementation of '-XX:VerifyIterativeGVN=1' (Use-Def verification).
|
||||
julong _verify_counter;
|
||||
|
@ -318,8 +318,10 @@ bool LibraryCallKit::inline_vector_nary_operation(int n) {
|
||||
const TypeInstPtr* elem_klass = gvn().type(argument(3))->isa_instptr();
|
||||
const TypeInt* vlen = gvn().type(argument(4))->isa_int();
|
||||
|
||||
if (opr == nullptr || vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr ||
|
||||
!opr->is_con() || vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) {
|
||||
if (opr == nullptr || !opr->is_con() ||
|
||||
vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con()) {
|
||||
log_if_needed(" ** missing constant: opr=%s vclass=%s etype=%s vlen=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -587,7 +589,11 @@ bool LibraryCallKit::inline_vector_mask_operation() {
|
||||
const TypeInt* vlen = gvn().type(argument(3))->isa_int();
|
||||
Node* mask = argument(4);
|
||||
|
||||
if (mask_klass == nullptr || elem_klass == nullptr || mask->is_top() || vlen == nullptr) {
|
||||
if (mask_klass == nullptr || mask_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con() ||
|
||||
oper == nullptr || !oper->is_con() ||
|
||||
mask->is_top()) {
|
||||
return false; // dead code
|
||||
}
|
||||
|
||||
@ -647,9 +653,11 @@ bool LibraryCallKit::inline_vector_frombits_coerced() {
|
||||
// MODE_BITS_COERCED_LONG_TO_MASK for VectorMask.fromLong operation.
|
||||
const TypeInt* mode = gvn().type(argument(5))->isa_int();
|
||||
|
||||
if (vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || mode == nullptr ||
|
||||
bits_type == nullptr || vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
!vlen->is_con() || !mode->is_con()) {
|
||||
if (vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con() ||
|
||||
bits_type == nullptr ||
|
||||
mode == nullptr || !mode->is_con()) {
|
||||
log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s bitwise=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -775,8 +783,10 @@ bool LibraryCallKit::inline_vector_mem_operation(bool is_store) {
|
||||
const TypeInt* vlen = gvn().type(argument(2))->isa_int();
|
||||
const TypeInt* from_ms = gvn().type(argument(6))->isa_int();
|
||||
|
||||
if (vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || !from_ms->is_con() ||
|
||||
vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) {
|
||||
if (vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con() ||
|
||||
from_ms == nullptr || !from_ms->is_con()) {
|
||||
log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s from_ms=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -983,9 +993,11 @@ bool LibraryCallKit::inline_vector_mem_masked_operation(bool is_store) {
|
||||
const TypeInt* vlen = gvn().type(argument(3))->isa_int();
|
||||
const TypeInt* from_ms = gvn().type(argument(7))->isa_int();
|
||||
|
||||
if (vector_klass == nullptr || mask_klass == nullptr || elem_klass == nullptr || vlen == nullptr ||
|
||||
vector_klass->const_oop() == nullptr || mask_klass->const_oop() == nullptr || from_ms == nullptr ||
|
||||
elem_klass->const_oop() == nullptr || !vlen->is_con() || !from_ms->is_con()) {
|
||||
if (vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
mask_klass == nullptr || mask_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con() ||
|
||||
from_ms == nullptr || !from_ms->is_con()) {
|
||||
log_if_needed(" ** missing constant: vclass=%s mclass=%s etype=%s vlen=%s from_ms=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -1222,8 +1234,10 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) {
|
||||
const TypeInt* vlen = gvn().type(argument(3))->isa_int();
|
||||
const TypeInstPtr* vector_idx_klass = gvn().type(argument(4))->isa_instptr();
|
||||
|
||||
if (vector_klass == nullptr || elem_klass == nullptr || vector_idx_klass == nullptr || vlen == nullptr ||
|
||||
vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || vector_idx_klass->const_oop() == nullptr || !vlen->is_con()) {
|
||||
if (vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con() ||
|
||||
vector_idx_klass == nullptr || vector_idx_klass->const_oop() == nullptr) {
|
||||
log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s viclass=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(2)->Opcode()],
|
||||
@ -1409,8 +1423,10 @@ bool LibraryCallKit::inline_vector_reduction() {
|
||||
const TypeInstPtr* elem_klass = gvn().type(argument(3))->isa_instptr();
|
||||
const TypeInt* vlen = gvn().type(argument(4))->isa_int();
|
||||
|
||||
if (opr == nullptr || vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr ||
|
||||
!opr->is_con() || vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) {
|
||||
if (opr == nullptr || !opr->is_con() ||
|
||||
vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con()) {
|
||||
log_if_needed(" ** missing constant: opr=%s vclass=%s etype=%s vlen=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -1547,8 +1563,10 @@ bool LibraryCallKit::inline_vector_test() {
|
||||
const TypeInstPtr* elem_klass = gvn().type(argument(2))->isa_instptr();
|
||||
const TypeInt* vlen = gvn().type(argument(3))->isa_int();
|
||||
|
||||
if (cond == nullptr || vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr ||
|
||||
!cond->is_con() || vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) {
|
||||
if (cond == nullptr || !cond->is_con() ||
|
||||
vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con()) {
|
||||
log_if_needed(" ** missing constant: cond=%s vclass=%s etype=%s vlen=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -2505,10 +2523,10 @@ bool LibraryCallKit::inline_vector_extract() {
|
||||
const TypeInt* vlen = gvn().type(argument(2))->isa_int();
|
||||
const TypeInt* idx = gvn().type(argument(4))->isa_int();
|
||||
|
||||
if (vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr || idx == nullptr) {
|
||||
return false; // dead code
|
||||
}
|
||||
if (vector_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) {
|
||||
if (vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con() ||
|
||||
idx == nullptr || !idx->is_con()) {
|
||||
log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -2811,9 +2829,11 @@ bool LibraryCallKit::inline_vector_compress_expand() {
|
||||
const TypeInstPtr* elem_klass = gvn().type(argument(3))->isa_instptr();
|
||||
const TypeInt* vlen = gvn().type(argument(4))->isa_int();
|
||||
|
||||
if (vector_klass == nullptr || elem_klass == nullptr || mask_klass == nullptr || vlen == nullptr ||
|
||||
vector_klass->const_oop() == nullptr || mask_klass->const_oop() == nullptr ||
|
||||
elem_klass->const_oop() == nullptr || !vlen->is_con() || !opr->is_con()) {
|
||||
if (opr == nullptr || !opr->is_con() ||
|
||||
vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
mask_klass == nullptr || mask_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con()) {
|
||||
log_if_needed(" ** missing constant: opr=%s vclass=%s mclass=%s etype=%s vlen=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -2892,9 +2912,9 @@ bool LibraryCallKit::inline_index_vector() {
|
||||
const TypeInstPtr* elem_klass = gvn().type(argument(1))->isa_instptr();
|
||||
const TypeInt* vlen = gvn().type(argument(2))->isa_int();
|
||||
|
||||
if (vector_klass == nullptr || elem_klass == nullptr || vlen == nullptr ||
|
||||
vector_klass->const_oop() == nullptr || !vlen->is_con() ||
|
||||
elem_klass->const_oop() == nullptr) {
|
||||
if (vector_klass == nullptr || vector_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con() ) {
|
||||
log_if_needed(" ** missing constant: vclass=%s etype=%s vlen=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
@ -3026,8 +3046,9 @@ bool LibraryCallKit::inline_index_partially_in_upper_range() {
|
||||
const TypeInstPtr* elem_klass = gvn().type(argument(1))->isa_instptr();
|
||||
const TypeInt* vlen = gvn().type(argument(2))->isa_int();
|
||||
|
||||
if (mask_klass == nullptr || elem_klass == nullptr || vlen == nullptr ||
|
||||
mask_klass->const_oop() == nullptr || elem_klass->const_oop() == nullptr || !vlen->is_con()) {
|
||||
if (mask_klass == nullptr || mask_klass->const_oop() == nullptr ||
|
||||
elem_klass == nullptr || elem_klass->const_oop() == nullptr ||
|
||||
vlen == nullptr || !vlen->is_con()) {
|
||||
log_if_needed(" ** missing constant: mclass=%s etype=%s vlen=%s",
|
||||
NodeClassNames[argument(0)->Opcode()],
|
||||
NodeClassNames[argument(1)->Opcode()],
|
||||
|
@ -2285,9 +2285,11 @@ JNI_ENTRY(jobjectArray, jni_NewObjectArray(JNIEnv *env, jsize length, jclass ele
|
||||
jobjectArray ret = nullptr;
|
||||
DT_RETURN_MARK(NewObjectArray, jobjectArray, (const jobjectArray&)ret);
|
||||
Klass* ek = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(elementClass));
|
||||
Klass* ak = ek->array_klass(CHECK_NULL);
|
||||
ObjArrayKlass::cast(ak)->initialize(CHECK_NULL);
|
||||
objArrayOop result = ObjArrayKlass::cast(ak)->allocate(length, CHECK_NULL);
|
||||
|
||||
// Make sure bottom_klass is initialized.
|
||||
ek->initialize(CHECK_NULL);
|
||||
objArrayOop result = oopFactory::new_objArray(ek, length, CHECK_NULL);
|
||||
|
||||
oop initial_value = JNIHandles::resolve(initialElement);
|
||||
if (initial_value != nullptr) { // array already initialized with null
|
||||
for (int index = 0; index < length; index++) {
|
||||
|
@ -12862,16 +12862,17 @@ myInit() {
|
||||
parameters uniquely identify the current location
|
||||
(where the exception was detected) and allow
|
||||
the mapping to source file and line number when that information is
|
||||
available. The <code>exception</code> field identifies the thrown
|
||||
available. The <code>exception</code> parameter identifies the thrown
|
||||
exception object. The <code>catch_method</code>
|
||||
and <code>catch_location</code> identify the location of the catch clause,
|
||||
if any, that handles the thrown exception. If there is no such catch clause,
|
||||
each field is set to 0. There is no guarantee that the thread will ever
|
||||
the <code>catch_method</code> is set to null and the <code>catch_location</code>is set to 0.
|
||||
There is no guarantee that the thread will ever
|
||||
reach this catch clause. If there are native methods on the call stack
|
||||
between the throw location and the catch clause, the exception may
|
||||
be reset by one of those native methods.
|
||||
Similarly, exceptions that are reported as uncaught (<code>catch_klass</code>
|
||||
et al. set to 0) may in fact be caught by native code.
|
||||
Similarly, exceptions that are reported as uncaught (<code>catch_method</code>
|
||||
set to null) may in fact be caught by native code.
|
||||
Agents can check for these occurrences by monitoring
|
||||
<eventlink id="ExceptionCatch"></eventlink> events.
|
||||
Note that finally clauses are implemented as catch and re-throw. Therefore they
|
||||
@ -12960,7 +12961,7 @@ myInit() {
|
||||
available. For exceptions caught in a Java programming language method, the
|
||||
<code>exception</code> object identifies the exception object. Exceptions
|
||||
caught in native methods are not necessarily available by the time the
|
||||
exception catch is reported, so the <code>exception</code> field is set
|
||||
exception catch is reported, so the <code>exception</code> parameter is set
|
||||
to null.
|
||||
</description>
|
||||
<origin>jvmdi</origin>
|
||||
|
@ -3550,6 +3550,13 @@ void VM_RedefineClasses::set_new_constant_pool(
|
||||
Array<u1>* new_fis = FieldInfoStream::create_FieldInfoStream(fields, java_fields, injected_fields, scratch_class->class_loader_data(), CHECK);
|
||||
scratch_class->set_fieldinfo_stream(new_fis);
|
||||
MetadataFactory::free_array<u1>(scratch_class->class_loader_data(), old_stream);
|
||||
|
||||
Array<u1>* old_table = scratch_class->fieldinfo_search_table();
|
||||
Array<u1>* search_table = FieldInfoStream::create_search_table(scratch_class->constants(), new_fis, scratch_class->class_loader_data(), CHECK);
|
||||
scratch_class->set_fieldinfo_search_table(search_table);
|
||||
MetadataFactory::free_array<u1>(scratch_class->class_loader_data(), old_table);
|
||||
|
||||
DEBUG_ONLY(FieldInfoStream::validate_search_table(scratch_class->constants(), new_fis, search_table));
|
||||
}
|
||||
|
||||
// Update constant pool indices in the inner classes info to use
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "code/location.hpp"
|
||||
#include "jni.h"
|
||||
#include "jvm.h"
|
||||
#include "memory/oopFactory.hpp"
|
||||
#include "oops/klass.inline.hpp"
|
||||
#include "oops/typeArrayOop.inline.hpp"
|
||||
#include "prims/vectorSupport.hpp"
|
||||
@ -109,9 +110,7 @@ Handle VectorSupport::allocate_vector_payload_helper(InstanceKlass* ik, frame* f
|
||||
int elem_size = type2aelembytes(elem_bt);
|
||||
|
||||
// On-heap vector values are represented as primitive arrays.
|
||||
TypeArrayKlass* tak = Universe::typeArrayKlass(elem_bt);
|
||||
|
||||
typeArrayOop arr = tak->allocate(num_elem, CHECK_NH); // safepoint
|
||||
typeArrayOop arr = oopFactory::new_typeArray(elem_bt, num_elem, CHECK_NH); // safepoint
|
||||
|
||||
if (location.is_register()) {
|
||||
// Value was in a callee-saved register.
|
||||
|
@ -583,28 +583,6 @@ WB_ENTRY(jboolean, WB_G1HasRegionsToUncommit(JNIEnv* env, jobject o))
|
||||
THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_G1HasRegionsToUncommit: G1 GC is not enabled");
|
||||
WB_END
|
||||
|
||||
#endif // INCLUDE_G1GC
|
||||
|
||||
#if INCLUDE_PARALLELGC
|
||||
|
||||
WB_ENTRY(jlong, WB_PSVirtualSpaceAlignment(JNIEnv* env, jobject o))
|
||||
if (UseParallelGC) {
|
||||
return GenAlignment;
|
||||
}
|
||||
THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_PSVirtualSpaceAlignment: Parallel GC is not enabled");
|
||||
WB_END
|
||||
|
||||
WB_ENTRY(jlong, WB_PSHeapGenerationAlignment(JNIEnv* env, jobject o))
|
||||
if (UseParallelGC) {
|
||||
return GenAlignment;
|
||||
}
|
||||
THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_PSHeapGenerationAlignment: Parallel GC is not enabled");
|
||||
WB_END
|
||||
|
||||
#endif // INCLUDE_PARALLELGC
|
||||
|
||||
#if INCLUDE_G1GC
|
||||
|
||||
WB_ENTRY(jobject, WB_G1AuxiliaryMemoryUsage(JNIEnv* env))
|
||||
if (UseG1GC) {
|
||||
ResourceMark rm(THREAD);
|
||||
@ -1097,6 +1075,22 @@ bool WhiteBox::validate_cgroup(bool cgroups_v2_enabled,
|
||||
}
|
||||
#endif
|
||||
|
||||
bool WhiteBox::is_asan_enabled() {
|
||||
#ifdef ADDRESS_SANITIZER
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WhiteBox::is_ubsan_enabled() {
|
||||
#ifdef UNDEFINED_BEHAVIOR_SANITIZER
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WhiteBox::compile_method(Method* method, int comp_level, int bci, JavaThread* THREAD) {
|
||||
// Screen for unavailable/bad comp level or null method
|
||||
AbstractCompiler* comp = CompileBroker::compiler(comp_level);
|
||||
@ -1908,6 +1902,14 @@ WB_ENTRY(jboolean, WB_IsMonitorInflated(JNIEnv* env, jobject wb, jobject obj))
|
||||
return (jboolean) obj_oop->mark().has_monitor();
|
||||
WB_END
|
||||
|
||||
WB_ENTRY(jboolean, WB_IsAsanEnabled(JNIEnv* env))
|
||||
return (jboolean) WhiteBox::is_asan_enabled();
|
||||
WB_END
|
||||
|
||||
WB_ENTRY(jboolean, WB_IsUbsanEnabled(JNIEnv* env))
|
||||
return (jboolean) WhiteBox::is_ubsan_enabled();
|
||||
WB_END
|
||||
|
||||
WB_ENTRY(jlong, WB_getInUseMonitorCount(JNIEnv* env, jobject wb))
|
||||
return (jlong) WhiteBox::get_in_use_monitor_count();
|
||||
WB_END
|
||||
@ -2773,10 +2775,6 @@ static JNINativeMethod methods[] = {
|
||||
{CC"g1MemoryNodeIds", CC"()[I", (void*)&WB_G1MemoryNodeIds },
|
||||
{CC"g1GetMixedGCInfo", CC"(I)[J", (void*)&WB_G1GetMixedGCInfo },
|
||||
#endif // INCLUDE_G1GC
|
||||
#if INCLUDE_PARALLELGC
|
||||
{CC"psVirtualSpaceAlignment",CC"()J", (void*)&WB_PSVirtualSpaceAlignment},
|
||||
{CC"psHeapGenerationAlignment",CC"()J", (void*)&WB_PSHeapGenerationAlignment},
|
||||
#endif
|
||||
{CC"NMTMalloc", CC"(J)J", (void*)&WB_NMTMalloc },
|
||||
{CC"NMTMallocWithPseudoStack", CC"(JI)J", (void*)&WB_NMTMallocWithPseudoStack},
|
||||
{CC"NMTMallocWithPseudoStackAndType", CC"(JII)J", (void*)&WB_NMTMallocWithPseudoStackAndType},
|
||||
@ -2908,6 +2906,8 @@ static JNINativeMethod methods[] = {
|
||||
(void*)&WB_AddModuleExportsToAll },
|
||||
{CC"deflateIdleMonitors", CC"()Z", (void*)&WB_DeflateIdleMonitors },
|
||||
{CC"isMonitorInflated0", CC"(Ljava/lang/Object;)Z", (void*)&WB_IsMonitorInflated },
|
||||
{CC"isAsanEnabled", CC"()Z", (void*)&WB_IsAsanEnabled },
|
||||
{CC"isUbsanEnabled", CC"()Z", (void*)&WB_IsUbsanEnabled },
|
||||
{CC"getInUseMonitorCount", CC"()J", (void*)&WB_getInUseMonitorCount },
|
||||
{CC"getLockStackCapacity", CC"()I", (void*)&WB_getLockStackCapacity },
|
||||
{CC"supportsRecursiveLightweightLocking", CC"()Z", (void*)&WB_supportsRecursiveLightweightLocking },
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -72,6 +72,9 @@ class WhiteBox : public AllStatic {
|
||||
#ifdef LINUX
|
||||
static bool validate_cgroup(bool cgroups_v2_enabled, const char* controllers_file, const char* proc_self_cgroup, const char* proc_self_mountinfo, u1* cg_flags);
|
||||
#endif
|
||||
// provide info about enabling of Address Sanitizer / Undefined Behavior Sanitizer
|
||||
static bool is_asan_enabled();
|
||||
static bool is_ubsan_enabled();
|
||||
};
|
||||
|
||||
#endif // SHARE_PRIMS_WHITEBOX_HPP
|
||||
|
@ -528,10 +528,6 @@ static SpecialFlag const special_jvm_flags[] = {
|
||||
{ "DynamicDumpSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() },
|
||||
{ "RequireSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() },
|
||||
{ "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() },
|
||||
#ifdef LINUX
|
||||
{ "UseLinuxPosixThreadCPUClocks", JDK_Version::jdk(24), JDK_Version::jdk(25), JDK_Version::jdk(26) },
|
||||
{ "UseOprofile", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::jdk(27) },
|
||||
#endif
|
||||
{ "LockingMode", JDK_Version::jdk(24), JDK_Version::jdk(26), JDK_Version::jdk(27) },
|
||||
#ifdef _LP64
|
||||
{ "UseCompressedClassPointers", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::undefined() },
|
||||
@ -541,7 +537,9 @@ static SpecialFlag const special_jvm_flags[] = {
|
||||
|
||||
// -------------- Obsolete Flags - sorted by expired_in --------------
|
||||
|
||||
{ "PerfDataSamplingInterval", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::jdk(26) },
|
||||
#ifdef LINUX
|
||||
{ "UseOprofile", JDK_Version::jdk(25), JDK_Version::jdk(26), JDK_Version::jdk(27) },
|
||||
#endif
|
||||
{ "MetaspaceReclaimPolicy", JDK_Version::undefined(), JDK_Version::jdk(21), JDK_Version::undefined() },
|
||||
{ "ZGenerational", JDK_Version::jdk(23), JDK_Version::jdk(24), JDK_Version::undefined() },
|
||||
{ "ZMarkStackSpaceLimit", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::undefined() },
|
||||
|
@ -1274,11 +1274,11 @@ bool Deoptimization::realloc_objects(JavaThread* thread, frame* fr, RegisterMap*
|
||||
assert(sv->field_size() % type2size[ak->element_type()] == 0, "non-integral array length");
|
||||
int len = sv->field_size() / type2size[ak->element_type()];
|
||||
InternalOOMEMark iom(THREAD);
|
||||
obj = ak->allocate(len, THREAD);
|
||||
obj = ak->allocate_instance(len, THREAD);
|
||||
} else if (k->is_objArray_klass()) {
|
||||
ObjArrayKlass* ak = ObjArrayKlass::cast(k);
|
||||
InternalOOMEMark iom(THREAD);
|
||||
obj = ak->allocate(sv->field_size(), THREAD);
|
||||
obj = ak->allocate_instance(sv->field_size(), THREAD);
|
||||
}
|
||||
|
||||
if (obj == nullptr) {
|
||||
|
@ -299,8 +299,9 @@ JVMFlag::Error TypeProfileLevelConstraintFunc(uint value, bool verbose) {
|
||||
}
|
||||
|
||||
JVMFlag::Error VerifyIterativeGVNConstraintFunc(uint value, bool verbose) {
|
||||
const int max_modes = 4;
|
||||
uint original_value = value;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int i = 0; i < max_modes; i++) {
|
||||
if (value % 10 > 1) {
|
||||
JVMFlag::printError(verbose,
|
||||
"Invalid value (" UINT32_FORMAT ") "
|
||||
@ -312,7 +313,7 @@ JVMFlag::Error VerifyIterativeGVNConstraintFunc(uint value, bool verbose) {
|
||||
if (value != 0) {
|
||||
JVMFlag::printError(verbose,
|
||||
"Invalid value (" UINT32_FORMAT ") "
|
||||
"for VerifyIterativeGVN: maximal 2 digits\n", original_value);
|
||||
"for VerifyIterativeGVN: maximal %d digits\n", original_value, max_modes);
|
||||
return JVMFlag::VIOLATES_CONSTRAINT;
|
||||
}
|
||||
return JVMFlag::SUCCESS;
|
||||
|
@ -2005,6 +2005,10 @@ const int ObjectAlignmentInBytes = 8;
|
||||
product(bool, UseThreadsLockThrottleLock, true, DIAGNOSTIC, \
|
||||
"Use an extra lock during Thread start and exit to alleviate" \
|
||||
"contention on Threads_lock.") \
|
||||
\
|
||||
develop(uint, BinarySearchThreshold, 16, \
|
||||
"Minimal number of elements in a sorted collection to prefer" \
|
||||
"binary search over simple linear search." ) \
|
||||
|
||||
// end of RUNTIME_FLAGS
|
||||
|
||||
|
@ -465,9 +465,7 @@ HandshakeState::HandshakeState(JavaThread* target) :
|
||||
_queue(),
|
||||
_lock(Monitor::nosafepoint, "HandshakeState_lock"),
|
||||
_active_handshaker(),
|
||||
_async_exceptions_blocked(false),
|
||||
_suspended(false),
|
||||
_async_suspend_handshake(false) {
|
||||
_async_exceptions_blocked(false) {
|
||||
}
|
||||
|
||||
HandshakeState::~HandshakeState() {
|
||||
@ -699,128 +697,8 @@ HandshakeState::ProcessResult HandshakeState::try_process(HandshakeOperation* ma
|
||||
return op == match_op ? HandshakeState::_succeeded : HandshakeState::_processed;
|
||||
}
|
||||
|
||||
void HandshakeState::do_self_suspend() {
|
||||
assert(Thread::current() == _handshakee, "should call from _handshakee");
|
||||
assert(_lock.owned_by_self(), "Lock must be held");
|
||||
assert(!_handshakee->has_last_Java_frame() || _handshakee->frame_anchor()->walkable(), "should have walkable stack");
|
||||
assert(_handshakee->thread_state() == _thread_blocked, "Caller should have transitioned to _thread_blocked");
|
||||
|
||||
while (is_suspended()) {
|
||||
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " suspended", p2i(_handshakee));
|
||||
_lock.wait_without_safepoint_check();
|
||||
}
|
||||
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " resumed", p2i(_handshakee));
|
||||
}
|
||||
|
||||
// This is the closure that prevents a suspended JavaThread from
|
||||
// escaping the suspend request.
|
||||
class ThreadSelfSuspensionHandshake : public AsyncHandshakeClosure {
|
||||
public:
|
||||
ThreadSelfSuspensionHandshake() : AsyncHandshakeClosure("ThreadSelfSuspensionHandshake") {}
|
||||
void do_thread(Thread* thr) {
|
||||
JavaThread* current = JavaThread::cast(thr);
|
||||
assert(current == Thread::current(), "Must be self executed.");
|
||||
JavaThreadState jts = current->thread_state();
|
||||
|
||||
current->set_thread_state(_thread_blocked);
|
||||
current->handshake_state()->do_self_suspend();
|
||||
current->set_thread_state(jts);
|
||||
current->handshake_state()->set_async_suspend_handshake(false);
|
||||
}
|
||||
virtual bool is_suspend() { return true; }
|
||||
};
|
||||
|
||||
bool HandshakeState::suspend_with_handshake(bool register_vthread_SR) {
|
||||
assert(_handshakee->threadObj() != nullptr, "cannot suspend with a null threadObj");
|
||||
if (_handshakee->is_exiting()) {
|
||||
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " exiting", p2i(_handshakee));
|
||||
return false;
|
||||
}
|
||||
if (has_async_suspend_handshake()) {
|
||||
if (is_suspended()) {
|
||||
// Target is already suspended.
|
||||
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " already suspended", p2i(_handshakee));
|
||||
return false;
|
||||
} else {
|
||||
// Target is going to wake up and leave suspension.
|
||||
// Let's just stop the thread from doing that.
|
||||
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " re-suspended", p2i(_handshakee));
|
||||
set_suspended(true, register_vthread_SR);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// no suspend request
|
||||
assert(!is_suspended(), "cannot be suspended without a suspend request");
|
||||
// Thread is safe, so it must execute the request, thus we can count it as suspended
|
||||
// from this point.
|
||||
set_suspended(true, register_vthread_SR);
|
||||
set_async_suspend_handshake(true);
|
||||
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " suspended, arming ThreadSuspension", p2i(_handshakee));
|
||||
ThreadSelfSuspensionHandshake* ts = new ThreadSelfSuspensionHandshake();
|
||||
Handshake::execute(ts, _handshakee);
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is the closure that synchronously honors the suspend request.
|
||||
class SuspendThreadHandshake : public HandshakeClosure {
|
||||
bool _register_vthread_SR;
|
||||
bool _did_suspend;
|
||||
public:
|
||||
SuspendThreadHandshake(bool register_vthread_SR) : HandshakeClosure("SuspendThread"),
|
||||
_register_vthread_SR(register_vthread_SR), _did_suspend(false) {}
|
||||
void do_thread(Thread* thr) {
|
||||
JavaThread* target = JavaThread::cast(thr);
|
||||
_did_suspend = target->handshake_state()->suspend_with_handshake(_register_vthread_SR);
|
||||
}
|
||||
bool did_suspend() { return _did_suspend; }
|
||||
};
|
||||
|
||||
bool HandshakeState::suspend(bool register_vthread_SR) {
|
||||
JVMTI_ONLY(assert(!_handshakee->is_in_VTMS_transition(), "no suspend allowed in VTMS transition");)
|
||||
JavaThread* self = JavaThread::current();
|
||||
if (_handshakee == self) {
|
||||
// If target is the current thread we can bypass the handshake machinery
|
||||
// and just suspend directly
|
||||
ThreadBlockInVM tbivm(self);
|
||||
MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag);
|
||||
set_suspended(true, register_vthread_SR);
|
||||
do_self_suspend();
|
||||
return true;
|
||||
} else {
|
||||
SuspendThreadHandshake st(register_vthread_SR);
|
||||
Handshake::execute(&st, _handshakee);
|
||||
return st.did_suspend();
|
||||
}
|
||||
}
|
||||
|
||||
bool HandshakeState::resume(bool register_vthread_SR) {
|
||||
MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag);
|
||||
if (!is_suspended()) {
|
||||
assert(!_handshakee->is_suspended(), "cannot be suspended without a suspend request");
|
||||
return false;
|
||||
}
|
||||
// Resume the thread.
|
||||
set_suspended(false, register_vthread_SR);
|
||||
_lock.notify();
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandshakeState::set_suspended(bool is_suspend, bool register_vthread_SR) {
|
||||
#if INCLUDE_JVMTI
|
||||
if (register_vthread_SR) {
|
||||
assert(_handshakee->is_vthread_mounted(), "sanity check");
|
||||
if (is_suspend) {
|
||||
JvmtiVTSuspender::register_vthread_suspend(_handshakee->vthread());
|
||||
} else {
|
||||
JvmtiVTSuspender::register_vthread_resume(_handshakee->vthread());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Atomic::store(&_suspended, is_suspend);
|
||||
}
|
||||
|
||||
void HandshakeState::handle_unsafe_access_error() {
|
||||
if (is_suspended()) {
|
||||
if (_handshakee->is_suspended()) {
|
||||
// A suspend handshake was added to the queue after the
|
||||
// unsafe access error. Since the suspender has already
|
||||
// considered this JT as suspended and assumes it won't go
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -35,8 +35,6 @@
|
||||
class HandshakeOperation;
|
||||
class AsyncHandshakeOperation;
|
||||
class JavaThread;
|
||||
class SuspendThreadHandshake;
|
||||
class ThreadSelfSuspensionHandshake;
|
||||
class UnsafeAccessErrorHandshake;
|
||||
class ThreadsListHandle;
|
||||
|
||||
@ -88,8 +86,6 @@ class JvmtiRawMonitor;
|
||||
// operation is only done by either VMThread/Handshaker on behalf of the
|
||||
// JavaThread or by the target JavaThread itself.
|
||||
class HandshakeState {
|
||||
friend ThreadSelfSuspensionHandshake;
|
||||
friend SuspendThreadHandshake;
|
||||
friend UnsafeAccessErrorHandshake;
|
||||
friend JavaThread;
|
||||
// This a back reference to the JavaThread,
|
||||
@ -98,7 +94,7 @@ class HandshakeState {
|
||||
// The queue containing handshake operations to be performed on _handshakee.
|
||||
FilterQueue<HandshakeOperation*> _queue;
|
||||
// Provides mutual exclusion to this state and queue. Also used for
|
||||
// JavaThread suspend/resume operations.
|
||||
// JavaThread suspend/resume operations performed by SuspendResumeManager.
|
||||
Monitor _lock;
|
||||
// Set to the thread executing the handshake operation.
|
||||
Thread* volatile _active_handshaker;
|
||||
@ -160,31 +156,5 @@ class HandshakeState {
|
||||
bool async_exceptions_blocked() { return _async_exceptions_blocked; }
|
||||
void set_async_exceptions_blocked(bool b) { _async_exceptions_blocked = b; }
|
||||
void handle_unsafe_access_error();
|
||||
|
||||
// Suspend/resume support
|
||||
private:
|
||||
// This flag is true when the thread owning this
|
||||
// HandshakeState (the _handshakee) is suspended.
|
||||
volatile bool _suspended;
|
||||
// This flag is true while there is async handshake (trap)
|
||||
// on queue. Since we do only need one, we can reuse it if
|
||||
// thread gets suspended again (after a resume)
|
||||
// and we have not yet processed it.
|
||||
bool _async_suspend_handshake;
|
||||
|
||||
// Called from the suspend handshake.
|
||||
bool suspend_with_handshake(bool register_vthread_SR);
|
||||
// Called from the async handshake (the trap)
|
||||
// to stop a thread from continuing execution when suspended.
|
||||
void do_self_suspend();
|
||||
|
||||
bool is_suspended() { return Atomic::load(&_suspended); }
|
||||
void set_suspended(bool to, bool register_vthread_SR);
|
||||
bool has_async_suspend_handshake() { return _async_suspend_handshake; }
|
||||
void set_async_suspend_handshake(bool to) { _async_suspend_handshake = to; }
|
||||
|
||||
bool suspend(bool register_vthread_SR);
|
||||
bool resume(bool register_vthread_SR);
|
||||
};
|
||||
|
||||
#endif // SHARE_RUNTIME_HANDSHAKE_HPP
|
||||
|
@ -498,6 +498,7 @@ JavaThread::JavaThread(MemTag mem_tag) :
|
||||
_pending_interrupted_exception(false),
|
||||
|
||||
_handshake(this),
|
||||
_suspend_resume_manager(this, &_handshake._lock),
|
||||
|
||||
_popframe_preserved_args(nullptr),
|
||||
_popframe_preserved_args_size(0),
|
||||
@ -1200,13 +1201,13 @@ bool JavaThread::java_suspend(bool register_vthread_SR) {
|
||||
|
||||
guarantee(Thread::is_JavaThread_protected(/* target */ this),
|
||||
"target JavaThread is not protected in calling context.");
|
||||
return this->handshake_state()->suspend(register_vthread_SR);
|
||||
return this->suspend_resume_manager()->suspend(register_vthread_SR);
|
||||
}
|
||||
|
||||
bool JavaThread::java_resume(bool register_vthread_SR) {
|
||||
guarantee(Thread::is_JavaThread_protected_by_TLH(/* target */ this),
|
||||
"missing ThreadsListHandle in calling context.");
|
||||
return this->handshake_state()->resume(register_vthread_SR);
|
||||
return this->suspend_resume_manager()->resume(register_vthread_SR);
|
||||
}
|
||||
|
||||
// Wait for another thread to perform object reallocation and relocking on behalf of
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "runtime/safepointMechanism.hpp"
|
||||
#include "runtime/stackWatermarkSet.hpp"
|
||||
#include "runtime/stackOverflow.hpp"
|
||||
#include "runtime/suspendResumeManager.hpp"
|
||||
#include "runtime/thread.hpp"
|
||||
#include "runtime/threadHeapSampler.hpp"
|
||||
#include "runtime/threadIdentifier.hpp"
|
||||
@ -694,9 +695,13 @@ private:
|
||||
|
||||
// Suspend/resume support for JavaThread
|
||||
// higher-level suspension/resume logic called by the public APIs
|
||||
private:
|
||||
SuspendResumeManager _suspend_resume_manager;
|
||||
public:
|
||||
bool java_suspend(bool register_vthread_SR);
|
||||
bool java_resume(bool register_vthread_SR);
|
||||
bool is_suspended() { return _handshake.is_suspended(); }
|
||||
bool is_suspended() { return _suspend_resume_manager.is_suspended(); }
|
||||
SuspendResumeManager* suspend_resume_manager() { return &_suspend_resume_manager; }
|
||||
|
||||
// Check for async exception in addition to safepoint.
|
||||
static void check_special_condition_for_native_trans(JavaThread *thread);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user