merge latest changes from master branch

This commit is contained in:
Daniel Fuchs 2025-06-12 17:32:28 +01:00
commit d4984d5e5a
104 changed files with 5760 additions and 1566 deletions

View File

@ -1,6 +1,6 @@
#!/bin/bash #!/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. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
# #
# This code is free software; you can redistribute it and/or modify it # 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. # Print additional help, e.g. a list of toolchains and JVM features.
# This must be done by the autoconf script. # 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 cat <<EOT

View File

@ -6687,6 +6687,7 @@ instruct reinterpretL(iRegLdst dst) %{
match(Set dst (VectorReinterpret dst)); match(Set dst (VectorReinterpret dst));
ins_cost(0); ins_cost(0);
format %{ "reinterpret $dst" %} format %{ "reinterpret $dst" %}
size(0);
ins_encode( /*empty*/ ); ins_encode( /*empty*/ );
ins_pipe(pipe_class_empty); ins_pipe(pipe_class_empty);
%} %}
@ -6695,6 +6696,7 @@ instruct reinterpretX(vecX dst) %{
match(Set dst (VectorReinterpret dst)); match(Set dst (VectorReinterpret dst));
ins_cost(0); ins_cost(0);
format %{ "reinterpret $dst" %} format %{ "reinterpret $dst" %}
size(0);
ins_encode( /*empty*/ ); ins_encode( /*empty*/ );
ins_pipe(pipe_class_empty); ins_pipe(pipe_class_empty);
%} %}
@ -6814,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" %} format %{ "CMOVE $dst, $crx eq, 0, $src1 \t// encode: preserve 0" %}
size(4); size(4);
ins_encode %{ ins_encode %{
// This is a Power7 instruction for which no machine description exists.
__ isel_0($dst$$Register, $crx$$CondRegister, Assembler::equal, $src1$$Register); __ isel_0($dst$$Register, $crx$$CondRegister, Assembler::equal, $src1$$Register);
%} %}
ins_pipe(pipe_class_default); ins_pipe(pipe_class_default);
@ -6946,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" %} format %{ "CMOVE $dst, $crx eq, 0, $src1 \t// decode: preserve 0" %}
size(4); size(4);
ins_encode %{ ins_encode %{
// This is a Power7 instruction for which no machine description exists.
__ isel_0($dst$$Register, $crx$$CondRegister, Assembler::equal, $src1$$Register); __ isel_0($dst$$Register, $crx$$CondRegister, Assembler::equal, $src1$$Register);
%} %}
ins_pipe(pipe_class_default); ins_pipe(pipe_class_default);
@ -7423,8 +7423,6 @@ instruct cmovI_reg_isel(cmpOp cmp, flagsRegSrc crx, iRegIdst dst, iRegIsrc src)
format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %} format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %}
size(4); size(4);
ins_encode %{ 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; int cc = $cmp$$cmpcode;
__ isel($dst$$Register, $crx$$CondRegister, __ isel($dst$$Register, $crx$$CondRegister,
(Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register); (Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register);
@ -7440,8 +7438,6 @@ instruct cmovL_reg_isel(cmpOp cmp, flagsRegSrc crx, iRegLdst dst, iRegLsrc src)
format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %} format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %}
size(4); size(4);
ins_encode %{ 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; int cc = $cmp$$cmpcode;
__ isel($dst$$Register, $crx$$CondRegister, __ isel($dst$$Register, $crx$$CondRegister,
(Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register); (Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register);
@ -7457,8 +7453,6 @@ instruct cmovN_reg_isel(cmpOp cmp, flagsRegSrc crx, iRegNdst dst, iRegNsrc src)
format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %} format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %}
size(4); size(4);
ins_encode %{ 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; int cc = $cmp$$cmpcode;
__ isel($dst$$Register, $crx$$CondRegister, __ isel($dst$$Register, $crx$$CondRegister,
(Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register); (Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register);
@ -7474,8 +7468,6 @@ instruct cmovP_reg_isel(cmpOp cmp, flagsRegSrc crx, iRegPdst dst, iRegPsrc src)
format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %} format %{ "CMOVE $cmp, $crx, $dst, $src\n\t" %}
size(4); size(4);
ins_encode %{ 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; int cc = $cmp$$cmpcode;
__ isel($dst$$Register, $crx$$CondRegister, __ isel($dst$$Register, $crx$$CondRegister,
(Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register); (Assembler::Condition)(cc & 3), /*invert*/((~cc) & 8), $src$$Register);
@ -9921,13 +9913,6 @@ instruct andcL_reg_reg(iRegLdst dst, iRegLsrc src1, iRegLsrc src2) %{
// of java.lang.Float etc., e.g. // of java.lang.Float etc., e.g.
// int floatToIntBits(float value) // int floatToIntBits(float value)
// float intBitsToFloat(int bits) // 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) %{ instruct moveL2D_reg(regD dst, iRegLsrc src) %{
match(Set dst (MoveL2D src)); match(Set dst (MoveL2D src));
@ -12434,6 +12419,7 @@ instruct minI_reg_reg_isel(iRegIdst dst, iRegIsrc src1, iRegIsrc src2, flagsRegC
effect(KILL cr0); effect(KILL cr0);
ins_cost(DEFAULT_COST*2); ins_cost(DEFAULT_COST*2);
size(8);
ins_encode %{ ins_encode %{
__ cmpw(CR0, $src1$$Register, $src2$$Register); __ cmpw(CR0, $src1$$Register, $src2$$Register);
__ isel($dst$$Register, CR0, Assembler::less, /*invert*/false, $src1$$Register, $src2$$Register); __ isel($dst$$Register, CR0, Assembler::less, /*invert*/false, $src1$$Register, $src2$$Register);
@ -12447,6 +12433,7 @@ instruct maxI_reg_reg_isel(iRegIdst dst, iRegIsrc src1, iRegIsrc src2, flagsRegC
effect(KILL cr0); effect(KILL cr0);
ins_cost(DEFAULT_COST*2); ins_cost(DEFAULT_COST*2);
size(8);
ins_encode %{ ins_encode %{
__ cmpw(CR0, $src1$$Register, $src2$$Register); __ cmpw(CR0, $src1$$Register, $src2$$Register);
__ isel($dst$$Register, CR0, Assembler::greater, /*invert*/false, $src1$$Register, $src2$$Register); __ isel($dst$$Register, CR0, Assembler::greater, /*invert*/false, $src1$$Register, $src2$$Register);
@ -12456,7 +12443,6 @@ instruct maxI_reg_reg_isel(iRegIdst dst, iRegIsrc src1, iRegIsrc src2, flagsRegC
//---------- Population Count Instructions ------------------------------------ //---------- Population Count Instructions ------------------------------------
// Popcnt for Power7.
instruct popCountI(iRegIdst dst, iRegIsrc src) %{ instruct popCountI(iRegIdst dst, iRegIsrc src) %{
match(Set dst (PopCountI src)); match(Set dst (PopCountI src));
predicate(UsePopCountInstruction); predicate(UsePopCountInstruction);
@ -12470,7 +12456,6 @@ instruct popCountI(iRegIdst dst, iRegIsrc src) %{
ins_pipe(pipe_class_default); ins_pipe(pipe_class_default);
%} %}
// Popcnt for Power7.
instruct popCountL(iRegIdst dst, iRegLsrc src) %{ instruct popCountL(iRegIdst dst, iRegLsrc src) %{
predicate(UsePopCountInstruction); predicate(UsePopCountInstruction);
match(Set dst (PopCountL src)); match(Set dst (PopCountL src));
@ -13299,6 +13284,7 @@ instruct repl2F_immF0(iRegLdst dst, immF_0 zero) %{
Matcher::vector_element_basic_type(n) == T_FLOAT); Matcher::vector_element_basic_type(n) == T_FLOAT);
format %{ "LI $dst, #0 \t// replicate2F" %} format %{ "LI $dst, #0 \t// replicate2F" %}
size(4);
ins_encode %{ ins_encode %{
__ li($dst$$Register, 0x0); __ li($dst$$Register, 0x0);
%} %}
@ -13927,6 +13913,7 @@ instruct overflowAddL_reg_reg(flagsRegCR0 cr0, iRegLsrc op1, iRegLsrc op2) %{
match(Set cr0 (OverflowAddL op1 op2)); match(Set cr0 (OverflowAddL op1 op2));
format %{ "add_ $op1, $op2\t# overflow check long" %} format %{ "add_ $op1, $op2\t# overflow check long" %}
size(12);
ins_encode %{ ins_encode %{
__ li(R0, 0); __ li(R0, 0);
__ mtxer(R0); // clear XER.SO __ mtxer(R0); // clear XER.SO
@ -13939,6 +13926,7 @@ instruct overflowSubL_reg_reg(flagsRegCR0 cr0, iRegLsrc op1, iRegLsrc op2) %{
match(Set cr0 (OverflowSubL op1 op2)); match(Set cr0 (OverflowSubL op1 op2));
format %{ "subfo_ R0, $op2, $op1\t# overflow check long" %} format %{ "subfo_ R0, $op2, $op1\t# overflow check long" %}
size(12);
ins_encode %{ ins_encode %{
__ li(R0, 0); __ li(R0, 0);
__ mtxer(R0); // clear XER.SO __ mtxer(R0); // clear XER.SO
@ -13951,6 +13939,7 @@ instruct overflowNegL_reg(flagsRegCR0 cr0, immL_0 zero, iRegLsrc op2) %{
match(Set cr0 (OverflowSubL zero op2)); match(Set cr0 (OverflowSubL zero op2));
format %{ "nego_ R0, $op2\t# overflow check long" %} format %{ "nego_ R0, $op2\t# overflow check long" %}
size(12);
ins_encode %{ ins_encode %{
__ li(R0, 0); __ li(R0, 0);
__ mtxer(R0); // clear XER.SO __ mtxer(R0); // clear XER.SO
@ -13963,6 +13952,7 @@ instruct overflowMulL_reg_reg(flagsRegCR0 cr0, iRegLsrc op1, iRegLsrc op2) %{
match(Set cr0 (OverflowMulL op1 op2)); match(Set cr0 (OverflowMulL op1 op2));
format %{ "mulldo_ R0, $op1, $op2\t# overflow check long" %} format %{ "mulldo_ R0, $op1, $op2\t# overflow check long" %}
size(12);
ins_encode %{ ins_encode %{
__ li(R0, 0); __ li(R0, 0);
__ mtxer(R0); // clear XER.SO __ mtxer(R0); // clear XER.SO
@ -14001,6 +13991,7 @@ instruct repl4F_immF0(vecX dst, immF_0 zero) %{
Matcher::vector_element_basic_type(n) == T_FLOAT); Matcher::vector_element_basic_type(n) == T_FLOAT);
format %{ "XXLXOR $dst, $zero \t// replicate4F" %} format %{ "XXLXOR $dst, $zero \t// replicate4F" %}
size(4);
ins_encode %{ ins_encode %{
__ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister); __ xxlxor($dst$$VectorSRegister, $dst$$VectorSRegister, $dst$$VectorSRegister);
%} %}

View File

@ -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)) { if (FLAG_IS_DEFAULT(UseCRC32Intrinsics)) {
FLAG_SET_DEFAULT(UseCRC32Intrinsics, true); FLAG_SET_DEFAULT(UseCRC32Intrinsics, true);
} }
} else { } else {
if (!FLAG_IS_DEFAULT(UseCRC32Intrinsics)) { 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); FLAG_SET_DEFAULT(UseCRC32Intrinsics, false);
} }

View File

@ -465,13 +465,19 @@ address TemplateInterpreterGenerator::generate_math_entry(AbstractInterpreter::M
__ call_VM_leaf0(CAST_FROM_FN_PTR(address, SharedRuntime::dtan)); __ call_VM_leaf0(CAST_FROM_FN_PTR(address, SharedRuntime::dtan));
} }
} else if (kind == Interpreter::java_lang_math_tanh) { } else if (kind == Interpreter::java_lang_math_tanh) {
assert(StubRoutines::dtanh() != nullptr, "not initialized"); if (StubRoutines::dtanh() != nullptr) {
__ movdbl(xmm0, Address(rsp, wordSize)); __ movdbl(xmm0, Address(rsp, wordSize));
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dtanh()))); __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dtanh())));
} else {
return nullptr; // Fallback to default implementation
}
} else if (kind == Interpreter::java_lang_math_cbrt) { } else if (kind == Interpreter::java_lang_math_cbrt) {
assert(StubRoutines::dcbrt() != nullptr, "not initialized"); if (StubRoutines::dcbrt() != nullptr) {
__ movdbl(xmm0, Address(rsp, wordSize)); __ movdbl(xmm0, Address(rsp, wordSize));
__ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dcbrt()))); __ call(RuntimeAddress(CAST_FROM_FN_PTR(address, StubRoutines::dcbrt())));
} else {
return nullptr; // Fallback to default implementation
}
} else if (kind == Interpreter::java_lang_math_abs) { } else if (kind == Interpreter::java_lang_math_abs) {
assert(StubRoutines::x86::double_sign_mask() != nullptr, "not initialized"); assert(StubRoutines::x86::double_sign_mask() != nullptr, "not initialized");
__ movdbl(xmm0, Address(rsp, wordSize)); __ movdbl(xmm0, Address(rsp, wordSize));

View File

@ -10531,7 +10531,8 @@ instruct xorI_rReg_im1_ndd(rRegI dst, rRegI src, immI_M1 imm)
// Xor Register with Immediate // Xor Register with Immediate
instruct xorI_rReg_imm(rRegI dst, immI src, rFlagsReg cr) 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)); match(Set dst (XorI dst src));
effect(KILL cr); 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); 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);
@ -10545,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) 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)); match(Set dst (XorI src1 src2));
effect(KILL cr); 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); 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);
@ -10563,6 +10565,7 @@ instruct xorI_rReg_mem_imm_ndd(rRegI dst, memory src1, immI src2, rFlagsReg cr)
predicate(UseAPX); predicate(UseAPX);
match(Set dst (XorI (LoadI src1) src2)); match(Set dst (XorI (LoadI src1) src2));
effect(KILL cr); 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); 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" %} format %{ "exorl $dst, $src1, $src2\t# int ndd" %}
@ -11205,7 +11208,8 @@ instruct xorL_rReg_im1_ndd(rRegL dst,rRegL src, immL_M1 imm)
// Xor Register with Immediate // Xor Register with Immediate
instruct xorL_rReg_imm(rRegL dst, immL32 src, rFlagsReg cr) 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)); match(Set dst (XorL dst src));
effect(KILL cr); 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); 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);
@ -11219,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) 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)); match(Set dst (XorL src1 src2));
effect(KILL cr); 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); 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);
@ -11238,6 +11243,7 @@ instruct xorL_rReg_mem_imm(rRegL dst, memory src1, immL32 src2, rFlagsReg cr)
match(Set dst (XorL (LoadL src1) src2)); match(Set dst (XorL (LoadL src1) src2));
effect(KILL cr); 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); 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" %} format %{ "exorq $dst, $src1, $src2\t# long ndd" %}
ins_encode %{ ins_encode %{

View File

@ -154,7 +154,8 @@ julong os::Bsd::available_memory() {
assert(kerr == KERN_SUCCESS, assert(kerr == KERN_SUCCESS,
"host_statistics64 failed - check mach_host_self() and count"); "host_statistics64 failed - check mach_host_self() and count");
if (kerr == KERN_SUCCESS) { 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 #endif
return available; return available;

View File

@ -3740,6 +3740,7 @@ void ClassFileParser::apply_parsed_class_metadata(
_cp->set_pool_holder(this_klass); _cp->set_pool_holder(this_klass);
this_klass->set_constants(_cp); this_klass->set_constants(_cp);
this_klass->set_fieldinfo_stream(_fieldinfo_stream); 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_fields_status(_fields_status);
this_klass->set_methods(_methods); this_klass->set_methods(_methods);
this_klass->set_inner_classes(_inner_classes); 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_permitted_subclasses(_permitted_subclasses);
this_klass->set_record_components(_record_components); 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 // Delay the setting of _local_interfaces and _transitive_interfaces until after
// initialize_supers() in fill_instance_klass(). It is because the _local_interfaces could // 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 // 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 // note that is not safe to use the fields in the parser from this point on
assert(nullptr == _cp, "invariant"); assert(nullptr == _cp, "invariant");
assert(nullptr == _fieldinfo_stream, "invariant"); assert(nullptr == _fieldinfo_stream, "invariant");
assert(nullptr == _fieldinfo_search_table, "invariant");
assert(nullptr == _fields_status, "invariant"); assert(nullptr == _fields_status, "invariant");
assert(nullptr == _methods, "invariant"); assert(nullptr == _methods, "invariant");
assert(nullptr == _inner_classes, "invariant"); assert(nullptr == _inner_classes, "invariant");
@ -5276,6 +5280,7 @@ ClassFileParser::ClassFileParser(ClassFileStream* stream,
_super_klass(), _super_klass(),
_cp(nullptr), _cp(nullptr),
_fieldinfo_stream(nullptr), _fieldinfo_stream(nullptr),
_fieldinfo_search_table(nullptr),
_fields_status(nullptr), _fields_status(nullptr),
_methods(nullptr), _methods(nullptr),
_inner_classes(nullptr), _inner_classes(nullptr),
@ -5352,6 +5357,7 @@ void ClassFileParser::clear_class_metadata() {
// deallocated if classfile parsing returns an error. // deallocated if classfile parsing returns an error.
_cp = nullptr; _cp = nullptr;
_fieldinfo_stream = nullptr; _fieldinfo_stream = nullptr;
_fieldinfo_search_table = nullptr;
_fields_status = nullptr; _fields_status = nullptr;
_methods = nullptr; _methods = nullptr;
_inner_classes = nullptr; _inner_classes = nullptr;
@ -5374,6 +5380,7 @@ ClassFileParser::~ClassFileParser() {
if (_fieldinfo_stream != nullptr) { if (_fieldinfo_stream != nullptr) {
MetadataFactory::free_array<u1>(_loader_data, _fieldinfo_stream); MetadataFactory::free_array<u1>(_loader_data, _fieldinfo_stream);
} }
MetadataFactory::free_array<u1>(_loader_data, _fieldinfo_search_table);
if (_fields_status != nullptr) { if (_fields_status != nullptr) {
MetadataFactory::free_array<FieldStatus>(_loader_data, _fields_status); MetadataFactory::free_array<FieldStatus>(_loader_data, _fields_status);
@ -5774,6 +5781,7 @@ void ClassFileParser::post_process_parsed_stream(const ClassFileStream* const st
_fieldinfo_stream = _fieldinfo_stream =
FieldInfoStream::create_FieldInfoStream(_temp_field_info, _java_fields_count, FieldInfoStream::create_FieldInfoStream(_temp_field_info, _java_fields_count,
injected_fields_count, loader_data(), CHECK); injected_fields_count, loader_data(), CHECK);
_fieldinfo_search_table = FieldInfoStream::create_search_table(_cp, _fieldinfo_stream, _loader_data, CHECK);
_fields_status = _fields_status =
MetadataFactory::new_array<FieldStatus>(_loader_data, _temp_field_info->length(), MetadataFactory::new_array<FieldStatus>(_loader_data, _temp_field_info->length(),
FieldStatus(0), CHECK); FieldStatus(0), CHECK);

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -123,6 +123,7 @@ class ClassFileParser {
const InstanceKlass* _super_klass; const InstanceKlass* _super_klass;
ConstantPool* _cp; ConstantPool* _cp;
Array<u1>* _fieldinfo_stream; Array<u1>* _fieldinfo_stream;
Array<u1>* _fieldinfo_search_table;
Array<FieldStatus>* _fields_status; Array<FieldStatus>* _fields_status;
Array<Method*>* _methods; Array<Method*>* _methods;
Array<u2>* _inner_classes; Array<u2>* _inner_classes;

View File

@ -301,7 +301,7 @@ void FieldLayout::reconstruct_layout(const InstanceKlass* ik, bool& has_instance
BasicType last_type; BasicType last_type;
int last_offset = -1; int last_offset = -1;
while (ik != nullptr) { 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()); BasicType type = Signature::basic_type(fs.signature());
// distinction between static and non-static fields is missing // distinction between static and non-static fields is missing
if (fs.access_flags().is_static()) continue; if (fs.access_flags().is_static()) continue;
@ -461,7 +461,7 @@ void FieldLayout::print(outputStream* output, bool is_static, const InstanceKlas
bool found = false; bool found = false;
const InstanceKlass* ik = super; const InstanceKlass* ik = super;
while (!found && ik != nullptr) { 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()) { if (fs.offset() == b->offset()) {
output->print_cr(" @%d \"%s\" %s %d/%d %s", output->print_cr(" @%d \"%s\" %s %d/%d %s",
b->offset(), b->offset(),

View File

@ -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); Array<u1>* new_fis = FieldInfoStream::create_FieldInfoStream(fields, java_fields, injected_fields, k->class_loader_data(), CHECK);
ik->set_fieldinfo_stream(new_fis); ik->set_fieldinfo_stream(new_fis);
MetadataFactory::free_array<u1>(k->class_loader_data(), old_stream); 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));
} }
} }

View File

@ -289,8 +289,6 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) {
case vmIntrinsics::_dsin: case vmIntrinsics::_dsin:
case vmIntrinsics::_dcos: case vmIntrinsics::_dcos:
case vmIntrinsics::_dtan: case vmIntrinsics::_dtan:
case vmIntrinsics::_dtanh:
case vmIntrinsics::_dcbrt:
case vmIntrinsics::_dlog: case vmIntrinsics::_dlog:
case vmIntrinsics::_dexp: case vmIntrinsics::_dexp:
case vmIntrinsics::_dpow: case vmIntrinsics::_dpow:
@ -316,6 +314,13 @@ bool vmIntrinsics::disabled_by_jvm_flags(vmIntrinsics::ID id) {
case vmIntrinsics::_fmaF: case vmIntrinsics::_fmaF:
if (!InlineMathNatives || !UseFMA) return true; if (!InlineMathNatives || !UseFMA) return true;
break; 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::_floatToFloat16:
case vmIntrinsics::_float16ToFloat: case vmIntrinsics::_float16ToFloat:
if (!InlineIntrinsics) return true; if (!InlineIntrinsics) return true;

View File

@ -625,6 +625,34 @@ void ShenandoahBarrierC2Support::verify(RootNode* root) {
} }
#endif #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) { 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 // That both nodes have the same control is not sufficient to prove
// domination, verify that there's no path from d to n // 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()) { 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"); assert(phase->ctrl_or_self(m->in(LoopNode::EntryControl)) != c, "following loop entry should lead to new control");
} else { } else {
if (m->is_Store() || m->is_LoadStore()) { // Take anti-dependencies into account
// Take anti-dependencies into account maybe_push_anti_dependent_loads(phase, m, c, wq);
Node* mem = m->in(MemNode::Memory); push_data_inputs_at_control(phase, m, c, wq);
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));
}
}
} }
} }
return true; return true;
@ -1006,7 +1021,20 @@ void ShenandoahBarrierC2Support::call_lrb_stub(Node*& ctrl, Node*& val, Node* lo
phase->register_new_node(val, ctrl); 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* ctrl = phase->get_ctrl(barrier);
Node* init_raw_mem = fixer.find_mem(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 // control will be after the expanded barrier. The raw memory (if
// its memory is control dependent on the barrier's input control) // its memory is control dependent on the barrier's input control)
// must stay above the barrier. // must stay above the barrier.
uses_to_ignore.clear(); collect_nodes_above_barrier(nodes_above_barrier, phase, ctrl, init_raw_mem);
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);
}
}
}
for (DUIterator_Fast imax, i = ctrl->fast_outs(imax); i < imax; i++) { for (DUIterator_Fast imax, i = ctrl->fast_outs(imax); i < imax; i++) {
Node* u = ctrl->fast_out(i); Node* u = ctrl->fast_out(i);
if (u->_idx < last && if (u->_idx < last &&
u != barrier && u != barrier &&
!u->depends_only_on_test() && // preserve dependency on test !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())) && (u->in(0) != ctrl || (!u->is_Region() && !u->is_Phi())) &&
(ctrl->Opcode() != Op_CatchProj || u->Opcode() != Op_CreateEx)) { (ctrl->Opcode() != Op_CatchProj || u->Opcode() != Op_CreateEx)) {
Node* old_c = phase->ctrl_or_self(u); Node* old_c = phase->ctrl_or_self(u);
Node* c = old_c; if (old_c != ctrl ||
if (c != ctrl ||
is_dominator_same_ctrl(old_c, barrier, u, phase) || is_dominator_same_ctrl(old_c, barrier, u, phase) ||
ShenandoahBarrierSetC2::is_shenandoah_state_load(u)) { ShenandoahBarrierSetC2::is_shenandoah_state_load(u)) {
phase->igvn().rehash_node_delayed(u); phase->igvn().rehash_node_delayed(u);
@ -1315,7 +1330,7 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) {
// Expand load-reference-barriers // Expand load-reference-barriers
MemoryGraphFixer fixer(Compile::AliasIdxRaw, true, phase); 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--) { for (int i = state->load_reference_barriers_count() - 1; i >= 0; i--) {
ShenandoahLoadReferenceBarrierNode* lrb = state->load_reference_barrier(i); ShenandoahLoadReferenceBarrierNode* lrb = state->load_reference_barrier(i);
uint last = phase->C->unique(); uint last = phase->C->unique();
@ -1410,7 +1425,7 @@ void ShenandoahBarrierC2Support::pin_and_expand(PhaseIdealLoop* phase) {
Node* out_val = val_phi; Node* out_val = val_phi;
phase->register_new_node(val_phi, region); 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; ctrl = orig_ctrl;

View File

@ -62,8 +62,12 @@ private:
PhaseIdealLoop* phase, int flags); PhaseIdealLoop* phase, int flags);
static void call_lrb_stub(Node*& ctrl, Node*& val, Node* load_addr, static void call_lrb_stub(Node*& ctrl, Node*& val, Node* load_addr,
DecoratorSet decorators, PhaseIdealLoop* phase); 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 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); static Node* get_load_addr(PhaseIdealLoop* phase, VectorSet& visited, Node* lrb);
public: public:
@ -76,6 +80,11 @@ public:
static bool expand(Compile* C, PhaseIterGVN& igvn); static bool expand(Compile* C, PhaseIterGVN& igvn);
static void pin_and_expand(PhaseIdealLoop* phase); 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 #ifdef ASSERT
static void verify(RootNode* root); static void verify(RootNode* root);
#endif #endif

View File

@ -216,6 +216,7 @@ class outputStream;
LOG_TAG(valuebasedclasses) \ LOG_TAG(valuebasedclasses) \
LOG_TAG(verification) \ LOG_TAG(verification) \
LOG_TAG(verify) \ LOG_TAG(verify) \
LOG_TAG(vmatree) \
LOG_TAG(vmmutex) \ LOG_TAG(vmmutex) \
LOG_TAG(vmoperation) \ LOG_TAG(vmoperation) \
LOG_TAG(vmthread) \ LOG_TAG(vmthread) \

View File

@ -91,7 +91,7 @@ void MemoryFileTracker::print_report_on(const MemoryFile* file, outputStream* st
NMTUtil::tag_to_name(prev->val().out.mem_tag())); NMTUtil::tag_to_name(prev->val().out.mem_tag()));
{ {
StreamIndentor si(stream, 4); 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(); stream->cr();
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -43,11 +43,8 @@
class NativeCallStackStorage : public CHeapObjBase { class NativeCallStackStorage : public CHeapObjBase {
public: public:
using StackIndex = int; using StackIndex = int;
private:
constexpr static const StackIndex invalid = std::numeric_limits<StackIndex>::max() - 1; constexpr static const StackIndex invalid = std::numeric_limits<StackIndex>::max() - 1;
public:
static bool equals(const StackIndex a, const StackIndex b) { static bool equals(const StackIndex a, const StackIndex b) {
return a == b; return a == b;
} }

View File

@ -28,21 +28,228 @@
#include "utilities/globalDefinitions.hpp" #include "utilities/globalDefinitions.hpp"
#include "utilities/growableArray.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] = { 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) { 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) {
if (A == B) {
// A 0-sized mapping isn't worth recording.
return SummaryDiff(); 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{ IntervalChange stA{
IntervalState{StateType::Released, empty_regiondata}, IntervalState{StateType::Released, empty_regiondata},
IntervalState{ state, metadata} IntervalState{ state, metadata}
@ -51,176 +258,400 @@ VMATree::SummaryDiff VMATree::register_mapping(position A, position B, StateType
IntervalState{ state, metadata}, IntervalState{ state, metadata},
IntervalState{StateType::Released, empty_regiondata} 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. // nodes: .....X.......Y...Z......W........U
// Find closest node that is LEQ A // request: A------------------B
bool LEQ_A_found = false; // X,Y = enclosing_nodes(A)
AddressState LEQ_A; // W,U = enclosing_nodes(B)
TreapNode* leqA_n = _tree.closest_leq(A); // 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)
if (leqA_n == nullptr) { // 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)
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) { // We update regions in 3 sections: 1) X..A..Y, 2) Y....W, 3) W..B..U
log_debug(nmt)("Cannot use the tag inplace if no pre-existing tag exists. From: " PTR_FORMAT " To: " PTR_FORMAT, A, B); // 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()) { // update region between n1 and n2
_tree.upsert(A, stA); 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 { return false;
LEQ_A_found = true; };
LEQ_A = AddressState{leqA_n->key(), leqA_n->val()}; GrowableArrayCHeap<position, mtNMT> to_be_removed;
StateType leqA_state = leqA_n->val().out.type(); // update regions in [Y,W)
StateType new_state = stA.out.type(); auto update_loop = [&]() {
// If we specify use_tag_inplace then the new region takes over the current tag instead of the tag in metadata. TreapNode* prev = nullptr;
// This is important because the VirtualMemoryTracker API doesn't require supplying the tag for some operations. _tree.visit_range_in_order(_A + 1, _B + 1, [&](TreapNode* curr) {
if (use_tag_inplace) { if (prev != nullptr) {
assert(leqA_n->val().out.type() != StateType::Released, "Should not use inplace the tag of a released region"); update_region(prev, curr, req, diff);
MemTag tag = leqA_n->val().out.mem_tag(); // during visit, structure of the tree should not be changed
stA.out.set_tag(tag); // keep the keys to be removed, and remove them later
stB.in.set_tag(tag); if (prev->val().is_noop()) {
} to_be_removed.push(prev->key());
}
// 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;
} }
} else { prev = curr;
// The address must be smaller. });
assert(A > leqA_n->key(), "must be"); };
// 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 if ( X_exists && !Y_exists && !U_exists) { row = 4; }
// state change, we just omit the node. if ( X_exists && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 5; }
// That happens, for example, when reserving within an already reserved region with identical metadata. if ( X_exists && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 6; }
stA.in = out_state(leqA_n); // .. and the region's prior state is the incoming state if ( X_exists && Y_exists && W_eq_B && !U_exists) { row = 7; }
if (stA.is_noop()) {
// Nothing to do. if ( X_eq_A && !Y_exists && !U_exists) { row = 8; }
} else { if ( X_eq_A && Y_exists && Y_eq_W && !W_eq_B && !U_exists) { row = 9; }
// Add new node. if ( X_eq_A && Y_exists && !Y_eq_W && !W_eq_B && !U_exists) { row = 10; }
_tree.upsert(A, stA); 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;
} }
} // row 1: .........A...YW.............B.....
case 1: {
// Now we handle B. ShouldNotReachHere();
// We first search all nodes that are (A, B]. All of these nodes break;
// 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 2: .........A...Y..........W...B.....
case 2: {
// Insert B node if needed update_A(Y);
if (B_needs_insert && // Was not already inserted upsert_if(A);
!stB.is_noop()) // The operation is differing update_loop();
{ remove_if(Y);
_tree.upsert(B, stB); update(W, B);
} remove_if(W);
upsert_if(B);
// We now need to: break;
// 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 3: .........A...Y.............WB.....
case 3: {
// Track the previous node. update_A(Y);
AddressState prev{A, stA}; upsert_if(A);
for (int i = 0; i < to_be_deleted_inbetween_a_b.length(); i++) { update_loop();
const AddressState delete_me = to_be_deleted_inbetween_a_b.at(i); remove_if(W);
_tree.remove(delete_me.address); break;
// 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;
} }
prev = delete_me; // row 4: .....X...A..................B.....
} case 4: {
A->val().in = X->val().out;
if (prev.address != A && prev.out().type() != StateType::Released) { update_A(B);
// The last node wasn't released, so it must be connected to a node outside of (A, B) upsert_if(A);
// A - prev - B - (some node >= B) upsert_if(B);
// It might be that prev.address == B == (some node >= B), this is fine. break;
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 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. // Remove the 'noop' nodes that found inside the loop
SingleDiff& rescom = diff.tag[NMTUtil::tag_to_index(stA.out.mem_tag())]; while(to_be_removed.length() != 0) {
if (state == StateType::Reserved) { _tree.remove(to_be_removed.pop());
rescom.reserve += B - A;
} else if (state == StateType::Committed) {
rescom.commit += B - A;
rescom.reserve += B - A;
} }
return diff; return diff;
} }
#ifdef ASSERT #ifdef ASSERT
void VMATree::print_on(outputStream* out) { void VMATree::print_on(outputStream* out) {
visit_in_order([&](TreapNode* current) { visit_in_order([&](TreapNode* current) {
out->print("%zu (%s) - %s - ", current->key(), NMTUtil::tag_to_name(out_state(current).mem_tag()), 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())); statetype_to_string(out_state(current).type()), current->val().out.reserved_stack(), current->val().out.committed_stack());
}); });
out->cr(); out->cr();
} }
@ -268,7 +699,7 @@ VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, con
SummaryDiff diff; SummaryDiff diff;
// Ignore any released ranges, these must be mtNone and have no stack // Ignore any released ranges, these must be mtNone and have no stack
if (type != StateType::Released) { 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); SummaryDiff result = register_mapping(from, end, type, new_data);
diff.add(result); diff.add(result);
} }
@ -289,7 +720,7 @@ VMATree::SummaryDiff VMATree::set_tag(const position start, const size size, con
StateType type = out.type(); StateType type = out.type();
if (type != StateType::Released) { 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); SummaryDiff result = register_mapping(from, end, type, new_data);
diff.add(result); diff.add(result);
} }

View File

@ -44,6 +44,7 @@ class VMATree {
public: public:
using position = size_t; using position = size_t;
using size = size_t; using size = size_t;
using SIndex = NativeCallStackStorage::StackIndex;
class PositionComparator { class PositionComparator {
public: 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: private:
static const char* statetype_strings[static_cast<uint8_t>(StateType::LAST)]; 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. // Each point has some stack and a tag associated with it.
struct RegionData { struct RegionData {
const NativeCallStackStorage::StackIndex stack_idx; const SIndex stack_idx;
const MemTag mem_tag; const MemTag mem_tag;
RegionData() : stack_idx(), mem_tag(mtNone) {} 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) {} : stack_idx(stack_idx), mem_tag(mem_tag) {}
static bool equals(const RegionData& a, const RegionData& b) { static bool equals(const RegionData& a, const RegionData& b) {
@ -91,15 +92,27 @@ private:
private: private:
// Store the type and mem_tag as two bytes // Store the type and mem_tag as two bytes
uint8_t type_tag[2]; uint8_t type_tag[2];
NativeCallStackStorage::StackIndex sidx; NativeCallStackStorage::StackIndex _reserved_stack;
NativeCallStackStorage::StackIndex _committed_stack;
public: 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) { IntervalState(const StateType type, const RegionData data) {
assert(!(type == StateType::Released) || data.mem_tag == mtNone, "Released state-type must have memory tag mtNone"); 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[0] = static_cast<uint8_t>(type);
type_tag[1] = static_cast<uint8_t>(data.mem_tag); 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 { StateType type() const {
@ -110,16 +123,50 @@ private:
return static_cast<MemTag>(type_tag[1]); return static_cast<MemTag>(type_tag[1]);
} }
RegionData regiondata() const { RegionData reserved_regiondata() const {
return RegionData{sidx, mem_tag()}; return RegionData{_reserved_stack, mem_tag()};
}
RegionData committed_regiondata() const {
return RegionData{_committed_stack, mem_tag()};
} }
void set_tag(MemTag tag) { void set_tag(MemTag tag) {
type_tag[1] = static_cast<uint8_t>(tag); type_tag[1] = static_cast<uint8_t>(tag);
} }
NativeCallStackStorage::StackIndex stack() const { NativeCallStackStorage::StackIndex reserved_stack() const {
return sidx; 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; IntervalState out;
bool is_noop() { 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() && 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 #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: private:
SummaryDiff register_mapping(position A, position B, StateType state, const RegionData& metadata, bool use_tag_inplace = false); 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: public:
SummaryDiff reserve_mapping(position from, size size, const RegionData& metadata) { SummaryDiff reserve_mapping(position from, size size, const RegionData& metadata) {

View File

@ -22,8 +22,11 @@
* *
*/ */
#include "memory/resourceArea.hpp"
#include "cds/cdsConfig.hpp"
#include "oops/fieldInfo.inline.hpp" #include "oops/fieldInfo.inline.hpp"
#include "runtime/atomic.hpp" #include "runtime/atomic.hpp"
#include "utilities/packedTable.hpp"
void FieldInfo::print(outputStream* os, ConstantPool* cp) { void FieldInfo::print(outputStream* os, ConstantPool* cp) {
os->print_cr("index=%d name_index=%d name=%s signature_index=%d signature=%s offset=%d " 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(), field_flags().as_uint(),
initializer_index(), initializer_index(),
generic_signature_index(), generic_signature_index(),
_field_flags.is_injected() ? lookup_symbol(generic_signature_index())->as_utf8() : cp->symbol_at(generic_signature_index())->as_utf8(), _field_flags.is_generic() ? (_field_flags.is_injected() ?
contended_group()); 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) { 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; StreamSizer s;
StreamFieldSizer sizer(&s); StreamFieldSizer sizer(&s);
assert(fields->length() == java_fields + injected_fields, "must be");
sizer.consumer()->accept_uint(java_fields); sizer.consumer()->accept_uint(java_fields);
sizer.consumer()->accept_uint(injected_fields); sizer.consumer()->accept_uint(injected_fields);
for (int i = 0; i < fields->length(); i++) { for (int i = 0; i < fields->length(); i++) {
FieldInfo* fi = fields->adr_at(i); FieldInfo* fi = fields->adr_at(i);
sizer.map_field_info(*fi); 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); 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>>; 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(java_fields);
writer.consumer()->accept_uint(injected_fields); writer.consumer()->accept_uint(injected_fields);
for (int i = 0; i < fields->length(); i++) { for (int i = 0; i < fields->length(); i++) {
FieldInfo* fi = fields->adr_at(i); writer.map_field_info(fields->at(i));
writer.map_field_info(*fi);
} }
#ifdef ASSERT #ifdef ASSERT
FieldInfoReader r(fis); FieldInfoReader r(fis);
int jfc = r.next_uint(); int jfc, ifc;
r.read_field_counts(&jfc, &ifc);
assert(jfc == java_fields, "Must be"); assert(jfc == java_fields, "Must be");
int ifc = r.next_uint();
assert(ifc == injected_fields, "Must be"); assert(ifc == injected_fields, "Must be");
for (int i = 0; i < jfc + ifc; i++) { for (int i = 0; i < jfc + ifc; i++) {
FieldInfo fi; FieldInfo fi;
@ -113,30 +121,221 @@ Array<u1>* FieldInfoStream::create_FieldInfoStream(GrowableArray<FieldInfo>* fie
return fis; return fis;
} }
GrowableArray<FieldInfo>* FieldInfoStream::create_FieldInfoArray(const Array<u1>* fis, int* java_fields_count, int* injected_fields_count) { int FieldInfoStream::compare_name_and_sig(const Symbol* n1, const Symbol* s1, const Symbol* n2, const Symbol* s2) {
int length = FieldInfoStream::num_total_fields(fis); int cmp = n1->fast_compare(n2);
GrowableArray<FieldInfo>* array = new GrowableArray<FieldInfo>(length); 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); FieldInfoReader r(fis);
*java_fields_count = r.next_uint(); int java_fields;
*injected_fields_count = r.next_uint(); 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()) { while (r.has_next()) {
FieldInfo fi; FieldInfo fi;
r.read_field_info(fi); r.read_field_info(fi);
array->append(fi); array->append(fi);
} }
assert(array->length() == length, "Must be"); assert(array->length() == length, "Must be");
assert(array->length() == *java_fields_count + *injected_fields_count, "Must be");
return array; return array;
} }
void FieldInfoStream::print_from_fieldinfo_stream(Array<u1>* fis, outputStream* os, ConstantPool* cp) { void FieldInfoStream::print_from_fieldinfo_stream(Array<u1>* fis, outputStream* os, ConstantPool* cp) {
int length = FieldInfoStream::num_total_fields(fis);
FieldInfoReader r(fis); FieldInfoReader r(fis);
int java_field_count = r.next_uint(); int java_fields_count;
int injected_fields_count = r.next_uint(); int injected_fields_count;
r.read_field_counts(&java_fields_count, &injected_fields_count);
while (r.has_next()) { while (r.has_next()) {
FieldInfo fi; FieldInfo fi;
r.read_field_info(fi); r.read_field_info(fi);
fi.print(os, cp); 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;
}
}

View File

@ -222,29 +222,28 @@ public:
void map_field_info(const FieldInfo& fi); void map_field_info(const FieldInfo& fi);
}; };
// Gadget for decoding and reading the stream of field records. // Gadget for decoding and reading the stream of field records.
class FieldInfoReader { class FieldInfoReader {
friend class FieldInfoStream;
friend class ClassFileParser;
friend class FieldStreamBase;
friend class FieldInfo;
UNSIGNED5::Reader<const u1*, int> _r; UNSIGNED5::Reader<const u1*, int> _r;
int _next_index; int _next_index;
public: public:
FieldInfoReader(const Array<u1>* fi); FieldInfoReader(const Array<u1>* fi);
private: private:
uint32_t next_uint() { return _r.next_uint(); } inline uint32_t next_uint() { return _r.next_uint(); }
void skip(int n) { int s = _r.try_skip(n); assert(s == n,""); } void skip(int n) { int s = _r.try_skip(n); assert(s == n,""); }
public: public:
int has_next() { return _r.has_next(); } void read_field_counts(int* java_fields, int* injected_fields);
int position() { return _r.position(); } int has_next() const { return _r.position() < _r.limit(); }
int next_index() { return _next_index; } 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); 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 // skip a whole field record, both required and optional bits
FieldInfoReader& skip_field_info(); FieldInfoReader& skip_field_info();
@ -271,6 +270,11 @@ class FieldInfoStream : AllStatic {
friend class JavaFieldStream; friend class JavaFieldStream;
friend class FieldStreamBase; friend class FieldStreamBase;
friend class ClassFileParser; 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: public:
static int num_java_fields(const Array<u1>* fis); 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 int num_total_fields(const Array<u1>* fis);
static Array<u1>* create_FieldInfoStream(GrowableArray<FieldInfo>* fields, int java_fields, int injected_fields, 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 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); 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 { class FieldStatus {

View File

@ -56,16 +56,27 @@ inline Symbol* FieldInfo::lookup_symbol(int symbol_index) const {
inline int FieldInfoStream::num_injected_java_fields(const Array<u1>* fis) { inline int FieldInfoStream::num_injected_java_fields(const Array<u1>* fis) {
FieldInfoReader fir(fis); FieldInfoReader fir(fis);
fir.skip(1); int java_fields_count;
return fir.next_uint(); 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) { inline int FieldInfoStream::num_total_fields(const Array<u1>* fis) {
FieldInfoReader fir(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> template<typename CON>
inline void Mapper<CON>::map_field_info(const FieldInfo& fi) { 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) inline FieldInfoReader::FieldInfoReader(const Array<u1>* fi)
: _r(fi->data(), 0), : _r(fi->data(), fi->length()),
_next_index(0) { } _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) { inline void FieldInfoReader::read_field_info(FieldInfo& fi) {
fi._index = _next_index++; fi._index = _next_index++;
fi._name_index = checked_cast<u2>(next_uint()); read_name_and_signature(&fi._name_index, &fi._signature_index);
fi._signature_index = checked_cast<u2>(next_uint());
fi._offset = next_uint(); fi._offset = next_uint();
fi._access_flags = AccessFlags(checked_cast<u2>(next_uint())); fi._access_flags = AccessFlags(checked_cast<u2>(next_uint()));
fi._field_flags = FieldInfo::FieldFlags(next_uint()); fi._field_flags = FieldInfo::FieldFlags(next_uint());

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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(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() { void initialize() {
int java_fields_count = _reader.next_uint(); int java_fields_count;
int injected_fields_count = _reader.next_uint(); int injected_fields_count;
assert( _limit <= java_fields_count + injected_fields_count, "Safety check"); _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) { if (_limit != 0) {
_reader.read_field_info(_fi_buf); _reader.read_field_info(_fi_buf);
} }
} }
public: public:
inline FieldStreamBase(InstanceKlass* klass); inline FieldStreamBase(InstanceKlass* klass);
@ -138,8 +144,11 @@ class FieldStreamBase : public StackObj {
// Iterate over only the Java fields // Iterate over only the Java fields
class JavaFieldStream : public FieldStreamBase { class JavaFieldStream : public FieldStreamBase {
Array<u1>* _search_table;
public: 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 { u2 name_index() const {
assert(!field()->field_flags().is_injected(), "regular only"); assert(!field()->field_flags().is_injected(), "regular only");
@ -149,7 +158,6 @@ class JavaFieldStream : public FieldStreamBase {
u2 signature_index() const { u2 signature_index() const {
assert(!field()->field_flags().is_injected(), "regular only"); assert(!field()->field_flags().is_injected(), "regular only");
return field()->signature_index(); return field()->signature_index();
return -1;
} }
u2 generic_signature_index() const { u2 generic_signature_index() const {
@ -164,6 +172,10 @@ class JavaFieldStream : public FieldStreamBase {
assert(!field()->field_flags().is_injected(), "regular only"); assert(!field()->field_flags().is_injected(), "regular only");
return field()->initializer_index(); 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 { class AllFieldStream : public FieldStreamBase {
public: public:
AllFieldStream(Array<u1>* fieldinfo, ConstantPool* constants): FieldStreamBase(fieldinfo, constants) {}
AllFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants()) {} AllFieldStream(const InstanceKlass* k): FieldStreamBase(k->fieldinfo_stream(), k->constants()) {}
}; };

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * 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) : FieldStreamBase::FieldStreamBase(const Array<u1>* fieldinfo_stream, ConstantPool* constants, int start, int limit) :
_fieldinfo_stream(fieldinfo_stream), _fieldinfo_stream(fieldinfo_stream),
_reader(FieldInfoReader(_fieldinfo_stream)), _reader(FieldInfoReader(_fieldinfo_stream)),
_constants(constantPoolHandle(Thread::current(), constants)), _index(start) { _constants(constantPoolHandle(Thread::current(), constants)),
_index = start; _index(start),
if (limit < start) { _limit(limit) {
_limit = FieldInfoStream::num_total_fields(_fieldinfo_stream);
} else {
_limit = limit;
}
initialize(); initialize();
} }
FieldStreamBase::FieldStreamBase(Array<u1>* fieldinfo_stream, ConstantPool* constants) : FieldStreamBase::FieldStreamBase(const Array<u1>* fieldinfo_stream, ConstantPool* constants) :
_fieldinfo_stream(fieldinfo_stream), _fieldinfo_stream(fieldinfo_stream),
_reader(FieldInfoReader(_fieldinfo_stream)), _reader(FieldInfoReader(_fieldinfo_stream)),
_constants(constantPoolHandle(Thread::current(), constants)), _constants(constantPoolHandle(Thread::current(), constants)),
_index(0), _index(0),
_limit(FieldInfoStream::num_total_fields(_fieldinfo_stream)) { _limit(-1) {
initialize(); initialize();
} }
@ -57,9 +53,28 @@ FieldStreamBase::FieldStreamBase(InstanceKlass* klass) :
_reader(FieldInfoReader(_fieldinfo_stream)), _reader(FieldInfoReader(_fieldinfo_stream)),
_constants(constantPoolHandle(Thread::current(), klass->constants())), _constants(constantPoolHandle(Thread::current(), klass->constants())),
_index(0), _index(0),
_limit(FieldInfoStream::num_total_fields(_fieldinfo_stream)) { _limit(-1) {
assert(klass == field_holder(), ""); assert(klass == field_holder(), "");
initialize(); 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 #endif // SHARE_OOPS_FIELDSTREAMS_INLINE_HPP

View File

@ -686,6 +686,11 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) {
} }
set_fieldinfo_stream(nullptr); 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()) { if (fields_status() != nullptr && !fields_status()->is_shared()) {
MetadataFactory::free_array<FieldStatus>(loader_data, fields_status()); 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 { bool InstanceKlass::find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const {
for (JavaFieldStream fs(this); !fs.done(); fs.next()) { JavaFieldStream fs(this);
Symbol* f_name = fs.name(); if (fs.lookup(name, sig)) {
Symbol* f_sig = fs.signature(); assert(fs.name() == name, "name must match");
if (f_name == name && f_sig == sig) { assert(fs.signature() == sig, "signature must match");
fd->reinitialize(const_cast<InstanceKlass*>(this), fs.to_FieldInfo()); fd->reinitialize(const_cast<InstanceKlass*>(this), fs.to_FieldInfo());
return true; return true;
}
} }
return false; return false;
} }
@ -2610,6 +2614,7 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) {
} }
it->push(&_fieldinfo_stream); 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() // _fields_status might be written into by Rewriter::scan_method() -> fd.set_has_initialized_final_update()
it->push(&_fields_status, MetaspaceClosure::_writable); it->push(&_fields_status, MetaspaceClosure::_writable);
@ -2710,6 +2715,8 @@ void InstanceKlass::remove_unshareable_info() {
DEBUG_ONLY(_shared_class_load_count = 0); DEBUG_ONLY(_shared_class_load_count = 0);
remove_unshareable_flags(); remove_unshareable_flags();
DEBUG_ONLY(FieldInfoStream::validate_search_table(_constants, _fieldinfo_stream, _fieldinfo_search_table));
} }
void InstanceKlass::remove_unshareable_flags() { 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()) { if (DiagnoseSyncOnValueBasedClasses && has_value_based_class_annotation() && !is_value_based()) {
set_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. // 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++; map++;
} }
st->cr(); 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 { void InstanceKlass::print_value_on(outputStream* st) const {

View File

@ -276,6 +276,7 @@ class InstanceKlass: public Klass {
// Fields information is stored in an UNSIGNED5 encoded stream (see fieldInfo.hpp) // Fields information is stored in an UNSIGNED5 encoded stream (see fieldInfo.hpp)
Array<u1>* _fieldinfo_stream; Array<u1>* _fieldinfo_stream;
Array<u1>* _fieldinfo_search_table;
Array<FieldStatus>* _fields_status; Array<FieldStatus>* _fields_status;
// embedded Java vtable follows here // embedded Java vtable follows here
@ -398,6 +399,9 @@ class InstanceKlass: public Klass {
Array<u1>* fieldinfo_stream() const { return _fieldinfo_stream; } Array<u1>* fieldinfo_stream() const { return _fieldinfo_stream; }
void set_fieldinfo_stream(Array<u1>* fis) { _fieldinfo_stream = fis; } 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; } Array<FieldStatus>* fields_status() const {return _fields_status; }
void set_fields_status(Array<FieldStatus>* array) { _fields_status = array; } void set_fields_status(Array<FieldStatus>* array) { _fields_status = array; }

View File

@ -678,10 +678,12 @@
"Print progress during Iterative Global Value Numbering") \ "Print progress during Iterative Global Value Numbering") \
\ \
develop(uint, VerifyIterativeGVN, 0, \ develop(uint, VerifyIterativeGVN, 0, \
"Verify Iterative Global Value Numbering" \ "Verify Iterative Global Value Numbering =DCBA, with:" \
"=XY, with Y: verify Def-Use modifications during IGVN" \ " D: verify Node::Identity did not miss opportunities" \
" X: verify that type(n) == n->Value() after IGVN" \ " C: verify Node::Ideal did not miss opportunities" \
"X and Y in 0=off; 1=on") \ " 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) \ constraint(VerifyIterativeGVNConstraintFunc, AtParse) \
\ \
develop(bool, TraceCISCSpill, false, \ develop(bool, TraceCISCSpill, false, \

View File

@ -6580,6 +6580,10 @@ bool LibraryCallKit::inline_vectorizedMismatch() {
memory_phi = _gvn.transform(memory_phi); memory_phi = _gvn.transform(memory_phi);
result_phi = _gvn.transform(result_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_control(exit_block);
set_all_memory(memory_phi); set_all_memory(memory_phi);
set_result(result_phi); set_result(result_phi);

View File

@ -1072,7 +1072,11 @@ void PhaseIterGVN::optimize() {
#ifdef ASSERT #ifdef ASSERT
void PhaseIterGVN::verify_optimize() { 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; ResourceMark rm;
Unique_Node_List worklist; Unique_Node_List worklist;
bool failure = false; bool failure = false;
@ -1080,7 +1084,10 @@ void PhaseIterGVN::verify_optimize() {
worklist.push(C->root()); worklist.push(C->root());
for (uint j = 0; j < worklist.size(); ++j) { for (uint j = 0; j < worklist.size(); ++j) {
Node* n = worklist.at(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 // traverse all inputs and outputs
for (uint i = 0; i < n->req(); i++) { for (uint i = 0; i < n->req(); i++) {
if (n->in(i) != nullptr) { 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). // in the verification code above if that is not possible for some reason (like Load nodes).
assert(!failure, "Missed optimization opportunity in PhaseIterGVN"); 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. // 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. // (1) Integer "widen" changes, but the range is the same.
// (2) LoadNode performs deep traversals. Load is not notified for changes far away. // (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. // (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 // 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. // the node never went through gvn.transform, which would be a bug.
const Type* told = type(n); 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. // after loop-opts, so that should take care of many of these cases.
return false; return false;
} }
tty->cr();
tty->print_cr("Missed Value optimization:"); stringStream ss; // Print as a block without tty lock.
n->dump_bfs(1, nullptr, ""); ss.cr();
tty->print_cr("Current type:"); ss.print_cr("Missed Value optimization:");
told->dump_on(tty); n->dump_bfs(1, nullptr, "", &ss);
tty->cr(); ss.print_cr("Current type:");
tty->print_cr("Optimized type:"); told->dump_on(&ss);
tnew->dump_on(tty); ss.cr();
tty->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; return true;
} }
#endif #endif
@ -1890,12 +2776,12 @@ void PhaseCCP::analyze() {
#ifdef ASSERT #ifdef ASSERT
// For every node n on verify list, check if type(n) == n->Value() // 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) { void PhaseCCP::verify_analyze(Unique_Node_List& worklist_verify) {
bool failure = false; bool failure = false;
while (worklist_verify.size()) { while (worklist_verify.size()) {
Node* n = worklist_verify.pop(); 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. // 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 // We should either make sure that these nodes are properly added back to the CCP worklist

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -494,7 +494,10 @@ public:
void optimize(); void optimize();
#ifdef ASSERT #ifdef ASSERT
void verify_optimize(); 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 #endif
#ifndef PRODUCT #ifndef PRODUCT
@ -593,6 +596,14 @@ public:
// '-XX:VerifyIterativeGVN=10' // '-XX:VerifyIterativeGVN=10'
return ((VerifyIterativeGVN % 100) / 10) == 1; 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: protected:
// Sub-quadratic implementation of '-XX:VerifyIterativeGVN=1' (Use-Def verification). // Sub-quadratic implementation of '-XX:VerifyIterativeGVN=1' (Use-Def verification).
julong _verify_counter; julong _verify_counter;

View File

@ -12862,16 +12862,17 @@ myInit() {
parameters uniquely identify the current location parameters uniquely identify the current location
(where the exception was detected) and allow (where the exception was detected) and allow
the mapping to source file and line number when that information is 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> exception object. The <code>catch_method</code>
and <code>catch_location</code> identify the location of the catch clause, 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, 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 reach this catch clause. If there are native methods on the call stack
between the throw location and the catch clause, the exception may between the throw location and the catch clause, the exception may
be reset by one of those native methods. be reset by one of those native methods.
Similarly, exceptions that are reported as uncaught (<code>catch_klass</code> Similarly, exceptions that are reported as uncaught (<code>catch_method</code>
et al. set to 0) may in fact be caught by native code. set to null) may in fact be caught by native code.
Agents can check for these occurrences by monitoring Agents can check for these occurrences by monitoring
<eventlink id="ExceptionCatch"></eventlink> events. <eventlink id="ExceptionCatch"></eventlink> events.
Note that finally clauses are implemented as catch and re-throw. Therefore they 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 available. For exceptions caught in a Java programming language method, the
<code>exception</code> object identifies the exception object. Exceptions <code>exception</code> object identifies the exception object. Exceptions
caught in native methods are not necessarily available by the time the 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. to null.
</description> </description>
<origin>jvmdi</origin> <origin>jvmdi</origin>

View File

@ -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); 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); scratch_class->set_fieldinfo_stream(new_fis);
MetadataFactory::free_array<u1>(scratch_class->class_loader_data(), old_stream); 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 // Update constant pool indices in the inner classes info to use

View File

@ -1075,6 +1075,22 @@ bool WhiteBox::validate_cgroup(bool cgroups_v2_enabled,
} }
#endif #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) { bool WhiteBox::compile_method(Method* method, int comp_level, int bci, JavaThread* THREAD) {
// Screen for unavailable/bad comp level or null method // Screen for unavailable/bad comp level or null method
AbstractCompiler* comp = CompileBroker::compiler(comp_level); AbstractCompiler* comp = CompileBroker::compiler(comp_level);
@ -1886,6 +1902,14 @@ WB_ENTRY(jboolean, WB_IsMonitorInflated(JNIEnv* env, jobject wb, jobject obj))
return (jboolean) obj_oop->mark().has_monitor(); return (jboolean) obj_oop->mark().has_monitor();
WB_END 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)) WB_ENTRY(jlong, WB_getInUseMonitorCount(JNIEnv* env, jobject wb))
return (jlong) WhiteBox::get_in_use_monitor_count(); return (jlong) WhiteBox::get_in_use_monitor_count();
WB_END WB_END
@ -2882,6 +2906,8 @@ static JNINativeMethod methods[] = {
(void*)&WB_AddModuleExportsToAll }, (void*)&WB_AddModuleExportsToAll },
{CC"deflateIdleMonitors", CC"()Z", (void*)&WB_DeflateIdleMonitors }, {CC"deflateIdleMonitors", CC"()Z", (void*)&WB_DeflateIdleMonitors },
{CC"isMonitorInflated0", CC"(Ljava/lang/Object;)Z", (void*)&WB_IsMonitorInflated }, {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"getInUseMonitorCount", CC"()J", (void*)&WB_getInUseMonitorCount },
{CC"getLockStackCapacity", CC"()I", (void*)&WB_getLockStackCapacity }, {CC"getLockStackCapacity", CC"()I", (void*)&WB_getLockStackCapacity },
{CC"supportsRecursiveLightweightLocking", CC"()Z", (void*)&WB_supportsRecursiveLightweightLocking }, {CC"supportsRecursiveLightweightLocking", CC"()Z", (void*)&WB_supportsRecursiveLightweightLocking },

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -72,6 +72,9 @@ class WhiteBox : public AllStatic {
#ifdef LINUX #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); 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 #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 #endif // SHARE_PRIMS_WHITEBOX_HPP

View File

@ -299,8 +299,9 @@ JVMFlag::Error TypeProfileLevelConstraintFunc(uint value, bool verbose) {
} }
JVMFlag::Error VerifyIterativeGVNConstraintFunc(uint value, bool verbose) { JVMFlag::Error VerifyIterativeGVNConstraintFunc(uint value, bool verbose) {
const int max_modes = 4;
uint original_value = value; uint original_value = value;
for (int i = 0; i < 2; i++) { for (int i = 0; i < max_modes; i++) {
if (value % 10 > 1) { if (value % 10 > 1) {
JVMFlag::printError(verbose, JVMFlag::printError(verbose,
"Invalid value (" UINT32_FORMAT ") " "Invalid value (" UINT32_FORMAT ") "
@ -312,7 +313,7 @@ JVMFlag::Error VerifyIterativeGVNConstraintFunc(uint value, bool verbose) {
if (value != 0) { if (value != 0) {
JVMFlag::printError(verbose, JVMFlag::printError(verbose,
"Invalid value (" UINT32_FORMAT ") " "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::VIOLATES_CONSTRAINT;
} }
return JVMFlag::SUCCESS; return JVMFlag::SUCCESS;

View File

@ -2005,6 +2005,10 @@ const int ObjectAlignmentInBytes = 8;
product(bool, UseThreadsLockThrottleLock, true, DIAGNOSTIC, \ product(bool, UseThreadsLockThrottleLock, true, DIAGNOSTIC, \
"Use an extra lock during Thread start and exit to alleviate" \ "Use an extra lock during Thread start and exit to alleviate" \
"contention on Threads_lock.") \ "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 // end of RUNTIME_FLAGS

View File

@ -465,9 +465,7 @@ HandshakeState::HandshakeState(JavaThread* target) :
_queue(), _queue(),
_lock(Monitor::nosafepoint, "HandshakeState_lock"), _lock(Monitor::nosafepoint, "HandshakeState_lock"),
_active_handshaker(), _active_handshaker(),
_async_exceptions_blocked(false), _async_exceptions_blocked(false) {
_suspended(false),
_async_suspend_handshake(false) {
} }
HandshakeState::~HandshakeState() { HandshakeState::~HandshakeState() {
@ -699,128 +697,8 @@ HandshakeState::ProcessResult HandshakeState::try_process(HandshakeOperation* ma
return op == match_op ? HandshakeState::_succeeded : HandshakeState::_processed; 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() { void HandshakeState::handle_unsafe_access_error() {
if (is_suspended()) { if (_handshakee->is_suspended()) {
// A suspend handshake was added to the queue after the // A suspend handshake was added to the queue after the
// unsafe access error. Since the suspender has already // unsafe access error. Since the suspender has already
// considered this JT as suspended and assumes it won't go // considered this JT as suspended and assumes it won't go

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -35,8 +35,6 @@
class HandshakeOperation; class HandshakeOperation;
class AsyncHandshakeOperation; class AsyncHandshakeOperation;
class JavaThread; class JavaThread;
class SuspendThreadHandshake;
class ThreadSelfSuspensionHandshake;
class UnsafeAccessErrorHandshake; class UnsafeAccessErrorHandshake;
class ThreadsListHandle; class ThreadsListHandle;
@ -88,8 +86,6 @@ class JvmtiRawMonitor;
// operation is only done by either VMThread/Handshaker on behalf of the // operation is only done by either VMThread/Handshaker on behalf of the
// JavaThread or by the target JavaThread itself. // JavaThread or by the target JavaThread itself.
class HandshakeState { class HandshakeState {
friend ThreadSelfSuspensionHandshake;
friend SuspendThreadHandshake;
friend UnsafeAccessErrorHandshake; friend UnsafeAccessErrorHandshake;
friend JavaThread; friend JavaThread;
// This a back reference to the JavaThread, // This a back reference to the JavaThread,
@ -98,7 +94,7 @@ class HandshakeState {
// The queue containing handshake operations to be performed on _handshakee. // The queue containing handshake operations to be performed on _handshakee.
FilterQueue<HandshakeOperation*> _queue; FilterQueue<HandshakeOperation*> _queue;
// Provides mutual exclusion to this state and queue. Also used for // Provides mutual exclusion to this state and queue. Also used for
// JavaThread suspend/resume operations. // JavaThread suspend/resume operations performed by SuspendResumeManager.
Monitor _lock; Monitor _lock;
// Set to the thread executing the handshake operation. // Set to the thread executing the handshake operation.
Thread* volatile _active_handshaker; Thread* volatile _active_handshaker;
@ -160,31 +156,5 @@ class HandshakeState {
bool async_exceptions_blocked() { return _async_exceptions_blocked; } bool async_exceptions_blocked() { return _async_exceptions_blocked; }
void set_async_exceptions_blocked(bool b) { _async_exceptions_blocked = b; } void set_async_exceptions_blocked(bool b) { _async_exceptions_blocked = b; }
void handle_unsafe_access_error(); 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 #endif // SHARE_RUNTIME_HANDSHAKE_HPP

View File

@ -498,6 +498,7 @@ JavaThread::JavaThread(MemTag mem_tag) :
_pending_interrupted_exception(false), _pending_interrupted_exception(false),
_handshake(this), _handshake(this),
_suspend_resume_manager(this, &_handshake._lock),
_popframe_preserved_args(nullptr), _popframe_preserved_args(nullptr),
_popframe_preserved_args_size(0), _popframe_preserved_args_size(0),
@ -1200,13 +1201,13 @@ bool JavaThread::java_suspend(bool register_vthread_SR) {
guarantee(Thread::is_JavaThread_protected(/* target */ this), guarantee(Thread::is_JavaThread_protected(/* target */ this),
"target JavaThread is not protected in calling context."); "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) { bool JavaThread::java_resume(bool register_vthread_SR) {
guarantee(Thread::is_JavaThread_protected_by_TLH(/* target */ this), guarantee(Thread::is_JavaThread_protected_by_TLH(/* target */ this),
"missing ThreadsListHandle in calling context."); "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 // Wait for another thread to perform object reallocation and relocking on behalf of

View File

@ -40,6 +40,7 @@
#include "runtime/safepointMechanism.hpp" #include "runtime/safepointMechanism.hpp"
#include "runtime/stackWatermarkSet.hpp" #include "runtime/stackWatermarkSet.hpp"
#include "runtime/stackOverflow.hpp" #include "runtime/stackOverflow.hpp"
#include "runtime/suspendResumeManager.hpp"
#include "runtime/thread.hpp" #include "runtime/thread.hpp"
#include "runtime/threadHeapSampler.hpp" #include "runtime/threadHeapSampler.hpp"
#include "runtime/threadIdentifier.hpp" #include "runtime/threadIdentifier.hpp"
@ -694,9 +695,13 @@ private:
// Suspend/resume support for JavaThread // Suspend/resume support for JavaThread
// higher-level suspension/resume logic called by the public APIs // 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_suspend(bool register_vthread_SR);
bool java_resume(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. // Check for async exception in addition to safepoint.
static void check_special_condition_for_native_trans(JavaThread *thread); static void check_special_condition_for_native_trans(JavaThread *thread);

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "logging/log.hpp"
#include "logging/logStream.hpp"
#include "memory/resourceArea.hpp"
#include "prims/jvmtiThreadState.hpp"
#include "runtime/atomic.hpp"
#include "runtime/globals.hpp"
#include "runtime/handshake.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/javaThread.inline.hpp"
#include "runtime/suspendResumeManager.hpp"
// 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->suspend_resume_manager()->do_owner_suspend();
current->set_thread_state(jts);
current->suspend_resume_manager()->set_async_suspend_handshake(false);
}
virtual bool is_suspend() { 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->suspend_resume_manager()->suspend_with_handshake(_register_vthread_SR);
}
bool did_suspend() { return _did_suspend; }
};
void SuspendResumeManager::set_suspended(bool is_suspend, bool register_vthread_SR) {
#if INCLUDE_JVMTI
if (register_vthread_SR) {
assert(_target->is_vthread_mounted(), "sanity check");
if (is_suspend) {
JvmtiVTSuspender::register_vthread_suspend(_target->vthread());
}
else {
JvmtiVTSuspender::register_vthread_resume(_target->vthread());
}
}
#endif
Atomic::store(&_suspended, is_suspend);
}
bool SuspendResumeManager::suspend(bool register_vthread_SR) {
JVMTI_ONLY(assert(!_target->is_in_VTMS_transition(), "no suspend allowed in VTMS transition");)
JavaThread* self = JavaThread::current();
if (_target == self) {
// If target is the current thread we can bypass the handshake machinery
// and just suspend directly
ThreadBlockInVM tbivm(self);
MutexLocker ml(_state_lock, Mutex::_no_safepoint_check_flag);
set_suspended(true, register_vthread_SR);
do_owner_suspend();
return true;
} else {
SuspendThreadHandshake st(register_vthread_SR);
Handshake::execute(&st, _target);
return st.did_suspend();
}
}
bool SuspendResumeManager::resume(bool register_vthread_SR) {
MutexLocker ml(_state_lock, Mutex::_no_safepoint_check_flag);
if (!is_suspended()) {
assert(!_target->is_suspended(), "cannot be suspended without a suspend request");
return false;
}
// Resume the thread.
set_suspended(false, register_vthread_SR);
_state_lock->notify();
return true;
}
void SuspendResumeManager::do_owner_suspend() {
assert(Thread::current() == _target, "should call from _target");
assert(_state_lock->owned_by_self(), "Lock must be held");
assert(!_target->has_last_Java_frame() || _target->frame_anchor()->walkable(), "should have walkable stack");
assert(_target->thread_state() == _thread_blocked, "Caller should have transitioned to _thread_blocked");
while (is_suspended()) {
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " suspended", p2i(_target));
_state_lock->wait_without_safepoint_check();
}
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " resumed", p2i(_target));
}
bool SuspendResumeManager::suspend_with_handshake(bool register_vthread_SR) {
assert(_target->threadObj() != nullptr, "cannot suspend with a null threadObj");
if (_target->is_exiting()) {
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " exiting", p2i(_target));
return false;
}
if (has_async_suspend_handshake()) {
if (is_suspended()) {
// Target is already suspended.
log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " already suspended", p2i(_target));
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(_target));
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(_target));
ThreadSelfSuspensionHandshake* ts = new ThreadSelfSuspensionHandshake();
Handshake::execute(ts, _target);
return true;
}
SuspendResumeManager::SuspendResumeManager(JavaThread* thread, Monitor* state_lock) : _target(thread), _state_lock(state_lock), _suspended(false), _async_suspend_handshake(false) {}

View File

@ -0,0 +1,70 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#ifndef SHARE_RUNTIME_SUSPENDRESUMEMANAGER_HPP
#define SHARE_RUNTIME_SUSPENDRESUMEMANAGER_HPP
class SuspendThreadHandshake;
class ThreadSelfSuspensionHandshake;
class SuspendResumeManager {
friend SuspendThreadHandshake;
friend ThreadSelfSuspensionHandshake;
friend JavaThread;
JavaThread* _target;
Monitor* _state_lock;
SuspendResumeManager(JavaThread* thread, Monitor* state_lock);
// This flag is true when the thread owning this
// SuspendResumeManager (the _target) 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;
bool suspend(bool register_vthread_SR);
bool resume(bool register_vthread_SR);
// Called from the async handshake (the trap)
// to stop a thread from continuing execution when suspended.
void do_owner_suspend();
// Called from the suspend handshake.
bool suspend_with_handshake(bool register_vthread_SR);
void set_suspended(bool to, bool register_vthread_SR);
bool is_suspended() {
return Atomic::load(&_suspended);
}
bool has_async_suspend_handshake() { return _async_suspend_handshake; }
void set_async_suspend_handshake(bool to) { _async_suspend_handshake = to; }
};
#endif // SHARE_RUNTIME_SUSPENDRESUMEMANAGER_HPP

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include <cstring>
#include "utilities/align.hpp"
#include "utilities/count_leading_zeros.hpp"
#include "utilities/packedTable.hpp"
// The thresholds are inclusive, and in practice the limits are rounded
// to the nearest power-of-two - 1.
// Based on the max_key and max_value we figure out the number of bits required to store
// key and value; imagine that only as bits (not aligned to byte boundary... yet).
// Then we concatenate the bits for key and value, and 'add' 1-7 padding zeroes
// (high-order bits) to align on bytes.
// In the end we have each element in the table consuming 1-8 bytes (case with 0 bits for key
// is ruled out).
PackedTableBase::PackedTableBase(uint32_t max_key, uint32_t max_value) {
unsigned int key_bits = max_key == 0 ? 0 : 32 - count_leading_zeros(max_key);
unsigned int value_bits = max_value == 0 ? 0 : 32 - count_leading_zeros(max_value);
_element_bytes = align_up(key_bits + value_bits, 8) / 8;
// shifting left by 32 is undefined behaviour, and in practice returns 1
_key_mask = key_bits >= 32 ? -1 : (1U << key_bits) - 1;
_value_shift = key_bits;
_value_mask = value_bits >= 32 ? -1 : (1U << value_bits) - 1;
guarantee(_element_bytes > 0, "wouldn't work");
assert(_element_bytes <= sizeof(uint64_t), "shouldn't happen");
}
// Note: we require the supplier to provide the elements in the final order as we can't easily sort
// within this method - qsort() accepts only pure function as comparator.
void PackedTableBuilder::fill(u1* table, size_t table_length, Supplier &supplier) const {
uint32_t key, value;
size_t offset = 0;
for (; offset <= table_length && supplier.next(&key, &value); offset += _element_bytes) {
assert((key & ~_key_mask) == 0, "key out of bounds");
assert((value & ~_value_mask) == 0, "value out of bounds: %x vs. %x (%x)", value, _value_mask, ~_value_mask);
uint64_t element = static_cast<uint64_t>(key) | (static_cast<uint64_t>(value) << _value_shift);
for (unsigned int i = 0; i < _element_bytes; ++i) {
table[offset + i] = static_cast<u1>(0xFF & element);
element >>= 8;
}
}
assert(offset == table_length, "Did not fill whole array");
assert(!supplier.next(&key, &value), "Supplier has more elements");
}
uint64_t PackedTableLookup::read_element(size_t offset) const {
uint64_t element = 0;
for (unsigned int i = 0; i < _element_bytes; ++i) {
element |= static_cast<uint64_t>(_table[offset + i]) << (8 * i);
}
assert((element & ~((uint64_t) _key_mask | ((uint64_t) _value_mask << _value_shift))) == 0, "read too much");
return element;
}
bool PackedTableLookup::search(Comparator& comparator, uint32_t* found_key, uint32_t* found_value) const {
unsigned int low = 0, high = checked_cast<unsigned int>(_table_length / _element_bytes);
assert(low < high, "must be");
while (low < high) {
unsigned int mid = low + (high - low) / 2;
assert(mid >= low && mid < high, "integer overflow?");
uint64_t element = read_element(_element_bytes * mid);
// Ignoring high 32 bits in element on purpose
uint32_t key = static_cast<uint32_t>(element) & _key_mask;
int cmp = comparator.compare_to(key);
if (cmp == 0) {
*found_key = key;
// Since __builtin_memcpy in read_element does not copy bits outside the element
// anything above _value_mask << _value_shift should be zero.
*found_value = checked_cast<uint32_t>(element >> _value_shift) & _value_mask;
return true;
} else if (cmp < 0) {
high = mid;
} else {
low = mid + 1;
}
}
return false;
}
#ifdef ASSERT
void PackedTableLookup::validate_order(Comparator &comparator) const {
auto validator = [&] (size_t offset, uint32_t key, uint32_t value) {
if (offset != 0) {
assert(comparator.compare_to(key) < 0, "not sorted");
}
comparator.reset(key);
};
iterate(validator);
}
#endif

View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "oops/array.hpp"
#include "utilities/globalDefinitions.hpp"
// Base for space-optimized structure supporting binary search. Each element
// consists of up to 32-bit key, and up to 32-bit value; these are packed
// into a bit-record with 1-byte alignment.
// The keys are ordered according to a custom comparator.
class PackedTableBase {
protected:
unsigned int _element_bytes;
uint32_t _key_mask;
unsigned int _value_shift;
uint32_t _value_mask;
public:
PackedTableBase(uint32_t max_key, uint32_t max_value);
// Returns number of bytes each element will occupy.
inline unsigned int element_bytes(void) const { return _element_bytes; }
};
// Helper class for constructing a packed table in the provided array.
class PackedTableBuilder: public PackedTableBase {
public:
class Supplier {
public:
// Returns elements with already ordered keys.
// This function should return true when the key and value was set,
// and false when there's no more elements.
// Packed table does NOT support duplicate keys.
virtual bool next(uint32_t* key, uint32_t* value) = 0;
};
// The thresholds are inclusive, and in practice the limits are rounded
// to the nearest power-of-two - 1.
// See PackedTableBase constructor for details.
PackedTableBuilder(uint32_t max_key, uint32_t max_value): PackedTableBase(max_key, max_value) {}
// Constructs a packed table in the provided array, filling it with elements
// from the supplier. Note that no comparator is requied by this method -
// the supplier must return elements with already ordered keys.
// The table_length (in bytes) should match number of elements provided
// by the supplier (when Supplier::next() returns false the whole array should
// be filled).
void fill(u1* table, size_t table_length, Supplier &supplier) const;
};
// Helper class for lookup in a packed table.
class PackedTableLookup: public PackedTableBase {
const u1* const _table;
const size_t _table_length;
uint64_t read_element(size_t offset) const;
public:
// The comparator implementation does not have to store a key (uint32_t);
// the idea is that key can point into a different structure that hosts data
// suitable for the actual comparison. That's why PackedTableLookup::search(...)
// returns the key it found as well as the value.
class Comparator {
public:
// Returns negative/0/positive if the target referred to by this comparator
// is lower/equal/higher than the target referred to by the key.
virtual int compare_to(uint32_t key) = 0;
// Changes the target this comparator refers to.
DEBUG_ONLY(virtual void reset(uint32_t key) = 0);
};
// The thresholds are inclusive, and in practice the limits are rounded
// to the nearest power-of-two - 1.
// See PackedTableBase constructor for details.
PackedTableLookup(uint32_t max_key, uint32_t max_value, const u1 *table, size_t table_length):
PackedTableBase(max_key, max_value), _table(table), _table_length(table_length) {}
PackedTableLookup(uint32_t max_key, uint32_t max_value, const Array<u1> *table):
PackedTableLookup(max_key, max_value, table->data(), static_cast<size_t>(table->length())) {}
// Performs a binary search in the packed table, looking for an element with key
// referring to a target equal according to the comparator.
// When the element is found, found_key and found_value are updated from the element
// and the function returns true.
// When the element is not found, found_key and found_value are not changed and
// the function returns false.
bool search(Comparator& comparator, uint32_t* found_key, uint32_t* found_value) const;
// Asserts that elements in the packed table follow the order defined by the comparator.
DEBUG_ONLY(void validate_order(Comparator &comparator) const);
template<typename Function>
void iterate(Function func) const {
for (size_t offset = 0; offset < _table_length; offset += _element_bytes) {
uint64_t element = read_element(offset);
uint32_t key = static_cast<uint32_t>(element) & _key_mask;
uint32_t value = checked_cast<uint32_t>(element >> _value_shift) & _value_mask;
func(offset, key, value);
}
}
};

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1997, 2023, 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -261,7 +261,7 @@ class UNSIGNED5 : AllStatic {
ARR _array; ARR _array;
OFF _limit; OFF _limit;
OFF _position; OFF _position;
int next_length() { int next_length() const {
return UNSIGNED5::check_length(_array, _position, _limit, GET()); return UNSIGNED5::check_length(_array, _position, _limit, GET());
} }
public: public:
@ -270,7 +270,7 @@ class UNSIGNED5 : AllStatic {
uint32_t next_uint() { uint32_t next_uint() {
return UNSIGNED5::read_uint(_array, _position, _limit, GET()); return UNSIGNED5::read_uint(_array, _position, _limit, GET());
} }
bool has_next() { bool has_next() const {
return next_length() != 0; return next_length() != 0;
} }
// tries to skip count logical entries; returns actual number skipped // tries to skip count logical entries; returns actual number skipped
@ -284,8 +284,9 @@ class UNSIGNED5 : AllStatic {
return actual; return actual;
} }
ARR array() { return _array; } ARR array() { return _array; }
OFF limit() { return _limit; } OFF limit() const { return _limit; }
OFF position() { return _position; } OFF position() const { return _position; }
void set_limit(OFF limit) { _limit = limit; }
void set_position(OFF position) { _position = position; } void set_position(OFF position) { _position = position; }
// For debugging, even in product builds (see debug.cpp). // For debugging, even in product builds (see debug.cpp).

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -234,4 +234,28 @@ class Bits { // package-private
// of an element by element copy. These numbers may change over time. // of an element by element copy. These numbers may change over time.
static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6; static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6;
static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6; static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6;
// Maximum number of bytes to set in one call to {@code Unsafe.setMemory}.
// This threshold allows safepoint polling during large memory operations.
static final long UNSAFE_SET_THRESHOLD = 1024 * 1024;
/**
* Sets a block of memory starting from a given address to a specified byte value.
*
* @param srcAddr
* the starting memory address
* @param count
* the number of bytes to set
* @param value
* the byte value to set
*/
static void setMemory(long srcAddr, long count, byte value) {
long offset = 0;
while (offset < count) {
long len = Math.min(UNSAFE_SET_THRESHOLD, count - offset);
UNSAFE.setMemory(srcAddr + offset, len, value);
offset += len;
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -114,7 +114,7 @@ class Direct$Type$Buffer$RW$$BO$
Bits.unreserveMemory(size, cap); Bits.unreserveMemory(size, cap);
throw x; throw x;
} }
UNSAFE.setMemory(base, size, (byte) 0); Bits.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) { if (pa && (base % ps != 0)) {
// Round up to page boundary // Round up to page boundary
address = base + ps - (base & (ps - 1)); address = base + ps - (base & (ps - 1));

View File

@ -150,7 +150,7 @@ public class LinkedBlockingDeque<E>
transient Node<E> last; transient Node<E> last;
/** Number of items in the deque */ /** Number of items in the deque */
private transient int count; private transient volatile int count;
/** @serial Maximum number of items in the deque */ /** @serial Maximum number of items in the deque */
private final int capacity; private final int capacity;
@ -206,10 +206,13 @@ public class LinkedBlockingDeque<E>
/** /**
* Links node as first element, or returns false if full. * Links node as first element, or returns false if full.
*
* @return true if the node was added; false otherwise
*/ */
private boolean linkFirst(Node<E> node) { private boolean linkFirst(Node<E> node) {
// assert lock.isHeldByCurrentThread(); // assert lock.isHeldByCurrentThread();
if (count >= capacity) int c;
if ((c = count) >= capacity)
return false; return false;
Node<E> f = first; Node<E> f = first;
node.next = f; node.next = f;
@ -218,17 +221,20 @@ public class LinkedBlockingDeque<E>
last = node; last = node;
else else
f.prev = node; f.prev = node;
++count; count = c + 1;
notEmpty.signal(); notEmpty.signal();
return true; return true;
} }
/** /**
* Links node as last element, or returns false if full. * Links node as last element, or returns false if full.
*
* @return true if the node was added; false otherwise
*/ */
private boolean linkLast(Node<E> node) { private boolean linkLast(Node<E> node) {
// assert lock.isHeldByCurrentThread(); // assert lock.isHeldByCurrentThread();
if (count >= capacity) int c;
if ((c = count) >= capacity)
return false; return false;
Node<E> l = last; Node<E> l = last;
node.prev = l; node.prev = l;
@ -237,7 +243,7 @@ public class LinkedBlockingDeque<E>
first = node; first = node;
else else
l.next = node; l.next = node;
++count; count = c + 1;
notEmpty.signal(); notEmpty.signal();
return true; return true;
} }
@ -334,6 +340,8 @@ public class LinkedBlockingDeque<E>
*/ */
public boolean offerFirst(E e) { public boolean offerFirst(E e) {
if (e == null) throw new NullPointerException(); if (e == null) throw new NullPointerException();
if (count >= capacity)
return false;
Node<E> node = new Node<E>(e); Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lock();
@ -349,6 +357,8 @@ public class LinkedBlockingDeque<E>
*/ */
public boolean offerLast(E e) { public boolean offerLast(E e) {
if (e == null) throw new NullPointerException(); if (e == null) throw new NullPointerException();
if (count >= capacity)
return false;
Node<E> node = new Node<E>(e); Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lock();
@ -367,7 +377,7 @@ public class LinkedBlockingDeque<E>
if (e == null) throw new NullPointerException(); if (e == null) throw new NullPointerException();
Node<E> node = new Node<E>(e); Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lockInterruptibly();
try { try {
while (!linkFirst(node)) while (!linkFirst(node))
notFull.await(); notFull.await();
@ -384,7 +394,7 @@ public class LinkedBlockingDeque<E>
if (e == null) throw new NullPointerException(); if (e == null) throw new NullPointerException();
Node<E> node = new Node<E>(e); Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lockInterruptibly();
try { try {
while (!linkLast(node)) while (!linkLast(node))
notFull.await(); notFull.await();
@ -458,6 +468,7 @@ public class LinkedBlockingDeque<E>
} }
public E pollFirst() { public E pollFirst() {
if (count == 0) return null;
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lock();
try { try {
@ -468,6 +479,7 @@ public class LinkedBlockingDeque<E>
} }
public E pollLast() { public E pollLast() {
if (count == 0) return null;
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lock();
try { try {
@ -479,7 +491,7 @@ public class LinkedBlockingDeque<E>
public E takeFirst() throws InterruptedException { public E takeFirst() throws InterruptedException {
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lockInterruptibly();
try { try {
E x; E x;
while ( (x = unlinkFirst()) == null) while ( (x = unlinkFirst()) == null)
@ -492,7 +504,7 @@ public class LinkedBlockingDeque<E>
public E takeLast() throws InterruptedException { public E takeLast() throws InterruptedException {
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lockInterruptibly();
try { try {
E x; E x;
while ( (x = unlinkLast()) == null) while ( (x = unlinkLast()) == null)
@ -558,6 +570,7 @@ public class LinkedBlockingDeque<E>
} }
public E peekFirst() { public E peekFirst() {
if (count == 0) return null;
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lock();
try { try {
@ -568,6 +581,7 @@ public class LinkedBlockingDeque<E>
} }
public E peekLast() { public E peekLast() {
if (count == 0) return null;
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lock();
try { try {
@ -718,13 +732,7 @@ public class LinkedBlockingDeque<E>
* insert or remove an element. * insert or remove an element.
*/ */
public int remainingCapacity() { public int remainingCapacity() {
final ReentrantLock lock = this.lock; return capacity - count;
lock.lock();
try {
return capacity - count;
} finally {
lock.unlock();
}
} }
/** /**
@ -806,13 +814,7 @@ public class LinkedBlockingDeque<E>
* @return the number of elements in this deque * @return the number of elements in this deque
*/ */
public int size() { public int size() {
final ReentrantLock lock = this.lock; return count;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
} }
/** /**
@ -858,7 +860,7 @@ public class LinkedBlockingDeque<E>
// Copy c into a private chain of Nodes // Copy c into a private chain of Nodes
Node<E> beg = null, end = null; Node<E> beg = null, end = null;
int n = 0; long n = 0;
for (E e : c) { for (E e : c) {
Objects.requireNonNull(e); Objects.requireNonNull(e);
n++; n++;
@ -878,14 +880,15 @@ public class LinkedBlockingDeque<E>
final ReentrantLock lock = this.lock; final ReentrantLock lock = this.lock;
lock.lock(); lock.lock();
try { try {
if (count + n <= capacity) { long cnt;
if ((cnt = count + n) <= capacity) {
beg.prev = last; beg.prev = last;
if (first == null) if (first == null)
first = beg; first = beg;
else else
last.next = beg; last.next = beg;
last = end; last = end;
count += n; count = (int)cnt;
notEmpty.signalAll(); notEmpty.signalAll();
return true; return true;
} }
@ -894,6 +897,7 @@ public class LinkedBlockingDeque<E>
} }
// Fall back to historic non-atomic implementation, failing // Fall back to historic non-atomic implementation, failing
// with IllegalStateException when the capacity is exceeded. // with IllegalStateException when the capacity is exceeded.
beg = end = null; // help GC
return super.addAll(c); return super.addAll(c);
} }
@ -994,8 +998,8 @@ public class LinkedBlockingDeque<E>
for (Node<E> f = first; f != null; ) { for (Node<E> f = first; f != null; ) {
f.item = null; f.item = null;
Node<E> n = f.next; Node<E> n = f.next;
f.prev = null; f.prev = f;
f.next = null; f.next = f;
f = n; f = n;
} }
first = last = null; first = last = null;

View File

@ -405,8 +405,7 @@ module java.base {
uses sun.text.spi.JavaTimeDateTimePatternProvider; uses sun.text.spi.JavaTimeDateTimePatternProvider;
uses sun.util.spi.CalendarProvider; uses sun.util.spi.CalendarProvider;
uses sun.util.locale.provider.LocaleDataMetaInfo; uses sun.util.locale.provider.LocaleDataMetaInfo;
uses sun.util.resources.LocaleData.CommonResourceBundleProvider; uses sun.util.resources.LocaleData.LocaleDataResourceBundleProvider;
uses sun.util.resources.LocaleData.SupplementaryResourceBundleProvider;
// Built-in service providers that are located via ServiceLoader // Built-in service providers that are located via ServiceLoader

View File

@ -94,6 +94,15 @@ public class Net {
return EXCLUSIVE_BIND; return EXCLUSIVE_BIND;
} }
private static final StableValue<Boolean> SHUTDOWN_WRITE_BEFORE_CLOSE = StableValue.of();
/**
* Tells whether a TCP connection should be shutdown for writing before closing.
*/
static boolean shouldShutdownWriteBeforeClose() {
return SHUTDOWN_WRITE_BEFORE_CLOSE.orElseSet(Net::shouldShutdownWriteBeforeClose0);
}
/** /**
* Tells whether both IPV6_XXX and IP_XXX socket options should be set on * Tells whether both IPV6_XXX and IP_XXX socket options should be set on
* IPv6 sockets. On some kernels, both IPV6_XXX and IP_XXX socket options * IPv6 sockets. On some kernels, both IPV6_XXX and IP_XXX socket options
@ -462,6 +471,8 @@ public class Net {
*/ */
private static native int isExclusiveBindAvailable(); private static native int isExclusiveBindAvailable();
private static native boolean shouldShutdownWriteBeforeClose0();
private static native boolean shouldSetBothIPv4AndIPv6Options0(); private static native boolean shouldSetBothIPv4AndIPv6Options0();
private static native boolean canIPv6SocketJoinIPv4Group0(); private static native boolean canIPv6SocketJoinIPv4Group0();

View File

@ -846,7 +846,7 @@ class SocketChannelImpl
/** /**
* Marks the beginning of a connect operation that might block. * Marks the beginning of a connect operation that might block.
* @param blocking true if configured blocking * @param blocking true if configured blocking
* @param isa the remote address * @param sa the remote socket address
* @throws ClosedChannelException if the channel is closed * @throws ClosedChannelException if the channel is closed
* @throws AlreadyConnectedException if already connected * @throws AlreadyConnectedException if already connected
* @throws ConnectionPendingException is a connection is pending * @throws ConnectionPendingException is a connection is pending
@ -1070,8 +1070,8 @@ class SocketChannelImpl
} }
/** /**
* Closes the socket if there are no I/O operations in progress and the * Closes the socket if there are no I/O operations in progress (or no I/O
* channel is not registered with a Selector. * operations tracked), and the channel is not registered with a Selector.
*/ */
private boolean tryClose() throws IOException { private boolean tryClose() throws IOException {
assert Thread.holdsLock(stateLock) && state == ST_CLOSING; assert Thread.holdsLock(stateLock) && state == ST_CLOSING;
@ -1096,11 +1096,21 @@ class SocketChannelImpl
} }
/** /**
* Closes this channel when configured in blocking mode. * Closes this channel when configured in blocking mode. If there are no I/O
* operations in progress (or tracked), then the channel's socket is closed. If
* there are I/O operations in progress then the behavior is platform specific.
* *
* If there is an I/O operation in progress then the socket is pre-closed * On Unix systems, the channel's socket is pre-closed. This unparks any virtual
* and the I/O threads signalled, in which case the final close is deferred * threads that are blocked in I/O operations on this channel. If there are
* until all I/O operations complete. * platform threads blocked on the channel's socket then the socket is dup'ed
* and the platform threads signalled. The final close is deferred until all I/O
* operations complete.
*
* On Windows, the channel's socket is pre-closed. This unparks any virtual
* threads that are blocked in I/O operations on this channel. If there are no
* virtual threads blocked in I/O operations on this channel then the channel's
* socket is closed. If there are virtual threads in I/O then the final close is
* deferred until all I/O operations on virtual threads complete.
* *
* Note that a channel configured blocking may be registered with a Selector * Note that a channel configured blocking may be registered with a Selector
* This arises when a key is canceled and the channel configured to blocking * This arises when a key is canceled and the channel configured to blocking
@ -1112,17 +1122,17 @@ class SocketChannelImpl
boolean connected = (state == ST_CONNECTED); boolean connected = (state == ST_CONNECTED);
state = ST_CLOSING; state = ST_CLOSING;
if (!tryClose()) { if (connected && Net.shouldShutdownWriteBeforeClose()) {
// shutdown output when linger interval not set to 0 // shutdown output when linger interval not set to 0
if (connected) { try {
try { var SO_LINGER = StandardSocketOptions.SO_LINGER;
var SO_LINGER = StandardSocketOptions.SO_LINGER; if ((int) Net.getSocketOption(fd, SO_LINGER) != 0) {
if ((int) Net.getSocketOption(fd, SO_LINGER) != 0) { Net.shutdown(fd, Net.SHUT_WR);
Net.shutdown(fd, Net.SHUT_WR); }
} } catch (IOException ignore) { }
} catch (IOException ignore) { } }
}
if (!tryClose()) {
// prepare file descriptor for closing // prepare file descriptor for closing
nd.preClose(fd, readerThread, writerThread); nd.preClose(fd, readerThread, writerThread);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,9 +23,6 @@
* questions. * questions.
*/ */
/*
*/
/* /*
* (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
* (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved * (C) Copyright IBM Corp. 1996 - 1999 - All Rights Reserved
@ -79,11 +76,11 @@
package sun.text.resources; package sun.text.resources;
import sun.util.resources.ParallelListResourceBundle; import sun.util.resources.OpenListResourceBundle;
public class FormatData extends ParallelListResourceBundle { public class FormatData extends OpenListResourceBundle {
/** /**
* Overrides ListResourceBundle * Overrides OpenListResourceBundle
*/ */
@Override @Override
protected final Object[][] getContents() { protected final Object[][] getContents() {
@ -119,6 +116,109 @@ public class FormatData extends ParallelListResourceBundle {
"Reiwa", "Reiwa",
}; };
// Moved from JavaTimeSupplementary
final String[] sharedQuarterNames = {
"Q1",
"Q2",
"Q3",
"Q4",
};
final String[] sharedQuarterNarrows = {
"1",
"2",
"3",
"4",
};
final String[] sharedDatePatterns = {
"GGGG y MMMM d, EEEE",
"GGGG y MMMM d",
"GGGG y MMM d",
"G y-MM-dd",
};
final String[] sharedDayAbbrs = {
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
};
final String[] sharedDayNarrows = {
"S",
"M",
"T",
"W",
"T",
"F",
"S",
};
final String[] sharedEras = {
"",
"AH",
};
final String[] sharedMonthNarrows = {
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"",
};
final String[] sharedTimePatterns = {
"HH:mm:ss zzzz",
"HH:mm:ss z",
"HH:mm:ss",
"HH:mm",
};
final String[] sharedAmPmMarkers = {
"AM",
"PM",
};
final String[] sharedJavaTimeDatePatterns = {
"G y MMMM d, EEEE",
"G y MMMM d",
"G y MMM d",
"GGGGG y-MM-dd",
};
final String[] sharedShortEras = {
"Before R.O.C.",
"R.O.C.",
};
final String[] sharedMonthAbbrs = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
"",
};
return new Object[][] { return new Object[][] {
{ "MonthNames", { "MonthNames",
new String[] { new String[] {
@ -138,39 +238,9 @@ public class FormatData extends ParallelListResourceBundle {
} }
}, },
{ "MonthAbbreviations", { "MonthAbbreviations",
new String[] { sharedMonthAbbrs },
"Jan", // abb january
"Feb", // abb february
"Mar", // abb march
"Apr", // abb april
"May", // abb may
"Jun", // abb june
"Jul", // abb july
"Aug", // abb august
"Sep", // abb september
"Oct", // abb october
"Nov", // abb november
"Dec", // abb december
"" // abb month 13 if applicable
}
},
{ "MonthNarrows", { "MonthNarrows",
new String[] { sharedMonthNarrows },
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"",
}
},
{ "DayNames", { "DayNames",
new String[] { new String[] {
"Sunday", // Sunday "Sunday", // Sunday
@ -183,33 +253,11 @@ public class FormatData extends ParallelListResourceBundle {
} }
}, },
{ "DayAbbreviations", { "DayAbbreviations",
new String[] { sharedDayAbbrs },
"Sun", // abb Sunday
"Mon", // abb Monday
"Tue", // abb Tuesday
"Wed", // abb Wednesday
"Thu", // abb Thursday
"Fri", // abb Friday
"Sat" // abb Saturday
}
},
{ "DayNarrows", { "DayNarrows",
new String[] { sharedDayNarrows },
"S",
"M",
"T",
"W",
"T",
"F",
"S",
}
},
{ "AmPmMarkers", { "AmPmMarkers",
new String[] { sharedAmPmMarkers },
"AM", // am marker
"PM" // pm marker
}
},
{ "narrow.AmPmMarkers", { "narrow.AmPmMarkers",
new String[] { new String[] {
"a", // am marker "a", // am marker
@ -227,22 +275,17 @@ public class FormatData extends ParallelListResourceBundle {
} }
}, },
{ "buddhist.Eras", { "buddhist.Eras",
buddhistEras buddhistEras },
},
{ "buddhist.short.Eras", { "buddhist.short.Eras",
buddhistEras buddhistEras },
},
{ "buddhist.narrow.Eras", { "buddhist.narrow.Eras",
buddhistEras buddhistEras },
},
{ "japanese.Eras", { "japanese.Eras",
japaneseEras }, japaneseEras },
{ "japanese.short.Eras", { "japanese.short.Eras",
japaneseEraAbbrs japaneseEraAbbrs },
},
{ "japanese.narrow.Eras", { "japanese.narrow.Eras",
japaneseEraAbbrs japaneseEraAbbrs },
},
{ "japanese.FirstYear", { "japanese.FirstYear",
new String[] { // Japanese imperial calendar year name new String[] { // Japanese imperial calendar year name
// empty in English // empty in English
@ -898,6 +941,164 @@ public class FormatData extends ParallelListResourceBundle {
} }
}, },
{ "DateTimePatternChars", "GyMdkHmsSEDFwWahKzZ" }, { "DateTimePatternChars", "GyMdkHmsSEDFwWahKzZ" },
// Moved from JavaTimeSupplementary
{ "QuarterAbbreviations",
sharedQuarterNames },
{ "QuarterNames",
sharedQuarterNames },
{ "QuarterNarrows",
sharedQuarterNarrows },
{ "field.dayperiod",
"Dayperiod" },
{ "field.era",
"Era" },
{ "field.hour",
"Hour" },
{ "field.minute",
"Minute" },
{ "field.month",
"Month" },
{ "field.second",
"Second" },
{ "field.week",
"Week" },
{ "field.weekday",
"Day of the Week" },
{ "field.year",
"Year" },
{ "field.zone",
"Zone" },
{ "islamic.DatePatterns",
sharedDatePatterns },
{ "islamic.DayAbbreviations",
sharedDayAbbrs },
{ "islamic.DayNames",
sharedDayAbbrs },
{ "islamic.DayNarrows",
sharedDayNarrows },
{ "islamic.Eras",
sharedEras },
{ "islamic.MonthAbbreviations",
new String[] {
"Muh.",
"Saf.",
"Rab. I",
"Rab. II",
"Jum. I",
"Jum. II",
"Raj.",
"Sha.",
"Ram.",
"Shaw.",
"Dhuʻl-Q.",
"Dhuʻl-H.",
"",
}
},
{ "islamic.MonthNames",
new String[] {
"Muharram",
"Safar",
"Rabiʻ I",
"Rabiʻ II",
"Jumada I",
"Jumada II",
"Rajab",
"Shaʻban",
"Ramadan",
"Shawwal",
"Dhuʻl-Qiʻdah",
"Dhuʻl-Hijjah",
"",
}
},
{ "islamic.MonthNarrows",
sharedMonthNarrows },
{ "islamic.QuarterNames",
sharedQuarterNames },
{ "islamic.QuarterNarrows",
sharedQuarterNarrows },
{ "islamic.TimePatterns",
sharedTimePatterns },
{ "islamic.abbreviated.AmPmMarkers",
sharedAmPmMarkers },
{ "islamic.long.Eras",
sharedEras },
{ "islamic.narrow.Eras",
sharedEras },
{ "islamic.short.Eras",
sharedEras },
{ "java.time.buddhist.DatePatterns",
sharedJavaTimeDatePatterns },
{ "java.time.buddhist.long.Eras",
new String[] {
"BC",
"BE",
}
},
{ "java.time.buddhist.short.Eras",
buddhistEras },
{ "java.time.islamic.DatePatterns",
sharedJavaTimeDatePatterns },
{ "java.time.japanese.DatePatterns",
new String[] {
"G y MMMM d (EEEE)",
"G y MMMM d",
"G y MMM d",
"GGGGGy.MM.dd",
}
},
{ "java.time.japanese.long.Eras",
japaneseEras },
{ "java.time.japanese.short.Eras",
japaneseEras },
{ "java.time.long.Eras",
new String[] {
"BCE",
"CE",
}
},
{ "java.time.roc.DatePatterns",
sharedJavaTimeDatePatterns },
{ "java.time.short.Eras",
julianEras },
{ "roc.AmPmMarkers",
sharedAmPmMarkers },
{ "roc.DatePatterns",
sharedDatePatterns },
{ "roc.DayNames",
sharedDayAbbrs },
{ "roc.DayNarrows",
sharedDayNarrows },
{ "roc.Eras",
sharedShortEras },
{ "roc.MonthAbbreviations",
sharedMonthAbbrs },
{ "roc.MonthNames",
sharedMonthAbbrs },
{ "roc.MonthNarrows",
sharedMonthNarrows },
{ "roc.QuarterNames",
sharedQuarterNames },
{ "roc.QuarterNarrows",
sharedQuarterNarrows },
{ "roc.TimePatterns",
sharedTimePatterns },
{ "roc.abbreviated.AmPmMarkers",
sharedAmPmMarkers },
{ "roc.long.Eras",
sharedShortEras },
{ "roc.narrow.AmPmMarkers",
sharedAmPmMarkers },
{ "roc.narrow.Eras",
sharedShortEras },
{ "roc.short.Eras",
sharedShortEras },
{ "timezone.gmtFormat",
"GMT{0}" },
{ "timezone.hourFormat",
"+HH:mm;-HH:mm" },
}; };
} }
} }

View File

@ -1,353 +0,0 @@
/*
* Copyright (c) 2013, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* COPYRIGHT AND PERMISSION NOTICE
*
* Copyright (C) 1991-2016 Unicode, Inc. All rights reserved.
* Distributed under the Terms of Use in
* http://www.unicode.org/copyright.html.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of the Unicode data files and any associated documentation
* (the "Data Files") or Unicode software and any associated documentation
* (the "Software") to deal in the Data Files or Software
* without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, and/or sell copies of
* the Data Files or Software, and to permit persons to whom the Data Files
* or Software are furnished to do so, provided that
* (a) this copyright and permission notice appear with all copies
* of the Data Files or Software,
* (b) this copyright and permission notice appear in associated
* documentation, and
* (c) there is clear notice in each modified Data File or in the Software
* as well as in the documentation associated with the Data File(s) or
* Software that the data or software has been modified.
*
* THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT OF THIRD PARTY RIGHTS.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
* NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
* DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THE DATA FILES OR SOFTWARE.
*
* Except as contained in this notice, the name of a copyright holder
* shall not be used in advertising or otherwise to promote the sale,
* use or other dealings in these Data Files or Software without prior
* written authorization of the copyright holder.
*/
// Note: this file has been generated by a tool.
package sun.text.resources;
import sun.util.resources.OpenListResourceBundle;
public class JavaTimeSupplementary extends OpenListResourceBundle {
@Override
protected final Object[][] getContents() {
final String[] sharedQuarterNames = {
"Q1",
"Q2",
"Q3",
"Q4",
};
final String[] sharedQuarterNarrows = {
"1",
"2",
"3",
"4",
};
final String[] sharedDatePatterns = {
"GGGG y MMMM d, EEEE",
"GGGG y MMMM d",
"GGGG y MMM d",
"G y-MM-dd",
};
final String[] sharedDayNames = {
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
};
final String[] sharedDayNarrows = {
"S",
"M",
"T",
"W",
"T",
"F",
"S",
};
final String[] sharedEras = {
"",
"AH",
};
final String[] sharedMonthNarrows = {
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"",
};
final String[] sharedTimePatterns = {
"HH:mm:ss zzzz",
"HH:mm:ss z",
"HH:mm:ss",
"HH:mm",
};
final String[] sharedAmPmMarkers = {
"AM",
"PM",
};
final String[] sharedJavaTimeDatePatterns = {
"G y MMMM d, EEEE",
"G y MMMM d",
"G y MMM d",
"GGGGG y-MM-dd",
};
final String[] sharedJavaTimeLongEras = {
"",
"Meiji",
"Taisho",
"Showa",
"Heisei",
"Reiwa",
};
final String[] sharedShortEras = {
"Before R.O.C.",
"R.O.C.",
};
final String[] sharedMonthNames = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
"",
};
return new Object[][] {
{ "QuarterAbbreviations",
sharedQuarterNames },
{ "QuarterNames",
sharedQuarterNames },
{ "QuarterNarrows",
sharedQuarterNarrows },
{ "field.dayperiod",
"Dayperiod" },
{ "field.era",
"Era" },
{ "field.hour",
"Hour" },
{ "field.minute",
"Minute" },
{ "field.month",
"Month" },
{ "field.second",
"Second" },
{ "field.week",
"Week" },
{ "field.weekday",
"Day of the Week" },
{ "field.year",
"Year" },
{ "field.zone",
"Zone" },
{ "islamic.DatePatterns",
sharedDatePatterns },
{ "islamic.DayAbbreviations",
sharedDayNames },
{ "islamic.DayNames",
sharedDayNames },
{ "islamic.DayNarrows",
sharedDayNarrows },
{ "islamic.Eras",
sharedEras },
{ "islamic.MonthAbbreviations",
new String[] {
"Muh.",
"Saf.",
"Rab. I",
"Rab. II",
"Jum. I",
"Jum. II",
"Raj.",
"Sha.",
"Ram.",
"Shaw.",
"Dhuʻl-Q.",
"Dhuʻl-H.",
"",
}
},
{ "islamic.MonthNames",
new String[] {
"Muharram",
"Safar",
"Rabiʻ I",
"Rabiʻ II",
"Jumada I",
"Jumada II",
"Rajab",
"Shaʻban",
"Ramadan",
"Shawwal",
"Dhuʻl-Qiʻdah",
"Dhuʻl-Hijjah",
"",
}
},
{ "islamic.MonthNarrows",
sharedMonthNarrows },
{ "islamic.QuarterNames",
sharedQuarterNames },
{ "islamic.QuarterNarrows",
sharedQuarterNarrows },
{ "islamic.TimePatterns",
sharedTimePatterns },
{ "islamic.abbreviated.AmPmMarkers",
sharedAmPmMarkers },
{ "islamic.long.Eras",
sharedEras },
{ "islamic.narrow.Eras",
sharedEras },
{ "islamic.short.Eras",
sharedEras },
{ "java.time.buddhist.DatePatterns",
sharedJavaTimeDatePatterns },
{ "java.time.buddhist.long.Eras",
new String[] {
"BC",
"BE",
}
},
{ "java.time.buddhist.short.Eras",
new String[] {
"BC",
"B.E.",
}
},
{ "java.time.islamic.DatePatterns",
sharedJavaTimeDatePatterns },
{ "java.time.japanese.DatePatterns",
new String[] {
"G y MMMM d (EEEE)",
"G y MMMM d",
"G y MMM d",
"GGGGGy.MM.dd",
}
},
{ "java.time.japanese.long.Eras",
sharedJavaTimeLongEras },
{ "java.time.japanese.short.Eras",
sharedJavaTimeLongEras },
{ "java.time.long.Eras",
new String[] {
"BCE",
"CE",
}
},
{ "java.time.roc.DatePatterns",
sharedJavaTimeDatePatterns },
{ "java.time.short.Eras",
new String[] {
"BC",
"AD",
}
},
{ "roc.AmPmMarkers",
sharedAmPmMarkers },
{ "roc.DatePatterns",
sharedDatePatterns },
{ "roc.DayNames",
sharedDayNames },
{ "roc.DayNarrows",
sharedDayNarrows },
{ "roc.Eras",
sharedShortEras },
{ "roc.MonthAbbreviations",
sharedMonthNames },
{ "roc.MonthNames",
sharedMonthNames },
{ "roc.MonthNarrows",
sharedMonthNarrows },
{ "roc.QuarterNames",
sharedQuarterNames },
{ "roc.QuarterNarrows",
sharedQuarterNarrows },
{ "roc.TimePatterns",
sharedTimePatterns },
{ "roc.abbreviated.AmPmMarkers",
sharedAmPmMarkers },
{ "roc.long.Eras",
sharedShortEras },
{ "roc.narrow.AmPmMarkers",
sharedAmPmMarkers },
{ "roc.narrow.Eras",
sharedShortEras },
{ "roc.short.Eras",
sharedShortEras },
{ "timezone.gmtFormat",
"GMT{0}" },
{ "timezone.hourFormat",
"+HH:mm;-HH:mm" },
};
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.text.resources;
import java.util.spi.ResourceBundleProvider;
/**
* An interface for the internal locale data provider for which {@code ResourceBundle}
* searches.
*/
public interface JavaTimeSupplementaryProvider extends ResourceBundleProvider {
}

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -65,7 +65,6 @@ import java.util.stream.Stream;
import jdk.internal.util.StaticProperty; import jdk.internal.util.StaticProperty;
import sun.util.resources.LocaleData; import sun.util.resources.LocaleData;
import sun.util.resources.OpenListResourceBundle; import sun.util.resources.OpenListResourceBundle;
import sun.util.resources.ParallelListResourceBundle;
import sun.util.resources.TimeZoneNamesBundle; import sun.util.resources.TimeZoneNamesBundle;
/** /**
@ -579,11 +578,7 @@ public class LocaleResources {
* resources required by JSR 310. * resources required by JSR 310.
*/ */
public ResourceBundle getJavaTimeFormatData() { public ResourceBundle getJavaTimeFormatData() {
ResourceBundle rb = localeData.getDateFormatData(locale); return localeData.getDateFormatData(locale);
if (rb instanceof ParallelListResourceBundle) {
localeData.setSupplementary((ParallelListResourceBundle) rb);
}
return rb;
} }
/** /**

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -44,7 +44,6 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle; import java.util.ResourceBundle;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -143,30 +142,6 @@ public class LocaleData {
return getBundle(type.getTextResourcesPackage() + ".FormatData", locale); return getBundle(type.getTextResourcesPackage() + ".FormatData", locale);
} }
public void setSupplementary(ParallelListResourceBundle formatData) {
if (!formatData.areParallelContentsComplete()) {
String suppName = type.getTextResourcesPackage() + ".JavaTimeSupplementary";
setSupplementary(suppName, formatData);
}
}
private boolean setSupplementary(String suppName, ParallelListResourceBundle formatData) {
ParallelListResourceBundle parent = (ParallelListResourceBundle) formatData.getParent();
boolean resetKeySet = false;
if (parent != null) {
resetKeySet = setSupplementary(suppName, parent);
}
OpenListResourceBundle supp = getSupplementary(suppName, formatData.getLocale());
formatData.setParallelContents(supp);
resetKeySet |= supp != null;
// If any parents or this bundle has parallel data, reset keyset to create
// a new keyset with the data.
if (resetKeySet) {
formatData.resetKeySet();
}
return resetKeySet;
}
/** /**
* Gets a number format data resource bundle, using privileges * Gets a number format data resource bundle, using privileges
* to allow accessing a sun.* package. * to allow accessing a sun.* package.
@ -179,18 +154,7 @@ public class LocaleData {
return Bundles.of(baseName, locale, LocaleDataStrategy.INSTANCE); return Bundles.of(baseName, locale, LocaleDataStrategy.INSTANCE);
} }
private static OpenListResourceBundle getSupplementary(final String baseName, final Locale locale) { public abstract static class LocaleDataResourceBundleProvider
OpenListResourceBundle rb = null;
try {
rb = (OpenListResourceBundle) Bundles.of(baseName, locale,
SupplementaryStrategy.INSTANCE);
} catch (MissingResourceException e) {
// return null if no supplementary is available
}
return rb;
}
private abstract static class LocaleDataResourceBundleProvider
implements ResourceBundleProvider { implements ResourceBundleProvider {
/** /**
* Changes baseName to its module dependent package name and * Changes baseName to its module dependent package name and
@ -212,20 +176,6 @@ public class LocaleData {
} }
} }
/**
* A ResourceBundleProvider implementation for loading locale data
* resource bundles except for the java.time supplementary data.
*/
public abstract static class CommonResourceBundleProvider extends LocaleDataResourceBundleProvider {
}
/**
* A ResourceBundleProvider implementation for loading supplementary
* resource bundles for java.time.
*/
public abstract static class SupplementaryResourceBundleProvider extends LocaleDataResourceBundleProvider {
}
// Bundles.Strategy implementations // Bundles.Strategy implementations
private static class LocaleDataStrategy implements Bundles.Strategy { private static class LocaleDataStrategy implements Bundles.Strategy {
@ -254,18 +204,20 @@ public class LocaleData {
if (candidates == null) { if (candidates == null) {
LocaleProviderAdapter.Type type = baseName.contains(DOTCLDR) ? CLDR : JRE; LocaleProviderAdapter.Type type = baseName.contains(DOTCLDR) ? CLDR : JRE;
LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type); LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type);
candidates = adapter instanceof ResourceBundleBasedAdapter ? candidates = adapter instanceof ResourceBundleBasedAdapter rbba ?
((ResourceBundleBasedAdapter)adapter).getCandidateLocales(baseName, locale) : rbba.getCandidateLocales(baseName, locale) :
defaultControl.getCandidateLocales(baseName, locale); defaultControl.getCandidateLocales(baseName, locale);
// Weed out Locales which are known to have no resource bundles // Weed out Locales which are known to have no resource bundles
int lastDot = baseName.lastIndexOf('.'); int lastDot = baseName.lastIndexOf('.');
String category = (lastDot >= 0) ? baseName.substring(lastDot + 1) : baseName; String category = (lastDot >= 0) ? baseName.substring(lastDot + 1) : baseName;
Set<String> langtags = ((JRELocaleProviderAdapter)adapter).getLanguageTagSet(category); if (adapter instanceof JRELocaleProviderAdapter jlpa) {
if (!langtags.isEmpty()) { var langtags = jlpa.getLanguageTagSet(category);
for (Iterator<Locale> itr = candidates.iterator(); itr.hasNext();) { if (!langtags.isEmpty()) {
if (!adapter.isSupportedProviderLocale(itr.next(), langtags)) { for (Iterator<Locale> itr = candidates.iterator(); itr.hasNext();) {
itr.remove(); if (!jlpa.isSupportedProviderLocale(itr.next(), langtags)) {
itr.remove();
}
} }
} }
} }
@ -302,36 +254,7 @@ public class LocaleData {
public Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName, public Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName,
Locale locale) { Locale locale) {
return inJavaBaseModule(baseName, locale) ? return inJavaBaseModule(baseName, locale) ?
null : CommonResourceBundleProvider.class; null : LocaleDataResourceBundleProvider.class;
}
}
private static class SupplementaryStrategy extends LocaleDataStrategy {
private static final SupplementaryStrategy INSTANCE
= new SupplementaryStrategy();
// TODO: avoid hard-coded Locales
private static final Set<Locale> JAVA_BASE_LOCALES
= Set.of(Locale.ROOT, Locale.ENGLISH, Locale.US);
private SupplementaryStrategy() {
}
@Override
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
// Specify only the given locale
return List.of(locale);
}
@Override
public Class<? extends ResourceBundleProvider> getResourceBundleProviderType(String baseName,
Locale locale) {
return inJavaBaseModule(baseName, locale) ?
null : SupplementaryResourceBundleProvider.class;
}
@Override
boolean inJavaBaseModule(String baseName, Locale locale) {
return JAVA_BASE_LOCALES.contains(locale);
} }
} }
} }

View File

@ -1,259 +0,0 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.util.resources;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicMarkableReference;
/**
* ParallelListResourceBundle is another variant of ListResourceBundle
* supporting "parallel" contents provided by another resource bundle
* (OpenListResourceBundle). Parallel contents, if any, are added into this
* bundle on demand.
*
* @author Masayoshi Okutsu
*/
public abstract class ParallelListResourceBundle extends ResourceBundle {
private volatile ConcurrentMap<String, Object> lookup;
private volatile Set<String> keyset;
private final AtomicMarkableReference<Object[][]> parallelContents
= new AtomicMarkableReference<>(null, false);
/**
* Sole constructor. (For invocation by subclass constructors, typically
* implicit.)
*/
protected ParallelListResourceBundle() {
}
/**
* Returns an array in which each item is a pair of objects in an
* Object array. The first element of each pair is the key, which
* must be a String, and the second element is the value
* associated with that key. See the class description for
* details.
*
* @return an array of an Object array representing a key-value pair.
*/
protected abstract Object[][] getContents();
/**
* Returns the parent of this resource bundle or null if there's no parent.
*
* @return the parent or null if no parent
*/
ResourceBundle getParent() {
return parent;
}
/**
* Sets the parallel contents to the data given by rb. If rb is null, this
* bundle will be marked as `complete'.
*
* @param rb an OpenResourceBundle for parallel contents, or null indicating
* there are no parallel contents for this bundle
*/
public void setParallelContents(OpenListResourceBundle rb) {
if (rb == null) {
parallelContents.compareAndSet(null, null, false, true);
} else {
parallelContents.compareAndSet(null, rb.getContents(), false, false);
}
}
/**
* Returns true if any parallel contents have been set or if this bundle is
* marked as complete.
*
* @return true if any parallel contents have been processed
*/
boolean areParallelContentsComplete() {
// Quick check for `complete'
if (parallelContents.isMarked()) {
return true;
}
boolean[] done = new boolean[1];
Object[][] data = parallelContents.get(done);
return data != null || done[0];
}
@Override
protected Object handleGetObject(String key) {
if (key == null) {
throw new NullPointerException();
}
loadLookupTablesIfNecessary();
return lookup.get(key);
}
@Override
public Enumeration<String> getKeys() {
return Collections.enumeration(keySet());
}
@Override
public boolean containsKey(String key) {
return keySet().contains(key);
}
@Override
protected Set<String> handleKeySet() {
loadLookupTablesIfNecessary();
return lookup.keySet();
}
@Override
@SuppressWarnings("UnusedAssignment")
public Set<String> keySet() {
Set<String> ks;
while ((ks = keyset) == null) {
ks = new KeySet(handleKeySet(), parent);
synchronized (this) {
if (keyset == null) {
keyset = ks;
}
}
}
return ks;
}
/**
* Discards any cached keyset value. This method is called from
* LocaleData for re-creating a KeySet.
*/
synchronized void resetKeySet() {
keyset = null;
}
/**
* Loads the lookup table if they haven't been loaded already.
*/
void loadLookupTablesIfNecessary() {
ConcurrentMap<String, Object> map = lookup;
if (map == null) {
map = new ConcurrentHashMap<>();
for (Object[] item : getContents()) {
map.put((String) item[0], item[1]);
}
}
// If there's any parallel contents data, merge the data into map.
Object[][] data = parallelContents.getReference();
if (data != null) {
for (Object[] item : data) {
map.putIfAbsent((String) item[0], item[1]);
}
parallelContents.set(null, true);
}
if (lookup == null) {
synchronized (this) {
if (lookup == null) {
lookup = map;
}
}
}
}
/**
* This class implements the Set interface for
* ParallelListResourceBundle methods.
*/
private static class KeySet extends AbstractSet<String> {
private final Set<String> set;
private final ResourceBundle parent;
private KeySet(Set<String> set, ResourceBundle parent) {
this.set = set;
this.parent = parent;
}
@Override
public boolean contains(Object o) {
if (set.contains(o)) {
return true;
}
return (parent != null) ? parent.containsKey((String) o) : false;
}
@Override
public Iterator<String> iterator() {
if (parent == null) {
return set.iterator();
}
return new Iterator<>() {
private Iterator<String> itr = set.iterator();
private boolean usingParent;
@Override
public boolean hasNext() {
if (itr.hasNext()) {
return true;
}
if (!usingParent) {
Set<String> nextset = new HashSet<>(parent.keySet());
nextset.removeAll(set);
itr = nextset.iterator();
usingParent = true;
}
return itr.hasNext();
}
@Override
public String next() {
if (hasNext()) {
return itr.next();
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public int size() {
if (parent == null) {
return set.size();
}
Set<String> allset = new HashSet<>(set);
allset.addAll(parent.keySet());
return allset.size();
}
}
}

View File

@ -205,6 +205,11 @@ Java_sun_nio_ch_Net_isExclusiveBindAvailable(JNIEnv *env, jclass clazz) {
return -1; return -1;
} }
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_shouldShutdownWriteBeforeClose0(JNIEnv *env, jclass clazz) {
return JNI_FALSE;
}
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_shouldSetBothIPv4AndIPv6Options0(JNIEnv* env, jclass cl) Java_sun_nio_ch_Net_shouldSetBothIPv4AndIPv6Options0(JNIEnv* env, jclass cl)
{ {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -117,6 +117,11 @@ Java_sun_nio_ch_Net_isExclusiveBindAvailable(JNIEnv *env, jclass clazz) {
return 1; return 1;
} }
JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_shouldShutdownWriteBeforeClose0(JNIEnv *env, jclass clazz) {
return JNI_TRUE;
}
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
Java_sun_nio_ch_Net_shouldSetBothIPv4AndIPv6Options0(JNIEnv* env, jclass cl) Java_sun_nio_ch_Net_shouldSetBothIPv4AndIPv6Options0(JNIEnv* env, jclass cl)
{ {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1997, 2006, 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -91,23 +91,4 @@ public class BasicPasswordFieldUI extends BasicTextFieldUI {
public View create(Element elem) { public View create(Element elem) {
return new PasswordView(elem); return new PasswordView(elem);
} }
/**
* Create the action map for Password Field. This map provides
* same actions for double mouse click and
* and for triple mouse click (see bug 4231444).
*/
ActionMap createActionMap() {
ActionMap map = super.createActionMap();
if (map.get(DefaultEditorKit.selectWordAction) != null) {
Action a = map.get(DefaultEditorKit.selectLineAction);
if (a != null) {
map.remove(DefaultEditorKit.selectWordAction);
map.put(DefaultEditorKit.selectWordAction, a);
}
}
return map;
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1997, 2023, 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -647,6 +647,22 @@ public abstract class BasicTextUI extends TextUI implements ViewFactory {
TransferHandler.getCopyAction()); TransferHandler.getCopyAction());
map.put(TransferHandler.getPasteAction().getValue(Action.NAME), map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
TransferHandler.getPasteAction()); TransferHandler.getPasteAction());
if (getComponent() instanceof JPasswordField) {
// Edit the action map for Password Field. This map provides
// same actions for double mouse click and
// and for triple mouse click (see bugs 4231444, 8354646).
if (map.get(DefaultEditorKit.selectWordAction) != null) {
map.remove(DefaultEditorKit.selectWordAction);
Action a = map.get(DefaultEditorKit.selectLineAction);
if (a != null) {
map.put(DefaultEditorKit.selectWordAction, a);
}
}
}
return map; return map;
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -108,19 +108,4 @@ public class SynthPasswordFieldUI extends SynthTextFieldUI {
int y, int w, int h) { int y, int w, int h) {
context.getPainter().paintPasswordFieldBorder(context, g, x, y, w, h); context.getPainter().paintPasswordFieldBorder(context, g, x, y, w, h);
} }
/**
* {@inheritDoc}
*/
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
ActionMap map = SwingUtilities.getUIActionMap(getComponent());
if (map != null && map.get(DefaultEditorKit.selectWordAction) != null) {
Action a = map.get(DefaultEditorKit.selectLineAction);
if (a != null) {
map.put(DefaultEditorKit.selectWordAction, a);
}
}
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -33,6 +33,6 @@ module jdk.localedata {
provides sun.util.locale.provider.LocaleDataMetaInfo with provides sun.util.locale.provider.LocaleDataMetaInfo with
sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo, sun.util.resources.cldr.provider.CLDRLocaleDataMetaInfo,
sun.util.resources.provider.NonBaseLocaleDataMetaInfo; sun.util.resources.provider.NonBaseLocaleDataMetaInfo;
provides sun.util.resources.LocaleData.CommonResourceBundleProvider with provides sun.util.resources.LocaleData.LocaleDataResourceBundleProvider with
sun.util.resources.provider.LocaleDataProvider; sun.util.resources.provider.LocaleDataProvider;
} }

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,9 +25,9 @@
package sun.text.resources.ext; package sun.text.resources.ext;
import sun.util.resources.ParallelListResourceBundle; import sun.util.resources.OpenListResourceBundle;
public class FormatData extends ParallelListResourceBundle { public class FormatData extends OpenListResourceBundle {
/** /**
* Exists to keep sun.text.resources.ext package alive * Exists to keep sun.text.resources.ext package alive
* with IncludeLocales jlink plugin * with IncludeLocales jlink plugin

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -76,11 +76,11 @@
package sun.text.resources.ext; package sun.text.resources.ext;
import sun.util.resources.ParallelListResourceBundle; import sun.util.resources.OpenListResourceBundle;
public class FormatData_ja extends ParallelListResourceBundle { public class FormatData_ja extends OpenListResourceBundle {
/** /**
* Overrides ParallelListResourceBundle * Overrides OpenListResourceBundle
*/ */
@Override @Override
protected final Object[][] getContents() { protected final Object[][] getContents() {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -32,7 +32,7 @@ import sun.util.resources.LocaleData;
/** /**
* Service Provider for loading locale data resource bundles in jdk.localedata * Service Provider for loading locale data resource bundles in jdk.localedata
*/ */
public class LocaleDataProvider extends LocaleData.CommonResourceBundleProvider { public class LocaleDataProvider extends LocaleData.LocaleDataResourceBundleProvider {
@Override @Override
public ResourceBundle getBundle(String baseName, Locale locale) { public ResourceBundle getBundle(String baseName, Locale locale) {
var bundleName = toBundleName(baseName, locale); var bundleName = toBundleName(baseName, locale);

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -140,6 +140,9 @@ public class DocCheck extends TestRunner {
var baseDir = DOCS_DIR.resolve(DIR); var baseDir = DOCS_DIR.resolve(DIR);
fileTester.processFiles(baseDir); fileTester.processFiles(baseDir);
files = fileTester.getFiles(); files = fileTester.getFiles();
if (html) {
new TidyChecker();
}
} }
public List<FileChecker> getCheckers() { public List<FileChecker> getCheckers() {

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,6 +26,6 @@
* @bug 8337109 * @bug 8337109
* @summary Check the html in the generated documentation * @summary Check the html in the generated documentation
* @library /test/langtools/tools/lib ../../doccheck /test/lib ../../../../tools/tester * @library /test/langtools/tools/lib ../../doccheck /test/lib ../../../../tools/tester
* @build DocTester toolbox.TestRunner * @build DocTester toolbox.TestRunner jtreg.SkippedException
* @run main/othervm -Ddoccheck.checks=html DocCheck * @run main/othervm -Ddoccheck.checks=html DocCheck
*/ */

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -38,6 +38,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import jtreg.SkippedException;
public class TidyChecker implements FileChecker, AutoCloseable { public class TidyChecker implements FileChecker, AutoCloseable {
private final Path TIDY; private final Path TIDY;
@ -164,8 +165,7 @@ public class TidyChecker implements FileChecker, AutoCloseable {
if (p.isPresent()) { if (p.isPresent()) {
tidyExePath = p.get(); tidyExePath = p.get();
} else { } else {
System.err.println("tidy not found on PATH"); throw new jtreg.SkippedException("tidy not found on PATH");
return Path.of("tidy"); //non-null placeholder return; exception would be better
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include "utilities/packedTable.hpp"
#include "unittest.hpp"
class Supplier: public PackedTableBuilder::Supplier {
uint32_t* _keys;
uint32_t* _values;
size_t _num_keys;
public:
Supplier(uint32_t* keys, uint32_t* values, size_t num_keys):
_keys(keys), _values(values), _num_keys(num_keys) {}
bool next(uint32_t* key, uint32_t* value) override {
if (_num_keys == 0) {
return false;
}
*key = *_keys;
++_keys;
if (_values != nullptr) {
*value = *_values;
++_values;
} else {
*value = 0;
}
--_num_keys;
return true;
}
};
class Comparator: public PackedTableLookup::Comparator {
uint32_t _current;
public:
int compare_to(uint32_t key) override {
return _current < key ? -1 : (_current > key ? 1 : 0);
}
void reset(uint32_t key) DEBUG_ONLY(override) {
_current = key;
}
};
static void test(uint32_t max_key, uint32_t max_value, unsigned int length) {
if (length > max_key + 1) {
// can't generate more keys, as keys must be unique
return;
}
PackedTableBuilder builder(max_key, max_value);
size_t table_length = length * builder.element_bytes();
u1* table = new u1[table_length];
uint32_t* keys = new uint32_t[length];
uint32_t* values = max_value != 0 ? new uint32_t[length] : nullptr;
for (unsigned int i = 0; i < length; ++i) {
keys[i] = i;
if (values != nullptr) {
values[i] = i % max_value;
}
}
Supplier sup(keys, values, length);
builder.fill(table, table_length, sup);
Comparator comparator;
PackedTableLookup lookup(max_key, max_value, table, table_length);
#ifdef ASSERT
lookup.validate_order(comparator);
#endif
for (unsigned int i = 0; i < length; ++i) {
uint32_t key, value;
comparator.reset(keys[i]);
EXPECT_TRUE(lookup.search(comparator, &key, &value));
EXPECT_EQ(key, keys[i]);
if (values != nullptr) {
EXPECT_EQ(value, values[i]);
} else {
EXPECT_EQ(value, 0U);
}
}
delete[] keys;
delete[] values;
}
static void test_with_bits(uint32_t max_key, uint32_t max_value) {
// Some small sizes
for (unsigned int i = 0; i <= 100; ++i) {
test(max_key, max_value, i);
}
test(max_key, max_value, 10000);
}
TEST(PackedTableLookup, lookup) {
for (int key_bits = 1; key_bits <= 32; ++key_bits) {
for (int value_bits = 0; value_bits <= 32; ++value_bits) {
test_with_bits(static_cast<uint32_t>((1ULL << key_bits) - 1),
static_cast<uint32_t>((1ULL << value_bits) - 1));
}
}
}
TEST(PackedTableBase, element_bytes) {
{
PackedTableBuilder builder(1, 0);
EXPECT_EQ(builder.element_bytes(), 1U);
}
{
PackedTableBuilder builder(15, 15);
EXPECT_EQ(builder.element_bytes(), 1U);
}
{
PackedTableBuilder builder(15, 16);
EXPECT_EQ(builder.element_bytes(), 2U);
}
{
PackedTableBuilder builder(31, 7);
EXPECT_EQ(builder.element_bytes(), 1U);
}
{
PackedTableBuilder builder(32, 7);
EXPECT_EQ(builder.element_bytes(), 2U);
}
{
PackedTableBuilder builder(-1, 0);
EXPECT_EQ(builder.element_bytes(), 4U);
}
{
PackedTableBuilder builder(-1, 1);
EXPECT_EQ(builder.element_bytes(), 5U);
}
{
PackedTableBuilder builder(-1, -1);
EXPECT_EQ(builder.element_bytes(), 8U);
}
}

View File

@ -91,6 +91,8 @@ requires.properties= \
vm.compiler1.enabled \ vm.compiler1.enabled \
vm.compiler2.enabled \ vm.compiler2.enabled \
vm.musl \ vm.musl \
vm.asan \
vm.ubsan \
vm.flagless \ vm.flagless \
container.support \ container.support \
systemd.support \ systemd.support \

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,9 +25,9 @@
* @test * @test
* @bug 8238756 * @bug 8238756
* @requires vm.debug == true & vm.flavor == "server" * @requires vm.debug == true & vm.flavor == "server"
* @summary Run with -Xcomp to test -XX:VerifyIterativeGVN=11 in debug builds. * @summary Run with -Xcomp to test -XX:VerifyIterativeGVN=1111 in debug builds.
* *
* @run main/othervm/timeout=300 -Xbatch -Xcomp -XX:VerifyIterativeGVN=11 compiler.c2.TestVerifyIterativeGVN * @run main/othervm/timeout=300 -Xcomp -XX:VerifyIterativeGVN=1111 compiler.c2.TestVerifyIterativeGVN
*/ */
package compiler.c2; package compiler.c2;

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8359121
* @summary Region node introduced by ArraysSupport.mismatch must be disconnected,
* and not just put aside as dead: when simplifying
* Proj -> Region -> If -> ...
* into
* Proj -> If -> ...
* -> Region
* the dead Region node must be removed from Proj's outputs.
* @modules java.base/jdk.internal.util
* @run main/othervm -Xcomp
* -XX:CompileCommand=compileonly,compiler.igvn.RemoveDeadRegionFromVectorizedMismatchIntrinsic::test
* compiler.igvn.RemoveDeadRegionFromVectorizedMismatchIntrinsic
* @run main compiler.igvn.RemoveDeadRegionFromVectorizedMismatchIntrinsic
*/
package compiler.igvn;
import jdk.internal.util.ArraysSupport;
public class RemoveDeadRegionFromVectorizedMismatchIntrinsic {
public static void main(String[] args) {
ArraysSupport.mismatch(new int[0], new int[0], 0); // loads ArraysSupport
test(new byte[0], new byte[0]);
}
public static int test(byte[] a, byte[] b) {
int i = ArraysSupport.vectorizedMismatch(a, 0, b, 0, 0, 0);
return i >= 0 ? i : 0;
}
}

View File

@ -122,10 +122,11 @@ public class BMITestRunner {
List<String> vmOpts = new LinkedList<String>(); List<String> vmOpts = new LinkedList<String>();
Collections.addAll(vmOpts, additionalVMOpts); Collections.addAll(vmOpts, additionalVMOpts);
// Hide timestamps from warnings (e.g. due to potential CDS // Hide timestamps from warnings (e.g. due to potential AOT
// saved/runtime state mismatch), to avoid false positives when // saved/runtime state mismatch), to avoid false positives when
// comparing output across runs. // comparing output across runs.
vmOpts.add("-Xlog:all=warning:stdout:level,tags"); vmOpts.add("-Xlog:all=warning:stdout:level,tags");
vmOpts.add("-Xlog:aot=off");
//setup mode-specific options //setup mode-specific options
switch (testVMMode) { switch (testVMMode) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -57,6 +57,19 @@ public class AndnTestI extends BmiIntrinsicBase.BmiTestCase {
(byte) 0x02, // 00010 implied 0F 38 leading opcode bytes (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes
(byte) 0x00, (byte) 0x00,
(byte) 0xF2}; (byte) 0xF2};
// from intel apx specifications EVEX.128.NP.0F38.W0 F2 /r
instrMaskAPX = new byte[]{
(byte) 0xFF,
(byte) 0x07,
(byte) 0x00,
(byte) 0x00,
(byte) 0xFF};
instrPatternAPX = new byte[]{
(byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction
(byte) 0x02, // 00010 implied 0F 38 leading opcode bytes
(byte) 0x00,
(byte) 0x00,
(byte) 0xF2};
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -59,6 +59,23 @@ public class BlsiTestI extends BmiIntrinsicBase.BmiTestCase {
(byte) 0x00, (byte) 0x00,
(byte) 0xF3, (byte) 0xF3,
(byte) 0b0001_1000}; // bits 543 == 011 (3) (byte) 0b0001_1000}; // bits 543 == 011 (3)
// from intel apx specifications EVEX.128.NP.0F38.W0 F3 /3(opcode extension)
instrMaskAPX = new byte[]{
(byte) 0xFF,
(byte) 0x07,
(byte) 0x00,
(byte) 0x00,
(byte) 0xFF,
(byte) 0x38};
instrPatternAPX = new byte[]{
(byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction
(byte) 0x02, // 00010 implied 0F 38 leading opcode bytes
(byte) 0x00,
(byte) 0x00,
(byte) 0xF3,
(byte) 0b0001_1000}; // bits 543 == 011 (3)
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -57,7 +57,24 @@ public class BlsmskTestI extends BmiIntrinsicBase.BmiTestCase {
(byte) 0x02, // 00010 implied 0F 38 leading opcode bytes (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes
(byte) 0x00, (byte) 0x00,
(byte) 0xF3, (byte) 0xF3,
(byte) 0b0001_0000}; // bits 543 == 011 (3) (byte) 0b0001_0000}; // bits 543 == 010 (2)
// from intel apx specifications EVEX.128.NP.0F38.W1 F3 /2(opcode extension part of ModRM.REG)
instrMaskAPX = new byte[]{
(byte) 0xFF,
(byte) 0x07,
(byte) 0x00,
(byte) 0x00,
(byte) 0xFF,
(byte) 0x38};
instrPatternAPX = new byte[]{
(byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction
(byte) 0x02, // 00010 implied 0F 38 leading opcode bytes
(byte) 0x00,
(byte) 0x00,
(byte) 0xF3,
(byte) 0b0001_0000}; // bits 543 == 010 (2)
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -58,7 +58,25 @@ public class BlsrTestI extends BmiIntrinsicBase.BmiTestCase {
(byte) 0x02, // 00010 implied 0F 38 leading opcode bytes (byte) 0x02, // 00010 implied 0F 38 leading opcode bytes
(byte) 0x00, (byte) 0x00,
(byte) 0xF3, (byte) 0xF3,
(byte) 0b0000_1000}; // bits 543 == 011 (3) (byte) 0b0000_1000}; // bits 543 == 001 (1)
// from intel apx specifications EVEX.128.NP.0F38.W1 F3 /1(opcode extension part of ModRM.REG)
instrMaskAPX = new byte[]{
(byte) 0xFF,
(byte) 0x07,
(byte) 0x00,
(byte) 0x00,
(byte) 0xFF,
(byte) 0x38};
instrPatternAPX = new byte[]{
(byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction
(byte) 0x02, // 00010 implied 0F 38 leading opcode bytes
(byte) 0x00,
(byte) 0x00,
(byte) 0xF3,
(byte) 0b0000_1000}; // bits 543 == 001 (1)
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -111,7 +111,8 @@ public class BmiIntrinsicBase extends CompilerWhiteBoxTest {
protected void checkEmittedCode(Executable executable) { protected void checkEmittedCode(Executable executable) {
final byte[] nativeCode = NMethod.get(executable, false).insts; final byte[] nativeCode = NMethod.get(executable, false).insts;
final byte[] matchInstrPattern = (((BmiTestCase) testCase).getTestCaseX64() && Platform.isX64()) ? ((BmiTestCase_x64) testCase).getInstrPattern_x64() : ((BmiTestCase) testCase).getInstrPattern(); final byte[] matchInstrPattern = (((BmiTestCase) testCase).getTestCaseX64() && Platform.isX64()) ? ((BmiTestCase_x64) testCase).getInstrPattern_x64() : ((BmiTestCase) testCase).getInstrPattern();
if (!((BmiTestCase) testCase).verifyPositive(nativeCode)) { boolean use_apx = CPUInfo.hasFeature("apx_f");
if (!((BmiTestCase) testCase).verifyPositive(nativeCode, use_apx)) {
throw new AssertionError(testCase.name() + " " + "CPU instructions expected not found in nativeCode: " + Utils.toHexString(nativeCode) + " ---- Expected instrPattern: " + throw new AssertionError(testCase.name() + " " + "CPU instructions expected not found in nativeCode: " + Utils.toHexString(nativeCode) + " ---- Expected instrPattern: " +
Utils.toHexString(matchInstrPattern)); Utils.toHexString(matchInstrPattern));
} else { } else {
@ -124,6 +125,8 @@ public class BmiIntrinsicBase extends CompilerWhiteBoxTest {
private final Method method; private final Method method;
protected byte[] instrMask; protected byte[] instrMask;
protected byte[] instrPattern; protected byte[] instrPattern;
protected byte[] instrMaskAPX;
protected byte[] instrPatternAPX;
protected boolean isLongOperation; protected boolean isLongOperation;
protected String cpuFlag = "bmi1"; protected String cpuFlag = "bmi1";
protected String vmFlag = "UseBMI1Instructions"; protected String vmFlag = "UseBMI1Instructions";
@ -160,6 +163,13 @@ public class BmiIntrinsicBase extends CompilerWhiteBoxTest {
return countCpuInstructions(nativeCode, instrMask, instrPattern); return countCpuInstructions(nativeCode, instrMask, instrPattern);
} }
protected int countCpuInstructionsAPX(byte[] nativeCode) {
if (instrMaskAPX == null || instrPatternAPX == null) {
return 0;
}
return countCpuInstructions(nativeCode, instrMaskAPX, instrPatternAPX);
}
public static int countCpuInstructions(byte[] nativeCode, byte[] instrMask, byte[] instrPattern) { public static int countCpuInstructions(byte[] nativeCode, byte[] instrMask, byte[] instrPattern) {
int count = 0; int count = 0;
int patternSize = Math.min(instrMask.length, instrPattern.length); int patternSize = Math.min(instrMask.length, instrPattern.length);
@ -181,8 +191,12 @@ public class BmiIntrinsicBase extends CompilerWhiteBoxTest {
return count; return count;
} }
public boolean verifyPositive(byte[] nativeCode) { public boolean verifyPositive(byte[] nativeCode, boolean use_apx) {
final int cnt = countCpuInstructions(nativeCode); int cnt = countCpuInstructions(nativeCode);
if (use_apx) {
System.out.println("CHECKING APX INST PATTERNS");
cnt += countCpuInstructionsAPX(nativeCode);
}
if (Platform.isX86()) { if (Platform.isX86()) {
return cnt >= (isLongOperation ? 2 : 1); return cnt >= (isLongOperation ? 2 : 1);
} else { } else {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021, 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -73,6 +73,21 @@ public class BzhiTestI2L extends BmiIntrinsicBase.BmiTestCase_x64 {
(byte) 0x62, // 00010 implied 0F 38 leading opcode bytes (byte) 0x62, // 00010 implied 0F 38 leading opcode bytes
(byte) 0xA8, (byte) 0xA8,
(byte) 0xF5}; (byte) 0xF5};
// from intel apx specifications EVEX.128.NP.0F38.W0 F5 /r
instrMaskAPX = new byte[]{
(byte) 0xFF,
(byte) 0x07,
(byte) 0x00,
(byte) 0x00,
(byte) 0xFF};
instrPatternAPX = new byte[]{
(byte) 0x62, // fixed prefix byte 0x62 for extended EVEX instruction
(byte) 0x02, // 00010 implied 0F 38 leading opcode bytes
(byte) 0x00,
(byte) 0x00,
(byte) 0xF5};
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -51,6 +51,10 @@ public class LZcntTestI extends BmiIntrinsicBase.BmiTestCase_x64 {
instrMask_x64 = new byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF}; instrMask_x64 = new byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF};
instrPattern_x64 = new byte[]{(byte) 0xF3, (byte) 0x00, (byte) 0x0F, (byte) 0xBD}; instrPattern_x64 = new byte[]{(byte) 0xF3, (byte) 0x00, (byte) 0x0F, (byte) 0xBD};
// REX2 variant
instrMaskAPX = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte)0x80, (byte) 0xFF};
instrPatternAPX = new byte[]{(byte) 0xF3, (byte) 0xD5, (byte) 0x80, (byte) 0xBD};
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -50,6 +50,10 @@ public class TZcntTestI extends BmiIntrinsicBase.BmiTestCase_x64 {
instrMask_x64 = new byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF}; instrMask_x64 = new byte[]{(byte) 0xFF, (byte) 0x00, (byte) 0xFF, (byte) 0xFF};
instrPattern_x64 = new byte[]{(byte) 0xF3, (byte) 0x00, (byte) 0x0F, (byte) 0xBC}; instrPattern_x64 = new byte[]{(byte) 0xF3, (byte) 0x00, (byte) 0x0F, (byte) 0xBC};
// REX2 variant
instrMaskAPX = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte)0x80, (byte) 0xFF};
instrPatternAPX = new byte[]{(byte) 0xF3, (byte) 0xD5, (byte) 0x80, (byte) 0xBC};
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {

View File

@ -0,0 +1,119 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package compiler.lib.template_framework.library;
import java.util.List;
import java.util.Set;
import compiler.lib.ir_framework.TestFramework;
import compiler.lib.compile_framework.CompileFramework;
import compiler.lib.template_framework.Template;
import compiler.lib.template_framework.TemplateToken;
import static compiler.lib.template_framework.Template.body;
import static compiler.lib.template_framework.Template.let;
/**
* This class provides a {@link #render} method that can be used to simplify generating
* source code when using the {@link TestFramework} (also known as IR Framework) to run
* a list of tests.
*
* <p>
* The idea is that the user only has to generate the code for the individual tests,
* and can then pass the corresponding list of {@link TemplateToken}s to this
* provided {@link #render} method which generates the surrounding class and the main
* method that invokes the {@link TestFramework}, so that all the generated tests
* are run.
*/
public final class TestFrameworkClass {
// Ensure there can be no instance, and we do not have to document the constructor.
private TestFrameworkClass() {}
/**
* This method renders a list of {@code testTemplateTokens} into the body of a class
* and generates a {@code main} method which launches the {@link TestFramework}
* to run the generated tests.
*
* <p>
* The generated {@code main} method is to be invoked with a {@code vmFlags} argument,
* which must be a {@link String[]}, specifying the VM flags for the Test VM, in which
* the tests will be run. Thus, one can generate the test class once, and invoke its
* {@code main} method multiple times, each time with a different set of VM flags.
*
* <p>
* The internal {@link Template} sets the {@link Hooks#CLASS_HOOK} for the scope of
* all test methods.
*
* @param packageName The package name of the test class.
* @param className The name of the test class.
* @param imports A set of imports.
* @param classpath The classpath from {@link CompileFramework#getEscapedClassPathOfCompiledClasses},
* so that the Test VM has access to the class files that are compiled from the
* generated source code.
* @param testTemplateTokens The list of tests to be generated into the test class.
* Every test must be annotated with {@code @Test}, so that
* the {@link TestFramework} can later find and run them.
* @return The generated source code of the test class as a {@link String}.
*/
public static String render(final String packageName,
final String className,
final Set<String> imports,
final String classpath,
final List<TemplateToken> testTemplateTokens) {
var template = Template.make(() -> body(
let("packageName", packageName),
let("className", className),
let("classpath", classpath),
"""
package #packageName;
// --- IMPORTS start ---
import compiler.lib.ir_framework.*;
""",
imports.stream().map(i -> "import " + i + ";\n").toList(),
"""
// --- IMPORTS end ---
public class #className {
// --- CLASS_HOOK insertions start ---
""",
Hooks.CLASS_HOOK.anchor(
"""
// --- CLASS_HOOK insertions end ---
public static void main(String[] vmFlags) {
TestFramework framework = new TestFramework(#className.class);
framework.addFlags("-classpath", "#classpath");
framework.addFlags(vmFlags);
framework.start();
}
// --- LIST OF TESTS start ---
""",
testTemplateTokens
),
"""
// --- LIST OF TESTS end ---
}
"""
));
return template.render();
}
}

View File

@ -31,6 +31,8 @@ package gc.arguments;
* @library /test/lib * @library /test/lib
* @library / * @library /
* @requires vm.bits == "64" * @requires vm.bits == "64"
* @comment ulimit clashes with the memory requirements of ASAN
* @requires !vm.asan
* @requires os.family == "linux" * @requires os.family == "linux"
* @requires vm.gc != "Z" * @requires vm.gc != "Z"
* @requires vm.opt.UseCompressedOops == null * @requires vm.opt.UseCompressedOops == null

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2025, Red Hat, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8358334
* @summary C2/Shenandoah: incorrect execution with Unsafe
* @requires vm.gc.Shenandoah
* @modules java.base/jdk.internal.misc:+open
*
* @run main/othervm -XX:-UseOnStackReplacement -XX:-BackgroundCompilation -XX:-TieredCompilation -XX:+UseShenandoahGC
* TestLostAntiDependencyAtExpansion
*
*
*/
import jdk.internal.misc.Unsafe;
public class TestLostAntiDependencyAtExpansion {
static final jdk.internal.misc.Unsafe UNSAFE = Unsafe.getUnsafe();
public static void main(String[] args) {
long addr = UNSAFE.allocateMemory(8);
for (int i = 0; i < 20_000; i++) {
UNSAFE.putLong(addr, 42L);
long res = test1(addr);
if (res != 42L) {
throw new RuntimeException("Incorrect result: " + res);
}
}
}
static class A {
long field;
}
static A a = new A();
private static long test1(long addr) {
long tmp = UNSAFE.getLong(addr);
UNSAFE.putLong(addr, 0L);
return tmp + a.field;
}
}

View File

@ -0,0 +1,140 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.*;
/*
* @test id=defaults
* @bug 8352075
* @library /test/lib
* @library /testlibrary/asm
* @run main/othervm LocalFieldLookupTest
*/
/*
* @test id=custom-threshold
* @bug 8352075
* @library /test/lib
* @library /testlibrary/asm
* @requires vm.debug == true
* @run main/othervm LocalFieldLookupTest
* @run main/othervm -XX:BinarySearchThreshold=0 LocalFieldLookupTest
* @run main/othervm -XX:BinarySearchThreshold=1 LocalFieldLookupTest
* @run main/othervm -XX:BinarySearchThreshold=15 LocalFieldLookupTest
* @run main/othervm -XX:BinarySearchThreshold=100000 LocalFieldLookupTest
*/
public class LocalFieldLookupTest {
private static final String TEST_CLASS_NAME = "Test";
private static final int MAX_FIELDS_IN_METHOD = 10000;
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// Test small classes, covering the tested thresholds
for (int i = 0; i <= 33; ++i) {
makeClass(i).newInstance();
}
// Test classes around 256 fields (index encoding 1/2 bytes) to check off-by-one errors
for (int i = 254; i <= 259; ++i) {
makeClass(255).newInstance();
}
// We would like to test #fields that create have the stream about 65536 bytes long;
// this value is not exposed, though, so these are rather experimentally found values,
// hence fragile. Moreover, since the stream length is incremented by about 8 bytes
// for each field we cannot test for off-by-one errors reliably.
for (int i = 8433; i <= 8437; ++i) {
makeClass(i).newInstance();
}
// The largest class we can create - this one has 65533 entries in the constant pool
makeClass(26205).newInstance();
}
public static Class<?> makeClass(int fields) throws ClassNotFoundException {
ClassWriter writer = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
writer.visit(49, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, TEST_CLASS_NAME,null, "java/lang/Object", null);
for (int i = 0; i < fields; i += 2) {
writer.visitField(ACC_PUBLIC, "f" + i, "I", null, null);
// Let's use duplicate names to confirm search takes signatures into account
if (i + 1 < fields) {
writer.visitField(ACC_PUBLIC, "f" + i, "J", null, null);
}
}
// We initialize fields in multiple methods to avoid running into bytecode limit per method
MethodVisitor fi = null;
for (int i = 0; i < fields; i+= 2) {
if (fi == null) {
fi = writer.visitMethod(ACC_PRIVATE, "init" + i, "()V", null, null);
fi.visitCode();
}
fi.visitVarInsn(Opcodes.ALOAD, 0);
fi.visitInsn(Opcodes.ICONST_2);
fi.visitFieldInsn(PUTFIELD, TEST_CLASS_NAME, "f" + i, "I");
if (i + 1 < fields) {
fi.visitVarInsn(Opcodes.ALOAD, 0);
fi.visitInsn(Opcodes.LCONST_1);
fi.visitFieldInsn(PUTFIELD, TEST_CLASS_NAME, "f" + i, "J");
}
if (i % MAX_FIELDS_IN_METHOD == MAX_FIELDS_IN_METHOD - 2) {
fi.visitInsn(Opcodes.RETURN);
fi.visitMaxs(0, 0);
fi.visitEnd();
fi = null;
}
}
if (fi != null) {
fi.visitInsn(Opcodes.RETURN);
fi.visitMaxs(0, 0);
fi.visitEnd();
}
{
MethodVisitor mv = writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
for (int i = 0; i < fields; i += MAX_FIELDS_IN_METHOD) {
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, TEST_CLASS_NAME, "init" + i, "()V", false);
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
writer.visitEnd();
byte[] bytecode = writer.toByteArray();
ClassLoader cl = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (!TEST_CLASS_NAME.equals(name)) {
throw new ClassNotFoundException();
}
return defineClass(TEST_CLASS_NAME, bytecode, 0, bytecode.length);
}
};
return cl.loadClass(TEST_CLASS_NAME);
}
}

View File

@ -38,6 +38,8 @@
* @requires os.family != "windows" & os.family != "aix" * @requires os.family != "windows" & os.family != "aix"
* @comment TODO: Decide libjsig support on static JDK with 8351367 * @comment TODO: Decide libjsig support on static JDK with 8351367
* @requires !jdk.static * @requires !jdk.static
* @comment loading of the jsig lib does currently not work well with ASAN lib
* @requires !vm.asan
* @library /vmTestbase * @library /vmTestbase
* /test/lib * /test/lib
* @run driver TestBreakSignalThreadDump load_libjsig * @run driver TestBreakSignalThreadDump load_libjsig

View File

@ -29,6 +29,8 @@
* @modules java.base/jdk.internal.misc * @modules java.base/jdk.internal.misc
* java.management * java.management
* @requires os.family == "linux" | os.family == "mac" * @requires os.family == "linux" | os.family == "mac"
* @comment loading of the jsig lib does currently not work well with ASAN lib
* @requires !vm.asan
* @comment TODO: Decide libjsig support on static JDK with 8351367 * @comment TODO: Decide libjsig support on static JDK with 8351367
* @requires !jdk.static * @requires !jdk.static
* @run driver XCheckJSig * @run driver XCheckJSig

View File

@ -72,6 +72,7 @@ import java.io.File;
import java.lang.StackWalker.StackFrame; import java.lang.StackWalker.StackFrame;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -312,12 +313,15 @@ class BulkLoaderTestApp {
} }
} }
static ArrayList<ClassLoader> savedLoaders = new ArrayList<>();
static Object initFromCustomLoader() throws Exception { static Object initFromCustomLoader() throws Exception {
String path = "cust.jar"; String path = "cust.jar";
URL url = new File(path).toURI().toURL(); URL url = new File(path).toURI().toURL();
URL[] urls = new URL[] {url}; URL[] urls = new URL[] {url};
URLClassLoader urlClassLoader = URLClassLoader urlClassLoader =
new URLClassLoader("MyLoader", urls, null); new URLClassLoader("MyLoader", urls, null);
savedLoaders.add(urlClassLoader);
Class c = Class.forName("SimpleCusty", true, urlClassLoader); Class c = Class.forName("SimpleCusty", true, urlClassLoader);
return c.newInstance(); return c.newInstance();
} }

View File

@ -36,6 +36,8 @@ import java.util.regex.Pattern;
* @summary Test of diagnostic command System.map * @summary Test of diagnostic command System.map
* @library /test/lib * @library /test/lib
* @requires (os.family == "linux" | os.family == "windows" | os.family == "mac") * @requires (os.family == "linux" | os.family == "windows" | os.family == "mac")
* @comment ASAN changes the memory map dump slightly, but the test has rather strict requirements
* @requires !vm.asan
* @requires os.arch != "riscv64" | !(vm.cpu.features ~= ".*qemu.*") * @requires os.arch != "riscv64" | !(vm.cpu.features ~= ".*qemu.*")
* @modules java.base/jdk.internal.misc * @modules java.base/jdk.internal.misc
* java.compiler * java.compiler

View File

@ -32,6 +32,8 @@ import jdk.test.lib.process.OutputAnalyzer;
* @summary Test of diagnostic command System.map * @summary Test of diagnostic command System.map
* @library /test/lib * @library /test/lib
* @requires (vm.gc != "Z") & (os.family == "linux" | os.family == "windows" | os.family == "mac") * @requires (vm.gc != "Z") & (os.family == "linux" | os.family == "windows" | os.family == "mac")
* @comment ASAN changes the memory map dump slightly, but the test has rather strict requirements
* @requires !vm.asan
* @modules java.base/jdk.internal.misc * @modules java.base/jdk.internal.misc
* java.compiler * java.compiler
* java.management * java.management
@ -47,6 +49,8 @@ import jdk.test.lib.process.OutputAnalyzer;
* @summary Test of diagnostic command System.map using ZGC * @summary Test of diagnostic command System.map using ZGC
* @library /test/lib * @library /test/lib
* @requires vm.gc.Z & (os.family == "linux" | os.family == "windows" | os.family == "mac") * @requires vm.gc.Z & (os.family == "linux" | os.family == "windows" | os.family == "mac")
* @comment ASAN changes the memory map dump slightly, but the test has rather strict requirements
* @requires !vm.asan
* @modules java.base/jdk.internal.misc * @modules java.base/jdk.internal.misc
* java.compiler * java.compiler
* java.management * java.management

View File

@ -0,0 +1,163 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @summary Test TestFrameworkClass.TEMPLATE which allows generating many tests and running them with the IR TestFramework.
* @modules java.base/jdk.internal.misc
* @library /test/lib /
* @compile ../../../compiler/lib/ir_framework/TestFramework.java
* @compile ../../../compiler/lib/generators/Generators.java
* @compile ../../../compiler/lib/verify/Verify.java
* @run driver template_framework.examples.TestWithTestFrameworkClass
*/
package template_framework.examples;
import java.util.List;
import java.util.Set;
import compiler.lib.compile_framework.CompileFramework;
import compiler.lib.generators.Generators;
import compiler.lib.template_framework.Template;
import compiler.lib.template_framework.TemplateToken;
import static compiler.lib.template_framework.Template.body;
import static compiler.lib.template_framework.Template.let;
import compiler.lib.template_framework.library.Hooks;
import compiler.lib.template_framework.library.TestFrameworkClass;
/**
* This is a basic IR verification test, in combination with Generators for random input generation
* and Verify for output verification.
* <p>
* The "@compile" command for JTREG is required so that the frameworks used in the Template code
* are compiled and available for the Test-VM.
* <p>
* Additionally, we must set the classpath for the Test VM, so that it has access to all compiled
* classes (see {@link CompileFramework#getEscapedClassPathOfCompiledClasses}).
*/
public class TestWithTestFrameworkClass {
public static void main(String[] args) {
// Create a new CompileFramework instance.
CompileFramework comp = new CompileFramework();
// Add a java source file.
comp.addJavaSourceCode("p.xyz.InnerTest", generate(comp));
// Compile the source file.
comp.compile();
// p.xyz.InnterTest.main(new String[] {});
comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {}});
// We can also pass VM flags for the Test VM.
// p.xyz.InnterTest.main(new String[] {"-Xbatch"});
comp.invoke("p.xyz.InnerTest", "main", new Object[] {new String[] {"-Xbatch"}});
}
// Generate a source Java file as String
public static String generate(CompileFramework comp) {
// A simple template that adds a comment.
var commentTemplate = Template.make(() -> body(
"""
// Comment inserted from test method to class hook.
"""
));
// We define a Test-Template:
// - static fields for inputs: INPUT_A and INPUT_B
// - Data generated with Generators and hashtag replacement #con1.
// - GOLD value precomputed with dedicated call to test.
// - This ensures that the GOLD value is computed in the interpreter
// most likely, since the test method is not yet compiled.
// This allows us later to compare to the results of the compiled
// code.
// The input data is cloned, so that the original INPUT_A is never
// modified and can serve as identical input in later calls to test.
// - In the Setup method, we clone the input data, since the input data
// could be modified inside the test method.
// - The test method makes use of hashtag replacements (#con2 and #op).
// - The Check method verifies the results of the test method with the
// GOLD value.
var testTemplate = Template.make("op", (String op) -> body(
let("size", Generators.G.safeRestrict(Generators.G.ints(), 10_000, 20_000).next()),
let("con1", Generators.G.ints().next()),
let("con2", Generators.G.safeRestrict(Generators.G.ints(), 1, Integer.MAX_VALUE).next()),
"""
// --- $test start ---
// $test with size=#size and op=#op
private static int[] $INPUT_A = new int[#size];
static {
Generators.G.fill(Generators.G.ints(), $INPUT_A);
}
private static int $INPUT_B = #con1;
private static Object $GOLD = $test($INPUT_A.clone(), $INPUT_B);
@Setup
public static Object[] $setup() {
// Must make sure to clone input arrays, if it is mutated in the test.
return new Object[] {$INPUT_A.clone(), $INPUT_B};
}
@Test
@Arguments(setup = "$setup")
public static Object $test(int[] a, int b) {
for (int i = 0; i < a.length; i++) {
int con = #con2;
a[i] = (a[i] * con) #op b;
}
return a;
}
@Check(test = "$test")
public static void $check(Object result) {
Verify.checkEQ(result, $GOLD);
}
// --- $test end ---
""",
// Good to know: we can insert to the class hook, which is set for the
// TestFrameworkClass scope:
Hooks.CLASS_HOOK.insert(commentTemplate.asToken())
));
// Create a test for each operator.
List<String> ops = List.of("+", "-", "*", "&", "|");
List<TemplateToken> testTemplateTokens = ops.stream().map(testTemplate::asToken).toList();
// Create the test class, which runs all testTemplateTokens.
return TestFrameworkClass.render(
// package and class name.
"p.xyz", "InnerTest",
// Set of imports.
Set.of("compiler.lib.generators.*",
"compiler.lib.verify.*"),
// classpath, so the Test VM has access to the compiled class files.
comp.getEscapedClassPathOfCompiledClasses(),
// The list of tests.
testTemplateTokens);
}
}

View File

@ -102,6 +102,8 @@ requires.properties= \
vm.cds.write.archived.java.heap \ vm.cds.write.archived.java.heap \
vm.continuations \ vm.continuations \
vm.musl \ vm.musl \
vm.asan \
vm.ubsan \
vm.debug \ vm.debug \
vm.hasSA \ vm.hasSA \
vm.hasJFR \ vm.hasJFR \

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2019, 2024, 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -40,6 +40,8 @@ import java.util.zip.ZipInputStream;
* @bug 8226346 * @bug 8226346
* @summary Check all output files for absolute path fragments * @summary Check all output files for absolute path fragments
* @requires !vm.debug * @requires !vm.debug
* @comment ASAN keeps the 'unwanted' paths in the binaries because of its build options
* @requires !vm.asan
* @run main/othervm -Xmx900m AbsPathsInImage * @run main/othervm -Xmx900m AbsPathsInImage
*/ */
public class AbsPathsInImage { public class AbsPathsInImage {

View File

@ -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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -30,7 +30,7 @@ import jdk.test.lib.Platform;
/* /*
* @test * @test
* @bug 6191897 * @bug 6191897 8354646
* @summary Verifies that ctrl+left/right does not move word-by-word in a TextField * @summary Verifies that ctrl+left/right does not move word-by-word in a TextField
* with echo character set * with echo character set
* @library /java/awt/regtesthelpers /test/lib * @library /java/awt/regtesthelpers /test/lib

View File

@ -24,21 +24,14 @@
/* @test /* @test
@bug 6481955 @bug 6481955
@summary Path length less than MAX_PATH (260) works on Windows @summary Path length less than MAX_PATH (260) works on Windows
@library /test/lib @requires (os.family == "windows")
*/ */
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import jtreg.SkippedException;
public class MaxPath { public class MaxPath {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
String osName = System.getProperty("os.name");
if (!osName.startsWith("Windows")) {
throw new SkippedException("This test is run only on Windows");
}
int MAX_PATH = 260; int MAX_PATH = 260;
String dir = new File(".").getAbsolutePath() + "\\"; String dir = new File(".").getAbsolutePath() + "\\";
String padding = "1234567890123456789012345678901234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890"; String padding = "1234567890123456789012345678901234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890";

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2001, 2007, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,29 +23,60 @@
/** /**
* @test * @test
* @bug 4490253 6535542 * @bug 4490253 6535542 8357959
* @key randomness
* @library /test/lib
* @build jdk.test.lib.RandomFactory
* @summary Verify that newly allocated direct buffers are initialized. * @summary Verify that newly allocated direct buffers are initialized.
* @run main/othervm AllocateDirectInit
*/ */
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Random;
import jdk.test.lib.RandomFactory;
public class AllocateDirectInit { public class AllocateDirectInit {
private static final int MAX_BIN_LIMIT = 16 * 1024 * 1024;
private static final int MAX_DEC_LIMIT = 10 * 1000 * 1000;
private static final int TRIES_PER_LIMIT = 1024;
private static final Random RND = RandomFactory.getRandom();
public static void main(String [] args){ public static void main(String [] args){
for (int i = 0; i < 1024; i++) { // Try power of two limits
ByteBuffer bb = ByteBuffer.allocateDirect(1024); for (int limit = 1; limit < MAX_BIN_LIMIT; limit *= 2) {
// printByteBuffer(bb); check(ByteBuffer.allocateDirect(limit - 1));
for (bb.position(0); bb.position() < bb.limit(); ) { check(ByteBuffer.allocateDirect(limit));
if ((bb.get() & 0xff) != 0) check(ByteBuffer.allocateDirect(limit + 1));
throw new RuntimeException("uninitialized buffer, position = " }
+ bb.position());
// Try power of ten limits
for (int limit = 1; limit < MAX_DEC_LIMIT; limit *= 10) {
check(ByteBuffer.allocateDirect(limit - 1));
check(ByteBuffer.allocateDirect(limit));
check(ByteBuffer.allocateDirect(limit + 1));
}
// Try random sizes within power of two limits
for (int limit = 1; limit < MAX_BIN_LIMIT; limit *= 2) {
for (int t = 0; t < TRIES_PER_LIMIT; t++) {
check(ByteBuffer.allocateDirect(RND.nextInt(limit)));
} }
} }
} }
private static void printByteBuffer(ByteBuffer bb) { private static void check(ByteBuffer bb) {
System.out.print("byte ["); while (bb.hasRemaining()) {
for (bb.position(0); bb.position() < bb.limit(); ) if (bb.get() != 0) {
System.out.print(" " + Integer.toHexString(bb.get() & 0xff)); int mismatchPos = bb.position();
System.out.println(" ]"); System.out.print("byte [");
for (bb.position(0); bb.position() < bb.limit(); ) {
System.out.print(" " + Integer.toHexString(bb.get() & 0xff));
}
System.out.println(" ]");
throw new RuntimeException("uninitialized buffer, position = " + mismatchPos);
}
}
} }
} }

View File

@ -0,0 +1,195 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8358764
* @summary Test closing a socket while a thread is blocked in read. The connection
* should be closed gracefuly so that the peer reads EOF.
* @run junit PeerReadsAfterAsyncClose
*/
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
class PeerReadsAfterAsyncClose {
static Stream<ThreadFactory> factories() {
return Stream.of(Thread.ofPlatform().factory(), Thread.ofVirtual().factory());
}
/**
* Close SocketChannel while a thread is blocked reading from the channel's socket.
*/
@ParameterizedTest
@MethodSource("factories")
void testCloseDuringSocketChannelRead(ThreadFactory factory) throws Exception {
var loopback = InetAddress.getLoopbackAddress();
try (var listener = new ServerSocket()) {
listener.bind(new InetSocketAddress(loopback, 0));
try (SocketChannel sc = SocketChannel.open(listener.getLocalSocketAddress());
Socket peer = listener.accept()) {
// start thread to read from channel
var cceThrown = new AtomicBoolean();
Thread thread = factory.newThread(() -> {
try {
sc.read(ByteBuffer.allocate(1));
fail();
} catch (ClosedChannelException e) {
cceThrown.set(true);
} catch (Throwable e) {
e.printStackTrace();
}
});
thread.start();
try {
// close SocketChannel when thread sampled in implRead
onReach(thread, "sun.nio.ch.SocketChannelImpl.implRead", () -> {
try {
sc.close();
} catch (IOException ignore) { }
});
// peer should read EOF
int n = peer.getInputStream().read();
assertEquals(-1, n);
} finally {
thread.join();
}
assertEquals(true, cceThrown.get(), "ClosedChannelException not thrown");
}
}
}
/**
* Close Socket while a thread is blocked reading from the socket.
*/
@ParameterizedTest
@MethodSource("factories")
void testCloseDuringSocketUntimedRead(ThreadFactory factory) throws Exception {
testCloseDuringSocketRead(factory, 0);
}
/**
* Close Socket while a thread is blocked reading from the socket with a timeout.
*/
@ParameterizedTest
@MethodSource("factories")
void testCloseDuringSockeTimedRead(ThreadFactory factory) throws Exception {
testCloseDuringSocketRead(factory, 60_000);
}
private void testCloseDuringSocketRead(ThreadFactory factory, int timeout) throws Exception {
var loopback = InetAddress.getLoopbackAddress();
try (var listener = new ServerSocket()) {
listener.bind(new InetSocketAddress(loopback, 0));
try (Socket s = new Socket(loopback, listener.getLocalPort());
Socket peer = listener.accept()) {
// start thread to read from socket
var seThrown = new AtomicBoolean();
Thread thread = factory.newThread(() -> {
try {
s.setSoTimeout(timeout);
s.getInputStream().read();
fail();
} catch (SocketException e) {
seThrown.set(true);
} catch (Throwable e) {
e.printStackTrace();
}
});
thread.start();
try {
// close Socket when thread sampled in implRead
onReach(thread, "sun.nio.ch.NioSocketImpl.implRead", () -> {
try {
s.close();
} catch (IOException ignore) { }
});
// peer should read EOF
int n = peer.getInputStream().read();
assertEquals(-1, n);
} finally {
thread.join();
}
assertEquals(true, seThrown.get(), "SocketException not thrown");
}
}
}
/**
* Runs the given action when the given target thread is sampled at the given
* location. The location takes the form "{@code c.m}" where
* {@code c} is the fully qualified class name and {@code m} is the method name.
*/
private void onReach(Thread target, String location, Runnable action) {
int index = location.lastIndexOf('.');
String className = location.substring(0, index);
String methodName = location.substring(index + 1);
Thread.ofPlatform().daemon(true).start(() -> {
try {
boolean found = false;
while (!found) {
found = contains(target.getStackTrace(), className, methodName);
if (!found) {
Thread.sleep(20);
}
}
action.run();
} catch (Exception e) {
e.printStackTrace();
}
});
}
/**
* Returns true if the given stack trace contains an element for the given class
* and method name.
*/
private boolean contains(StackTraceElement[] stack, String className, String methodName) {
return Arrays.stream(stack)
.anyMatch(e -> className.equals(e.getClassName())
&& methodName.equals(e.getMethodName()));
}
}

View File

@ -43,8 +43,10 @@ import java.util.Queue;
import java.util.concurrent.BlockingDeque; import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import junit.framework.Test; import junit.framework.Test;
@ -1886,4 +1888,112 @@ public class LinkedBlockingDequeTest extends JSR166TestCase {
} }
} }
public void testInterruptedExceptionThrownInBlockingMethods() throws InterruptedException {
// Ensure that putFirst(), putLast(), takeFirst(), and takeLast()
// immediately throw an InterruptedException if the thread is
// interrupted, to be consistent with other blocking queues such as
// ArrayBlockingQueue and LinkedBlockingQueue
try (var pool = Executors.newSingleThreadExecutor()) {
Future<Void> success = pool.submit(() -> {
var queue = new LinkedBlockingDeque<>();
Thread.currentThread().interrupt();
try {
queue.putFirst(42);
fail("Expected InterruptedException in putFirst()");
} catch (InterruptedException expected) {
// good that's what we want
assertFalse(Thread.currentThread().isInterrupted());
}
Thread.currentThread().interrupt();
try {
queue.putLast(42);
fail("Expected InterruptedException in putLast()");
} catch (InterruptedException expected) {
// good that's what we want
assertFalse(Thread.currentThread().isInterrupted());
}
queue.add(42);
Thread.currentThread().interrupt();
try {
queue.takeFirst();
fail("Expected InterruptedException in takeFirst()");
} catch (InterruptedException expected) {
// good that's what we want
assertFalse(Thread.currentThread().isInterrupted());
}
queue.add(42);
Thread.currentThread().interrupt();
try {
queue.takeLast();
fail("Expected InterruptedException in takeLast()");
} catch (InterruptedException expected) {
// good that's what we want
assertFalse(Thread.currentThread().isInterrupted());
}
return null;
});
try {
success.get();
} catch (ExecutionException e) {
try {
throw e.getCause();
} catch (Error | RuntimeException unchecked) {
throw unchecked;
} catch (Throwable cause) {
throw new AssertionError(cause);
}
}
}
}
public void testWeaklyConsistentIterationWithClear() {
final LinkedBlockingDeque<Item> q = new LinkedBlockingDeque<>();
q.add(one);
q.add(two);
q.add(three);
final Iterator<Item> it = q.iterator();
mustEqual(one, it.next());
q.clear();
q.add(four);
q.add(five);
q.add(six);
mustEqual(two, it.next());
mustEqual(four, it.next());
mustEqual(five, it.next());
mustEqual(six, it.next());
mustEqual(3, q.size());
}
public void testWeaklyConsistentIterationWithIteratorRemove() {
final LinkedBlockingDeque<Item> q = new LinkedBlockingDeque<>();
q.add(one);
q.add(two);
q.add(three);
q.add(four);
q.add(five);
final Iterator<Item> it1 = q.iterator();
final Iterator<Item> it2 = q.iterator();
final Iterator<Item> it3 = q.iterator();
mustEqual(one, it1.next());
mustEqual(two, it1.next());
it1.remove(); // removing "two"
mustEqual(one, it2.next());
it2.remove(); // removing "one"
mustEqual(three, it2.next());
mustEqual(four, it2.next());
it2.remove(); // removing "four"
mustEqual(one, it3.next());
mustEqual(three, it3.next());
mustEqual(five, it3.next());
assertFalse(it3.hasNext());
mustEqual(three, it1.next());
mustEqual(five, it1.next());
assertFalse(it1.hasNext());
mustEqual(five, it2.next());
assertFalse(it2.hasNext());
mustEqual(2, q.size());
}
} }

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @key headful
* @bug 4231444 8354646
* @summary Password fields' ActionMap needs to replace
* DefaultEditorKit.selectWordAction with
* DefaultEditorKit.selectLineAction.
*
* @run main PasswordSelectionWordTest
*/
import javax.swing.Action;
import javax.swing.JPasswordField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.basic.BasicTextUI;
import javax.swing.text.DefaultEditorKit;
import java.awt.event.ActionEvent;
public class PasswordSelectionWordTest {
public static void main(String[] args) throws Exception {
for (UIManager.LookAndFeelInfo laf :
UIManager.getInstalledLookAndFeels()) {
System.out.println("Testing LAF: " + laf.getClassName());
SwingUtilities.invokeAndWait(() -> {
if (setLookAndFeel(laf)) {
runTest();
}
});
}
}
private static boolean setLookAndFeel(UIManager.LookAndFeelInfo laf) {
try {
UIManager.setLookAndFeel(laf.getClassName());
return true;
} catch (UnsupportedLookAndFeelException e) {
System.err.println("Skipping unsupported look and feel:");
e.printStackTrace();
return false;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void runTest() {
String str = "one two three";
JPasswordField field = new JPasswordField(str);
if (!(field.getUI() instanceof BasicTextUI)) {
throw new RuntimeException("Unexpected condition: JPasswordField UI was " + field.getUI());
}
System.out.println("Testing " + field.getUI());
// do something (anything) to initialize the Views:
field.setSize(100, 100);
field.addNotify();
Action action = field.getActionMap().get(
DefaultEditorKit.selectWordAction);
action.actionPerformed(new ActionEvent(field, 0, ""));
int selectionStart = field.getSelectionStart();
int selectionEnd = field.getSelectionEnd();
System.out.println("selectionStart = " + selectionStart);
System.out.println("selectionEnd = " + selectionEnd);
if (selectionStart != 0 || selectionEnd != str.length()) {
throw new RuntimeException("selectionStart = " + selectionStart +
" and selectionEnd = " + selectionEnd);
}
}
}

Some files were not shown because too many files have changed in this diff Show More