diff --git a/doc/hotspot-style.html b/doc/hotspot-style.html index f0e07630a21a..54a7e42949a3 100644 --- a/doc/hotspot-style.html +++ b/doc/hotspot-style.html @@ -268,8 +268,8 @@

Conventions for Lock-free synchronization context, such as a held mutex or at a safepoint. In such cases the variable should be declared volatile and it should NOT be accessed as a normal C++ lvalue. Rather, access should be -performed via functions from Atomic, such as -Atomic::load, Atomic::store, etc.

+performed via functions from AtomicAccess, such as +AtomicAccess::load, AtomicAccess::store, etc.

This special formulation makes it more clear to maintainers that the variable is accessed concurrently in a lock-free manner.

Source Files

diff --git a/doc/hotspot-style.md b/doc/hotspot-style.md index a1ee04036325..8cc18f329637 100644 --- a/doc/hotspot-style.md +++ b/doc/hotspot-style.md @@ -140,8 +140,8 @@ change should be done with a "setter" accessor matched to the simple Sometimes variables are accessed concurrently without appropriate synchronization context, such as a held mutex or at a safepoint. In such cases the variable should be declared `volatile` and it should NOT be accessed as a normal C++ lvalue. Rather, -access should be performed via functions from `Atomic`, such as `Atomic::load`, -`Atomic::store`, etc. +access should be performed via functions from `AtomicAccess`, such as `AtomicAccess::load`, +`AtomicAccess::store`, etc. This special formulation makes it more clear to maintainers that the variable is accessed concurrently in a lock-free manner. diff --git a/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java b/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java index 1b2a3ebebaf0..f99635a9dee1 100644 --- a/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java +++ b/make/jdk/src/classes/build/tools/blockedcertsconverter/BlockedCertsConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,11 +28,9 @@ import java.io.IOException; import java.math.BigInteger; import java.security.MessageDigest; -import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -40,6 +38,7 @@ import java.util.Set; import java.util.TreeSet; +import sun.security.util.CurveDB; import sun.security.util.DerInputStream; import sun.security.util.DerOutputStream; import sun.security.util.DerValue; @@ -140,11 +139,8 @@ private static List altEncodings(X509Certificate c) List algIds = Arrays.asList(seq[1], altAlgId(seq[1])); List sigs; - PublicKey p = c.getPublicKey(); - if (p instanceof ECPublicKey) { - ECPublicKey ep = (ECPublicKey) p; - BigInteger mod = ep.getParams().getOrder(); - sigs = Arrays.asList(seq[2], altSig(mod, seq[2])); + if (c.getSigAlgName().contains("ECDSA")) { + sigs = Arrays.asList(seq[2], altSig(seq[2])); } else { sigs = Arrays.asList(seq[2]); } @@ -163,14 +159,27 @@ private static List altEncodings(X509Certificate c) return result; } - private static DerValue altSig(BigInteger mod, DerValue sig) + private static DerValue altSig(DerValue sig) throws IOException { byte[] sigBits = sig.getBitString(); + int sigLength = sigBits.length; DerInputStream in = - new DerInputStream(sigBits, 0, sigBits.length, false); + new DerInputStream(sigBits, 0, sigLength, false); DerValue[] values = in.getSequence(2); BigInteger r = values[0].getBigInteger(); BigInteger s = values[1].getBigInteger(); + BigInteger mod; + // The following checks cover more than 99.99999% of signature lengths + // for the three NIST-P curves. Fail if the length lies outside. + if (sigLength >= 100 && sigLength <= 104) { + mod = CurveDB.P_384.getOrder(); + } else if (sigLength >= 68 && sigLength <= 72) { + mod = CurveDB.P_256.getOrder(); + } else if (sigLength >= 135 && sigLength <= 139) { + mod = CurveDB.P_521.getOrder(); + } else { + throw new RuntimeException("Unrecognized signature length: " + sigLength); + } BigInteger s2 = s.negate().mod(mod); DerOutputStream out = new DerOutputStream(); out.putInteger(r); diff --git a/make/modules/java.sql.rowset/Java.gmk b/make/modules/java.sql.rowset/Java.gmk index ecfe3e6e6412..9326a59220d1 100644 --- a/make/modules/java.sql.rowset/Java.gmk +++ b/make/modules/java.sql.rowset/Java.gmk @@ -29,6 +29,7 @@ DISABLED_WARNINGS_java += dangling-doc-comments DOCLINT += -Xdoclint:all/protected \ '-Xdoclint/package:java.*,javax.*' +COPY += .xsd CLEAN_FILES += $(wildcard \ $(TOPDIR)/src/java.sql.rowset/share/classes/com/sun/rowset/*.properties \ $(TOPDIR)/src/java.sql.rowset/share/classes/javax/sql/rowset/*.properties) diff --git a/make/modules/jdk.jpackage/Lib.gmk b/make/modules/jdk.jpackage/Lib.gmk index 86b11bdafeea..cee8309c679d 100644 --- a/make/modules/jdk.jpackage/Lib.gmk +++ b/make/modules/jdk.jpackage/Lib.gmk @@ -55,8 +55,8 @@ $(eval $(call SetupJdkExecutable, BUILD_JPACKAGEAPPLAUNCHER, \ EXTRA_SRC := common, \ INCLUDE_FILES := $(JPACKAGEAPPLAUNCHER_INCLUDE_FILES), \ OPTIMIZATION := SIZE, \ + DISABLED_WARNINGS_clang_ExecCommand.c := format-nonliteral, \ DISABLED_WARNINGS_clang_JvmLauncherLib.c := format-nonliteral, \ - DISABLED_WARNINGS_clang_LinuxPackage.c := format-nonliteral, \ DISABLED_WARNINGS_clang_Log.cpp := unused-const-variable, \ CFLAGS_FILTER_OUT := -MD, \ CXXFLAGS_FILTER_OUT := -MD, \ @@ -93,6 +93,7 @@ ifeq ($(call isTargetOs, linux), true) LINK_TYPE := C++, \ OPTIMIZATION := SIZE, \ DISABLED_WARNINGS_gcc_Log.cpp := unused-const-variable, \ + DISABLED_WARNINGS_clang_ExecCommand.c := format-nonliteral, \ DISABLED_WARNINGS_clang_JvmLauncherLib.c := format-nonliteral, \ DISABLED_WARNINGS_clang_tstrings.cpp := format-nonliteral, \ LD_SET_ORIGIN := false, \ diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 745a3f58a7fc..c9d5d9b4dedb 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -2379,13 +2379,14 @@ void LIR_Assembler::store_parameter(jobject o, int offset_from_rsp_in_words) { } void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ cbz(obj, *slow_path->entry()); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); - __ test_flat_array_oop(obj, tmp, *slow_path->entry()); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry()); } } @@ -2467,11 +2468,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { } // Handle value type arrays - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); @@ -2989,6 +2992,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); Register tmp = op->tmp()->as_pointer_register(); bool not_null = op->not_null(); diff --git a/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp b/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp index 9c8c7393354d..e3140a7ba6ed 100644 --- a/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/codeBuffer_aarch64.cpp @@ -35,9 +35,6 @@ void CodeBuffer::share_trampoline_for(address dest, int caller_offset) { bool created; Offsets* offsets = _shared_trampoline_requests->put_if_absent(dest, &created); - if (created) { - _shared_trampoline_requests->maybe_grow(); - } offsets->push(caller_offset); _finalize_stubs = true; } diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp index e3af4f150f76..51d3959e4753 100644 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp @@ -549,7 +549,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, Register obj = stub->obj()->as_register(); if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, /* wide = */ false); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), /* wide = */ false); } __ cbz(obj, *stub->continuation()); ce->store_parameter(obj, 0); diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp index c2bf73092cc5..caa75144893c 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp @@ -44,6 +44,7 @@ #include "prims/jvmtiThreadState.hpp" #include "runtime/basicLock.hpp" #include "runtime/frame.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/javaThread.hpp" #include "runtime/safepointMechanism.hpp" #include "runtime/sharedRuntime.hpp" @@ -211,10 +212,12 @@ void InterpreterMacroAssembler::get_method_counters(Register method, } void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); } void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj); Label slow_path, done; @@ -1202,19 +1205,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re mov(tmp, array); profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset()))); - Label not_flat; - test_non_flat_array_oop(array, tmp, not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp, not_flat); - set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); - bind(not_flat); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp, not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp, not_null_free); - set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); - bind(not_null_free); + bind(not_null_free); + } bind(profile_continue); } @@ -1278,6 +1285,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp, Register left, Register right, Register tmp) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { Label profile_continue; diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index dd9215c55bd6..0077a2daab5f 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -2404,6 +2404,7 @@ void MacroAssembler::null_check(Register reg, int offset) { } void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(markword, rscratch2); mov(rscratch2, markWord::value_type_pattern_mask); andr(markword, markword, rscratch2); @@ -2413,6 +2414,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va } void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(tmp, rscratch1); if (can_be_null) { cbz(object, not_value_type); @@ -2426,16 +2428,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 tbnz(flags, ResolvedFieldEntry::is_null_free_value_type_shift, is_null_free_value_type); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 tbz(flags, ResolvedFieldEntry::is_null_free_value_type_shift, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); assert(temp_reg == noreg, "not needed"); // keep signature uniform with x86 tbnz(flags, ResolvedFieldEntry::is_flat_shift, is_flat); } @@ -2452,23 +2457,28 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int } void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label&is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); tst(lh, Klass::_lh_array_tag_flat_value_bit_inplace); br(Assembler::NE, is_flat_array); } @@ -5695,11 +5705,13 @@ void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators, } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); ldr(offset, Address(value_klass, ValueKlass::adr_members_offset())); ldrw(offset, Address(offset, ValueKlass::payload_offset_offset())); } void MacroAssembler::payload_address(Register oop, Register data, Register value_klass) { + assert(Arguments::is_valhalla_enabled(), "Must be"); // ((address) (void*) o) + vk->payload_offset(); Register offset = (data == oop) ? rscratch1 : data; payload_offset(value_klass, offset); @@ -5887,7 +5899,11 @@ void MacroAssembler::adrp(Register reg1, const Address &dest, uint64_t &byte_off code_section()->relocate(inst_mark(), dest.rspec()); // 8143067: Ensure that the adrp can reach the dest from anywhere within // the code cache so that if it is relocated we know it will still reach - if (offset_high >= -(1<<20) && offset_low < (1<<20)) { + // For AOT code force far distance for external_word relocation. + const bool force_far = AOTCodeCache::is_on_for_dump() && + (dest.rspec().type() == relocInfo::external_word_type) && + !CodeCache::contains(dest.target()); + if (!force_far && offset_high >= -(1<<20) && offset_low < (1<<20)) { _adrp(reg1, dest.target()); } else { uint64_t target = (uint64_t)dest.target(); @@ -7152,6 +7168,7 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i // Move a value between registers/stack slots and update the reg_state bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(from->is_valid() && to->is_valid(), "source and destination must be valid"); if (reg_state[to->value()] == reg_written) { return true; // Already written @@ -7214,6 +7231,7 @@ bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState re // Calculate the extra stack space required for packing or unpacking value // args and adjust the stack pointer int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { + assert(ValueTypePassFieldsAsArgs, "Must be"); int sp_inc = args_on_stack * VMRegImpl::stack_slot_size; sp_inc = align_up(sp_inc, StackAlignmentInBytes); assert(sp_inc > 0, "sanity"); @@ -7245,6 +7263,7 @@ int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index, VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(sig->at(sig_index)._bt == T_VOID, "should be at end delimiter"); assert(from->is_valid(), "source must be valid"); bool progress = false; @@ -7391,6 +7410,7 @@ bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, VMRegPair* from, int from_count, int& from_index, VMReg to, RegState reg_state[], Register val_array) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(sig->at(sig_index)._bt == T_METADATA, "should be at delimiter"); assert(to->is_valid(), "destination must be valid"); diff --git a/src/hotspot/cpu/aarch64/matcher_aarch64.hpp b/src/hotspot/cpu/aarch64/matcher_aarch64.hpp index 42bb61f6889a..ede5a4814950 100644 --- a/src/hotspot/cpu/aarch64/matcher_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/matcher_aarch64.hpp @@ -130,11 +130,6 @@ return true; } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return false; diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 92f604c507e5..b59c23489bea 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -2621,10 +2621,12 @@ class StubGenerator: public StubCodeGenerator { __ eor(rscratch2, rscratch2, scratch_src_klass); __ cbnz(rscratch2, L_failed); - if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { // Check for flat value type array -> return -1 __ test_flat_array_oop(src, rscratch2, L_failed); + } + if (Arguments::is_valhalla_enabled()) { // Check for null-free (non-flat) value type array -> handle as object array __ test_null_free_array_oop(src, rscratch2, L_objArray); } diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp index f1ab668d211d..0f8b21f47927 100644 --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp @@ -29,9 +29,9 @@ #include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/collectedHeap.hpp" #include "gc/shared/tlab_globals.hpp" +#include "interpreter/interp_masm.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" -#include "interpreter/interp_masm.hpp" #include "interpreter/templateTable.hpp" #include "memory/universe.hpp" #include "oops/methodData.hpp" @@ -45,6 +45,7 @@ #include "prims/methodHandles.hpp" #include "runtime/arguments.hpp" #include "runtime/frame.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "runtime/synchronizer.hpp" @@ -2020,12 +2021,14 @@ void TemplateTable::if_acmp(Condition cc) { Label taken, not_taken; __ pop_ptr(r1); - __ profile_acmp(r2, r1, r0, r4); - Register is_value_type_mask = rscratch1; - __ mov(is_value_type_mask, markWord::value_type_pattern); if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(r2, r1, r0, r4); + + Register is_value_type_mask = rscratch1; + __ mov(is_value_type_mask, markWord::value_type_pattern); + // The substitutability test is only necessary if r1 and r0 are not the same... __ cmp(r1, r0); __ br(Assembler::EQ, (cc == equal) ? taken : not_taken); @@ -2785,7 +2788,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ b(Done); } else { Label is_flat; - __ test_field_is_flat(flags, noreg /* temp */, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, noreg /* temp */, is_flat); + } __ load_heap_oop(r0, field, rscratch1, rscratch2); __ push(atos); if (rc == may_rewrite) { @@ -2793,15 +2798,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr } __ b(Done); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker) - __ mov(r0, obj); - __ read_flat_field(cache, r0); - __ verify_oop(r0); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, bc, r1); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker) + __ mov(r0, obj); + __ read_flat_field(cache, r0); + __ verify_oop(r0); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc, r1); + } + __ b(Done); } - __ b(Done); } } @@ -3046,7 +3053,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr __ b(Done); } else { Label null_free_reference, is_flat, rewrite_value; - __ test_field_is_flat(flags, noreg /* temp */, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, noreg /* temp */, is_flat); + } __ test_field_is_null_free_value_type(flags, noreg /* temp */, null_free_reference); pop_and_check_object(obj); // Store into the field @@ -3065,8 +3074,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr do_oop_store(_masm, field, r0, IN_HEAP); __ b(rewrite_value); __ bind(is_flat); - pop_and_check_object(r7); - __ write_flat_field(cache, index, flags, r7); + if (UseFieldFlattening) { + pop_and_check_object(r7); + __ write_flat_field(cache, index, flags, r7); + } __ bind(rewrite_value); if (rc == may_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, bc, r19, true, byte_no); @@ -3287,17 +3298,25 @@ void TemplateTable::fast_storefield(TosState state) switch (bytecode()) { case Bytecodes::_fast_vputfield: { - Label is_flat, done; - __ test_field_is_flat(r5, noreg /* temp */, is_flat); - __ null_check(r0); - do_oop_store(_masm, field, r0, IN_HEAP); - __ b(done); - __ bind(is_flat); - __ load_field_entry(r4, r5); - // Re-shuffle registers because of VM calls calling convention - __ mov(r7, r2); - __ write_flat_field(r4, r6, r8, r7); - __ bind(done); + if (Arguments::is_valhalla_enabled()) { + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(r5, noreg /* temp */, is_flat); + } + __ null_check(r0); + do_oop_store(_masm, field, r0, IN_HEAP); + __ b(done); + __ bind(is_flat); + if (UseFieldFlattening) { + __ load_field_entry(r4, r5); + // Re-shuffle registers because of VM calls calling convention + __ mov(r7, r2); + __ write_flat_field(r4, r6, r8, r7); + } + __ bind(done); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_aputfield: @@ -3397,9 +3416,13 @@ void TemplateTable::fast_accessfield(TosState state) switch (bytecode()) { case Bytecodes::_fast_vgetfield: { - // field is flat - __ read_flat_field(r2, r0); - __ verify_oop(r0); + if (UseFieldFlattening) { + // field is flat + __ read_flat_field(r2, r0); + __ verify_oop(r0); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_agetfield: @@ -4104,8 +4127,10 @@ void TemplateTable::monitorenter() __ null_check(r0); Label is_value_type; - __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(rscratch1, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(rscratch1, is_value_type); + } const Address monitor_block_top( rfp, frame::interpreter_frame_monitor_block_top_offset * wordSize); @@ -4210,9 +4235,11 @@ void TemplateTable::monitorenter() __ dispatch_next(vtos); __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), r0); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), r0); + __ should_not_reach_here(); + } } @@ -4223,17 +4250,19 @@ void TemplateTable::monitorexit() // check for null object __ null_check(r0); - const int is_value_type_mask = markWord::value_type_pattern; - Label has_identity; - __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); - __ mov(rscratch2, is_value_type_mask); - __ andr(rscratch1, rscratch1, rscratch2); - __ cmp(rscratch1, rscratch2); - __ br(Assembler::NE, has_identity); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_illegal_monitor_state_exception)); - __ should_not_reach_here(); - __ bind(has_identity); + if (Arguments::is_valhalla_enabled()) { + const int is_value_type_mask = markWord::value_type_pattern; + Label has_identity; + __ ldr(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); + __ mov(rscratch2, is_value_type_mask); + __ andr(rscratch1, rscratch1, rscratch2); + __ cmp(rscratch1, rscratch2); + __ br(Assembler::NE, has_identity); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + } const Address monitor_block_top( rfp, frame::interpreter_frame_monitor_block_top_offset * wordSize); diff --git a/src/hotspot/cpu/arm/matcher_arm.hpp b/src/hotspot/cpu/arm/matcher_arm.hpp index a3139d91f302..7980838c5cca 100644 --- a/src/hotspot/cpu/arm/matcher_arm.hpp +++ b/src/hotspot/cpu/arm/matcher_arm.hpp @@ -122,11 +122,6 @@ return VM_Version::has_simd(); } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return false; // not supported diff --git a/src/hotspot/cpu/arm/vm_version_arm.hpp b/src/hotspot/cpu/arm/vm_version_arm.hpp index c400cad24b45..4ade00a5d1fa 100644 --- a/src/hotspot/cpu/arm/vm_version_arm.hpp +++ b/src/hotspot/cpu/arm/vm_version_arm.hpp @@ -29,9 +29,6 @@ #include "runtime/globals_extension.hpp" class VM_Version: public Abstract_VM_Version { - static bool _has_simd; - static bool _has_mp_ext; - protected: // Are we done with vm version initialization static bool _is_initialized; diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index ca84fa198462..6834cf1a6870 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -1778,14 +1778,15 @@ void LIR_Assembler::unwind_op(LIR_Opr exceptionOop) { void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ cmpdi(CR0, obj, 0); __ bc_far_optimized(Assembler::bcondCRbiIs1, __ bi0(CR0, Assembler::equal), *slow_path->entry()); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry(), true); - __ test_flat_array_oop(obj, tmp, *slow_path->entry(), true); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry(), true); } } @@ -1853,12 +1854,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { return; } - // Handle value type arrays - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + // Handle value type arrays + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); @@ -3075,6 +3078,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); //Register tmp = op->tmp()->as_pointer_register(); not needed! LIR_Address* mdo_addr = op->mdp()->as_address_ptr(); diff --git a/src/hotspot/cpu/ppc/c2_init_ppc.cpp b/src/hotspot/cpu/ppc/c2_init_ppc.cpp index 3c524f3b80e9..d4174192fe91 100644 --- a/src/hotspot/cpu/ppc/c2_init_ppc.cpp +++ b/src/hotspot/cpu/ppc/c2_init_ppc.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2024 SAP SE. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ * */ +#include "asm/macroAssembler.hpp" #include "opto/compile.hpp" #include "opto/node.hpp" #include "runtime/globals.hpp" @@ -34,4 +35,7 @@ void Compile::pd_compiler2_init() { guarantee(CodeEntryAlignment >= InteriorEntryAlignment, ""); +#ifdef _LP64 + MacroAssembler::initialize_compressed_oops_fit_in_31_bits(); +#endif } diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index a393f6fbb118..cc45d60f3295 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp @@ -763,7 +763,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, // If 'do_load()' returns false, the to-be-stored value is already available in 'obj' if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, false); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), false); } // Fast path: reference is null. diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp index 4226b3dbdbcd..57febf78fed2 100644 --- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp @@ -1509,15 +1509,19 @@ template void InterpreterMacroAssembler::profile_array_type(Re profile_obj_type(array, R28_mdx, in_bytes(ArrayData::array_offset()), tmp1, tmp2); - Label not_flat; - test_non_flat_array_oop(array, tmp1, not_flat); - set_mdp_flag_at(ArrayData::flat_array_byte_constant(), tmp1); - bind(not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp1, not_flat); + set_mdp_flag_at(ArrayData::flat_array_byte_constant(), tmp1); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp1, not_null_free); - set_mdp_flag_at(ArrayData::null_free_array_byte_constant(), tmp1); - bind(not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp1, not_null_free); + set_mdp_flag_at(ArrayData::null_free_array_byte_constant(), tmp1); + bind(not_null_free); + } bind(profile_continue); } @@ -1579,6 +1583,7 @@ void InterpreterMacroAssembler::profile_acmp(Register left, Register right, Register tmp1, Register tmp2) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { Label profile_continue; assert_different_registers(left, right, tmp1, tmp2); @@ -2431,11 +2436,13 @@ void InterpreterMacroAssembler::notify_method_exit(bool is_native_method, TosSta } void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); } void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj, Register value) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj, value); Label slow_path, done; diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index e3294587f0d8..c58c1b248742 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -202,36 +202,27 @@ address MacroAssembler::get_address_of_calculate_address_from_global_toc_at(addr } #ifdef _LP64 -// Patch compressed oops or klass constants. -// Assembler sequence is -// 1) compressed oops: -// lis rx = const.hi -// ori rx = rx | const.lo -// 2) compressed klass: -// lis rx = const.hi -// clrldi rx = rx & 0xFFFFffff // clearMS32b, optional -// ori rx = rx | const.lo -// Clrldi will be passed by. -address MacroAssembler::patch_set_narrow_oop(address a, address bound, narrowOop data) { +bool MacroAssembler::_compressed_oops_fit_in_31_bits = false; + +void MacroAssembler::initialize_compressed_oops_fit_in_31_bits() { + if (UseCompressedOops) { + address max_oop = align_down(CompressedOops::end() - 1, MinObjAlignmentInBytes); + narrowOop max_encoded_oop = CompressedOops::encode(cast_to_oop(max_oop)); + _compressed_oops_fit_in_31_bits = Assembler::is_uimm((jlong)max_encoded_oop, 31); + } +} + +// Patch compressed oop constants. +// The relocation points to the ori of an adjacent lis/ori pair. +// +// lis rx = const.hi +// ori rx = rx | const.lo +address MacroAssembler::patch_set_narrow_oop(address a, address lower_bound, narrowOop data) { assert(UseCompressedOops, "Should only patch compressed oops"); const address inst2_addr = a; - const int inst2 = *(int *)inst2_addr; - - // The relocation points to the second instruction, the ori, - // and the ori reads and writes the same register dst. - const int dst = inv_rta_field(inst2); - assert(is_ori(inst2) && inv_rs_field(inst2) == dst, "must be ori reading and writing dst"); - // Now, find the preceding addis which writes to dst. - int inst1 = 0; - address inst1_addr = inst2_addr - BytesPerInstWord; - bool inst1_found = false; - while (inst1_addr >= bound) { - inst1 = *(int *)inst1_addr; - if (is_lis(inst1) && inv_rs_field(inst1) == dst) { inst1_found = true; break; } - inst1_addr -= BytesPerInstWord; - } - assert(inst1_found, "inst is not lis"); + assert(is_set_narrow_oop(inst2_addr, lower_bound), "must be lis/ori narrow oop constant"); + const address inst1_addr = inst2_addr - BytesPerInstWord; uint32_t data_value = CompressedOops::narrow_oop_value(data); int xc = (data_value >> 16) & 0xffff; @@ -243,27 +234,12 @@ address MacroAssembler::patch_set_narrow_oop(address a, address bound, narrowOop } // Get compressed oop constant. -narrowOop MacroAssembler::get_narrow_oop(address a, address bound) { +narrowOop MacroAssembler::get_narrow_oop(address a, address lower_bound) { assert(UseCompressedOops, "Should only patch compressed oops"); const address inst2_addr = a; - const int inst2 = *(int *)inst2_addr; - - // The relocation points to the second instruction, the ori, - // and the ori reads and writes the same register dst. - const int dst = inv_rta_field(inst2); - assert(is_ori(inst2) && inv_rs_field(inst2) == dst, "must be ori reading and writing dst"); - // Now, find the preceding lis which writes to dst. - int inst1 = 0; - address inst1_addr = inst2_addr - BytesPerInstWord; - bool inst1_found = false; - - while (inst1_addr >= bound) { - inst1 = *(int *) inst1_addr; - if (is_lis(inst1) && inv_rs_field(inst1) == dst) { inst1_found = true; break;} - inst1_addr -= BytesPerInstWord; - } - assert(inst1_found, "inst is not lis"); + assert(is_set_narrow_oop(inst2_addr, lower_bound), "must be lis/ori narrow oop constant"); + const address inst1_addr = inst2_addr - BytesPerInstWord; uint xl = ((unsigned int) (get_imm(inst2_addr, 0) & 0xffff)); uint xh = (((get_imm(inst1_addr, 0)) & 0xffff) << 16); @@ -1278,7 +1254,7 @@ void MacroAssembler::post_call_nop() { relocate(post_call_nop_Relocation::spec()); InlineSkippedInstructionsCounter skipCounter(this); Assembler::emit_int32(Assembler::CMPLI_OPCODE | Assembler::opp_u_field(1, 9, 9)); - assert(is_post_call_nop(*(int*)(pc() - 4)), "post call not not found"); + assert(is_post_call_nop(*(int*)(pc() - 4)), "post call nop not found"); } int MacroAssembler::ic_check_size() { @@ -3286,6 +3262,7 @@ void MacroAssembler::load_method_holder(Register holder, Register method) { } void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(markword, R0); andi(R0, markword, markWord::value_type_pattern_mask); cmpwi(CR0, R0, markWord::value_type_pattern); @@ -3293,6 +3270,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va } void MacroAssembler::test_oop_is_not_value_type(Register object, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); if (can_be_null) { cmpdi(CR0, object, 0); beq(CR0, not_value_type); @@ -3304,16 +3282,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Label& not_valu } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Label& is_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_null_free_value_type_shift); bne(CR0, is_null_free_value_type); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_null_free_value_type_shift); beq(CR0, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); testbitdi(CR0, R0, flags, ResolvedFieldEntry::is_flat_shift); bne(CR0, is_flat); } @@ -3336,22 +3317,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int } void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array, bool maybe_far) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array, maybe_far); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array, bool maybe_far) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array, maybe_far); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label& is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); testbitdi(CR0, R0, lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace)); bne(CR0, is_flat_array); } @@ -3365,11 +3351,13 @@ void MacroAssembler::load_metadata(Register dst, Register src) { } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); ld(offset, in_bytes(ValueKlass::adr_members_offset()), value_klass); lwz(offset, in_bytes(ValueKlass::payload_offset_offset()), offset); } void MacroAssembler::payload_address(Register oop, Register data, Register value_klass, Register t1) { + assert(Arguments::is_valhalla_enabled(), "Must be"); // ((address) (void*) o) + vk->payload_offset(); payload_offset(value_klass, t1); add(data, oop, t1); diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp index d336ef81dd70..1bb1290ff252 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.hpp @@ -131,11 +131,19 @@ class MacroAssembler: public Assembler { static address get_address_of_calculate_address_from_global_toc_at(address a, address addr); #ifdef _LP64 + private: + static bool _compressed_oops_fit_in_31_bits; + + public: + static void initialize_compressed_oops_fit_in_31_bits(); + inline static bool compressed_oops_fit_in_31_bits() { + return _compressed_oops_fit_in_31_bits; + } // Patch narrow oop constant. - inline static bool is_set_narrow_oop(address a, address bound); + inline static bool is_set_narrow_oop(address a, address lower_bound); // Returns address of first instruction in sequence. - static address patch_set_narrow_oop(address a, address bound, narrowOop data); - static narrowOop get_narrow_oop(address a, address bound); + static address patch_set_narrow_oop(address a, address lower_bound, narrowOop data); + static narrowOop get_narrow_oop(address a, address lower_bound); #endif inline static bool is_load_const_at(address a); diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp index cdeb8527beaa..440b8e72dbec 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.inline.hpp @@ -137,7 +137,7 @@ inline bool MacroAssembler::is_calculate_address_from_global_toc_at(address a, a #ifdef _LP64 // Detect narrow oop constants. -inline bool MacroAssembler::is_set_narrow_oop(address a, address bound) { +inline bool MacroAssembler::is_set_narrow_oop(address a, address lower_bound) { const address inst2_addr = a; const int inst2 = *(int *)a; // The relocation points to the second instruction, the ori. @@ -147,15 +147,11 @@ inline bool MacroAssembler::is_set_narrow_oop(address a, address bound) { const int dst = inv_rta_field(inst2); if (inv_rs_field(inst2) != dst) return false; - // Now, find the preceding addis which writes to dst. - int inst1 = 0; - address inst1_addr = inst2_addr - BytesPerInstWord; - while (inst1_addr >= bound) { - inst1 = *(int *) inst1_addr; - if (is_lis(inst1) && inv_rs_field(inst1) == dst) return true; - inst1_addr -= BytesPerInstWord; - } - return false; + // The preceding instruction must be the lis which writes dst. + const address inst1_addr = inst2_addr - BytesPerInstWord; + if (inst1_addr < lower_bound) return false; + const int inst1 = *(int *) inst1_addr; + return is_lis(inst1) && inv_rs_field(inst1) == dst; } #endif diff --git a/src/hotspot/cpu/ppc/matcher_ppc.hpp b/src/hotspot/cpu/ppc/matcher_ppc.hpp index 209d317a0335..356682bb760b 100644 --- a/src/hotspot/cpu/ppc/matcher_ppc.hpp +++ b/src/hotspot/cpu/ppc/matcher_ppc.hpp @@ -130,11 +130,6 @@ return false; } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return false; diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 1126d8884618..a3c36163e466 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -3776,6 +3776,15 @@ operand immN() %{ interface(CONST_INTER); %} +operand immN_31bit() %{ + predicate(MacroAssembler::compressed_oops_fit_in_31_bits()); + match(ConN); + + op_cost(5); + format %{ %} + interface(CONST_INTER); +%} + // nullptr Pointer Immediate operand immN_0() %{ predicate(n->get_narrowcon() == 0); @@ -5678,6 +5687,21 @@ instruct loadBase(iRegLdst dst) %{ ins_pipe(pipe_class_default); %} +instruct loadConN_31bit(iRegNdst dst, immN_31bit src) %{ + match(Set dst src); + ins_cost(DEFAULT_COST*2); + + format %{ "LIS/ORI $dst, $src" %} + size(8); + ins_encode %{ + AddressLiteral addrlit = __ constant_oop_address((jobject)$src$$constant); + __ lis($dst$$Register, 0); // Will get patched. + __ relocate(addrlit.rspec(), /*compressed format*/ 1); + __ ori($dst$$Register, $dst$$Register, 0); // Will get patched. + %} + ins_pipe(pipe_class_default); +%} + instruct loadConN(iRegNdst dst, immN src) %{ match(Set dst src); ins_cost(DEFAULT_COST*3); diff --git a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp index cbb3af06c8a5..75c3a69c81f7 100644 --- a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp @@ -2594,10 +2594,14 @@ class StubGenerator: public StubCodeGenerator { __ bne(CR5, L_failed); // Check for flat value type array -> return -1 - __ test_flat_array_oop(src, temp, L_failed); + if (UseArrayFlattening) { + __ test_flat_array_oop(src, temp, L_failed); + } // Check for null-free (non-flat) value type array -> handle as object array - __ test_null_free_array_oop(src, temp, L_objArray); + if (Arguments::is_valhalla_enabled()) { + __ test_null_free_array_oop(src, temp, L_objArray); + } __ cmpwi(CR6, lh, Klass::_lh_neutral_value); // if (!src->is_Array()) return -1; __ bge(CR6, L_failed); @@ -2695,6 +2699,17 @@ class StubGenerator: public StubCodeGenerator { __ cmpw(CR0, lh, temp); __ bne(CR0, L_failed); +#ifdef ASSERT + if (Arguments::is_valhalla_enabled()) { + BLOCK_COMMENT("assert not null-free array {"); + Label L; + __ test_non_null_free_array_oop(dst, temp, L); + __ stop("unexpected null-free array"); + __ bind(L); + BLOCK_COMMENT("} assert not null-free array"); + } +#endif + // It is safe to examine both src.length and dst.length. arraycopy_range_checks(src, src_pos, dst, dst_pos, length, temp, lh, L_failed); diff --git a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp index 5ee4e94afe99..2040b2f5a4ec 100644 --- a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp @@ -1866,10 +1866,10 @@ void TemplateTable::if_acmp(Condition cc) { __ pop_ptr(Rfirst); - __ profile_acmp(Rfirst, Rsecond, R11_scratch1, R12_scratch2); - - const int is_value_type_mask = markWord::value_type_pattern; if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(Rfirst, Rsecond, R11_scratch1, R12_scratch2); + + const int is_value_type_mask = markWord::value_type_pattern; Label taken, not_taken; __ cmpd(CR0, Rfirst, Rsecond); __ beq(CR0, (cc == equal) ? taken : not_taken); @@ -2852,7 +2852,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ push(atos); } else { Label is_flat; - __ test_field_is_flat(Rflags, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(Rflags, is_flat); + } do_oop_load(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, /* nv temp */ Rflags, IN_HEAP); __ verify_oop(R17_tos); __ push(atos); @@ -2863,13 +2865,15 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ dispatch_epilog(vtos, Bytecodes::length_for(bytecode())); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker) - __ mr(R17_tos, Rclass_or_obj); - __ read_flat_field(Rcache, R17_tos); - __ verify_oop(R17_tos); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, Rbc, Rscratch); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker) + __ mr(R17_tos, Rclass_or_obj); + __ read_flat_field(Rcache, R17_tos); + __ verify_oop(R17_tos); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, Rbc, Rscratch); + } } } } @@ -3215,7 +3219,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP); } else { Label null_free_reference, is_flat, rewrite_value; - __ test_field_is_flat(Rflags, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(Rflags, is_flat); + } __ test_field_is_null_free_value_type(Rflags, null_free_reference); pop_and_check_object(Rclass_or_obj); // Store into the field @@ -3237,8 +3243,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr __ b(rewrite_value); __ bind(is_flat); - pop_and_check_object(Rclass_or_obj); - __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos); + if (UseFieldFlattening) { + pop_and_check_object(Rclass_or_obj); + __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos); + } __ bind(rewrite_value); if (rc == may_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, Rbc, Rscratch, true, byte_no); @@ -3316,14 +3324,22 @@ void TemplateTable::fast_storefield(TosState state) { switch(bytecode()) { case Bytecodes::_fast_vputfield: { - Label is_flat, done; - __ test_field_is_flat(Rflags, is_flat); - __ null_check_throw(R17_tos, -1, Rscratch); - do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP); - __ b(done); - __ bind(is_flat); - __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos); - __ bind(done); + if (Arguments::is_valhalla_enabled()) { + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(Rflags, is_flat); + } + __ null_check_throw(R17_tos, -1, Rscratch); + do_oop_store(_masm, Rclass_or_obj, Roffset, R17_tos, Rscratch, Rscratch2, Rscratch3, IN_HEAP); + __ b(done); + __ bind(is_flat); + if (UseFieldFlattening) { + __ write_flat_field(Rcache, Rscratch, Rscratch2, Rclass_or_obj, R17_tos); + } + __ bind(done); + } else { + __ should_not_reach_here(); + } break; } @@ -3404,17 +3420,22 @@ void TemplateTable::fast_accessfield(TosState state) { switch(bytecode()) { case Bytecodes::_fast_vgetfield: { - // field is flat - __ read_flat_field(Rcache, R17_tos); - __ verify_oop(R17_tos); - __ dispatch_epilog(state, Bytecodes::length_for(bytecode())); - - __ bind(LisVolatile); - if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ fence(); } - __ read_flat_field(Rcache, R17_tos); - __ verify_oop(R17_tos); - __ twi_0(R17_tos); - __ isync(); + if (UseFieldFlattening) { + // field is flat + __ read_flat_field(Rcache, R17_tos); + __ verify_oop(R17_tos); + __ dispatch_epilog(state, Bytecodes::length_for(bytecode())); + + __ bind(LisVolatile); + if (support_IRIW_for_not_multiple_copy_atomic_cpu) { __ fence(); } + __ read_flat_field(Rcache, R17_tos); + __ verify_oop(R17_tos); + __ twi_0(R17_tos); + __ isync(); + } else { + __ bind(LisVolatile); + __ should_not_reach_here(); + } break; } case Bytecodes::_fast_agetfield: @@ -4317,8 +4338,10 @@ void TemplateTable::monitorenter() { __ null_check_throw(Robj_to_lock, -1, Rscratch1); Label is_value_type; - __ ld(Rscratch1, oopDesc::mark_offset_in_bytes(), Robj_to_lock); - __ test_markword_is_value_type(Rscratch1, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ ld(Rscratch1, oopDesc::mark_offset_in_bytes(), Robj_to_lock); + __ test_markword_is_value_type(Rscratch1, is_value_type); + } // Check if any slot is present => short cut to allocation if not. __ cmpld(CR0, Rcurrent_monitor, Rbot); @@ -4378,9 +4401,11 @@ void TemplateTable::monitorenter() { __ dispatch_next(vtos); __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), Robj_to_lock); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), Robj_to_lock); + __ should_not_reach_here(); + } } void TemplateTable::monitorexit() { @@ -4404,11 +4429,13 @@ void TemplateTable::monitorexit() { // Null pointer check. __ null_check_throw(Robj_to_lock, -1, Rscratch); - const int is_value_type_mask = markWord::value_type_pattern; - __ ld(Rscratch, oopDesc::mark_offset_in_bytes(), Robj_to_lock); - __ andi(Rscratch, Rscratch, is_value_type_mask); - __ cmpwi(CR0, Rscratch, is_value_type_mask); - __ beq(CR0, Lillegal_monitor_state); + if (Arguments::is_valhalla_enabled()) { + const int is_value_type_mask = markWord::value_type_pattern; + __ ld(Rscratch, oopDesc::mark_offset_in_bytes(), Robj_to_lock); + __ andi(Rscratch, Rscratch, is_value_type_mask); + __ cmpwi(CR0, Rscratch, is_value_type_mask); + __ beq(CR0, Lillegal_monitor_state); + } // Check corner case: unbalanced monitorEnter / Exit. __ cmpld(CR0, Rcurrent_monitor, Rbot); diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp index 97d049c428b1..70cccefb2ea5 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_arraycopy_riscv.cpp @@ -241,13 +241,14 @@ void LIR_Assembler::arraycopy_type_check(Register src, Register src_pos, Registe } void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ beqz(obj, *slow_path->entry(), /* is_far */ true); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); - __ test_flat_array_oop(obj, tmp, *slow_path->entry()); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry()); } } @@ -278,11 +279,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { return; } - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), diff --git a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp index a105faf9137a..f73e41bcb4ba 100644 --- a/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRAssembler_riscv.cpp @@ -1322,6 +1322,7 @@ void LIR_Assembler::emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); Register tmp = op->tmp()->as_pointer_register(); bool not_null = op->not_null(); @@ -1390,10 +1391,12 @@ void LIR_Assembler::emit_compare_and_swap(LIR_OpCompareAndSwap* op) { } } -void LIR_Assembler::intrinsic_op(LIR_Code code, LIR_Opr value, LIR_Opr unused, LIR_Opr dest, LIR_Op* op) { +void LIR_Assembler::intrinsic_op(LIR_Code code, LIR_Opr value, LIR_Opr tmp, LIR_Opr dest, LIR_Op* op) { switch (code) { case lir_abs: __ fabs_d(dest->as_double_reg(), value->as_double_reg()); break; case lir_sqrt: __ fsqrt_d(dest->as_double_reg(), value->as_double_reg()); break; + case lir_f2hf: __ flt_to_flt16(dest->as_register(), value->as_float_reg(), tmp->as_float_reg()); break; + case lir_hf2f: __ flt16_to_flt(dest->as_float_reg(), value->as_register()); break; default: ShouldNotReachHere(); } } @@ -1701,6 +1704,7 @@ void LIR_Assembler::emit_updatecrc32(LIR_OpUpdateCRC32* op) { __ zext(crc, crc, 32); __ update_byte_crc32(crc, val, res); __ notr(res, crc); // ~crc + __ sext(res, res, 32); } void LIR_Assembler::check_conflict(ciKlass* exact_klass, intptr_t current_klass, diff --git a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp index a3594d15feea..e37726b6632a 100644 --- a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp @@ -640,7 +640,9 @@ void LIRGenerator::do_MathIntrinsic(Intrinsic* x) { break; case vmIntrinsics::_dabs: // fall through case vmIntrinsics::_dsqrt: // fall through - case vmIntrinsics::_dsqrt_strict: { + case vmIntrinsics::_dsqrt_strict: // fall through + case vmIntrinsics::_floatToFloat16: // fall through + case vmIntrinsics::_float16ToFloat: { assert(x->number_of_arguments() == 1, "wrong type"); LIRItem value(x->argument_at(0), this); value.load_item(); @@ -656,6 +658,17 @@ void LIRGenerator::do_MathIntrinsic(Intrinsic* x) { __ abs(value.result(), dst, LIR_OprFact::illegalOpr); break; } + case vmIntrinsics::_floatToFloat16: { + // flt_to_flt16 needs a float temporary to move the result through. + LIR_Opr tmp = new_register(T_FLOAT); + __ f2hf(value.result(), dst, tmp); + break; + } + case vmIntrinsics::_float16ToFloat: { + // flt16_to_flt only needs t0/t1, which are not visible to the allocator. + __ hf2f(value.result(), dst, LIR_OprFact::illegalOpr); + break; + } default: ShouldNotReachHere(); } diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index 72a20ca50012..7d412087753e 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -2108,6 +2108,40 @@ void C2_MacroAssembler::enc_cmove(int cmpFlag, Register op1, Register op2, Regis } } +// Set dst to 0 if the condition specified by cmpFlag is true, or 1 otherwise. +// cmpFlag may include unsigned_branch_mask for an unsigned comparison. +void C2_MacroAssembler::enc_cmove_zero_one(int cmpFlag, Register op1, Register op2, Register dst) { + bool is_unsigned = (cmpFlag & unsigned_branch_mask) == unsigned_branch_mask; + int op_select = cmpFlag & (~unsigned_branch_mask); + + switch (op_select) { + case BoolTest::eq: + xorr(dst, op1, op2); + snez(dst, dst); + break; + case BoolTest::ne: + xorr(dst, op1, op2); + seqz(dst, dst); + break; + case BoolTest::lt: + is_unsigned ? sltu(dst, op1, op2) : slt(dst, op1, op2); + seqz(dst, dst); + break; + case BoolTest::gt: + is_unsigned ? sltu(dst, op2, op1) : slt(dst, op2, op1); + seqz(dst, dst); + break; + case BoolTest::le: + is_unsigned ? sltu(dst, op2, op1) : slt(dst, op2, op1); + break; + case BoolTest::ge: + is_unsigned ? sltu(dst, op1, op2) : slt(dst, op1, op2); + break; + default: + ShouldNotReachHere(); + } +} + void C2_MacroAssembler::enc_cmove_cmp_fp(int cmpFlag, FloatRegister op1, FloatRegister op2, Register dst, Register src, bool is_single) { int op_select = cmpFlag & (~unsigned_branch_mask); @@ -2213,6 +2247,41 @@ void C2_MacroAssembler::enc_cmove_fp_cmp_fp(int cmpFlag, } } +// Set dst to 0 if the floating-point condition is true, or 1 otherwise. +// cmpFlag may include double_branch_mask for a double comparison. +// CmpF/CmpD treat unordered as less: eq/gt/ge are false for NaN, while +// ne/lt/le are true. +void C2_MacroAssembler::enc_cmove_cmp_fp_zero_one(int cmpFlag, FloatRegister op1, FloatRegister op2, Register dst) { + bool is_single = (cmpFlag & double_branch_mask) == 0; + int op_select = cmpFlag & (~double_branch_mask); + + switch (op_select) { + case BoolTest::eq: + is_single ? feq_s(dst, op1, op2) : feq_d(dst, op1, op2); + seqz(dst, dst); + break; + case BoolTest::ne: + is_single ? feq_s(dst, op1, op2) : feq_d(dst, op1, op2); + break; + case BoolTest::lt: + is_single ? fle_s(dst, op2, op1) : fle_d(dst, op2, op1); + break; + case BoolTest::le: + is_single ? flt_s(dst, op2, op1) : flt_d(dst, op2, op1); + break; + case BoolTest::gt: + is_single ? flt_s(dst, op2, op1) : flt_d(dst, op2, op1); + seqz(dst, dst); + break; + case BoolTest::ge: + is_single ? fle_s(dst, op2, op1) : fle_d(dst, op2, op1); + seqz(dst, dst); + break; + default: + ShouldNotReachHere(); + } +} + static void vshift_left(C2_MacroAssembler* masm, VectorRegister dst, VectorRegister src, int shift) { assert(shift >= 0 && shift <= BitsPerInt, "unexpected shift amount"); if (shift < BitsPerInt) { diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp index 093f367b7903..1713b60ccc6a 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.hpp @@ -131,6 +131,9 @@ Register op1, Register op2, Register dst, Register src); + void enc_cmove_zero_one(int cmpFlag, + Register op1, Register op2, Register dst); + void enc_cmove_cmp_fp(int cmpFlag, FloatRegister op1, FloatRegister op2, Register dst, Register src, bool is_single); @@ -142,6 +145,10 @@ FloatRegister dst, FloatRegister src, bool cmp_single, bool cmov_single); + void enc_cmove_cmp_fp_zero_one(int cmpFlag, + FloatRegister op1, FloatRegister op2, + Register dst); + void compress_bits_v(VectorRegister dst, VectorRegister src, VectorRegister bit_mask, VectorRegister src_tmp, VectorRegister mask_tmp, VectorRegister tmp1, VectorRegister tmp2, diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp index 0d1f603b9cc8..40489bd32cb8 100644 --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp @@ -568,7 +568,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, Register obj = stub->obj()->as_register(); if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, false /* wide */); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), false /* wide */); } __ beqz(obj, *stub->continuation(), /* is_far */ true); diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp index 3204f081db6f..9c9e636b5b23 100644 --- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp +++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp @@ -44,6 +44,7 @@ #include "prims/jvmtiThreadState.hpp" #include "runtime/basicLock.hpp" #include "runtime/frame.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/javaThread.hpp" #include "runtime/safepointMechanism.hpp" #include "runtime/sharedRuntime.hpp" @@ -1203,19 +1204,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re mv(tmp, array); profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset())), t1); - Label not_flat; - test_non_flat_array_oop(array, tmp, not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp, not_flat); - set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); - bind(not_flat); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp, not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp, not_null_free); - set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); - bind(not_null_free); + bind(not_null_free); + } bind(profile_continue); } @@ -1278,6 +1283,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp, Register left, Register right, Register tmp) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { Label profile_continue; @@ -1867,11 +1873,13 @@ void InterpreterMacroAssembler::get_method_counters(Register method, } void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); } void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj); Label slow_path, done; diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index 733464b8b269..8e383ea9e544 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -3792,7 +3792,7 @@ void MacroAssembler::cmp_klass_bne(Register obj, Register klass, } // Move an oop into a register. -void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) { +void MacroAssembler::movoop(Register dst, jobject obj) { int oop_index; if (obj == nullptr) { oop_index = oop_recorder()->allocate_oop_index(obj); @@ -3808,7 +3808,7 @@ void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) { RelocationHolder rspec = oop_Relocation::spec(oop_index); if (BarrierSet::barrier_set()->barrier_set_assembler()->supports_instruction_patching()) { - movptr(dst, Address((address)obj, rspec), tmp); + movptr(dst, Address((address)obj, rspec)); } else { address dummy = address(uintptr_t(pc()) & -wordSize); // A nearby aligned address ld(dst, Address(dummy, rspec)); @@ -3816,7 +3816,7 @@ void MacroAssembler::movoop(Register dst, jobject obj, Register tmp) { } // Move a metadata address into a register. -void MacroAssembler::mov_metadata(Register dst, Metadata* obj, Register tmp) { +void MacroAssembler::mov_metadata(Register dst, Metadata* obj) { assert((uintptr_t)obj < (1ull << 48), "48-bit overflow in metadata"); int oop_index; if (obj == nullptr) { @@ -3825,15 +3825,17 @@ void MacroAssembler::mov_metadata(Register dst, Metadata* obj, Register tmp) { oop_index = oop_recorder()->find_index(obj); } RelocationHolder rspec = metadata_Relocation::spec(oop_index); - movptr(dst, Address((address)obj, rspec), tmp); + movptr(dst, Address((address)obj, rspec)); } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); ld(offset, Address(value_klass, ValueKlass::adr_members_offset())); lwu(offset, Address(offset, ValueKlass::payload_offset_offset())); } void MacroAssembler::payload_address(Register oop, Register data, Register value_klass) { + assert(Arguments::is_valhalla_enabled(), "Must be"); assert_different_registers(data, t0); // ((address) (void*) o) + vk->payload_offset(); Register offset = (data == oop) ? t0 : data; @@ -3932,21 +3934,25 @@ void MacroAssembler::null_check(Register reg, int offset) { } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_bit(temp_reg, flags, ResolvedFieldEntry::is_null_free_value_type_shift); bnez(temp_reg, is_null_free_value_type); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_bit(temp_reg, flags, ResolvedFieldEntry::is_null_free_value_type_shift); beqz(temp_reg, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); test_bit(temp_reg, flags, ResolvedFieldEntry::is_flat_shift); bnez(temp_reg, is_flat); } void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(markword, t1); mv(t1, markWord::value_type_pattern_mask); andr(markword, markword, t1); @@ -3955,6 +3961,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va } void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); assert_different_registers(tmp, t0); if (can_be_null) { beqz(object, not_value_type); @@ -3979,22 +3986,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int } void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label&is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_bit(t0, lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace)); bnez(t0, is_flat_array); } @@ -6873,6 +6885,66 @@ void MacroAssembler::float_to_float16_NaN(Register dst, FloatRegister src, orr(dst, tmp2, tmp1); } +// j.l.Float.float16ToFloat +void MacroAssembler::flt16_to_flt(FloatRegister dst, Register src) { + assert_different_registers(src, t0, t1); + Label L_NaN_Inf, L_done; + + // On riscv, NaN needs a special process as fcvt does not work in that case. + // On riscv, Inf does not need a special process as fcvt can handle it correctly. + // but we consider to get the slow path to process NaN and Inf at the same time, + // as both of them are rare cases, and if we try to get the slow path to handle + // only NaN case it would sacrifise the performance for normal cases, + // i.e. non-NaN and non-Inf cases. + + // check whether it's a NaN or +/- Inf. + mv(t0, 0x7c00); + andr(t1, src, t0); + beq(t0, t1, L_NaN_Inf); + + // non-NaN or non-Inf cases, just use built-in instructions. + fmv_h_x(dst, src); + fcvt_s_h(dst, dst); + j(L_done); + + bind(L_NaN_Inf); + // construct a NaN in 32 bits from the NaN in 16 bits, + // we need the payloads of non-canonical NaNs to be preserved. + mv(t1, 0x7f800000); + // The upper 16 bits of a short argument are unspecified. Sign-extend the + // low 16 bits before shifting so that the float sign bit comes from the + // float16 sign bit. + sext(t0, src, 16); + slli(t0, t0, 13); + orr(t1, t0, t1); + fmv_w_x(dst, t1); + + bind(L_done); +} + +// j.l.Float.floatToFloat16 +void MacroAssembler::flt_to_flt16(Register dst, FloatRegister src, FloatRegister ftmp) { + assert_different_registers(dst, t0, t1); + Label L_NaN, L_done; + + // On riscv, NaN needs a special process as fcvt does not work in that case. + + // check whether it's a NaN. + // replace fclass with feq as performance optimization. + feq_s(t0, src, src); + beqz(t0, L_NaN); + + // non-NaN cases, just use built-in instructions. + fcvt_h_s(ftmp, src); + fmv_x_h(dst, ftmp); + j(L_done); + + bind(L_NaN); + float_to_float16_NaN(dst, src, t0, t1); + + bind(L_done); +} + #define FCVT_SAFE(FLOATCVT, FLOATSIG) \ void MacroAssembler::FLOATCVT##_safe(Register dst, FloatRegister src, Register tmp) { \ Label done; \ diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index 7a65ce5a27e5..de1352a1215a 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -191,8 +191,8 @@ class MacroAssembler: public Assembler { void resolve_jobject(Register value, Register tmp1, Register tmp2); void resolve_global_jobject(Register value, Register tmp1, Register tmp2); - void movoop(Register dst, jobject obj, Register tmp = noreg); - void mov_metadata(Register dst, Metadata* obj, Register tmp = noreg); + void movoop(Register dst, jobject obj); + void mov_metadata(Register dst, Metadata* obj); void bang_stack_size(Register size, Register tmp); void set_narrow_oop(Register dst, jobject obj); void set_narrow_klass(Register dst, Klass* k); @@ -1560,6 +1560,13 @@ INSN(sd_release, sd, sd_rl); // Helper routine processing the slow path of NaN when converting float to float16 void float_to_float16_NaN(Register dst, FloatRegister src, Register tmp1, Register tmp2); + // j.l.Float.float16ToFloat and j.l.Float.floatToFloat16, with the NaN handling + // emitted inline. C2 uses C2_MacroAssembler::float16_to_float() and + // C2_MacroAssembler::float_to_float16() instead, which keep it out of line. + // Both clobber t0 and t1. + void flt16_to_flt(FloatRegister dst, Register src); + void flt_to_flt16(Register dst, FloatRegister src, FloatRegister ftmp); + // vector load/store unit-stride instructions void vlex_v(VectorRegister vd, Register base, Assembler::SEW sew, VectorMask vm = unmasked) { switch (sew) { @@ -1598,11 +1605,15 @@ INSN(sd_release, sd, sd_rl); } // vector pseudo instructions - // rotate vector register left with shift bits, 32-bit version + // Rotate 32-bit vector elements left. The caller must configure SEW=32. inline void vrole32_vi(VectorRegister vd, uint32_t shift, VectorRegister tmp_vr) { - vsrl_vi(tmp_vr, vd, 32 - shift); - vsll_vi(vd, vd, shift); - vor_vv(vd, vd, tmp_vr); + if (UseZvbb) { + vror_vi(vd, vd, 32 - shift); + } else { + vsrl_vi(tmp_vr, vd, 32 - shift); + vsll_vi(vd, vd, shift); + vor_vv(vd, vd, tmp_vr); + } } inline void vl1r_v(VectorRegister vd, Register rs) { diff --git a/src/hotspot/cpu/riscv/matcher_riscv.hpp b/src/hotspot/cpu/riscv/matcher_riscv.hpp index bb3d2d015f2d..570bfbf711ad 100644 --- a/src/hotspot/cpu/riscv/matcher_riscv.hpp +++ b/src/hotspot/cpu/riscv/matcher_riscv.hpp @@ -129,11 +129,6 @@ return UseRVV; } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static bool supports_vector_variable_rotates(void) { return UseZvbb; diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 8dfc989846e5..5513eafa7762 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -2372,9 +2372,9 @@ encode %{ } else { relocInfo::relocType rtype = $src->constant_reloc(); if (rtype == relocInfo::oop_type) { - __ movoop(dst_reg, (jobject)con, t0); + __ movoop(dst_reg, (jobject)con); } else if (rtype == relocInfo::metadata_type) { - __ mov_metadata(dst_reg, (Metadata*)con, t0); + __ mov_metadata(dst_reg, (Metadata*)con); } else { assert(rtype == relocInfo::none || rtype == relocInfo::external_word_type, "unexpected reloc type"); // Load fake address constants as a plain immediate. They are not real @@ -10870,6 +10870,23 @@ instruct far_cmpP_narrowOop_imm0_branch(cmpOpEqNe cmp, iRegN op1, immP0 zero, la // --------- CMoveI --------- +instruct cmovI_cmpI_cmpL_zero_one(iRegINoSp dst, iRegIorL op1, iRegIorL op2, + cmpOp cop, immI_1 one, immI0 zero) %{ + match(Set dst (CMoveI (Binary cop (CmpI op1 op2)) (Binary one zero))); + match(Set dst (CMoveI (Binary cop (CmpL op1 op2)) (Binary one zero))); + + ins_cost(ALU_COST * 2); + format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpI_cmpL_zero_one" %} + + ins_encode %{ + __ enc_cmove_zero_one($cop$$cmpcode, + as_Register($op1$$reg), as_Register($op2$$reg), + as_Register($dst$$reg)); + %} + + ins_pipe(pipe_class_compare); +%} + instruct cmovI_cmpI(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOp cop) %{ match(Set dst (CMoveI (Binary cop (CmpI op1 op2)) (Binary dst src))); ins_cost(ALU_COST + BRANCH_COST); @@ -10887,6 +10904,23 @@ instruct cmovI_cmpI(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOp cop) % ins_pipe(pipe_class_compare); %} +instruct cmovI_cmpU_cmpUL_zero_one(iRegINoSp dst, iRegIorL op1, iRegIorL op2, + cmpOpU cop, immI_1 one, immI0 zero) %{ + match(Set dst (CMoveI (Binary cop (CmpU op1 op2)) (Binary one zero))); + match(Set dst (CMoveI (Binary cop (CmpUL op1 op2)) (Binary one zero))); + + ins_cost(ALU_COST * 2); + format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpU_cmpUL_zero_one" %} + + ins_encode %{ + __ enc_cmove_zero_one($cop$$cmpcode | C2_MacroAssembler::unsigned_branch_mask, + as_Register($op1$$reg), as_Register($op2$$reg), + as_Register($dst$$reg)); + %} + + ins_pipe(pipe_class_compare); +%} + instruct cmovI_cmpU(iRegINoSp dst, iRegI src, iRegI op1, iRegI op2, cmpOpU cop) %{ match(Set dst (CMoveI (Binary cop (CmpU op1 op2)) (Binary dst src))); ins_cost(ALU_COST + BRANCH_COST); @@ -10938,6 +10972,22 @@ instruct cmovI_cmpUL(iRegINoSp dst, iRegI src, iRegL op1, iRegL op2, cmpOpU cop) ins_pipe(pipe_class_compare); %} +instruct cmovI_cmpF_zero_one(iRegINoSp dst, fRegF op1, fRegF op2, + cmpOp cop, immI_1 one, immI0 zero) %{ + match(Set dst (CMoveI (Binary cop (CmpF op1 op2)) (Binary one zero))); + + ins_cost(ALU_COST * 2); + format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpF_zero_one" %} + + ins_encode %{ + __ enc_cmove_cmp_fp_zero_one($cop$$cmpcode, + as_FloatRegister($op1$$reg), as_FloatRegister($op2$$reg), + as_Register($dst$$reg)); + %} + + ins_pipe(pipe_class_compare); +%} + instruct cmovI_cmpF(iRegINoSp dst, iRegI src, fRegF op1, fRegF op2, cmpOp cop) %{ match(Set dst (CMoveI (Binary cop (CmpF op1 op2)) (Binary dst src))); ins_cost(ALU_COST + BRANCH_COST); @@ -10955,6 +11005,22 @@ instruct cmovI_cmpF(iRegINoSp dst, iRegI src, fRegF op1, fRegF op2, cmpOp cop) % ins_pipe(pipe_class_compare); %} +instruct cmovI_cmpD_zero_one(iRegINoSp dst, fRegD op1, fRegD op2, + cmpOp cop, immI_1 one, immI0 zero) %{ + match(Set dst (CMoveI (Binary cop (CmpD op1 op2)) (Binary one zero))); + + ins_cost(ALU_COST * 2); + format %{ "CMoveI $dst, ($op1 $cop $op2), 1, 0\t#@cmovI_cmpD_zero_one" %} + + ins_encode %{ + __ enc_cmove_cmp_fp_zero_one($cop$$cmpcode | C2_MacroAssembler::double_branch_mask, + as_FloatRegister($op1$$reg), as_FloatRegister($op2$$reg), + as_Register($dst$$reg)); + %} + + ins_pipe(pipe_class_compare); +%} + instruct cmovI_cmpD(iRegINoSp dst, iRegI src, fRegD op1, fRegD op2, cmpOp cop) %{ match(Set dst (CMoveI (Binary cop (CmpD op1 op2)) (Binary dst src))); ins_cost(ALU_COST + BRANCH_COST); diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index f7951a4a2d12..09c790f6dd10 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -47,6 +47,36 @@ source %{ } } + // test_predicate selects the mask reduction; cmpcode selects the outer eq/ne condition. + static void vectorTestCMove(C2_MacroAssembler* masm, int cmpcode, Register dst, Register src, + VectorRegister mask, BasicType bt, uint vector_length, BoolTest::mask test_predicate) { + assert(test_predicate == BoolTest::ne || test_predicate == BoolTest::overflow, "unsupported predicate"); + assert(cmpcode == BoolTest::eq || cmpcode == BoolTest::ne, "unsupported condition"); + __ vsetvli_helper(bt, vector_length); + __ vcpop_m(t0, mask); + if (test_predicate == BoolTest::overflow) { + // Subtract vector_length so we can compare against zr without + // materializing vector_length in another register. + __ sub(t0, t0, vector_length); + } + __ enc_cmove(cmpcode, t0, zr, dst, src); + } + + static void vectorTestCMove(C2_MacroAssembler* masm, int cmpcode, FloatRegister dst, FloatRegister src, + VectorRegister mask, BasicType bt, uint vector_length, + BoolTest::mask test_predicate, bool is_single) { + assert(test_predicate == BoolTest::ne || test_predicate == BoolTest::overflow, "unsupported predicate"); + assert(cmpcode == BoolTest::eq || cmpcode == BoolTest::ne, "unsupported condition"); + __ vsetvli_helper(bt, vector_length); + __ vcpop_m(t0, mask); + if (test_predicate == BoolTest::overflow) { + // Subtract vector_length so we can compare against zr without + // materializing vector_length in another register. + __ sub(t0, t0, vector_length); + } + __ enc_cmove_fp_cmp(cmpcode, t0, zr, dst, src, is_single); + } + bool Matcher::match_rule_supported_auto_vectorization(int opcode, int vlen, BasicType bt) { if (opcode == Op_MulReductionVF || opcode == Op_MulReductionVD) { return false; @@ -101,15 +131,6 @@ source %{ return false; } break; - case Op_VectorLoadShuffle: - case Op_VectorRearrange: - // vlen >= 4 is required, because min vector size for byte is 4 on riscv, - // VectorLoadShuffle is from byte to X, so it requires vlen >= 4. - // VectorRearrange depends on VectorLoadShuffle, so it also requires vlen >= 4. - if (vlen < 4) { - return false; - } - break; case Op_MulReductionVI: case Op_MulReductionVL: // When vlen < 4, our log2(vlen) implementation does not help to gain performance improvement. @@ -6155,6 +6176,115 @@ instruct cmovI_vtest_alltrue_negate(iRegINoSp dst, cmpOp cop, vRegMask op1, vReg ins_pipe(pipe_slow); %} +// CMove with register values and a condition produced by VectorTest. +// +// The two rules above directly materialize a boolean (0 or 1) from anyTrue()/allTrue(). +// The rules below select between arbitrary register values, using the population +// count to form the condition passed to the corresponding cmove encoder: +// +// anytrue: vcpop.m t0, op1 // t0 != 0 iff some element is set +// alltrue: vcpop.m t0, op1; sub t0, t0, vl // t0 == 0 iff every element is set +// +// The outer Bool condition ($cop) is restricted to eq/ne by cmpOpEqNe. +// Separate rules distinguish the VectorTest predicates: ne for anyTrue, +// overflow for allTrue. + +instruct cmovI_vtest_anytrue(iRegINoSp dst, iRegI src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne); + match(Set dst (CMoveI (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveI $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovI_vtest_anytrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::ne); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovI_vtest_alltrue(iRegINoSp dst, iRegI src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow); + match(Set dst (CMoveI (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveI $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovI_vtest_alltrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::overflow); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovL_vtest_anytrue(iRegLNoSp dst, iRegL src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne); + match(Set dst (CMoveL (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveL $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovL_vtest_anytrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::ne); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovL_vtest_alltrue(iRegLNoSp dst, iRegL src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow); + match(Set dst (CMoveL (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveL $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovL_vtest_alltrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$Register, $src$$Register, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::overflow); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovF_vtest_anytrue(fRegF dst, fRegF src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne); + match(Set dst (CMoveF (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveF $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovF_vtest_anytrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::ne, /* is_single */ true); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovF_vtest_alltrue(fRegF dst, fRegF src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow); + match(Set dst (CMoveF (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveF $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovF_vtest_alltrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::overflow, /* is_single */ true); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovD_vtest_anytrue(fRegD dst, fRegD src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::ne); + match(Set dst (CMoveD (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveD $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovD_vtest_anytrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::ne, /* is_single */ false); + %} + ins_pipe(pipe_slow); +%} + +instruct cmovD_vtest_alltrue(fRegD dst, fRegD src, cmpOpEqNe cop, vRegMask op1, vRegMask op2) %{ + predicate(static_cast(n->in(1)->in(2))->get_predicate() == BoolTest::overflow); + match(Set dst (CMoveD (Binary cop (VectorTest op1 op2)) (Binary dst src))); + format %{ "CMoveD $dst, (vectortest $cop $op1 $op2), $dst, $src\t#@cmovD_vtest_alltrue" %} + ins_encode %{ + vectorTestCMove(masm, $cop$$cmpcode, $dst$$FloatRegister, $src$$FloatRegister, + as_VectorRegister($op1$$reg), Matcher::vector_element_basic_type(this, $op1), + Matcher::vector_length(this, $op1), BoolTest::overflow, /* is_single */ false); + %} + ins_pipe(pipe_slow); +%} + // anytrue instruct vtest_anytrue_branch(cmpOpEqNe cop, vRegMask op1, vRegMask op2, label lbl) %{ diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index 074f80297aa1..9793dddb68be 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -2247,10 +2247,12 @@ class StubGenerator: public StubCodeGenerator { __ load_klass(t1, dst); __ bne(t1, scratch_src_klass, L_failed); - if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { // Check for flat value type array -> return -1 __ test_flat_array_oop(src, t1, L_failed); + } + if (Arguments::is_valhalla_enabled()) { // Check for null-free (non-flat) value type array -> handle as object array __ test_null_free_array_oop(src, t1, L_objArray); } @@ -5816,10 +5818,11 @@ class StubGenerator: public StubCodeGenerator { } } - // generate load for the i'th register - void gen_load(uint i, Register base) { - assert(i < L, "invalid i: %u", i); - __ ld(_regs[i], Address(base, 8 * i)); + // Generate loads for all cached registers. + void gen_loads(Register base) { + for (uint i = 0; i < L; i++) { + __ ld(_regs[i], Address(base, 8 * i)); + } } // add i'th 32-bit integer to dest @@ -5840,72 +5843,84 @@ class StubGenerator: public StubCodeGenerator { typedef RegCache<8> BufRegCache; - // a += value + x + ac; + // a += (d ^ (b & (c ^ d))) + x + ac; // a = Integer.rotateLeft(a, s) + b; - void m5_FF_GG_HH_II_epilogue(BufRegCache& reg_cache, - Register a, Register b, Register c, Register d, - int k, int s, int t, - Register value) { + void md5_FF(BufRegCache& reg_cache, + Register a, Register b, Register c, Register d, + int k, int s, int t, + Register rtmp1) { + // rtmp1 = c ^ d + __ xorr(rtmp1, c, d); + // a += ac __ addw(a, a, t, t1); - // a += x; + // rtmp1 = b & (c ^ d) = b & rtmp1 + __ andr(rtmp1, b, rtmp1); + + // a += x reg_cache.add_u32(a, k); - // a += value; - __ addw(a, a, value); - // a = Integer.rotateLeft(a, s) + b; + // rtmp1 = d ^ (b & (c ^ d)) = d ^ rtmp1 + __ xorr(rtmp1, d, rtmp1); + + // a += d ^ (b & (c ^ d)) + __ addw(a, a, rtmp1); + + // a = Integer.rotateLeft(a, s) + b __ rolw(a, a, s); __ addw(a, a, b); } - // a += ((b & c) | ((~b) & d)) + x + ac; + // a += ((c & (~d)) + (b & d)) + x + ac; // a = Integer.rotateLeft(a, s) + b; - void md5_FF(BufRegCache& reg_cache, + void md5_GG(BufRegCache& reg_cache, Register a, Register b, Register c, Register d, int k, int s, int t, Register rtmp1, Register rtmp2) { - // rtmp1 = b & c - __ andr(rtmp1, b, c); - - // rtmp2 = (~b) & d - __ andn(rtmp2, d, b); - - // rtmp1 = (b & c) | ((~b) & d) - __ orr(rtmp1, rtmp1, rtmp2); + // a += x + ac + reg_cache.add_u32(a, k); + __ addw(a, a, t, t1); - m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1); - } + // rtmp1 = c & (~d) + __ andn(rtmp1, c, d); - // a += ((b & d) | (c & (~d))) + x + ac; - // a = Integer.rotateLeft(a, s) + b; - void md5_GG(BufRegCache& reg_cache, - Register a, Register b, Register c, Register d, - int k, int s, int t, - Register rtmp1, Register rtmp2) { - // rtmp1 = b & d - __ andr(rtmp1, b, d); + // rtmp2 = b & d + __ andr(rtmp2, b, d); - // rtmp2 = c & (~d) - __ andn(rtmp2, c, d); + // a += c & (~d) + __ addw(a, a, rtmp1); - // rtmp1 = (b & d) | (c & (~d)) - __ orr(rtmp1, rtmp1, rtmp2); + // a += b & d + __ addw(a, a, rtmp2); - m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1); + // a = Integer.rotateLeft(a, s) + b + __ rolw(a, a, s); + __ addw(a, a, b); } - // a += ((b ^ c) ^ d) + x + ac; + // a += (b ^ (c ^ d)) + x + ac; // a = Integer.rotateLeft(a, s) + b; void md5_HH(BufRegCache& reg_cache, Register a, Register b, Register c, Register d, int k, int s, int t, - Register rtmp1, Register rtmp2) { - // rtmp1 = (b ^ c) ^ d - __ xorr(rtmp2, b, c); - __ xorr(rtmp1, rtmp2, d); + Register rtmp1) { + // rtmp1 = c ^ d + __ xorr(rtmp1, c, d); + + // a += x + ac + reg_cache.add_u32(a, k); + __ addw(a, a, t, t1); + + // rtmp1 = b ^ (c ^ d) + __ xorr(rtmp1, b, rtmp1); - m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1); + // a += b ^ (c ^ d) + __ addw(a, a, rtmp1); + + // a = Integer.rotateLeft(a, s) + b + __ rolw(a, a, s); + __ addw(a, a, b); } // a += (c ^ (b | (~d))) + x + ac; @@ -5914,11 +5929,20 @@ class StubGenerator: public StubCodeGenerator { Register a, Register b, Register c, Register d, int k, int s, int t, Register rtmp1, Register rtmp2) { + // a += x + ac + reg_cache.add_u32(a, k); + __ addw(a, a, t, t1); + // rtmp1 = c ^ (b | (~d)) __ orn(rtmp2, b, d); __ xorr(rtmp1, c, rtmp2); - m5_FF_GG_HH_II_epilogue(reg_cache, a, b, c, d, k, s, t, rtmp1); + // a += c ^ (b | (~d)) + __ addw(a, a, rtmp1); + + // a = Integer.rotateLeft(a, s) + b + __ rolw(a, a, s); + __ addw(a, a, b); } // Arguments: @@ -6077,31 +6101,25 @@ class StubGenerator: public StubCodeGenerator { __ mv(c, state2); __ mv(d, state3); + reg_cache.gen_loads(buf); + // Round 1 - reg_cache.gen_load(0, buf); - md5_FF(reg_cache, a, b, c, d, 0, S11, 0xd76aa478, rtmp1, rtmp2); - md5_FF(reg_cache, d, a, b, c, 1, S12, 0xe8c7b756, rtmp1, rtmp2); - reg_cache.gen_load(1, buf); - md5_FF(reg_cache, c, d, a, b, 2, S13, 0x242070db, rtmp1, rtmp2); - md5_FF(reg_cache, b, c, d, a, 3, S14, 0xc1bdceee, rtmp1, rtmp2); - reg_cache.gen_load(2, buf); - md5_FF(reg_cache, a, b, c, d, 4, S11, 0xf57c0faf, rtmp1, rtmp2); - md5_FF(reg_cache, d, a, b, c, 5, S12, 0x4787c62a, rtmp1, rtmp2); - reg_cache.gen_load(3, buf); - md5_FF(reg_cache, c, d, a, b, 6, S13, 0xa8304613, rtmp1, rtmp2); - md5_FF(reg_cache, b, c, d, a, 7, S14, 0xfd469501, rtmp1, rtmp2); - reg_cache.gen_load(4, buf); - md5_FF(reg_cache, a, b, c, d, 8, S11, 0x698098d8, rtmp1, rtmp2); - md5_FF(reg_cache, d, a, b, c, 9, S12, 0x8b44f7af, rtmp1, rtmp2); - reg_cache.gen_load(5, buf); - md5_FF(reg_cache, c, d, a, b, 10, S13, 0xffff5bb1, rtmp1, rtmp2); - md5_FF(reg_cache, b, c, d, a, 11, S14, 0x895cd7be, rtmp1, rtmp2); - reg_cache.gen_load(6, buf); - md5_FF(reg_cache, a, b, c, d, 12, S11, 0x6b901122, rtmp1, rtmp2); - md5_FF(reg_cache, d, a, b, c, 13, S12, 0xfd987193, rtmp1, rtmp2); - reg_cache.gen_load(7, buf); - md5_FF(reg_cache, c, d, a, b, 14, S13, 0xa679438e, rtmp1, rtmp2); - md5_FF(reg_cache, b, c, d, a, 15, S14, 0x49b40821, rtmp1, rtmp2); + md5_FF(reg_cache, a, b, c, d, 0, S11, 0xd76aa478, rtmp1); + md5_FF(reg_cache, d, a, b, c, 1, S12, 0xe8c7b756, rtmp1); + md5_FF(reg_cache, c, d, a, b, 2, S13, 0x242070db, rtmp1); + md5_FF(reg_cache, b, c, d, a, 3, S14, 0xc1bdceee, rtmp1); + md5_FF(reg_cache, a, b, c, d, 4, S11, 0xf57c0faf, rtmp1); + md5_FF(reg_cache, d, a, b, c, 5, S12, 0x4787c62a, rtmp1); + md5_FF(reg_cache, c, d, a, b, 6, S13, 0xa8304613, rtmp1); + md5_FF(reg_cache, b, c, d, a, 7, S14, 0xfd469501, rtmp1); + md5_FF(reg_cache, a, b, c, d, 8, S11, 0x698098d8, rtmp1); + md5_FF(reg_cache, d, a, b, c, 9, S12, 0x8b44f7af, rtmp1); + md5_FF(reg_cache, c, d, a, b, 10, S13, 0xffff5bb1, rtmp1); + md5_FF(reg_cache, b, c, d, a, 11, S14, 0x895cd7be, rtmp1); + md5_FF(reg_cache, a, b, c, d, 12, S11, 0x6b901122, rtmp1); + md5_FF(reg_cache, d, a, b, c, 13, S12, 0xfd987193, rtmp1); + md5_FF(reg_cache, c, d, a, b, 14, S13, 0xa679438e, rtmp1); + md5_FF(reg_cache, b, c, d, a, 15, S14, 0x49b40821, rtmp1); // Round 2 md5_GG(reg_cache, a, b, c, d, 1, S21, 0xf61e2562, rtmp1, rtmp2); @@ -6122,22 +6140,22 @@ class StubGenerator: public StubCodeGenerator { md5_GG(reg_cache, b, c, d, a, 12, S24, 0x8d2a4c8a, rtmp1, rtmp2); // Round 3 - md5_HH(reg_cache, a, b, c, d, 5, S31, 0xfffa3942, rtmp1, rtmp2); - md5_HH(reg_cache, d, a, b, c, 8, S32, 0x8771f681, rtmp1, rtmp2); - md5_HH(reg_cache, c, d, a, b, 11, S33, 0x6d9d6122, rtmp1, rtmp2); - md5_HH(reg_cache, b, c, d, a, 14, S34, 0xfde5380c, rtmp1, rtmp2); - md5_HH(reg_cache, a, b, c, d, 1, S31, 0xa4beea44, rtmp1, rtmp2); - md5_HH(reg_cache, d, a, b, c, 4, S32, 0x4bdecfa9, rtmp1, rtmp2); - md5_HH(reg_cache, c, d, a, b, 7, S33, 0xf6bb4b60, rtmp1, rtmp2); - md5_HH(reg_cache, b, c, d, a, 10, S34, 0xbebfbc70, rtmp1, rtmp2); - md5_HH(reg_cache, a, b, c, d, 13, S31, 0x289b7ec6, rtmp1, rtmp2); - md5_HH(reg_cache, d, a, b, c, 0, S32, 0xeaa127fa, rtmp1, rtmp2); - md5_HH(reg_cache, c, d, a, b, 3, S33, 0xd4ef3085, rtmp1, rtmp2); - md5_HH(reg_cache, b, c, d, a, 6, S34, 0x04881d05, rtmp1, rtmp2); - md5_HH(reg_cache, a, b, c, d, 9, S31, 0xd9d4d039, rtmp1, rtmp2); - md5_HH(reg_cache, d, a, b, c, 12, S32, 0xe6db99e5, rtmp1, rtmp2); - md5_HH(reg_cache, c, d, a, b, 15, S33, 0x1fa27cf8, rtmp1, rtmp2); - md5_HH(reg_cache, b, c, d, a, 2, S34, 0xc4ac5665, rtmp1, rtmp2); + md5_HH(reg_cache, a, b, c, d, 5, S31, 0xfffa3942, rtmp1); + md5_HH(reg_cache, d, a, b, c, 8, S32, 0x8771f681, rtmp1); + md5_HH(reg_cache, c, d, a, b, 11, S33, 0x6d9d6122, rtmp1); + md5_HH(reg_cache, b, c, d, a, 14, S34, 0xfde5380c, rtmp1); + md5_HH(reg_cache, a, b, c, d, 1, S31, 0xa4beea44, rtmp1); + md5_HH(reg_cache, d, a, b, c, 4, S32, 0x4bdecfa9, rtmp1); + md5_HH(reg_cache, c, d, a, b, 7, S33, 0xf6bb4b60, rtmp1); + md5_HH(reg_cache, b, c, d, a, 10, S34, 0xbebfbc70, rtmp1); + md5_HH(reg_cache, a, b, c, d, 13, S31, 0x289b7ec6, rtmp1); + md5_HH(reg_cache, d, a, b, c, 0, S32, 0xeaa127fa, rtmp1); + md5_HH(reg_cache, c, d, a, b, 3, S33, 0xd4ef3085, rtmp1); + md5_HH(reg_cache, b, c, d, a, 6, S34, 0x04881d05, rtmp1); + md5_HH(reg_cache, a, b, c, d, 9, S31, 0xd9d4d039, rtmp1); + md5_HH(reg_cache, d, a, b, c, 12, S32, 0xe6db99e5, rtmp1); + md5_HH(reg_cache, c, d, a, b, 15, S33, 0x1fa27cf8, rtmp1); + md5_HH(reg_cache, b, c, d, a, 2, S34, 0xc4ac5665, rtmp1); // Round 4 md5_II(reg_cache, a, b, c, d, 0, S41, 0xf4292244, rtmp1, rtmp2); @@ -7820,6 +7838,7 @@ static const int64_t right_3_bits = right_n_bits(3); __ kernel_crc32(crc, buf, len, c_rarg3, c_rarg4, c_rarg5, c_rarg6, // tmp's for tables c_rarg7, t2, t3, t4, t5, t6); // misc tmps + __ sext(crc, crc, 32); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(); @@ -7857,6 +7876,7 @@ static const int64_t right_3_bits = right_n_bits(3); c_rarg3, c_rarg4, // byte_table, clmul_table c_rarg5, c_rarg6, c_rarg7, // accum_hi, k1, k2 t2, t3, t4); // scratch1, scratch2, fold_end + __ sext(crc, crc, 32); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(); diff --git a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp index a98b9e496671..2848ccf8a133 100644 --- a/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateInterpreterGenerator_riscv.cpp @@ -967,9 +967,55 @@ address TemplateInterpreterGenerator::generate_CRC32C_updateBytes_entry(Abstract return entry; } -// Not supported -address TemplateInterpreterGenerator::generate_Float_float16ToFloat_entry() { return nullptr; } -address TemplateInterpreterGenerator::generate_Float_floatToFloat16_entry() { return nullptr; } +/** + * Method entry for static method: + * java.lang.Float.float16ToFloat(short floatBinary16) + */ +address TemplateInterpreterGenerator::generate_Float_float16ToFloat_entry() { + assert(VM_Version::supports_float16_float_conversion(), "this intrinsic is not supported"); + if (StubRoutines::hf2f_adr() == nullptr) { + return nullptr; + } + + // x19_sender_sp: sender sp + // stack: + // [ arg ] <-- esp + // retaddr in ra + // result in f10 + + address entry_point = __ pc(); + __ lh(c_rarg0, Address(esp)); // Load and sign-extend the low 16 bits of the argument + __ andi(sp, x19_sender_sp, -16); // Restore caller's SP + // The stub is a leaf which returns through 'ra', so it returns straight + // back to our caller: this is a tail call. + __ far_jump(RuntimeAddress(StubRoutines::hf2f_adr())); + return entry_point; +} + +/** + * Method entry for static method: + * java.lang.Float.floatToFloat16(float value) + */ +address TemplateInterpreterGenerator::generate_Float_floatToFloat16_entry() { + assert(VM_Version::supports_float16_float_conversion(), "this intrinsic is not supported"); + if (StubRoutines::f2hf_adr() == nullptr) { + return nullptr; + } + + // x19_sender_sp: sender sp + // stack: + // [ arg ] <-- esp + // retaddr in ra + // result in x10 + + address entry_point = __ pc(); + __ flw(f10, Address(esp)); + __ andi(sp, x19_sender_sp, -16); // Restore caller's SP + // The stub is a leaf which returns through 'ra', so it returns straight + // back to our caller: this is a tail call. + __ far_jump(RuntimeAddress(StubRoutines::f2hf_adr())); + return entry_point; +} void TemplateInterpreterGenerator::bang_stack_shadow_pages(bool native_call) { // See more discussion in stackOverflow.hpp. diff --git a/src/hotspot/cpu/riscv/templateTable_riscv.cpp b/src/hotspot/cpu/riscv/templateTable_riscv.cpp index 82b2f0eeb3d9..02ad154bf652 100644 --- a/src/hotspot/cpu/riscv/templateTable_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateTable_riscv.cpp @@ -46,6 +46,7 @@ #include "prims/methodHandles.hpp" #include "runtime/arguments.hpp" #include "runtime/frame.inline.hpp" +#include "runtime/globals.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "runtime/synchronizer.hpp" @@ -1906,12 +1907,12 @@ void TemplateTable::if_acmp(Condition cc) { Label taken, not_taken; __ pop_ptr(x11); - __ profile_acmp(x12, x11, x10, x14); + if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(x12, x11, x10, x14); - Register is_value_type_mask = t1; - __ mv(is_value_type_mask, markWord::value_type_pattern); + Register is_value_type_mask = t1; + __ mv(is_value_type_mask, markWord::value_type_pattern); - if (Arguments::is_valhalla_enabled()) { // The substitutability test is only necessary if x11 and x10 are not the same... if (cc == equal) { __ beq(x11, x10, taken); @@ -2181,7 +2182,6 @@ void TemplateTable::fast_binaryswitch() { __ load_unsigned_byte(t0, Address(temp, 0)); __ add(xbcp, xbcp, j); - __ la(xbcp, Address(xbcp, 0)); __ dispatch_only(vtos, /*generate_poll*/true); // default case -> j = default offset @@ -2194,7 +2194,6 @@ void TemplateTable::fast_binaryswitch() { __ load_unsigned_byte(t0, Address(temp, 0)); __ add(xbcp, xbcp, j); - __ la(xbcp, Address(xbcp, 0)); __ dispatch_only(vtos, /*generate_poll*/true); } @@ -2721,7 +2720,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ j(Done); } else { Label is_flat; - __ test_field_is_flat(flags, x28, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, x28, is_flat); + } __ load_heap_oop(x10, field, x28, x29); __ push(atos); if (rc == may_rewrite) { @@ -2729,15 +2730,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr } __ j(Done); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker) - __ mv(x10, obj); - __ read_flat_field(cache, x10); - __ verify_oop(x10); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, bc, x11); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker) + __ mv(x10, obj); + __ read_flat_field(cache, x10); + __ verify_oop(x10); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc, x11); + } + __ j(Done); } - __ j(Done); } } @@ -2996,7 +2999,9 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr __ j(Done); } else { Label null_free_reference, is_flat, rewrite_value; - __ test_field_is_flat(flags, x28, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, x28, is_flat); + } __ test_field_is_null_free_value_type(flags, x28, null_free_reference); pop_and_check_object(obj); { @@ -3022,8 +3027,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr } __ j(rewrite_value); __ bind(is_flat); - pop_and_check_object(x17); - __ write_flat_field(cache, index, flags, x17); + if (UseFieldFlattening) { + pop_and_check_object(x17); + __ write_flat_field(cache, index, flags, x17); + } __ bind(rewrite_value); if (rc == may_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, bc, x9, true, byte_no); @@ -3275,17 +3282,25 @@ void TemplateTable::fast_storefield(TosState state) { switch (bytecode()) { case Bytecodes::_fast_vputfield: { - Label is_flat, done; - __ test_field_is_flat(x13, x28, is_flat); - __ null_check(x10); - __ store_heap_oop(field, x10, x28, x29, x15, IN_HEAP); - __ j(done); - __ bind(is_flat); - __ load_field_entry(x14, x13); - // Re-shuffle registers because of VM calls calling convention - __ mv(x17, x12); - __ write_flat_field(x14, x16, x18, x17); - __ bind(done); + if (Arguments::is_valhalla_enabled()) { + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(x13, x28, is_flat); + } + __ null_check(x10); + __ store_heap_oop(field, x10, x28, x29, x15, IN_HEAP); + __ j(done); + __ bind(is_flat); + if (UseFieldFlattening) { + __ load_field_entry(x14, x13); + // Re-shuffle registers because of VM calls calling convention + __ mv(x17, x12); + __ write_flat_field(x14, x16, x18, x17); + } + __ bind(done); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_aputfield: @@ -3373,9 +3388,13 @@ void TemplateTable::fast_accessfield(TosState state) { switch (bytecode()) { case Bytecodes::_fast_vgetfield: { - // field is flat - __ read_flat_field(x12, x10); - __ verify_oop(x10); + if (UseFieldFlattening) { + // field is flat + __ read_flat_field(x12, x10); + __ verify_oop(x10); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_agetfield: @@ -4070,8 +4089,10 @@ void TemplateTable::monitorenter() { __ null_check(x10); Label is_value_type; - __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(t0, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(t0, is_value_type); + } const Address monitor_block_top( fp, frame::interpreter_frame_monitor_block_top_offset * wordSize); @@ -4174,9 +4195,11 @@ void TemplateTable::monitorenter() { __ dispatch_next(vtos); __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), x10); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), x10); + __ should_not_reach_here(); + } } void TemplateTable::monitorexit() { @@ -4185,16 +4208,18 @@ void TemplateTable::monitorexit() { // check for null object __ null_check(x10); - const int is_value_type_mask = markWord::value_type_pattern; - Label has_identity; - __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes())); - __ mv(t1, is_value_type_mask); - __ andr(t0, t0, t1); - __ bne(t0, t1, has_identity); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_illegal_monitor_state_exception)); - __ should_not_reach_here(); - __ bind(has_identity); + if (Arguments::is_valhalla_enabled()) { + const int is_value_type_mask = markWord::value_type_pattern; + Label has_identity; + __ ld(t0, Address(x10, oopDesc::mark_offset_in_bytes())); + __ mv(t1, is_value_type_mask); + __ andr(t0, t0, t1); + __ bne(t0, t1, has_identity); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + } const Address monitor_block_top( fp, frame::interpreter_frame_monitor_block_top_offset * wordSize); diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 58901ae4d361..68e870fee2d0 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -559,10 +559,20 @@ class VM_Version : public Abstract_VM_Version { static bool supports_fast_class_init_checks() { return true; } static bool supports_fencei_barrier() { return ext_Zifencei.enabled(); } + // Zfhmin only provides half-precision load/store/convert instructions, + // Zfh additionally provides half-precision arithmetic. Either one is + // enough to convert between float16 and float. static bool supports_float16_float_conversion() { return UseZfh || UseZfhmin; } + // Shared code gates the Float.float16ToFloat/floatToFloat16 intrinsics on + // this, and those only need the conversion instructions. Note that it does + // not imply half-precision arithmetic is available, which needs UseZfh. + static bool supports_float16() { + return supports_float16_float_conversion(); + } + // Check intrinsic support static bool is_intrinsic_supported(vmIntrinsicID id); diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp index 11c65ec91bc2..f3407d3af1cf 100644 --- a/src/hotspot/cpu/s390/assembler_s390.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.hpp @@ -3281,6 +3281,15 @@ class Assembler : public AbstractAssembler { static bool is_z_nop(address x) { return is_z_nop(* (short *) x); } + // A post-call NOP is encoded as CLFI SP, imm32 (RIL-a format, 6 bytes). + // Byte 0 = 0xC2 (opcode1), byte 1 = 0xFF (R1=SP=15 in bits 39..36, + // opcode2=0xF in bits 35..32). The 4-byte imm32 carries the payload. + // Result goes only to the condition code, which is caller-scratch after a call. + static bool is_post_call_nop(address x) { + // Check the first 2 bytes for the CLFI SP opcode signature (0xC2 0xFF). + // Only 2 bytes are read to avoid a page-boundary out-of-bounds access. + return (*(unsigned short*) x) == (unsigned short)((0xc2 << 8) | 0xff); + } static bool is_z_illtrap(address x) { return *(uint8_t*)x == 0u; } diff --git a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp index d7799769ff83..d410668306a0 100644 --- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp @@ -2063,11 +2063,13 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { } // Handle value type arrays - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); @@ -3065,6 +3067,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); Register tmp = op->tmp()->as_pointer_register(); LIR_Address* mdo_addr = op->mdp()->as_address_ptr(); @@ -3185,13 +3188,14 @@ void LIR_Assembler::emit_opSubstitutabilityCheck(LIR_OpSubstitutabilityCheck* op } void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ compare64_and_branch(obj, (intptr_t)0, Assembler::bcondEqual, *slow_path->entry()); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); - __ test_flat_array_oop(obj, tmp, *slow_path->entry()); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry()); } } diff --git a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp index 725d3623128a..a7d186c0517e 100644 --- a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp +++ b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp @@ -331,7 +331,7 @@ inline void Thaw::patch_caller_links(intptr_t* sp, intptr_t* bottom) { callers_sp = _cont.entryFP(); } else { assert(!Interpreter::contains(pc), "sp:" PTR_FORMAT " pc:" PTR_FORMAT, p2i(sp), p2i(pc)); - CodeBlob* cb = CodeCache::find_blob(pc); + CodeBlob* cb = CodeCache::find_blob_fast(pc); callers_sp = sp + cb->frame_size(); } // set the back link diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp index 2946ed60d3e7..9256e9ae0d3b 100644 --- a/src/hotspot/cpu/s390/frame_s390.hpp +++ b/src/hotspot/cpu/s390/frame_s390.hpp @@ -489,10 +489,19 @@ // Mark a frame as not fully initialized. Must not be used for frames in the valid back chain. void mark_not_fully_initialized() const { DEBUG_ONLY(own_abi()->callers_sp = NOT_FULLY_INITIALIZED;) } + enum class kind { + unknown, // The frame's pc is not necessarily in the CodeCache. + // CodeCache::find_blob_fast() can yield wrong results here and must not be used. + code_blob, // The frame's pc is known to be in the CodeCache but not likely in an nmethod. + // CodeCache::find_blob_fast() is correct but not faster. + nmethod // This frame is likely from an nmethod. + // CodeCache lookup is optimized via NativePostCallNop payload. + }; + private: // Initialize frame members (_pc and _sp must be given) - inline void setup(); + inline void setup(kind knd); public: @@ -500,6 +509,7 @@ inline frame(intptr_t* sp, intptr_t* fp, address pc); // To be used, if sp was not extended to match callee's calling convention. inline frame(intptr_t* sp, address pc, intptr_t* unextended_sp = nullptr, intptr_t* fp = nullptr, CodeBlob* cb = nullptr); + inline frame(intptr_t* sp, address pc, kind knd); inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap); inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map = nullptr); diff --git a/src/hotspot/cpu/s390/frame_s390.inline.hpp b/src/hotspot/cpu/s390/frame_s390.inline.hpp index 7acdd01a9c91..08f86de40e21 100644 --- a/src/hotspot/cpu/s390/frame_s390.inline.hpp +++ b/src/hotspot/cpu/s390/frame_s390.inline.hpp @@ -35,14 +35,14 @@ // Inline functions for z/Architecture frames: // Initialize frame members (_sp must be given) -inline void frame::setup() { +inline void frame::setup(kind knd) { if (_pc == nullptr) { _pc = (address)own_abi()->return_pc; assert(_pc != nullptr, "must have PC"); } if (_cb == nullptr) { - _cb = CodeCache::find_blob(_pc); + _cb = (knd == kind::nmethod) ? CodeCache::find_blob_fast(_pc) : CodeCache::find_blob(_pc); } if (_unextended_sp == nullptr) { @@ -99,20 +99,26 @@ inline frame::frame() : _sp(nullptr), _pc(nullptr), _cb(nullptr), _oop_map(nullp inline frame::frame(intptr_t* sp, address pc, intptr_t* unextended_sp, intptr_t* fp, CodeBlob* cb) : _sp(sp), _pc(pc), _cb(cb), _oop_map(nullptr), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) { - setup(); + setup(kind::nmethod); } -inline frame::frame(intptr_t* sp) : frame(sp, nullptr) {} +inline frame::frame(intptr_t* sp) : frame(sp, nullptr, kind::nmethod) {} inline frame::frame(intptr_t* sp, intptr_t* fp, address pc) : _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr), _deopt_state(unknown), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(nullptr), _fp(fp) { - setup(); + setup(kind::nmethod); +} + +inline frame::frame(intptr_t* sp, address pc, kind knd) + : _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr), + _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(sp), _fp(nullptr) { + setup(knd); } inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map) :_sp(sp), _pc(pc), _cb(cb), _oop_map(oop_map), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) { - setup(); + setup(kind::nmethod); } inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap) @@ -120,7 +126,7 @@ inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address // In thaw, non-heap frames use this constructor to pass oop_map. I don't know why. assert(_on_heap || _cb != nullptr, "these frames are always heap frames"); if (cb != nullptr) { - setup(); + setup(kind::nmethod); } } @@ -129,7 +135,7 @@ inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address inline frame::frame(void* sp, void* pc, void* unextended_sp) : _sp((intptr_t*)sp), _pc((address)pc), _cb(nullptr), _oop_map(nullptr), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp((intptr_t*)unextended_sp) { - setup(); + setup(kind::unknown); } #endif @@ -405,7 +411,7 @@ inline frame frame::sender_raw(RegisterMap* map) const { // Must be native-compiled frame, i.e. the marshaling code for native // methods that exists in the core system. - return frame(sender_sp(), sender_pc()); + return frame(sender_sp(), sender_pc(), kind::code_blob); } inline frame frame::sender_for_compiled_frame(RegisterMap *map) const { @@ -452,7 +458,7 @@ inline frame frame::sender_for_compiled_frame(RegisterMap *map) const { return Continuation::continuation_bottom_sender(map->thread(), *this, sender_sp); } } - return frame(sender_sp, sender_pc); + return frame(sender_sp, sender_pc, kind::nmethod); } template diff --git a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp index c3775f8038b9..5f0db9eabaa1 100644 --- a/src/hotspot/cpu/s390/interp_masm_s390.cpp +++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp @@ -1367,6 +1367,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp, Register left, Register right, Register tmp) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { assert_different_registers(mdp, left, right, tmp); Label profile_continue; @@ -1638,15 +1639,19 @@ template void InterpreterMacroAssembler::profile_array_type(Re profile_obj_type(array, Address(tmp1, in_bytes(ArrayData::array_offset())), tmp2); - Label not_flat; - test_non_flat_array_oop(array, tmp2, not_flat); - set_mdp_flag_at(tmp1, ArrayData::flat_array_byte_constant()); - bind(not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp2, not_flat); + set_mdp_flag_at(tmp1, ArrayData::flat_array_byte_constant()); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp2, not_null_free); - set_mdp_flag_at(tmp1, ArrayData::null_free_array_byte_constant()); - bind(not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp2, not_null_free); + set_mdp_flag_at(tmp1, ArrayData::null_free_array_byte_constant()); + bind(not_null_free); + } bind(profile_continue); } @@ -2216,6 +2221,7 @@ void InterpreterMacroAssembler::pop_interpreter_frame(Register return_pc, Regist //------------------------------------- void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, obj); assert_different_registers(entry, Z_ARG2); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); @@ -2223,6 +2229,7 @@ void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj); Label slow_path, done; diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp index b3e1b223bf0e..dd65e52b70a5 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp @@ -3939,6 +3939,7 @@ void MacroAssembler::null_check(Register reg, Register tmp, int64_t offset) { //------------------------------------- void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); static_assert(markWord::value_type_pattern <= 0x7FFF, "must fit in simm16 for z_chi"); z_nilf(markword, markWord::value_type_pattern_mask); z_chi(markword, markWord::value_type_pattern); @@ -3946,6 +3947,7 @@ void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_va } void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); if (can_be_null) { z_ltgr(object, object); branch_optimized(bcondEqual, not_value_type); @@ -3957,16 +3959,19 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Label& is_null_free) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); testbit(flags, ResolvedFieldEntry::is_null_free_value_type_shift); z_brc(Assembler::bcondAllOne, is_null_free); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); testbit(flags, ResolvedFieldEntry::is_null_free_value_type_shift); z_brc(Assembler::bcondAllZero, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); testbit(flags, ResolvedFieldEntry::is_flat_shift); z_brc(Assembler::bcondAllOne, is_flat); } @@ -4280,22 +4285,27 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int } void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label& is_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label& is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); testbit(lh, exact_log2(Klass::_lh_array_tag_flat_value_bit_inplace)); z_brnaz(is_flat_array); } @@ -4544,11 +4554,13 @@ void MacroAssembler::store_heap_oop(Register Roop, const Address &a, } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); z_lg(offset, Address(value_klass, ValueKlass::adr_members_offset())); z_llgf(offset, Address(offset, ValueKlass::payload_offset_offset())); } void MacroAssembler::payload_addr(Register oop, Register data, Register value_klass) { + assert(Arguments::is_valhalla_enabled(), "Must be"); // ((address) (void*) o) + vk->payload_offset(); // // oop must differ from value_klass: payload_offset() overwrites value_klass @@ -6928,10 +6940,12 @@ void MacroAssembler::post_call_nop() { if (!Continuations::enabled()) { return; } - nop(); - // TODO: - // 1. https://bugs.openjdk.org/browse/JDK-8300002 - // 2. https://bugs.openjdk.org/browse/JDK-8290965 + // We use CLFI SP, imm32 to encode post call nops. + // Refer to NativePostCallNop for details. + relocate(post_call_nop_Relocation::spec()); + InlineSkippedInstructionsCounter skipCounter(this); + z_clfi(Z_SP, 0); + assert(is_post_call_nop(pc() - 6), "post call nop not found"); } void MacroAssembler::push_cont_fastpath() { @@ -7142,22 +7156,26 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i } bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); Unimplemented(); } bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index, VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); Unimplemented(); } bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, VMRegPair* from, int from_count, int& from_index, VMReg to, RegState reg_state[], Register val_array) { + assert(ValueTypePassFieldsAsArgs, "Must be"); Unimplemented(); } int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { + assert(ValueTypePassFieldsAsArgs, "Must be"); Unimplemented(); } diff --git a/src/hotspot/cpu/s390/matcher_s390.hpp b/src/hotspot/cpu/s390/matcher_s390.hpp index 188d46438aca..7e006bdc6c7b 100644 --- a/src/hotspot/cpu/s390/matcher_s390.hpp +++ b/src/hotspot/cpu/s390/matcher_s390.hpp @@ -125,11 +125,6 @@ return false; } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - return false; - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return false; diff --git a/src/hotspot/cpu/s390/nativeInst_s390.cpp b/src/hotspot/cpu/s390/nativeInst_s390.cpp index 3520e9a34935..0a49826adf2f 100644 --- a/src/hotspot/cpu/s390/nativeInst_s390.cpp +++ b/src/hotspot/cpu/s390/nativeInst_s390.cpp @@ -634,6 +634,26 @@ void NativeGeneralJump::replace_mt_safe(address instr_addr, address code_buffer) void NativeDeoptInstruction::verify() { } +bool NativePostCallNop::patch(int32_t oopmap_slot, int32_t cb_offset) { + if (((oopmap_slot & 0xff) != oopmap_slot) || ((cb_offset & 0xffffff) != cb_offset)) { + return false; // cannot encode + } + uint32_t data = ((uint32_t)oopmap_slot << 24) | (uint32_t)cb_offset; + assert(data != 0, "must be"); + // Write the 4-byte payload into the imm32 field of the CLFI instruction + // (bytes 2..5). s390 is big-endian so a direct 32-bit write is correct. + *(uint32_t*) addr_at(displacement_offset) = data; + ICache::invalidate_range(addr_at(0), instruction_size); + + int32_t oopmap_slot_dec, cb_offset_dec; + assert(check(), "pcn not recognized after patch"); + assert(decode(oopmap_slot_dec, cb_offset_dec), "encoding failed"); + assert(oopmap_slot == oopmap_slot_dec, "oopmap slot encoding is wrong"); + assert(cb_offset == cb_offset_dec, "cb offset encoding is wrong"); + + return true; // successfully encoded +} + void NativePostCallNop::make_deopt() { NativeDeoptInstruction::insert(addr_at(0)); } diff --git a/src/hotspot/cpu/s390/nativeInst_s390.hpp b/src/hotspot/cpu/s390/nativeInst_s390.hpp index 0ba97830bb70..adc0395d4889 100644 --- a/src/hotspot/cpu/s390/nativeInst_s390.hpp +++ b/src/hotspot/cpu/s390/nativeInst_s390.hpp @@ -653,16 +653,50 @@ class NativeGeneralJump: public NativeInstruction { }; class NativePostCallNop: public NativeInstruction { + + // Post-call NOPs are encoded as CLFI R0, imm32 (RIL-a format, 6 bytes): + // + // Bit |0 7|8 11|12 15|16 47| + // +---------+------+------+--------------------------+ + // | 0xC2 | R1 | 0xF | I2 (imm32) | + // +---------+------+------+--------------------------+ + // | opcode1 | 0x0 |op2=F | oopmap_slot[31:24] | + // | |(R0) | | | cb_offset[23:0] | + // + // Byte 0 = 0xC2 (opcode1, from CLFI_ZOPC bits 47..40) + // Byte 1 = 0x0F (R1=R0=0x0 in bits 39..36; opcode2=0xF in bits 35..32) + // Bytes 2..5 = imm32 payload + // + // CLFI sets only the condition code — no GPR is written. + // The R1 field is fixed to R0 (encoding 0), allowing check() to + // recognize the instruction by testing only the first two bytes. + public: - enum z_specific_constants { - // The check reads a 2-byte nop instruction. Since s390 nop is 2 bytes (BCR instruction), - // we can safely read it in a single stage without risk of out-of-bounds access. - // The nop instruction is checked by is_nop() which reads a short (2 bytes). - first_check_size = 2 + + enum z_pcn_constants { + // Only the first 2 bytes are needed to identify the instruction. + // This avoids an out-of-bounds read if the return address is at a + // page boundary (same rationale as x86 first_check_size). + first_check_size = 2, + instruction_size = 6, // CLFI is a 6-byte RIL-a instruction + displacement_offset = 2 // imm32 payload starts at byte 2 }; - bool check() const { return is_nop(); } - bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { return false; } - bool patch(int32_t oopmap_slot, int32_t cb_offset) { Unimplemented(); return false; } + + bool check() const { return Assembler::is_post_call_nop(addr_at(0)); } + + bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { + // s390 is big-endian; read the 4-byte imm32 at byte offset 2. + // Use uint32_t to avoid signed integer arithmetic on the raw bit pattern. + uint32_t data = *(uint32_t*) addr_at(displacement_offset); + if (data == 0) { + return false; // no payload encoded yet + } + cb_offset = (int32_t)(data & 0xffffff); + oopmap_slot = (int32_t)(data >> 24); // top 8 bits, no masking needed + return true; + } + + bool patch(int32_t oopmap_slot, int32_t cb_offset); void make_deopt(); }; diff --git a/src/hotspot/cpu/s390/templateTable_s390.cpp b/src/hotspot/cpu/s390/templateTable_s390.cpp index dcb3fa4fc3f1..7234db81cd19 100644 --- a/src/hotspot/cpu/s390/templateTable_s390.cpp +++ b/src/hotspot/cpu/s390/templateTable_s390.cpp @@ -2116,9 +2116,9 @@ void TemplateTable::if_acmp(Condition cc) { Label taken, not_taken; __ pop_ptr(Z_ARG5); - __ profile_acmp(Z_tmp_1, Z_ARG5, Z_tos, Z_tmp_2); - if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(Z_tmp_1, Z_ARG5, Z_tos, Z_tmp_2); + __ compareU64_and_branch(Z_tos, Z_ARG5, Assembler::bcondEqual, (cc == equal) ? taken : not_taken); // Use z_ltgr for efficient null checks @@ -2961,7 +2961,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr // still holds the ResolvedFieldEntry* for read_flat_field below. __ load_sized_value(atos_flags, Address(atos_entry, in_bytes(ResolvedFieldEntry::flags_offset())), sizeof(u1), false); Label is_flat; - __ test_field_is_flat(atos_flags, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(atos_flags, is_flat); + } __ load_heap_oop(Z_tos, field, oopLoad_tmp1, oopLoad_tmp2); __ push(atos); if (do_rewrite) { @@ -2969,14 +2971,17 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr } __ z_bru(Done); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker). - // atos_entry (Z_ARG5) still holds the ResolvedFieldEntry* here. - __ z_lgr(Z_tos, obj); - __ read_flat_field(atos_entry, Z_tos); - __ verify_oop(Z_tos); - __ push(atos); - if (do_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, bc_reg, patch_tmp); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker). + // atos_entry (Z_ARG5) still holds the ResolvedFieldEntry* here. + __ z_lgr(Z_tos, obj); + __ read_flat_field(atos_entry, Z_tos); + __ verify_oop(Z_tos); + __ push(atos); + if (do_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc_reg, patch_tmp); + } + __ z_bru(Done); } } } @@ -3351,8 +3356,10 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr oopStore_tmp1, oopStore_tmp2, oopStore_tmp3, IN_HEAP); } else { Label null_free_reference, is_flat, rewrite_value, done_valhalla; - __ z_tmll(flags, 1 << ResolvedFieldEntry::is_flat_shift); - __ branch_optimized(Assembler::bcondAllOne, is_flat); + if (UseFieldFlattening) { + __ z_tmll(flags, 1 << ResolvedFieldEntry::is_flat_shift); + __ branch_optimized(Assembler::bcondAllOne, is_flat); + } __ z_tmll(flags, 1 << ResolvedFieldEntry::is_null_free_value_type_shift); __ branch_optimized(Assembler::bcondAllOne, null_free_reference); pop_and_check_object(obj); @@ -3374,19 +3381,22 @@ void TemplateTable::putfield_or_static(int byte_no, bool is_static, RewriteContr oopStore_tmp1, oopStore_tmp2, oopStore_tmp3, IN_HEAP); __ z_bru(rewrite_value); __ bind(is_flat); - pop_and_check_object(oopStore_tmp1); - { - // cache (Z_ARG5) is dead here — overwritten by load_resolved_field_entry. - // tos_state (Z_ARG4) is dead here — its life ended after branch-table dispatch. - // Reload the ResolvedFieldEntry pointer into tos_state (Z_ARG4); use - // oopStore_tmp2 (Z_R1_scratch) as the index scratch. - // Pass oopStore_tmp3 (Z_ARG2) as tmp2 so that flags (Z_tmp_1) is not - // clobbered; flags must survive to the volatility check after Done. - Register flat_entry = tos_state; - Register flat_index = oopStore_tmp2; // Z_R1_scratch (index scratch, discarded after load) - __ load_field_entry(flat_entry, flat_index); - __ write_flat_field(flat_entry, flat_index, oopStore_tmp3, oopStore_tmp1); - }__ bind(rewrite_value); + if (UseFieldFlattening) { + pop_and_check_object(oopStore_tmp1); + { + // cache (Z_ARG5) is dead here — overwritten by load_resolved_field_entry. + // tos_state (Z_ARG4) is dead here — its life ended after branch-table dispatch. + // Reload the ResolvedFieldEntry pointer into tos_state (Z_ARG4); use + // oopStore_tmp2 (Z_R1_scratch) as the index scratch. + // Pass oopStore_tmp3 (Z_ARG2) as tmp2 so that flags (Z_tmp_1) is not + // clobbered; flags must survive to the volatility check after Done. + Register flat_entry = tos_state; + Register flat_index = oopStore_tmp2; // Z_R1_scratch (index scratch, discarded after load) + __ load_field_entry(flat_entry, flat_index); + __ write_flat_field(flat_entry, flat_index, oopStore_tmp3, oopStore_tmp1); + } + } + __ bind(rewrite_value); if (do_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, bc_reg, patch_tmp, true, byte_no); } @@ -3550,34 +3560,40 @@ void TemplateTable::fast_storefield(TosState state) { switch (bytecode()) { case Bytecodes::_fast_vputfield: { - // Register state at this point: - // obj/cache = Z_tmp_1 (R10) = target object (pop_and_check_object wrote obj here, - // overwriting the entry pointer that cache held earlier) - // off = Z_tmp_2 (R11) = field offset - // flags = Z_ARG5 (R6) = field flags - // Z_tos = Z_ARG1 (R2) = value oop being stored - // - // For write_flat_field we need the ResolvedFieldEntry pointer. - // Re-load it into Z_ARG3 (R4) using Z_ARG4 (R5) as the index scratch; - // both are volatile and not carrying live values at this point. - Label is_flat, done; - __ test_field_is_flat(flags, is_flat); - __ null_check(Z_tos); // Value being stored must not be null for null-free flat field. - do_oop_store(_masm, field, Z_tos, - Z_ARG2, Z_ARG3, Z_ARG4, IN_HEAP); - __ branch_optimized(Assembler::bcondAlways, done); - __ bind(is_flat); - { - // Reload the ResolvedFieldEntry pointer. pop_and_check_object above - // overwrote cache/Z_tmp_1 with the object. - Register flat_entry = Z_ARG4; // R5 — safe for call_VM arg_3 slot - Register flat_index = Z_ARG3; // R4 — discarded after load_field_entry - __ load_field_entry(flat_entry, flat_index); - // entry=R5, tmp1=Z_ARG2=R3, tmp2=flat_index=Z_ARG3=R4, obj=Z_tmp_1=R10 - // All four are distinct; obj(R10) != Z_tos(R2) so implementation is safe. - __ write_flat_field(flat_entry, Z_ARG2, flat_index, obj); + if (Arguments::is_valhalla_enabled()) { + // Register state at this point: + // obj/cache = Z_tmp_1 (R10) = target object (pop_and_check_object wrote obj here, + // overwriting the entry pointer that cache held earlier) + // off = Z_tmp_2 (R11) = field offset + // flags = Z_ARG5 (R6) = field flags + // Z_tos = Z_ARG1 (R2) = value oop being stored + // + // For write_flat_field we need the ResolvedFieldEntry pointer. + // Re-load it into Z_ARG3 (R4) using Z_ARG4 (R5) as the index scratch; + // both are volatile and not carrying live values at this point. + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(flags, is_flat); + } + __ null_check(Z_tos); // Value being stored must not be null for null-free flat field. + do_oop_store(_masm, field, Z_tos, + Z_ARG2, Z_ARG3, Z_ARG4, IN_HEAP); + __ branch_optimized(Assembler::bcondAlways, done); + __ bind(is_flat); + if (UseFieldFlattening) { + // Reload the ResolvedFieldEntry pointer. pop_and_check_object above + // overwrote cache/Z_tmp_1 with the object. + Register flat_entry = Z_ARG4; // R5 — safe for call_VM arg_3 slot + Register flat_index = Z_ARG3; // R4 — discarded after load_field_entry + __ load_field_entry(flat_entry, flat_index); + // entry=R5, tmp1=Z_ARG2=R3, tmp2=flat_index=Z_ARG3=R4, obj=Z_tmp_1=R10 + // All four are distinct; obj(R10) != Z_tos(R2) so implementation is safe. + __ write_flat_field(flat_entry, Z_ARG2, flat_index, obj); + } + __ bind(done); + } else { + __ should_not_reach_here(); } - __ bind(done); } break; case Bytecodes::_fast_aputfield: @@ -3675,9 +3691,13 @@ void TemplateTable::fast_accessfield(TosState state) { // access field switch (bytecode()) { case Bytecodes::_fast_vgetfield: - // field is flat; cache (Z_tmp_1) holds ResolvedFieldEntry*, obj (Z_tos) is receiver/result - __ read_flat_field(cache, obj); - __ verify_oop(Z_tos); + if (UseFieldFlattening) { + // field is flat; cache (Z_tmp_1) holds ResolvedFieldEntry*, obj (Z_tos) is receiver/result + __ read_flat_field(cache, obj); + __ verify_oop(Z_tos); + } else { + __ stop("ShouldNotReachHere"); + } return; case Bytecodes::_fast_agetfield: do_oop_load(_masm, field, Z_tos, Z_tmp_1, Z_tmp_2, IN_HEAP); @@ -4511,8 +4531,10 @@ void TemplateTable::monitorenter() { // Check for value type (Valhalla feature) NearLabel is_value_type; - __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(Z_R1_scratch, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(Z_R1_scratch, is_value_type); + } const int entry_size = frame::interpreter_frame_monitor_size_in_bytes(); NearLabel allocated; // Initialize entry pointer. @@ -4592,10 +4614,12 @@ void TemplateTable::monitorenter() { __ dispatch_next(vtos); // Handle value type exception (Valhalla feature) - __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), Z_tos); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ bind(is_value_type); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), Z_tos); + __ should_not_reach_here(); + } BLOCK_COMMENT("} monitorenter"); } @@ -4611,14 +4635,16 @@ void TemplateTable::monitorexit() { // Check for value type (Valhalla feature) NearLabel is_value_type, has_identity; - __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(Z_R1_scratch, is_value_type); - __ z_bru(has_identity); - __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_illegal_monitor_state_exception)); - __ should_not_reach_here(); - __ bind(has_identity); + if (Arguments::is_valhalla_enabled()) { + __ z_lg(Z_R1_scratch, Address(Z_tos, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(Z_R1_scratch, is_value_type); + __ z_bru(has_identity); + __ bind(is_value_type); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + } NearLabel found, not_found; const Register Rcurr_monitor = Z_ARG2; diff --git a/src/hotspot/cpu/x86/assembler_x86.cpp b/src/hotspot/cpu/x86/assembler_x86.cpp index b020c817dd19..2191eb676a66 100644 --- a/src/hotspot/cpu/x86/assembler_x86.cpp +++ b/src/hotspot/cpu/x86/assembler_x86.cpp @@ -10790,14 +10790,14 @@ void Assembler::vpbroadcastb(XMMRegister dst, Address src, int vector_len) { // duplicate 2-byte integer data from src into programmed locations in dest : requires AVX512BW and AVX512VL void Assembler::vpbroadcastw(XMMRegister dst, XMMRegister src, int vector_len) { - assert(VM_Version::supports_avx2(), ""); + assert(vector_len == AVX_512bit ? VM_Version::supports_avx512bw() : VM_Version::supports_avx2(), ""); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ _legacy_mode_bw, /* no_mask_reg */ true, /* uses_vl */ true); int encode = vex_prefix_and_encode(dst->encoding(), 0, src->encoding(), VEX_SIMD_66, VEX_OPCODE_0F_38, &attributes); emit_int16(0x79, (0xC0 | encode)); } void Assembler::vpbroadcastw(XMMRegister dst, Address src, int vector_len) { - assert(VM_Version::supports_avx2(), ""); + assert(vector_len == AVX_512bit ? VM_Version::supports_avx512bw() : VM_Version::supports_avx2(), ""); assert(dst != xnoreg, "sanity"); InstructionMark im(this); InstructionAttr attributes(vector_len, /* vex_w */ false, /* legacy_mode */ _legacy_mode_bw, /* no_mask_reg */ true, /* uses_vl */ true); diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index 96300e612089..2f470ab44558 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -2493,14 +2493,15 @@ void LIR_Assembler::store_parameter(Metadata* m, int offset_from_rsp_in_words) { void LIR_Assembler::arraycopy_valuetype_check(Register obj, Register tmp, CodeStub* slow_path, bool is_dest, bool null_check) { + assert(Arguments::is_valhalla_enabled(), "Value types must be enabled"); if (null_check) { __ testptr(obj, obj); __ jcc(Assembler::zero, *slow_path->entry()); } if (is_dest) { __ test_null_free_array_oop(obj, tmp, *slow_path->entry()); - __ test_flat_array_oop(obj, tmp, *slow_path->entry()); - } else { + } + if (UseArrayFlattening) { __ test_flat_array_oop(obj, tmp, *slow_path->entry()); } } @@ -2607,12 +2608,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { return; } - // Handle value type arrays - if (flags & LIR_OpArrayCopy::src_valuetype_check) { - arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); - } - if (flags & LIR_OpArrayCopy::dst_valuetype_check) { - arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + if (Arguments::is_valhalla_enabled()) { + // Handle value type arrays + if (flags & LIR_OpArrayCopy::src_valuetype_check) { + arraycopy_valuetype_check(src, tmp, stub, false, (flags & LIR_OpArrayCopy::src_null_check)); + } + if (flags & LIR_OpArrayCopy::dst_valuetype_check) { + arraycopy_valuetype_check(dst, tmp, stub, true, (flags & LIR_OpArrayCopy::dst_null_check)); + } } assert(default_type != nullptr && default_type->is_array_klass() && default_type->is_loaded(), "must be true at this point"); @@ -3152,6 +3155,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { } void LIR_Assembler::emit_profile_value_type(LIR_OpProfileValueType* op) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); Register obj = op->obj()->as_register(); Register tmp = op->tmp()->as_pointer_register(); Address mdo_addr = as_Address(op->mdp()->as_address_ptr()); diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index dcce0aa9c00b..b4ca8648b326 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -1430,112 +1430,6 @@ void C2_MacroAssembler::vinsert(BasicType typ, XMMRegister dst, XMMRegister src, } } -void C2_MacroAssembler::vgather8b_masked(BasicType elem_bt, XMMRegister dst, - Register base, Register idx_base, - Register mask, Register mask_idx, - Register rtmp, int vlen_enc) { - vpxor(dst, dst, dst, vlen_enc); - if (elem_bt == T_SHORT) { - for (int i = 0; i < 4; i++) { - // dst[i] = mask[i] ? src[idx_base[i]] : 0 - Label skip_load; - btq(mask, mask_idx); - jccb(Assembler::carryClear, skip_load); - movl(rtmp, Address(idx_base, i * 4)); - pinsrw(dst, Address(base, rtmp, Address::times_2), i); - bind(skip_load); - incq(mask_idx); - } - } else { - assert(elem_bt == T_BYTE, "only handles short and byte"); - for (int i = 0; i < 8; i++) { - // dst[i] = mask[i] ? src[idx_base[i]] : 0 - Label skip_load; - btq(mask, mask_idx); - jccb(Assembler::carryClear, skip_load); - movl(rtmp, Address(idx_base, i * 4)); - pinsrb(dst, Address(base, rtmp), i); - bind(skip_load); - incq(mask_idx); - } - } -} - -void C2_MacroAssembler::vgather8b(BasicType elem_bt, XMMRegister dst, - Register base, Register idx_base, - Register rtmp, int vlen_enc) { - vpxor(dst, dst, dst, vlen_enc); - if (elem_bt == T_SHORT) { - for (int i = 0; i < 4; i++) { - // dst[i] = src[idx_base[i]] - movl(rtmp, Address(idx_base, i * 4)); - pinsrw(dst, Address(base, rtmp, Address::times_2), i); - } - } else { - assert(elem_bt == T_BYTE, "only handles short and byte"); - for (int i = 0; i < 8; i++) { - // dst[i] = src[idx_base[i]] - movl(rtmp, Address(idx_base, i * 4)); - pinsrb(dst, Address(base, rtmp), i); - } - } -} - -/* - * Gather using hybrid algorithm, first partially unroll scalar loop - * to accumulate values from gather indices into a quad-word(64bit) slice. - * A slice may hold 8 bytes or 4 short values. This is followed by a vector - * permutation to place the slice into appropriate vector lane - * locations in destination vector. Following pseudo code describes the - * algorithm in detail: - * - * DST_VEC = ZERO_VEC - * PERM_INDEX = {0, 1, 2, 3, 4, 5, 6, 7, 8..} - * TWO_VEC = {2, 2, 2, 2, 2, 2, 2, 2, 2..} - * FOREACH_ITER: - * TMP_VEC_64 = PICK_SUB_WORDS_FROM_GATHER_INDICES - * TEMP_PERM_VEC = PERMUTE TMP_VEC_64 PERM_INDEX - * DST_VEC = DST_VEC OR TEMP_PERM_VEC - * PERM_INDEX = PERM_INDEX - TWO_VEC - * - * With each iteration, doubleword permute indices (0,1) corresponding - * to gathered quadword gets right shifted by two lane positions. - * - */ -void C2_MacroAssembler::vgather_subword(BasicType elem_ty, XMMRegister dst, - Register base, Register idx_base, - Register mask, XMMRegister xtmp1, - XMMRegister xtmp2, XMMRegister temp_dst, - Register rtmp, Register mask_idx, - Register length, int vector_len, int vlen_enc) { - Label GATHER8_LOOP; - assert(is_signed_subword_type(elem_ty), "only handles short and byte"); - movl(length, vector_len); - vpxor(xtmp1, xtmp1, xtmp1, vlen_enc); // xtmp1 = {0, ...} - vpxor(dst, dst, dst, vlen_enc); // dst = {0, ...} - vallones(xtmp2, vlen_enc); - vpsubd(xtmp2, xtmp1, xtmp2, vlen_enc); - vpslld(xtmp2, xtmp2, 1, vlen_enc); // xtmp2 = {2, 2, ...} - load_iota_indices(xtmp1, vector_len * type2aelembytes(elem_ty), T_INT); // xtmp1 = {0, 1, 2, ...} - - bind(GATHER8_LOOP); - // TMP_VEC_64(temp_dst) = PICK_SUB_WORDS_FROM_GATHER_INDICES - if (mask == noreg) { - vgather8b(elem_ty, temp_dst, base, idx_base, rtmp, vlen_enc); - } else { - vgather8b_masked(elem_ty, temp_dst, base, idx_base, mask, mask_idx, rtmp, vlen_enc); - } - // TEMP_PERM_VEC(temp_dst) = PERMUTE TMP_VEC_64(temp_dst) PERM_INDEX(xtmp1) - vpermd(temp_dst, xtmp1, temp_dst, vlen_enc == Assembler::AVX_512bit ? vlen_enc : Assembler::AVX_256bit); - // PERM_INDEX(xtmp1) = PERM_INDEX(xtmp1) - TWO_VEC(xtmp2) - vpsubd(xtmp1, xtmp1, xtmp2, vlen_enc); - // DST_VEC = DST_VEC OR TEMP_PERM_VEC - vpor(dst, dst, temp_dst, vlen_enc); - addptr(idx_base, 32 >> (type2aelembytes(elem_ty) - 1)); - subl(length, 8 >> (type2aelembytes(elem_ty) - 1)); - jcc(Assembler::notEqual, GATHER8_LOOP); -} - void C2_MacroAssembler::vgather(BasicType typ, XMMRegister dst, Register base, XMMRegister idx, XMMRegister mask, int vector_len) { switch(typ) { case T_INT: diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index a6471cbd3fdb..761879cbc9ca 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -509,15 +509,6 @@ void efp16sh(int opcode, XMMRegister dst, XMMRegister src1, XMMRegister src2); - void vgather_subword(BasicType elem_ty, XMMRegister dst, Register base, Register idx_base, Register mask, - XMMRegister xtmp1, XMMRegister xtmp2, XMMRegister xtmp3, Register rtmp, - Register midx, Register length, int vector_len, int vlen_enc); - - void vgather8b_masked(BasicType elem_bt, XMMRegister dst, Register base, Register idx_base, - Register mask, Register midx, Register rtmp, int vlen_enc); - void vgather8b(BasicType elem_bt, XMMRegister dst, Register base, Register idx_base, - Register rtmp, int vlen_enc); - void vector_saturating_op(int opc, BasicType elem_bt, XMMRegister dst, XMMRegister src1, XMMRegister src2, bool is_unsigned, int vlen_enc); void vector_saturating_op(int opc, BasicType elem_bt, XMMRegister dst, XMMRegister src1, Address src2, bool is_unsigned, int vlen_enc); diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86.hpp b/src/hotspot/cpu/x86/foreignGlobals_x86.hpp index 7b3859ce6613..bc671ce4249a 100644 --- a/src/hotspot/cpu/x86/foreignGlobals_x86.hpp +++ b/src/hotspot/cpu/x86/foreignGlobals_x86.hpp @@ -32,7 +32,6 @@ struct ABIDescriptor { GrowableArray _integer_return_registers; GrowableArray _vector_argument_registers; GrowableArray _vector_return_registers; - size_t _X87_return_registers_noof; GrowableArray _integer_additional_volatile_registers; GrowableArray _vector_additional_volatile_registers; diff --git a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp index 55952bba063a..67462ba3cbee 100644 --- a/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp +++ b/src/hotspot/cpu/x86/foreignGlobals_x86_64.cpp @@ -54,8 +54,6 @@ const ABIDescriptor ForeignGlobals::parse_abi_descriptor(jobject jabi) { refArrayOop outputStorage = jdk_internal_foreign_abi_ABIDescriptor::outputStorage(abi_oop); parse_register_array(outputStorage, StorageType::INTEGER, abi._integer_return_registers, as_Register); parse_register_array(outputStorage, StorageType::VECTOR, abi._vector_return_registers, as_XMMRegister); - refArrayOop subarray = oop_cast(outputStorage->obj_at((int) StorageType::X87)); - abi._X87_return_registers_noof = subarray->length(); refArrayOop volatileStorage = jdk_internal_foreign_abi_ABIDescriptor::volatileStorage(abi_oop); parse_register_array(volatileStorage, StorageType::INTEGER, abi._integer_additional_volatile_registers, as_Register); diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp index 0ef885fbffde..53bcd1300fcc 100644 --- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp @@ -652,7 +652,7 @@ void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, Register obj = stub->obj()->as_register(); if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, /* wide = */ false); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, stub->info(), /* wide = */ false); } __ cmpptr(obj, NULL_WORD); __ jcc(Assembler::equal, *stub->continuation()); diff --git a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp index c2f5eb68c2bc..efafe816e155 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.cpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp @@ -1121,12 +1121,14 @@ void InterpreterMacroAssembler::get_method_counters(Register method, } void InterpreterMacroAssembler::read_flat_field(Register entry, Register obj) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); call_VM(obj, CAST_FROM_FN_PTR(address, InterpreterRuntime::read_flat_field), obj, entry); } void InterpreterMacroAssembler::write_flat_field(Register entry, Register tmp1, Register tmp2, Register obj, Register value) { + assert(UseFieldFlattening, "Should be used only if field flattening is enabled"); assert_different_registers(entry, tmp1, tmp2, obj, value); Label slow_path, done; @@ -1610,19 +1612,23 @@ template void InterpreterMacroAssembler::profile_array_type(Re mov(tmp, array); profile_obj_type(tmp, Address(mdp, in_bytes(ArrayData::array_offset()))); - Label not_flat; - test_non_flat_array_oop(array, tmp, not_flat); + if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { + Label not_flat; + test_non_flat_array_oop(array, tmp, not_flat); - set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::flat_array_byte_constant()); - bind(not_flat); + bind(not_flat); + } - Label not_null_free; - test_non_null_free_array_oop(array, tmp, not_null_free); + Label not_null_free; + test_non_null_free_array_oop(array, tmp, not_null_free); - set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); + set_mdp_flag_at(mdp, ArrayData::null_free_array_byte_constant()); - bind(not_null_free); + bind(not_null_free); + } bind(profile_continue); } @@ -1687,6 +1693,7 @@ void InterpreterMacroAssembler::profile_acmp(Register mdp, Register left, Register right, Register tmp) { + assert(Arguments::is_valhalla_enabled(), "Profiling acmp makes sense only for value types"); if (ProfileInterpreter) { Label profile_continue; diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index ae7304b354b2..216ed9f92e75 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -2406,12 +2406,14 @@ void MacroAssembler::null_check(Register reg, int offset) { } void MacroAssembler::test_markword_is_value_type(Register markword, Label& is_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); andptr(markword, markWord::value_type_pattern_mask); cmpptr(markword, markWord::value_type_pattern); jcc(Assembler::equal, is_value_type); } void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, Label& not_value_type, bool can_be_null) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); if (can_be_null) { testptr(object, object); jcc(Assembler::zero, not_value_type); @@ -2424,18 +2426,21 @@ void MacroAssembler::test_oop_is_not_value_type(Register object, Register tmp, L } void MacroAssembler::test_field_is_null_free_value_type(Register flags, Register temp_reg, Label& is_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); movl(temp_reg, flags); testl(temp_reg, 1 << ResolvedFieldEntry::is_null_free_value_type_shift); jcc(Assembler::notEqual, is_null_free_value_type); } void MacroAssembler::test_field_is_not_null_free_value_type(Register flags, Register temp_reg, Label& not_null_free_value_type) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); movl(temp_reg, flags); testl(temp_reg, 1 << ResolvedFieldEntry::is_null_free_value_type_shift); jcc(Assembler::equal, not_null_free_value_type); } void MacroAssembler::test_field_is_flat(Register flags, Register temp_reg, Label& is_flat) { + assert(UseFieldFlattening, "should not be used without field flattening enabled"); movl(temp_reg, flags); testl(temp_reg, 1 << ResolvedFieldEntry::is_flat_shift); jcc(Assembler::notEqual, is_flat); @@ -2450,23 +2455,28 @@ void MacroAssembler::test_oop_prototype_bit(Register oop, Register temp_reg, int void MacroAssembler::test_flat_array_oop(Register oop, Register temp_reg, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, true, is_flat_array); } void MacroAssembler::test_non_flat_array_oop(Register oop, Register temp_reg, Label& is_non_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::flat_array_bit_in_place, false, is_non_flat_array); } void MacroAssembler::test_null_free_array_oop(Register oop, Register temp_reg, Label&is_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, true, is_null_free_array); } void MacroAssembler::test_non_null_free_array_oop(Register oop, Register temp_reg, Label&is_non_null_free_array) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types enabled"); test_oop_prototype_bit(oop, temp_reg, markWord::null_free_array_bit_in_place, false, is_non_null_free_array); } void MacroAssembler::test_flat_array_layout(Register lh, Label& is_flat_array) { + assert(UseArrayFlattening, "should not be used without array flattening enabled"); testl(lh, Klass::_lh_array_tag_flat_value_bit_inplace); jcc(Assembler::notZero, is_flat_array); } @@ -5622,11 +5632,13 @@ void MacroAssembler::access_store_at(BasicType type, DecoratorSet decorators, Ad } void MacroAssembler::payload_offset(Register value_klass, Register offset) { + assert(Arguments::is_valhalla_enabled(), "Must be"); movptr(offset, Address(value_klass, ValueKlass::adr_members_offset())); movl(offset, Address(offset, ValueKlass::payload_offset_offset())); } void MacroAssembler::payload_addr(Register oop, Register data, Register value_klass) { + assert(Arguments::is_valhalla_enabled(), "Must be"); // ((address) (void*) o) + vk->payload_offset(); Register offset = (data == oop) ? rscratch1 : data; payload_offset(value_klass, offset); @@ -6088,6 +6100,7 @@ int MacroAssembler::store_value_type_fields_to_buf(ciValueKlass* vk, bool from_i // Move a value between registers/stack slots and update the reg_state bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(from->is_valid() && to->is_valid(), "source and destination must be valid"); if (reg_state[to->value()] == reg_written) { return true; // Already written @@ -6151,6 +6164,7 @@ bool MacroAssembler::move_helper(VMReg from, VMReg to, BasicType bt, RegState re // Calculate the extra stack space required for packing or unpacking value // args and adjust the stack pointer (see MacroAssembler::remove_frame). int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { + assert(ValueTypePassFieldsAsArgs, "Must be"); int sp_inc = args_on_stack * VMRegImpl::stack_slot_size; sp_inc = align_up(sp_inc, StackAlignmentInBytes); assert(sp_inc > 0, "sanity"); @@ -6170,6 +6184,7 @@ int MacroAssembler::extend_stack_for_value_args(int args_on_stack) { bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int& sig_index, VMReg from, int& from_index, VMRegPair* to, int to_count, int& to_index, RegState reg_state[]) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(sig->at(sig_index)._bt == T_VOID, "should be at end delimiter"); assert(from->is_valid(), "source must be valid"); bool progress = false; @@ -6307,6 +6322,7 @@ bool MacroAssembler::unpack_value_helper(const GrowableArray* sig, int bool MacroAssembler::pack_value_helper(const GrowableArray* sig, int& sig_index, int vtarg_index, VMRegPair* from, int from_count, int& from_index, VMReg to, RegState reg_state[], Register val_array) { + assert(ValueTypePassFieldsAsArgs, "Must be"); assert(sig->at(sig_index)._bt == T_METADATA, "should be at delimiter"); assert(to->is_valid(), "destination must be valid"); diff --git a/src/hotspot/cpu/x86/matcher_x86.hpp b/src/hotspot/cpu/x86/matcher_x86.hpp index eda8a05d974a..5b012768cb9d 100644 --- a/src/hotspot/cpu/x86/matcher_x86.hpp +++ b/src/hotspot/cpu/x86/matcher_x86.hpp @@ -115,16 +115,6 @@ return (UseAVX >= 2); } - // Does target support predicated operation emulation. - static bool supports_vector_predicate_op_emulation(int vopc, int vlen, BasicType bt) { - switch(vopc) { - case Op_LoadVectorGatherMasked: - return is_subword_type(bt) && VM_Version::supports_avx2(); - default: - return false; - } - } - // Does the CPU supports vector variable rotate instructions? static constexpr bool supports_vector_variable_rotates(void) { return true; @@ -185,9 +175,6 @@ return 7; case Op_MulVL: return VM_Version::supports_avx512vldq() ? 0 : 6; - case Op_LoadVectorGather: - case Op_LoadVectorGatherMasked: - return is_subword_type(ety) ? 50 : 0; case Op_VectorCastF2X: // fall through case Op_VectorCastD2X: return is_floating_point_type(ety) ? 0 : (is_subword_type(ety) ? 35 : 30); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp index 2575d0e5007b..9c95408ad8ff 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp @@ -3601,10 +3601,12 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh __ cmpq(r10_src_klass, rax); __ jcc(Assembler::notEqual, L_failed); - if (Arguments::is_valhalla_enabled()) { + if (UseArrayFlattening) { // Check for flat value type array -> return -1 __ test_flat_array_oop(src, rax, L_failed); + } + if (Arguments::is_valhalla_enabled()) { // Check for null-free (non-flat) value type array -> handle as object array __ test_null_free_array_oop(src, rax, L_objArray); } @@ -3738,12 +3740,14 @@ __ BIND(L_checkcast_copy); #ifdef ASSERT { - BLOCK_COMMENT("assert not null-free array {"); - Label L; - __ test_non_null_free_array_oop(dst, rklass_tmp, L); - __ stop("unexpected null-free array"); - __ bind(L); - BLOCK_COMMENT("} assert not null-free array"); + if (Arguments::is_valhalla_enabled()) { + BLOCK_COMMENT("assert not null-free array {"); + Label L; + __ test_non_null_free_array_oop(dst, rklass_tmp, L); + __ stop("unexpected null-free array"); + __ bind(L); + BLOCK_COMMENT("} assert not null-free array"); + } } #endif diff --git a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp index 620498378e55..8961f621301c 100644 --- a/src/hotspot/cpu/x86/templateTable_x86.cpp +++ b/src/hotspot/cpu/x86/templateTable_x86.cpp @@ -1971,10 +1971,10 @@ void TemplateTable::if_acmp(Condition cc) { Label taken, not_taken; __ pop_ptr(rdx); - __ profile_acmp(rbx, rdx, rax, rcx); - - const int is_value_type_mask = markWord::value_type_pattern; if (Arguments::is_valhalla_enabled()) { + __ profile_acmp(rbx, rdx, rax, rcx); + + const int is_value_type_mask = markWord::value_type_pattern; __ cmpoop(rdx, rax); __ jcc(Assembler::equal, (cc == equal) ? taken : not_taken); @@ -2713,7 +2713,9 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr __ jmp(Done); } else { Label is_flat; - __ test_field_is_flat(flags, rscratch1, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, rscratch1, is_flat); + } pop_and_check_object(obj); __ load_heap_oop(rax, field); __ push(atos); @@ -2722,13 +2724,15 @@ void TemplateTable::getfield_or_static(int byte_no, bool is_static, RewriteContr } __ jmp(Done); __ bind(is_flat); - // field is flat (null-free or nullable with a null-marker) - pop_and_check_object(rax); - __ read_flat_field(rcx, rax); - __ verify_oop(rax); - __ push(atos); - if (rc == may_rewrite) { - patch_bytecode(Bytecodes::_fast_vgetfield, bc, rbx); + if (UseFieldFlattening) { + // field is flat (null-free or nullable with a null-marker) + pop_and_check_object(rax); + __ read_flat_field(rcx, rax); + __ verify_oop(rax); + __ push(atos); + if (rc == may_rewrite) { + patch_bytecode(Bytecodes::_fast_vgetfield, bc, rbx); + } } __ jmp(Done); } @@ -2984,7 +2988,7 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri patch_bytecode(Bytecodes::_fast_aputfield, bc, rbx, true, byte_no); } __ jmp(Done); - } else { + } else { // Valhalla __ pop(atos); if (is_static) { Label is_nullable; @@ -2995,7 +2999,9 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri __ jmp(Done); } else { Label is_flat, null_free_reference, rewrite_value; - __ test_field_is_flat(flags, rscratch1, is_flat); + if (UseFieldFlattening) { + __ test_field_is_flat(flags, rscratch1, is_flat); + } __ test_field_is_null_free_value_type(flags, rscratch1, null_free_reference); pop_and_check_object(obj); // Store into the field @@ -3011,8 +3017,10 @@ void TemplateTable::putfield_or_static_helper(int byte_no, bool is_static, Rewri do_oop_store(_masm, field, rax); __ jmp(rewrite_value); __ bind(is_flat); - pop_and_check_object(rscratch2); - __ write_flat_field(rcx, r8, rscratch1, rscratch2, rax); + if (UseFieldFlattening) { + pop_and_check_object(rscratch2); + __ write_flat_field(rcx, r8, rscratch1, rscratch2, rax); + } __ bind(rewrite_value); if (rc == may_rewrite) { patch_bytecode(Bytecodes::_fast_vputfield, bc, rbx, true, byte_no); @@ -3240,17 +3248,25 @@ void TemplateTable::fast_storefield_helper(Address field, Register rax, Register switch (bytecode()) { case Bytecodes::_fast_vputfield: { - // Field is either flat (nullable or not) or non-flat and null-free - Label is_flat, done; - __ test_field_is_flat(flags, rscratch1, is_flat); - __ null_check(rax); // FIXME JDK-8341120 - do_oop_store(_masm, field, rax); - __ jmp(done); - __ bind(is_flat); - __ load_field_entry(r8, r9); - __ movptr(rscratch2, rcx); // re-shuffle registers because of VM call calling convention - __ write_flat_field(r8, rscratch1, r9, rscratch2, rax); - __ bind(done); + if (Arguments::is_valhalla_enabled()) { + // Field is either flat (nullable or not) or non-flat and null-free + Label is_flat, done; + if (UseFieldFlattening) { + __ test_field_is_flat(flags, rscratch1, is_flat); + } + __ null_check(rax); + do_oop_store(_masm, field, rax); + __ jmp(done); + __ bind(is_flat); + if (UseFieldFlattening) { + __ load_field_entry(r8, r9); + __ movptr(rscratch2, rcx); // re-shuffle registers because of VM call calling convention + __ write_flat_field(r8, rscratch1, r9, rscratch2, rax); + } + __ bind(done); + } else { + __ should_not_reach_here(); + } } break; case Bytecodes::_fast_aputfield: @@ -3322,8 +3338,12 @@ void TemplateTable::fast_accessfield(TosState state) { // access field switch (bytecode()) { case Bytecodes::_fast_vgetfield: - __ read_flat_field(rcx, rax); - __ verify_oop(rax); + if (UseFieldFlattening) { + __ read_flat_field(rcx, rax); + __ verify_oop(rax); + } else { + __ should_not_reach_here(); + } break; case Bytecodes::_fast_agetfield: do_oop_load(_masm, field, rax); @@ -4065,8 +4085,10 @@ void TemplateTable::monitorenter() { __ null_check(rax); Label is_value_type; - __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); - __ test_markword_is_value_type(rbx, is_value_type); + if (Arguments::is_valhalla_enabled()) { + __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); + __ test_markword_is_value_type(rbx, is_value_type); + } const Address monitor_block_top( rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize); @@ -4162,9 +4184,11 @@ void TemplateTable::monitorenter() { __ dispatch_next(vtos); __ bind(is_value_type); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_identity_exception), rax); - __ should_not_reach_here(); + if (Arguments::is_valhalla_enabled()) { + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_identity_exception), rax); + __ should_not_reach_here(); + } } void TemplateTable::monitorexit() { @@ -4173,16 +4197,18 @@ void TemplateTable::monitorexit() { // check for null object __ null_check(rax); - const int is_value_type_mask = markWord::value_type_pattern; - Label has_identity; - __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); - __ andptr(rbx, is_value_type_mask); - __ cmpl(rbx, is_value_type_mask); - __ jcc(Assembler::notEqual, has_identity); - __ call_VM(noreg, CAST_FROM_FN_PTR(address, - InterpreterRuntime::throw_illegal_monitor_state_exception)); - __ should_not_reach_here(); - __ bind(has_identity); + if (Arguments::is_valhalla_enabled()) { + const int is_value_type_mask = markWord::value_type_pattern; + Label has_identity; + __ movptr(rbx, Address(rax, oopDesc::mark_offset_in_bytes())); + __ andptr(rbx, is_value_type_mask); + __ cmpl(rbx, is_value_type_mask); + __ jcc(Assembler::notEqual, has_identity); + __ call_VM(noreg, CAST_FROM_FN_PTR(address, + InterpreterRuntime::throw_illegal_monitor_state_exception)); + __ should_not_reach_here(); + __ bind(has_identity); + } const Address monitor_block_top( rbp, frame::interpreter_frame_monitor_block_top_offset * wordSize); diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 62c30a668d7f..df62cf56c7c9 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -3507,16 +3507,6 @@ bool Matcher::match_rule_supported_vector(int opcode, int vlen, BasicType bt) { } break; case Op_LoadVectorGatherMasked: - if (!is_subword_type(bt) && size_in_bits < 512 && !VM_Version::supports_avx512vl()) { - return false; - } - if (is_subword_type(bt) && - ((size_in_bits > 256 && !VM_Version::supports_avx512bw()) || - (size_in_bits < 64) || - (bt == T_SHORT && !VM_Version::supports_bmi2()))) { - return false; - } - break; case Op_StoreVectorScatterMasked: case Op_StoreVectorScatter: if (is_subword_type(bt)) { @@ -3526,10 +3516,9 @@ bool Matcher::match_rule_supported_vector(int opcode, int vlen, BasicType bt) { } // fallthrough case Op_LoadVectorGather: - if (!is_subword_type(bt) && size_in_bits == 64) { + if (is_subword_type(bt)) { return false; - } - if (is_subword_type(bt) && size_in_bits < 64) { + } else if (size_in_bits == 64) { return false; } break; @@ -10698,10 +10687,12 @@ instruct compareAndExchangeB( effect(KILL cr); format %{ "cmpxchgb $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax; rax -> byte\n\t" %} ins_encode %{ __ lock(); __ cmpxchgb($newval$$Register, $mem_ptr$$Address); + __ narrow_subword_type($oldval$$Register, T_BYTE); %} ins_pipe( pipe_cmpxchg ); %} @@ -10715,10 +10706,12 @@ instruct compareAndExchangeS( effect(KILL cr); format %{ "cmpxchgw $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax; rax -> short\n\t" %} ins_encode %{ __ lock(); __ cmpxchgw($newval$$Register, $mem_ptr$$Address); + __ narrow_subword_type($oldval$$Register, T_SHORT); %} ins_pipe( pipe_cmpxchg ); %} @@ -10732,7 +10725,8 @@ instruct compareAndExchangeI( effect(KILL cr); format %{ "cmpxchgl $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax\n\t" %} ins_encode %{ __ lock(); __ cmpxchgl($newval$$Register, $mem_ptr$$Address); @@ -10749,7 +10743,8 @@ instruct compareAndExchangeL( effect(KILL cr); format %{ "cmpxchgq $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax\n\t" %} ins_encode %{ __ lock(); __ cmpxchgq($newval$$Register, $mem_ptr$$Address); @@ -10766,7 +10761,8 @@ instruct compareAndExchangeN( effect(KILL cr); format %{ "cmpxchgl $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax\n\t" %} ins_encode %{ __ lock(); __ cmpxchgl($newval$$Register, $mem_ptr$$Address); @@ -10784,7 +10780,8 @@ instruct compareAndExchangeP( effect(KILL cr); format %{ "cmpxchgq $mem_ptr,$newval\t# " - "If rax == $mem_ptr then store $newval into $mem_ptr\n\t" %} + "If rax == $mem_ptr then store $newval into $mem_ptr, " + "else store $mem_ptr into rax\n\t" %} ins_encode %{ __ lock(); __ cmpxchgq($newval$$Register, $mem_ptr$$Address); @@ -18379,7 +18376,7 @@ instruct storeV(memory mem, vec src) %{ // ---------------------------------------- Gather ------------------------------------ -// Gather BYTE, SHORT, INT, LONG, FLOAT, DOUBLE +// Gather INT, LONG, FLOAT, DOUBLE instruct gather(legVec dst, memory mem, legVec idx, rRegP tmp, legVec mask) %{ predicate(!VM_Version::supports_avx512vl() && !is_subword_type(Matcher::vector_element_basic_type(n)) && @@ -18436,118 +18433,6 @@ instruct evgather_masked(vec dst, memory mem, vec idx, kReg mask, kReg ktmp, rRe ins_pipe( pipe_slow ); %} -instruct vgather_subwordLE8B(vec dst, memory mem, rRegP idx_base, rRegP tmp, rRegI rtmp) %{ - predicate(is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8); - match(Set dst (LoadVectorGather mem idx_base)); - effect(TEMP tmp, TEMP rtmp); - format %{ "vector_gatherLE8 $dst, $mem, $idx_base\t! using $tmp and $rtmp as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ lea($tmp$$Register, $mem$$Address); - __ vgather8b(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp$$Register, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_subwordGT8B(vec dst, memory mem, rRegP idx_base, rRegP tmp, rRegP idx_base_temp, - vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegI length, rFlagsReg cr) %{ - predicate(is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8); - match(Set dst (LoadVectorGather mem idx_base)); - effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP length, KILL cr); - format %{ "vector_gatherGT8 $dst, $mem, $idx_base\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp and $length as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - int vector_len = Matcher::vector_length(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ lea($tmp$$Register, $mem$$Address); - __ movptr($idx_base_temp$$Register, $idx_base$$Register); - __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, noreg, $xtmp1$$XMMRegister, - $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, noreg, $length$$Register, vector_len, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_masked_subwordLE8B_avx3(vec dst, memory mem, rRegP idx_base, kReg mask, rRegL mask_idx, rRegP tmp, rRegI rtmp, rRegL rtmp2, rFlagsReg cr) %{ - predicate(VM_Version::supports_avx512bw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8); - match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask))); - effect(TEMP mask_idx, TEMP tmp, TEMP rtmp, TEMP rtmp2, KILL cr); - format %{ "vector_masked_gatherLE8 $dst, $mem, $idx_base, $mask\t! using $mask_idx, $tmp, $rtmp and $rtmp2 as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ xorq($mask_idx$$Register, $mask_idx$$Register); - __ lea($tmp$$Register, $mem$$Address); - __ kmovql($rtmp2$$Register, $mask$$KRegister); - __ vgather8b_masked(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp2$$Register, $mask_idx$$Register, $rtmp$$Register, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_masked_subwordGT8B_avx3(vec dst, memory mem, rRegP idx_base, kReg mask, rRegP tmp, rRegP idx_base_temp, - vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegL rtmp2, rRegL mask_idx, rRegI length, rFlagsReg cr) %{ - predicate(VM_Version::supports_avx512bw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8); - match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask))); - effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP rtmp2, TEMP mask_idx, TEMP length, KILL cr); - format %{ "vector_gatherGT8_masked $dst, $mem, $idx_base, $mask\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp, $rtmp2, $mask_idx and $length as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - int vector_len = Matcher::vector_length(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ xorq($mask_idx$$Register, $mask_idx$$Register); - __ lea($tmp$$Register, $mem$$Address); - __ movptr($idx_base_temp$$Register, $idx_base$$Register); - __ kmovql($rtmp2$$Register, $mask$$KRegister); - __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, $rtmp2$$Register, $xtmp1$$XMMRegister, - $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, $mask_idx$$Register, $length$$Register, vector_len, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_masked_subwordLE8B_avx2(vec dst, memory mem, rRegP idx_base, vec mask, rRegI mask_idx, rRegP tmp, rRegI rtmp, rRegI rtmp2, rFlagsReg cr) %{ - predicate(!VM_Version::supports_avx512vlbw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) <= 8); - match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask))); - effect(TEMP mask_idx, TEMP tmp, TEMP rtmp, TEMP rtmp2, KILL cr); - format %{ "vector_masked_gatherLE8 $dst, $mem, $idx_base, $mask\t! using $mask_idx, $tmp, $rtmp and $rtmp2 as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ lea($tmp$$Register, $mem$$Address); - __ vpmovmskb($rtmp2$$Register, $mask$$XMMRegister, vlen_enc); - if (elem_bt == T_SHORT) { - __ movl($mask_idx$$Register, 0x55555555); - __ pextl($rtmp2$$Register, $rtmp2$$Register, $mask_idx$$Register); - } - __ xorl($mask_idx$$Register, $mask_idx$$Register); - __ vgather8b_masked(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base$$Register, $rtmp2$$Register, $mask_idx$$Register, $rtmp$$Register, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - -instruct vgather_masked_subwordGT8B_avx2(vec dst, memory mem, rRegP idx_base, vec mask, rRegP tmp, rRegP idx_base_temp, - vec xtmp1, vec xtmp2, vec xtmp3, rRegI rtmp, rRegI rtmp2, rRegI mask_idx, rRegI length, rFlagsReg cr) %{ - predicate(!VM_Version::supports_avx512vlbw() && is_subword_type(Matcher::vector_element_basic_type(n)) && Matcher::vector_length_in_bytes(n) > 8); - match(Set dst (LoadVectorGatherMasked mem (Binary idx_base mask))); - effect(TEMP_DEF dst, TEMP tmp, TEMP idx_base_temp, TEMP xtmp1, TEMP xtmp2, TEMP xtmp3, TEMP rtmp, TEMP rtmp2, TEMP mask_idx, TEMP length, KILL cr); - format %{ "vector_gatherGT8_masked $dst, $mem, $idx_base, $mask\t! using $tmp, $idx_base_temp, $xtmp1, $xtmp2, $xtmp3, $rtmp, $rtmp2, $mask_idx and $length as TEMP" %} - ins_encode %{ - int vlen_enc = vector_length_encoding(this); - int vector_len = Matcher::vector_length(this); - BasicType elem_bt = Matcher::vector_element_basic_type(this); - __ lea($tmp$$Register, $mem$$Address); - __ movptr($idx_base_temp$$Register, $idx_base$$Register); - __ vpmovmskb($rtmp2$$Register, $mask$$XMMRegister, vlen_enc); - if (elem_bt == T_SHORT) { - __ movl($mask_idx$$Register, 0x55555555); - __ pextl($rtmp2$$Register, $rtmp2$$Register, $mask_idx$$Register); - } - __ xorl($mask_idx$$Register, $mask_idx$$Register); - __ vgather_subword(elem_bt, $dst$$XMMRegister, $tmp$$Register, $idx_base_temp$$Register, $rtmp2$$Register, $xtmp1$$XMMRegister, - $xtmp2$$XMMRegister, $xtmp3$$XMMRegister, $rtmp$$Register, $mask_idx$$Register, $length$$Register, vector_len, vlen_enc); - %} - ins_pipe( pipe_slow ); -%} - // ====================Scatter======================================= // Scatter INT, LONG, FLOAT, DOUBLE @@ -18676,15 +18561,13 @@ instruct ReplHF_imm(vec dst, immH con, rRegI rtmp) %{ ins_pipe( pipe_slow ); %} -instruct ReplHF_reg(vec dst, regF src, rRegI rtmp) %{ +instruct ReplHF_reg(vec dst, regF src) %{ predicate(VM_Version::supports_avx512_fp16() && Matcher::vector_element_basic_type(n) == T_SHORT); match(Set dst (Replicate src)); - effect(TEMP rtmp); - format %{ "replicateHF $dst, $src \t! using $rtmp as TEMP" %} + format %{ "replicateHF $dst, $src" %} ins_encode %{ int vlen_enc = vector_length_encoding(this); - __ evmovw($rtmp$$Register, $src$$XMMRegister); - __ evpbroadcastw($dst$$XMMRegister, $rtmp$$Register, vlen_enc); + __ vpbroadcastw($dst$$XMMRegister, $src$$XMMRegister, vlen_enc); %} ins_pipe( pipe_slow ); %} diff --git a/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp b/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp new file mode 100644 index 000000000000..2ed9d8f558ad --- /dev/null +++ b/src/hotspot/os/bsd/gc/z/zAdaptiveHeap_bsd.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/z/zAdaptiveHeap.hpp" +#include "runtime/os.inline.hpp" +#include "utilities/debug.hpp" + +void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) { + info._physical_memory = os::Machine::physical_memory(); + info._is_valid = os::Machine::available_memory(info._available_memory); +} + +bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) { +#ifdef __APPLE__ + const int processor_count = os::processor_count(); + + if (processor_count <= 0) { + return false; + } + + mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; + host_cpu_load_info_data_t load_data; + + kern_return_t ret = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&load_data, &count); + if (ret != KERN_SUCCESS) { + assert(false, "This should never happen"); + return false; + } + + // The CPU ticks counters are 32-bit and may experience wrapping behaviour. + natural_t ticks = load_data.cpu_ticks[CPU_STATE_USER] + + load_data.cpu_ticks[CPU_STATE_NICE] + + load_data.cpu_ticks[CPU_STATE_SYSTEM]; + + value._elapsed_time = double(ticks) / CLK_TCK; + value._processor_count = double(processor_count); + return true; +#else + Unimplemented(); +#endif +} + +bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) { + ShouldNotReachHere(); +} + +bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) { +#ifdef __APPLE__ + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + vm_statistics64_data_t vmstat; + kern_return_t kerr = host_statistics64(mach_host_self(), HOST_VM_INFO64, + (host_info64_t)&vmstat, &count); + assert(kerr == KERN_SUCCESS, + "host_statistics64 failed - check mach_host_self() and count"); + if (kerr == KERN_SUCCESS) { + value = vmstat.compressor_page_count * os::vm_page_size(); + return true; + } +#endif + return false; +} diff --git a/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp b/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp index 0c42d7790886..11218d651839 100644 --- a/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp +++ b/src/hotspot/os/bsd/gc/z/zLargePages_bsd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,11 @@ #include "gc/z/zLargePages.hpp" #include "runtime/globals.hpp" +#include "utilities/debug.hpp" + +bool ZLargePages::pd_collapse(void* addr, size_t bytes) { + ShouldNotCallThis(); +} void ZLargePages::pd_initialize() { if (UseLargePages) { diff --git a/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp b/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp index 3acaa9ab8f94..c7046b806c20 100644 --- a/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp +++ b/src/hotspot/os/bsd/gc/z/zNUMA_bsd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,8 @@ void ZNUMA::pd_initialize() { _enabled = false; + _node_count = 1; + _bound_node_count = 1; _count = !FLAG_IS_DEFAULT(ZFakeNUMA) ? ZFakeNUMA : 1; diff --git a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp index 861fda7a71d7..f80604cf764d 100644 --- a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp +++ b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -92,7 +92,7 @@ bool ZPhysicalMemoryBacking::is_initialized() const { return _initialized; } -void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const { +void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const { // Does nothing } @@ -178,3 +178,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const { fatal("Failed to map memory (%s)", err.to_string()); } } + +void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const { + ShouldNotReachHere(); +} diff --git a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp index 9fa64fb51bd3..a8cbdfc9ad9e 100644 --- a/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp +++ b/src/hotspot/os/bsd/gc/z/zPhysicalMemoryBacking_bsd.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,13 +38,15 @@ class ZPhysicalMemoryBacking { bool is_initialized() const; - void warn_commit_limits(size_t max_capacity) const; + void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const; size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id) const; size_t uncommit(zbacking_offset offset, size_t length) const; void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const; void unmap(zaddress_unsafe addr, size_t size) const; + + void collapse(zaddress_unsafe addr, size_t size) const; }; #endif // OS_BSD_GC_Z_ZPHYSICALMEMORYBACKING_BSD_HPP diff --git a/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp b/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp new file mode 100644 index 000000000000..869b51f7b018 --- /dev/null +++ b/src/hotspot/os/linux/gc/z/zAdaptiveHeap_linux.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.hpp" +#include "gc/z/zNUMA.inline.hpp" +#include "jvm_io.h" +#include "logging/log.hpp" +#include "os_linux.hpp" +#include "osContainer_linux.hpp" +#include "runtime/os.inline.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) { + info._physical_memory = 0; + info._available_memory = 0; + info._is_valid = false; + + if (!ZNUMA::is_bound()) { + info._physical_memory = os::Machine::physical_memory(); + info._is_valid = os::Machine::available_memory(info._available_memory); + return; + } + + // In the case where the user has bound the process and -UseNUMA is provided, + // we must still gather information from all bound nodes. Even though our memory + // management does not try to do any NUMA optimizations when managing with memory. + assert(!ZNUMA::is_enabled() || ZNUMA::bound_count() == ZNUMA::count(), + "UseNUMA implies that bound_count(%u) == count(%u)", ZNUMA::bound_count(), ZNUMA::count()); + + for (uint32_t numa_id = 0; numa_id < ZNUMA::bound_count(); numa_id++) { + const int node = ZNUMA::numa_id_to_node(numa_id); + char path[128]; + jio_snprintf(path, sizeof(path), "/sys/devices/system/node/node%d/meminfo", node); + + physical_memory_size_type node_physical_memory = 0; + physical_memory_size_type node_available_memory = 0; + bool found_mem_total = false; + bool found_mem_available = false; + bool found_mem_free = false; + bool found_active_file = false; + bool found_inactive_file = false; + bool found_sreclaimable = false; + + FILE* f = os::fopen(path, "r"); + if (f) { + char line[256]; + while (fgets(line, sizeof(line), f) != nullptr) { + int n = -1; + physical_memory_size_type read_value = 0; + if (sscanf(line, "Node %d MemTotal: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_physical_memory = read_value * K; + found_mem_total = true; + } else if (sscanf(line, "Node %d MemAvailable: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + // If the Kernel has an approximation of MemAvailable, use it + node_available_memory = read_value * K; + found_mem_available = true; + } else if (!found_mem_available && sscanf(line, "Node %d MemFree: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_available_memory += read_value * K; + found_mem_free = true; + } else if (!found_mem_available && sscanf(line, "Node %d Active(file): " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_available_memory += read_value * K; + found_active_file = true; + } else if (!found_mem_available && sscanf(line, "Node %d Inactive(file): " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_available_memory += read_value * K; + found_inactive_file = true; + } else if (!found_mem_available && sscanf(line, "Node %d SReclaimable: " PHYS_MEM_TYPE_FORMAT " kB", &n, &read_value) == 2) { + node_available_memory += read_value * K; + found_sreclaimable = true; + } + + if (found_mem_total && (found_mem_available || (found_mem_free && found_active_file && found_inactive_file && found_sreclaimable))) { + break; + } + } + + fclose(f); + + if (!found_mem_total) { + // Some Kernels might not have "MemTotal" available in the node-specific file. + // Fall back to numa_node_size64 if that's the case. + long long res = os::Linux::numa_node_size64(node, nullptr); + if (res != -1) { + node_physical_memory = (physical_memory_size_type)res; + found_mem_total = true; + } + } + } + + if (!(found_mem_total && (found_mem_available || (found_mem_free && found_active_file && found_inactive_file && found_sreclaimable)))) { + static bool runs_once = [&]() { + log_warning_p(gc, heap)("Failed to read one of the NUMA-node specific values: " + "MemTotal: %d, MemAvailable: %d, MemFree: %d, Active(file): %d, Inactive(file): %d, SReclaimable: %d", + found_mem_total, found_mem_available, found_mem_free, found_active_file, found_inactive_file, found_sreclaimable); + return true; + }(); + assert(false, "This should not happen"); + + info._physical_memory = os::Machine::physical_memory(); + info._is_valid = os::Machine::available_memory(info._available_memory); + return; + } + + info._physical_memory += node_physical_memory; + info._available_memory += node_available_memory; + } + + info._is_valid = true; +} + +bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) { + os::Linux::CPUPerfTicks ticks; + + if (!os::Linux::get_tick_information(&ticks, -1)) { + return false; + } + + const int online_cpus = checked_cast(::sysconf(_SC_NPROCESSORS_ONLN)); + + if (online_cpus == -1) { + assert(false, "This should never happen"); + return false; + } + assert(online_cpus > 0, "Unespected online CPU count: %d", online_cpus); + + const uint64_t sum = ticks.used + ticks.usedKernel; + value._elapsed_time = double(sum) / os::Posix::clock_tics_per_second(); + value._processor_count = double(online_cpus); + return true; +} + +bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) { + precond(os::is_containerized()); + uint64_t result; + double processors; + // os::Container::processor_count does not account for the processors + // availiable in the container, but for the process. This can give a scew as + // cpu_usage_in_micros is the whole container. + if (OSContainer::cpu_usage_in_micros(result) && + os::Container::processor_count(processors)) { + value._elapsed_time = double(result) / MICROUNITS; + value._processor_count = processors; + return true; + } + + return false; +} + +bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) { + return false; +} diff --git a/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp b/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp index c9f99a4b6db4..76b5b38c5a34 100644 --- a/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp +++ b/src/hotspot/os/linux/gc/z/zLargePages_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,13 +21,78 @@ * questions. */ +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zLargePages.hpp" #include "hugepages.hpp" #include "os_linux.hpp" #include "runtime/globals.hpp" +#include "runtime/globals_extension.hpp" + +#include + +// Define MADV_COLLAPSE here so we can build HotSpot on old systems. +#define MADV_COLLAPSE_value 25 +#ifndef MADV_COLLAPSE +#define MADV_COLLAPSE MADV_COLLAPSE_value +#else + // Sanity-check our assumed default value if we build with a new enough libc. + static_assert(MADV_COLLAPSE == MADV_COLLAPSE_value); +#endif + +bool ZLargePages::pd_collapse(void* addr, size_t bytes) { + // When MADV_COLLAPSE races with THP khugepaged, you sometimes get + // EAGAIN. We just do it again then. + // Note: mlock, mlockall, DMA / GUP with FOLL_PIN/FOLL_LONGTERM can + // can all cause EAGAIN to be persistent. We treat this as a + // user error, and allow a calling thread to spin forever here. + // We may need to evaluate potential live-locking here in the future. + for (;;) { + const int result = ::madvise(addr, bytes, MADV_COLLAPSE); + if (result == 0) { + return true; + } + if (result == -1 && errno == EAGAIN) { + continue; + } + + return false; + } +} + +static bool madv_collapse_available() { + const size_t size = 2 * M; + void* const res = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (res == MAP_FAILED) { + return false; + } + + assert(size >= os::vm_page_size(), "Unexpected page size"); + os::pretouch_memory(res, (void*)(((char*)res) + os::vm_page_size())); + + const bool result = ZLargePages::pd_collapse(res, size); + + munmap(res, size); + + return result; +} void ZLargePages::pd_initialize() { + // Evaluate if we should move madv collapse logic to os::Linux / HugePages so + // that validate_thps_configured can log the warning correctly if shmem THP + // is disabled. + // Maybe combined with a general GC callback. Let os::Linux / HugePages contain + // the OS logic, let the GC do the extra log warnings as part of the + // validation callback. + const auto can_collapse = []() { + return ZMemoryHeating && madv_collapse_available(); + }; + if (os::Linux::thp_requested()) { + if (can_collapse()) { + _state = Collapse; + return; + } // Check if the OS config turned off transparent huge pages for shmem. _os_enforced_transparent_mode = HugePages::shmem_thp_info().is_disabled(); _state = _os_enforced_transparent_mode ? Disabled : Transparent; @@ -35,7 +100,25 @@ void ZLargePages::pd_initialize() { } if (UseLargePages) { - _state = Explicit; + if (!ZAdaptiveHeapSizing || ZAdaptiveHeap::explicit_max_capacity()) { + _state = Explicit; + return; + } + + log_warning(gc, init)("UseLargePages requires a max heap size to be set (-Xmx) when running with Adaptive Heap Sizing. " + "Disabling the use of explicit large pages for the heap"); + } + + // Because os::Linux::large_page_init does not set the origin, we cannot tell + // whether the user explicitly disabled large pages or explicitly requested + // large pages which the OS configuration did not support. + // Look into adding a similar cached user request as os::Linux::thp_requested + // for UseLargePages. + const bool large_pages_explicitly_disabled = !FLAG_IS_DEFAULT(UseLargePages) && !UseLargePages; + const bool thp_explicitly_disabled = !FLAG_IS_DEFAULT(UseTransparentHugePages) && !UseTransparentHugePages; + + if (!large_pages_explicitly_disabled && !thp_explicitly_disabled && can_collapse()) { + _state = Collapse; return; } diff --git a/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp b/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp index 3245d07caf9f..f44d4bac7f4f 100644 --- a/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp +++ b/src/hotspot/os/linux/gc/z/zNUMA_linux.cpp @@ -102,7 +102,6 @@ class ZNUMAConverter { public: void initialize() { precond(!_initialized); - precond(UseNUMA); populate_id_mappings(); populate_node_mappings(); @@ -140,15 +139,23 @@ static ZNUMAConverter z_numa_converter; void ZNUMA::pd_initialize() { _enabled = UseNUMA; - if (UseNUMA) { + const int configured_nodes = os::Linux::numa_num_configured_nodes(); + if (configured_nodes > 0) { z_numa_converter.initialize(); - _count = z_numa_converter.count(); + + _node_count = integer_cast(configured_nodes); + _bound_node_count = z_numa_converter.count(); } else { - // UseNUMA and is_faked() are mutually excluded in zArguments.cpp. - _count = !FLAG_IS_DEFAULT(ZFakeNUMA) - ? ZFakeNUMA - : 1; // No NUMA nodes + _node_count = 1; + _bound_node_count = 1; } + + // UseNUMA and is_faked() are mutually excluded in zArguments.cpp. + _count = UseNUMA + ? _bound_node_count + : !FLAG_IS_DEFAULT(ZFakeNUMA) + ? ZFakeNUMA + : 1; // No NUMA nodes } uint32_t ZNUMA::id() { diff --git a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp index 28159ae48016..f6b92d746f3b 100644 --- a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp +++ b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,7 @@ */ #include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddress.inline.hpp" #include "gc/z/zArray.inline.hpp" #include "gc/z/zErrno.hpp" @@ -35,6 +36,8 @@ #include "hugepages.hpp" #include "logging/log.hpp" #include "os_linux.hpp" +#include "runtime/globals.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/init.hpp" #include "runtime/os.hpp" #include "runtime/safefetch.hpp" @@ -116,6 +119,8 @@ static const char* ZPreferredHugetlbfsMountpoints[] = { static int z_fallocate_hugetlbfs_attempts = 3; static bool z_fallocate_supported = true; +static size_t z_max_map_count = 0; + ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity) : _fd(-1), _filesystem(0), @@ -151,6 +156,14 @@ ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity) _block_size = buf.f_bsize; _available = buf.f_bavail * _block_size; + // Note that the available space on a tmpfs or a hugetlbfs filesystem + // will be zero if no size limit was specified when it was mounted. + if (_available == 0) { + log_info_p(gc, init)("Available space on backing filesystem: N/A"); + } else { + log_info_p(gc, init)("Available space on backing filesystem: %zuM", _available / M); + } + log_info_p(gc, init)("Heap Backing Filesystem: %s (" UINT64_FORMAT_X ")", is_tmpfs() ? ZFILESYSTEM_TMPFS : is_hugetlbfs() ? ZFILESYSTEM_HUGETLBFS : "other", _filesystem); @@ -192,6 +205,9 @@ ZPhysicalMemoryBacking::ZPhysicalMemoryBacking(size_t max_capacity) return; } + // Should we try to use fallocate + z_fallocate_supported = ZLargePages::should_try_fallocate(); + // Successfully initialized _initialized = true; } @@ -308,12 +324,9 @@ void ZPhysicalMemoryBacking::warn_available_space(size_t max_capacity) const { // will be zero if no size limit was specified when it was mounted. if (_available == 0) { // No size limit set, skip check - log_info_p(gc, init)("Available space on backing filesystem: N/A"); return; } - log_info_p(gc, init)("Available space on backing filesystem: %zuM", _available / M); - // Warn if the filesystem doesn't currently have enough space available to hold // the max heap size. The max heap size will be capped if we later hit this limit // when trying to expand the heap. @@ -328,7 +341,11 @@ void ZPhysicalMemoryBacking::warn_available_space(size_t max_capacity) const { } } -void ZPhysicalMemoryBacking::warn_max_map_count(size_t max_capacity) const { +void ZPhysicalMemoryBacking::compute_max_map_count() const { + if (z_max_map_count != 0) { + return; + } + const char* const filename = ZFILENAME_PROC_MAX_MAP_COUNT; FILE* const file = os::fopen(filename, "r"); if (file == nullptr) { @@ -337,38 +354,52 @@ void ZPhysicalMemoryBacking::warn_max_map_count(size_t max_capacity) const { return; } - size_t actual_max_map_count = 0; - const int result = fscanf(file, "%zu", &actual_max_map_count); + const int result = fscanf(file, "%zu", &z_max_map_count); fclose(file); if (result != 1) { // Failed to read file, skip check log_debug_p(gc, init)("Failed to read %s", filename); return; } +} + +void ZPhysicalMemoryBacking::warn_max_map_count(size_t expected_capacity, size_t max_capacity) const { + // In the worst case, ZGC needs 2 mappings per granule. The reason is that if the + // fragmentation is at maximum, the mappings will be interleaved between being + // backed by memory vs not being backed by memory. + const size_t mappings_per_granule = 2; + const size_t expected_mappings = (expected_capacity / ZGranuleSize) * mappings_per_granule; + const size_t max_mappings = (max_capacity / ZGranuleSize) * mappings_per_granule; // The required max map count is impossible to calculate exactly since subsystems // other than ZGC are also creating memory mappings, and we have no control over that. // However, ZGC tends to create the most mappings and dominate the total count. - // In the worst cases, ZGC will map each granule three times, i.e. once per heap view. - // We speculate that we need another 20% to allow for non-ZGC subsystems to map memory. - const size_t required_max_map_count = (max_capacity / ZGranuleSize) * 3 * 1.2; - if (actual_max_map_count < required_max_map_count) { - log_warning_p(gc)("***** WARNING! INCORRECT SYSTEM CONFIGURATION DETECTED! *****"); - log_warning_p(gc)("The system limit on number of memory mappings per process might be too low for the given"); - log_warning_p(gc)("max Java heap size (%zuM). Please adjust %s to allow for at", - max_capacity / M, filename); - log_warning_p(gc)("least %zu mappings (current limit is %zu). Continuing execution " - "with the current", required_max_map_count, actual_max_map_count); - log_warning_p(gc)("limit could lead to a premature OutOfMemoryError being thrown, due to failure to map memory."); + const size_t required_max_map_count = expected_mappings + expected_mappings / 5; + if (z_max_map_count >= required_max_map_count) { + return; } + + const size_t recommended_max_map_count = max_mappings + max_mappings / 5; + + const char* const filename = ZFILENAME_PROC_MAX_MAP_COUNT; + log_warning_p(gc)("***** WARNING! INCORRECT SYSTEM CONFIGURATION DETECTED! *****"); + log_warning_p(gc)("The system limit on number of memory mappings per process might be too low for the"); + log_warning_p(gc)("Java heap size (%zuM). Please adjust %s to allow for at", + expected_capacity / M, filename); + log_warning_p(gc)("least %zu mappings (current limit is %zu). Continuing execution " + "with the current", recommended_max_map_count, z_max_map_count); + log_warning_p(gc)("limit could lead to a premature OutOfMemoryError being thrown, due to failure to map memory."); } -void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const { +void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const { // Warn if available space is too low - warn_available_space(max_capacity); + warn_available_space(expected_capacity); + + // Compute max map count the first time (during bootstrapping) + compute_max_map_count(); // Warn if max map count is too low - warn_max_map_count(max_capacity); + warn_max_map_count(expected_capacity, max_capacity); } bool ZPhysicalMemoryBacking::is_tmpfs() const { @@ -513,21 +544,24 @@ ZErrno ZPhysicalMemoryBacking::fallocate_fill_hole(zbacking_offset offset, size_ // some point touch these segments, otherwise we can not punch hole in them. // Also note that we need to use compat mode when using transparent huge pages, // since we need to use madvise(2) on the mapping before the page is allocated. - if (z_fallocate_supported && !ZLargePages::is_enabled()) { - const ZErrno err = fallocate_fill_hole_syscall(offset, length); - if (!err) { - // Success - return 0; - } + if (z_fallocate_supported) { + precond(ZLargePages::should_try_fallocate()); + + const ZErrno err = fallocate_fill_hole_syscall(offset, length); + + if (!err) { + // Success + return 0; + } - if (err != ENOSYS && err != EOPNOTSUPP) { - // Failed - return err; - } + if (err != ENOSYS && err != EOPNOTSUPP) { + // Failed + return err; + } - // Not supported - log_debug_p(gc)("Falling back to fallocate() compatibility mode"); - z_fallocate_supported = false; + // Not supported + log_debug_p(gc)("Falling back to fallocate() compatibility mode"); + z_fallocate_supported = false; } return fallocate_fill_hole_compat(offset, length); @@ -593,6 +627,8 @@ ZErrno ZPhysicalMemoryBacking::fallocate(bool punch_hole, zbacking_offset offset return err; } +static Atomic warned_failed_commit{false}; + bool ZPhysicalMemoryBacking::commit_inner(zbacking_offset offset, size_t length) const { log_trace(gc, heap)("Committing memory: %zuM-%zuM (%zuM)", untype(offset) / M, untype(to_zbacking_offset_end(offset, length)) / M, length / M); @@ -615,8 +651,11 @@ bool ZPhysicalMemoryBacking::commit_inner(zbacking_offset offset, size_t length) goto retry; } + if (!warned_failed_commit.exchange(true, memory_order_relaxed)) { + log_error_p(gc)("Failed to commit memory (%s)", err.to_string()); + } + // Failed - log_error_p(gc)("Failed to commit memory (%s)", err.to_string()); return false; } @@ -705,3 +744,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const { fatal("Failed to map memory (%s)", err.to_string()); } } + +void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const { + ZLargePages::pd_collapse((void*)untype(addr), size); +} diff --git a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp index 4b083d1c79c3..f192ba0dc286 100644 --- a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp +++ b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,14 +31,14 @@ class ZErrno; class ZPhysicalMemoryBacking { private: int _fd; - size_t _size; uint64_t _filesystem; size_t _block_size; size_t _available; bool _initialized; void warn_available_space(size_t max_capacity) const; - void warn_max_map_count(size_t max_capacity) const; + void compute_max_map_count() const; + void warn_max_map_count(size_t expected_capacity, size_t max_capacity) const; int create_mem_fd(const char* name) const; int create_file_fd(const char* name) const; @@ -67,13 +67,15 @@ class ZPhysicalMemoryBacking { bool is_initialized() const; - void warn_commit_limits(size_t max_capacity) const; + void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const; size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id) const; size_t uncommit(zbacking_offset offset, size_t length) const; void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const; void unmap(zaddress_unsafe addr, size_t size) const; + + void collapse(zaddress_unsafe addr, size_t size) const; }; #endif // OS_LINUX_GC_Z_ZPHYSICALMEMORYBACKING_LINUX_HPP diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index b2db5a51d54c..ffe0b6f56310 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -3513,6 +3513,8 @@ bool os::Linux::libnuma_init() { libnuma_dlsym(handle, "numa_num_configured_nodes"))); set_numa_available(CAST_TO_FN_PTR(numa_available_func_t, libnuma_dlsym(handle, "numa_available"))); + set_numa_node_size64(CAST_TO_FN_PTR(numa_node_size64_func_t, + libnuma_dlsym(handle, "numa_node_size64"))); set_numa_tonode_memory(CAST_TO_FN_PTR(numa_tonode_memory_func_t, libnuma_dlsym(handle, "numa_tonode_memory"))); set_numa_interleave_memory(CAST_TO_FN_PTR(numa_interleave_memory_func_t, @@ -3802,6 +3804,7 @@ os::Linux::numa_node_to_cpus_func_t os::Linux::_numa_node_to_cpus; os::Linux::numa_node_to_cpus_v2_func_t os::Linux::_numa_node_to_cpus_v2; os::Linux::numa_max_node_func_t os::Linux::_numa_max_node; os::Linux::numa_num_configured_nodes_func_t os::Linux::_numa_num_configured_nodes; +os::Linux::numa_node_size64_func_t os::Linux::_numa_node_size64; os::Linux::numa_available_func_t os::Linux::_numa_available; os::Linux::numa_tonode_memory_func_t os::Linux::_numa_tonode_memory; os::Linux::numa_interleave_memory_func_t os::Linux::_numa_interleave_memory; diff --git a/src/hotspot/os/linux/os_linux.hpp b/src/hotspot/os/linux/os_linux.hpp index 5b7259f2c24c..a2925f7207bf 100644 --- a/src/hotspot/os/linux/os_linux.hpp +++ b/src/hotspot/os/linux/os_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -215,6 +215,7 @@ class os::Linux { typedef int (*numa_node_to_cpus_v2_func_t)(int node, void *mask); typedef int (*numa_max_node_func_t)(void); typedef int (*numa_num_configured_nodes_func_t)(void); + typedef long long (*numa_node_size64_func_t)(int node, long long* freep); typedef int (*numa_available_func_t)(void); typedef int (*numa_tonode_memory_func_t)(void *start, size_t size, int node); typedef void (*numa_interleave_memory_func_t)(void *start, size_t size, unsigned long *nodemask); @@ -237,6 +238,7 @@ class os::Linux { static numa_node_to_cpus_v2_func_t _numa_node_to_cpus_v2; static numa_max_node_func_t _numa_max_node; static numa_num_configured_nodes_func_t _numa_num_configured_nodes; + static numa_node_size64_func_t _numa_node_size64; static numa_available_func_t _numa_available; static numa_tonode_memory_func_t _numa_tonode_memory; static numa_interleave_memory_func_t _numa_interleave_memory; @@ -266,6 +268,7 @@ class os::Linux { static void set_numa_node_to_cpus_v2(numa_node_to_cpus_v2_func_t func) { _numa_node_to_cpus_v2 = func; } static void set_numa_max_node(numa_max_node_func_t func) { _numa_max_node = func; } static void set_numa_num_configured_nodes(numa_num_configured_nodes_func_t func) { _numa_num_configured_nodes = func; } + static void set_numa_node_size64(numa_node_size64_func_t func) { _numa_node_size64 = func; } static void set_numa_available(numa_available_func_t func) { _numa_available = func; } static void set_numa_tonode_memory(numa_tonode_memory_func_t func) { _numa_tonode_memory = func; } static void set_numa_interleave_memory(numa_interleave_memory_func_t func) { _numa_interleave_memory = func; } @@ -307,6 +310,9 @@ class os::Linux { static int numa_num_configured_nodes() { return _numa_num_configured_nodes != nullptr ? _numa_num_configured_nodes() : -1; } + static long long numa_node_size64(int node, long long* freep) { + return _numa_node_size64 != nullptr ? _numa_node_size64(node, freep) : -1; + } static int numa_available() { return _numa_available != nullptr ? _numa_available() : -1; } static int numa_tonode_memory(void *start, size_t size, int node) { return _numa_tonode_memory != nullptr ? _numa_tonode_memory(start, size, node) : -1; diff --git a/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp b/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp new file mode 100644 index 000000000000..eb706c323625 --- /dev/null +++ b/src/hotspot/os/windows/gc/z/zAdaptiveHeap_windows.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/z/zAdaptiveHeap.hpp" +#include "runtime/os.inline.hpp" + +#include +#include + +void ZAdaptiveHeap::pd_machine_memory_info(ZMachineMemoryInfo& info) { + info._physical_memory = os::Machine::physical_memory(); + info._is_valid = os::Machine::available_memory(info._available_memory); +} + +bool ZAdaptiveHeap::pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value) { + const int processor_count = os::processor_count(); + + if (processor_count <= 0 || processor_count > 64) { + // GetSystemTimes is not accurate on systems with more than 64 cores. + return false; + } + + FILETIME idle, kernel, user; + if (GetSystemTimes(&idle, &kernel, &user) == 0) { + assert(false, "this should not fail"); + return false; + } + + // Kernel time includes idle time + jlong ticks = jlong_from(user.dwHighDateTime, user.dwLowDateTime) + + jlong_from(kernel.dwHighDateTime, kernel.dwLowDateTime) - + jlong_from(idle.dwHighDateTime, idle.dwLowDateTime); + + // Ticks are 100 ns + value._elapsed_time = double(ticks) / 1e7; + value._processor_count = double(processor_count); + return true; +} + +bool ZAdaptiveHeap::pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value) { + ShouldNotReachHere(); +} + +bool ZAdaptiveHeap::pd_machine_compressed_memory(physical_memory_size_type& value) { + return false; +} diff --git a/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp b/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp index dcd178da8212..70aaa3447a1b 100644 --- a/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp +++ b/src/hotspot/os/windows/gc/z/zLargePages_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,11 @@ #include "gc/z/zLargePages.hpp" #include "gc/z/zSyscall_windows.hpp" #include "runtime/globals.hpp" +#include "utilities/debug.hpp" + +bool ZLargePages::pd_collapse(void* addr, size_t bytes) { + ShouldNotCallThis(); +} void ZLargePages::pd_initialize() { if (UseLargePages) { diff --git a/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp b/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp index e2bd68035844..2a49e58b656d 100644 --- a/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp +++ b/src/hotspot/os/windows/gc/z/zNUMA_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,8 @@ void ZNUMA::pd_initialize() { _enabled = false; + _node_count = 1; + _bound_node_count = 1; _count = !FLAG_IS_DEFAULT(ZFakeNUMA) ? ZFakeNUMA : 1; diff --git a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp index b18abd7e9792..15f1a846de6d 100644 --- a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp +++ b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -226,7 +226,7 @@ bool ZPhysicalMemoryBacking::is_initialized() const { return true; } -void ZPhysicalMemoryBacking::warn_commit_limits(size_t max_capacity) const { +void ZPhysicalMemoryBacking::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const { // Does nothing } @@ -258,3 +258,7 @@ void ZPhysicalMemoryBacking::unmap(zaddress_unsafe addr, size_t size) const { _impl->unmap(addr, size); } + +void ZPhysicalMemoryBacking::collapse(zaddress_unsafe addr, size_t size) const { + ShouldNotReachHere(); +} diff --git a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp index 91d59f496098..3e20c655c785 100644 --- a/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp +++ b/src/hotspot/os/windows/gc/z/zPhysicalMemoryBacking_windows.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,13 +40,15 @@ class ZPhysicalMemoryBacking { bool is_initialized() const; - void warn_commit_limits(size_t max_capacity) const; + void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const; size_t commit(zbacking_offset offset, size_t length, uint32_t numa_id); size_t uncommit(zbacking_offset offset, size_t length); void map(zaddress_unsafe addr, size_t size, zbacking_offset offset) const; void unmap(zaddress_unsafe addr, size_t size) const; + + void collapse(zaddress_unsafe addr, size_t size) const; }; #endif // OS_WINDOWS_GC_Z_ZPHYSICALMEMORYBACKING_WINDOWS_HPP diff --git a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp index 6643187170ab..f63bdb62dff5 100644 --- a/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp +++ b/src/hotspot/os_cpu/linux_s390/javaThread_linux_s390.cpp @@ -47,7 +47,8 @@ frame JavaThread::pd_last_frame() { } #endif - return frame(sp, pc); + // Likely the frame of a RuntimeStub (e.g. after a call to the VM). + return frame(sp, pc, frame::kind::code_blob); } bool JavaThread::pd_get_top_frame_for_profiling(frame* fr_addr, void* ucontext, bool isInJava) { @@ -71,7 +72,8 @@ bool JavaThread::pd_get_top_frame_for_profiling(frame* fr_addr, void* ucontext, return false; } - frame ret_frame((intptr_t*)uc->uc_mcontext.gregs[15/*Z_SP*/], pc); + // pc is from ucontext — could be anywhere (native, compiled, unknown). + frame ret_frame((intptr_t*)uc->uc_mcontext.gregs[15/*Z_SP*/], pc, frame::kind::unknown); if (ret_frame.fp() == nullptr) { // The found frame does not have a valid frame pointer. diff --git a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp index cbb06d2d07d8..17aa7fd84841 100644 --- a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp +++ b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp @@ -145,14 +145,14 @@ frame os::fetch_frame_from_context(const void* ucVoid) { // compiled frame stack bang return fetch_compiled_frame_from_context(ucVoid); } - return frame(sp, epc); + return frame(sp, epc, frame::kind::unknown); } frame os::fetch_compiled_frame_from_context(const void* ucVoid) { const ucontext_t* uc = (const ucontext_t*)ucVoid; intptr_t* sp = os::Linux::ucontext_get_sp(uc); address lr = ucontext_get_lr(uc); - return frame(sp, lr); + return frame(sp, lr, frame::kind::unknown); } intptr_t* os::fetch_bcp_from_context(const void* ucVoid) { @@ -171,23 +171,23 @@ frame os::get_sender_for_C_frame(frame* fr) { // of gpr10. if ((Interpreter::code() != nullptr && Interpreter::contains(fr->pc())) || (CodeCache::contains(fr->pc()) && !StubRoutines::contains(fr->pc()))) { - return frame(fr->sender_sp(), fr->sender_pc()); + return frame(fr->sender_sp(), fr->sender_pc(), frame::kind::unknown); } else { if (StubRoutines::contains(fr->pc())) { StubCodeDesc* desc = StubCodeDesc::desc_for(fr->pc()); if (desc && !strcmp(desc->name(),"call_stub")) { - return frame(fr->sender_sp(), fr->callstub_sender_pc()); + return frame(fr->sender_sp(), fr->callstub_sender_pc(), frame::kind::unknown); } else { - return frame(fr->sender_sp(), fr->sender_pc()); + return frame(fr->sender_sp(), fr->sender_pc(), frame::kind::unknown); } } else { intptr_t* sender_sp = fr->sender_sp(); address sender_fp = (address)*sender_sp; ptrdiff_t entry_len = sender_fp - (address)sender_sp; if (entry_len < frame::z_abi_160_size) { - return frame(sender_sp, fr->sender_pc()); + return frame(sender_sp, fr->sender_pc(), frame::kind::unknown); } else { - return frame(sender_sp, fr->native_sender_pc()); + return frame(sender_sp, fr->native_sender_pc(), frame::kind::unknown); } } } @@ -200,7 +200,7 @@ frame os::current_frame() { assert (csp != nullptr, "sp should not be null"); // Pass a dummy pc. This way we don't have to load it from the // stack, since we don't know in which slot we can find it. - frame topframe(csp, (address)0x8); + frame topframe(csp, (address)0x8, frame::kind::unknown); if (os::is_first_C_frame(&topframe)) { // Stack is not walkable. return frame(); diff --git a/src/hotspot/share/adlc/output_c.cpp b/src/hotspot/share/adlc/output_c.cpp index ec764a72846b..a21c6472ce9d 100644 --- a/src/hotspot/share/adlc/output_c.cpp +++ b/src/hotspot/share/adlc/output_c.cpp @@ -3279,7 +3279,6 @@ void ArchDesc::defineClasses(FILE *fp) { if ( memory_operand != InstructForm::NO_MEMORY_OPERAND ) { if( memory_operand == InstructForm::MANY_MEMORY_OPERANDS ) { - fprintf(fp,"const TypePtr *%sNode::adr_type() const { return TypePtr::BOTTOM; }\n", instr->_ident); fprintf(fp,"const MachOper* %sNode::memory_operand() const { return (MachOper*)-1; }\n", instr->_ident); } else { fprintf(fp,"const MachOper* %sNode::memory_operand() const { return _opnds[%d]; }\n", instr->_ident, memory_operand); diff --git a/src/hotspot/share/adlc/output_h.cpp b/src/hotspot/share/adlc/output_h.cpp index 616e457cdd3e..cd25acea664e 100644 --- a/src/hotspot/share/adlc/output_h.cpp +++ b/src/hotspot/share/adlc/output_h.cpp @@ -728,8 +728,8 @@ void gen_inst_format(FILE *fp, FormDict &globals, InstructForm &inst, bool for_c } else if( inst.is_ideal_mem() ) { // Print out the field name if available to improve readability - fprintf(fp, " if (ra->C->alias_type(adr_type())->field() != nullptr) {\n"); - fprintf(fp, " ciField* f = ra->C->alias_type(adr_type())->field();\n"); + fprintf(fp, " if (ra->C->alias_type(in_adr_type())->field() != nullptr) {\n"); + fprintf(fp, " ciField* f = ra->C->alias_type(in_adr_type())->field();\n"); fprintf(fp, " st->print(\" %s Field: \");\n", commentSeperator); fprintf(fp, " if (f->is_volatile())\n"); fprintf(fp, " st->print(\"volatile \");\n"); @@ -740,7 +740,7 @@ void gen_inst_format(FILE *fp, FormDict &globals, InstructForm &inst, bool for_c fprintf(fp, " st->print(\" (constant)\");\n"); fprintf(fp, " } else {\n"); // Make sure 'Volatile' gets printed out - fprintf(fp, " if (ra->C->alias_type(adr_type())->is_volatile())\n"); + fprintf(fp, " if (ra->C->alias_type(in_adr_type())->is_volatile())\n"); fprintf(fp, " st->print(\" volatile!\");\n"); fprintf(fp, " }\n"); } @@ -1885,9 +1885,6 @@ void ArchDesc::declareClasses(FILE *fp) { // Analyze machine instructions that either USE or DEF memory. int memory_operand = instr->memory_operand(_globalNames); if ( memory_operand != InstructForm::NO_MEMORY_OPERAND ) { - if( memory_operand == InstructForm::MANY_MEMORY_OPERANDS ) { - fprintf(fp," virtual const TypePtr *adr_type() const;\n"); - } fprintf(fp," virtual const MachOper *memory_operand() const;\n"); } diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index 50bad1daef21..c260b118ed91 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -1389,7 +1389,7 @@ void GraphBuilder::if_node(Value x, If::Condition cond, Value y, ValueStack* sta } } } - if ((stream()->cur_bc() == Bytecodes::_if_acmpeq || stream()->cur_bc() == Bytecodes::_if_acmpne) && + if (Arguments::is_valhalla_enabled() && (stream()->cur_bc() == Bytecodes::_if_acmpeq || stream()->cur_bc() == Bytecodes::_if_acmpne) && profile_acmp()) { compilation()->set_would_profile(true); append(new ProfileACmpTypes(method(), bci(), x, y)); @@ -1803,14 +1803,24 @@ Value GraphBuilder::make_constant(ciConstant field_value, ciField* field) { } } -void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, ciField* enclosing_field) { +void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, + CopyNullChecks& null_checks, ciField* enclosing_field) { for (int i = 0; i < vk->nof_declared_nonstatic_fields(); i++) { ciField* field = vk->declared_nonstatic_field_at(i); int offset = field->offset_in_bytes() - vk->payload_offset(); if (field->is_flat()) { - copy_value_content(field->type()->as_value_klass(), src, src_off + offset, dest, dest_off + offset, state_before, enclosing_field); + copy_value_content(field->type()->as_value_klass(), src, src_off + offset, dest, dest_off + offset, state_before, null_checks, enclosing_field); if (!field->is_null_free()) { // Nullable, copy the null marker using Unsafe because null markers are not real fields + // Unsafe accesses do not perform implicit null checks. Add explicit ones if needed. + if (null_checks.needs_dst) { + null_check(dest); + null_checks.needs_dst = false; + } + if (null_checks.needs_src) { + null_check(src); + null_checks.needs_src = false; + } int null_marker_offset = field->null_marker_offset() - vk->payload_offset(); Value offset = append(new Constant(new LongConstant(src_off + null_marker_offset))); Value nm = append(new UnsafeGet(T_BOOLEAN, src, offset, false)); @@ -1819,9 +1829,11 @@ void GraphBuilder::copy_value_content(ciValueKlass* vk, Value src, int src_off, } } else { Value value = append(new LoadField(src, src_off + offset, field, false, state_before, false)); + null_checks.needs_src = false; StoreField* store = new StoreField(dest, dest_off + offset, field, value, false, state_before, false); store->set_enclosing_field(enclosing_field); append(store); + null_checks.needs_dst = false; } } } @@ -2052,9 +2064,10 @@ void GraphBuilder::access_field(Bytecodes::Code code) { NewInstance* buffer = new NewInstance(value_klass, state_before, false, true); _memory->new_instance(buffer); apush(append_split(buffer)); + CopyNullChecks null_checks = { false, false }; copy_value_content(value_klass, pending_field_access()->obj(), - pending_field_access()->offset() + field->offset_in_bytes() - field->holder()->as_value_klass()->payload_offset(), - buffer, value_klass->payload_offset(), state_before); + pending_field_access()->offset() + field->offset_in_bytes() - field->holder()->as_value_klass()->payload_offset(), + buffer, value_klass->payload_offset(), state_before, null_checks); set_pending_field_access(nullptr); } else { if (!field->is_null_free() && !value_klass->is_initialized()) { @@ -2064,15 +2077,20 @@ void GraphBuilder::access_field(Bytecodes::Code code) { return; } + CopyNullChecks null_checks = { true, false }; + if (value_klass->is_empty()) { + // The content copy emits no access that can implicitly null-check the holder. + null_check(obj); + null_checks.needs_src = false; + } + NewInstance* buffer = new NewInstance(value_klass, state_before, false, true); _memory->new_instance(buffer); append_split(buffer); - if (value_klass->is_initialized() && value_klass->is_empty()) { - // Needs an explicit null check because below code does not perform any actual load if there are no fields - null_check(obj); - } - copy_value_content(value_klass, obj, field->offset_in_bytes(), buffer, value_klass->payload_offset(), state_before); + copy_value_content(value_klass, obj, field->offset_in_bytes(), buffer, value_klass->payload_offset(), + state_before, null_checks); + assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted"); Instruction* result = buffer; if (!field->is_null_free()) { @@ -2131,23 +2149,28 @@ void GraphBuilder::access_field(Bytecodes::Code code) { append(new StoreField(obj, offset, field, val, false, state_before, needs_patching)); } else if (field->is_null_free()) { assert(!value_klass->is_empty(), "should have been handled"); - copy_value_content(value_klass, val, value_klass->payload_offset(), obj, offset, state_before, field); + CopyNullChecks null_checks = { true, true }; + copy_value_content(value_klass, val, value_klass->payload_offset(), obj, offset, state_before, null_checks, field); + assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted"); } else { if (!value_klass->is_initialized()) { // null_reset_value is not available, bailout for now bailout("store to an uninitialized nullable non-atomic flat field"); return; } + CopyNullChecks null_checks = { false, true }; if (value_klass->is_empty()) { - // Needs an explicit null check because the code below does not emit a store that performs an implicit null check for empty classes. + // The content copy emits no access that can implicitly null-check the holder. null_check(obj); + null_checks.needs_dst = false; } // Store the subfields when field is a nullable non-atomic field Value object_null = append(new Constant(objectNull)); Value null_reset_value = append(new Constant(new ObjectConstant(value_klass->get_null_reset_value().as_object()))); Value src = append(new IfOp(val, Instruction::neq, object_null, val, null_reset_value, state_before, false)); - copy_value_content(value_klass, src, value_klass->payload_offset(), obj, offset, state_before); + copy_value_content(value_klass, src, value_klass->payload_offset(), obj, offset, state_before, null_checks); + assert(!null_checks.needs_src && !null_checks.needs_dst, "null checks should have been emitted"); // Store the null marker Value int_one = append(new Constant(new IntConstant(1))); diff --git a/src/hotspot/share/c1/c1_GraphBuilder.hpp b/src/hotspot/share/c1/c1_GraphBuilder.hpp index 0a4f31983508..e6cb358fc901 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.hpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.hpp @@ -299,7 +299,13 @@ class GraphBuilder { void throw_op(int bci); // value types - void copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, ciField* enclosing_field = nullptr); + struct CopyNullChecks { + bool needs_src; + bool needs_dst; + }; + + void copy_value_content(ciValueKlass* vk, Value src, int src_off, Value dest, int dest_off, ValueStack* state_before, + CopyNullChecks& null_checks, ciField* enclosing_field = nullptr); // stack/code manipulation helpers Instruction* append_with_bci(Instruction* instr, int bci); diff --git a/src/hotspot/share/c1/c1_LIR.hpp b/src/hotspot/share/c1/c1_LIR.hpp index 5f2a0bcbd843..55f7562c7e15 100644 --- a/src/hotspot/share/c1/c1_LIR.hpp +++ b/src/hotspot/share/c1/c1_LIR.hpp @@ -2133,7 +2133,9 @@ class LIR_OpProfileValueType : public LIR_Op { , _obj(obj) , _flag(flag) , _tmp(tmp) - , _not_null(not_null) { } + , _not_null(not_null) { + assert(Arguments::is_valhalla_enabled(), "Should not be called without value types enabled"); + } LIR_Opr mdp() const { return _mdp; } LIR_Opr obj() const { return _obj; } diff --git a/src/hotspot/share/c1/c1_Optimizer.cpp b/src/hotspot/share/c1/c1_Optimizer.cpp index 17ad26a311a2..58c9e5bf4c5e 100644 --- a/src/hotspot/share/c1/c1_Optimizer.cpp +++ b/src/hotspot/share/c1/c1_Optimizer.cpp @@ -767,9 +767,9 @@ void NullCheckVisitor::do_Throw (Throw* x) { nce()->clear_las void NullCheckVisitor::do_Base (Base* x) {} void NullCheckVisitor::do_OsrEntry (OsrEntry* x) {} void NullCheckVisitor::do_ExceptionObject(ExceptionObject* x) { nce()->handle_ExceptionObject(x); } -void NullCheckVisitor::do_UnsafeGet (UnsafeGet* x) {} -void NullCheckVisitor::do_UnsafePut (UnsafePut* x) {} -void NullCheckVisitor::do_UnsafeGetAndSet(UnsafeGetAndSet* x) {} +void NullCheckVisitor::do_UnsafeGet (UnsafeGet* x) { nce()->clear_last_explicit_null_check(); } +void NullCheckVisitor::do_UnsafePut (UnsafePut* x) { nce()->clear_last_explicit_null_check(); } +void NullCheckVisitor::do_UnsafeGetAndSet(UnsafeGetAndSet* x) { nce()->clear_last_explicit_null_check(); } void NullCheckVisitor::do_ProfileCall (ProfileCall* x) { nce()->clear_last_explicit_null_check(); nce()->handle_ProfileCall(x); } void NullCheckVisitor::do_ProfileReturnType (ProfileReturnType* x) { nce()->handle_ProfileReturnType(x); } @@ -1094,6 +1094,7 @@ void NullCheckEliminator::handle_Invoke(Invoke* x) { void NullCheckEliminator::handle_NewInstance(NewInstance* x) { + clear_last_explicit_null_check(); set_put(x); if (PrintNullCheckElimination) { tty->print_cr("NewInstance %d is non-null", x->id()); diff --git a/src/hotspot/share/cds/aotArtifactFinder.cpp b/src/hotspot/share/cds/aotArtifactFinder.cpp index 30476c96109c..90009271fb45 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.cpp +++ b/src/hotspot/share/cds/aotArtifactFinder.cpp @@ -25,10 +25,10 @@ #include "cds/aotArtifactFinder.hpp" #include "cds/aotClassInitializer.hpp" #include "cds/aotClassLinker.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLogging.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/dumpTimeClassInfo.inline.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -44,7 +44,7 @@ static GrowableArrayCHeap* _all_cached_classes = nullptr; // This is a stack that tracks all the AOT-inited classes that are waiting to be passed -// to HeapShared::copy_and_rescan_aot_inited_mirror(). +// to AOTHeap::copy_and_rescan_aot_inited_mirror(). static GrowableArrayCHeap* _pending_aot_inited_classes = nullptr; static const int TABLE_SIZE = 15889; // prime number @@ -99,17 +99,17 @@ void AOTArtifactFinder::find_artifacts() { BasicType bt = (BasicType)i; if (!is_reference_type(bt)) { oop orig_mirror = Universe::java_mirror(bt); - oop scratch_mirror = HeapShared::scratch_java_mirror(bt); - HeapShared::scan_java_mirror(orig_mirror); + oop scratch_mirror = AOTHeap::scratch_java_mirror(bt); + AOTHeap::scan_java_mirror(orig_mirror); log_trace(aot, heap, mirror)( "Archived %s mirror object from " PTR_FORMAT, type2name(bt), p2i(scratch_mirror)); - Universe::set_archived_basic_type_mirror_index(bt, HeapShared::append_root(scratch_mirror)); + Universe::set_archived_basic_type_mirror_index(bt, AOTHeap::append_root(scratch_mirror)); } } // Universe::fillerArrayKlass() isn't in the class hierarchy, so handle it specially. - HeapShared::scan_java_mirror(Universe::fillerArrayKlass()->java_mirror()); + AOTHeap::scan_java_mirror(Universe::fillerArrayKlass()->java_mirror()); } #endif @@ -121,7 +121,7 @@ void AOTArtifactFinder::find_artifacts() { // This class is loading only by AOT tooling (not as part of the app's training run). // Skip this class for now, but it might be added later if // - One of its subtypes is included - // - One of its instances is found by HeapShared. + // - One of its instances is found by AOTHeap. skip = true; } } @@ -157,7 +157,7 @@ void AOTArtifactFinder::find_artifacts() { if (CDSConfig::is_dumping_aot_linked_classes()) { while (_pending_aot_inited_classes->length() > 0) { InstanceKlass* ik = _pending_aot_inited_classes->pop(); - HeapShared::copy_and_rescan_aot_inited_mirror(ik); + AOTHeap::copy_and_rescan_aot_inited_mirror(ik); } } #endif @@ -184,7 +184,7 @@ void AOTArtifactFinder::find_artifacts() { void AOTArtifactFinder::start_scanning_for_oops() { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::start_scanning_for_oops(); + AOTHeap::start_scanning_for_oops(); } #endif } @@ -192,7 +192,7 @@ void AOTArtifactFinder::start_scanning_for_oops() { void AOTArtifactFinder::end_scanning_for_oops() { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::end_scanning_for_oops(); + AOTHeap::end_scanning_for_oops(); } #endif } @@ -216,7 +216,9 @@ void AOTArtifactFinder::add_aot_inited_class(InstanceKlass* ik) { add_aot_inited_class(s); } - Array* interfaces = ik->local_interfaces(); + // An uninitialized direct interface may have initialized superinterfaces + // that declare default methods. Preserve their initialization state too. + Array* interfaces = ik->transitive_interfaces(); int len = interfaces->length(); for (int i = 0; i < len; i++) { InstanceKlass* intf = interfaces->at(i); @@ -306,7 +308,7 @@ void AOTArtifactFinder::add_cached_class(Klass* k) { void AOTArtifactFinder::scan_oops_in_instance_class(InstanceKlass* ik) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::scan_java_class(ik); + AOTHeap::scan_java_class(ik); scan_oops_in_array_class(ik->array_klasses()); } #endif @@ -316,7 +318,7 @@ void AOTArtifactFinder::scan_oops_in_array_class(ArrayKlass* ak) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { while (ak != nullptr) { - HeapShared::scan_java_class(ak); + AOTHeap::scan_java_class(ak); ak = ak->array_klass_or_null(); } } diff --git a/src/hotspot/share/cds/aotArtifactFinder.hpp b/src/hotspot/share/cds/aotArtifactFinder.hpp index 50057b6caee6..25aa64f3639b 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.hpp +++ b/src/hotspot/share/cds/aotArtifactFinder.hpp @@ -39,10 +39,10 @@ class TypeArrayKlass; // It also decides what Klasses must be cached in aot-initialized state. // // ArchiveBuilder uses [1] as roots to scan for all MetaspaceObjs that need to be cached. -// HeapShared uses [2] to create an image of the archived heap. +// AOTHeap uses [2] to create an image of the archived heap. // // [1] is stored in _all_cached_classes in aotArtifactFinder.cpp. -// [2] is stored in HeapShared::archived_object_cache(). +// [2] is stored in AOTHeap::archived_object_cache(). // // Although many Klasses and heap objects are created in the assembly phase, we only store a subset of them into // the AOT cache. For example: @@ -68,9 +68,9 @@ class TypeArrayKlass; // turn discover more Klasses and java heap objects. // - The scanning continues until we reach a steady state. // -// Note1: See TODO comments in HeapShared::archive_object() for exceptions to this rule. +// Note1: See TODO comments in AOTHeap::archive_object() for exceptions to this rule. // -// Note2: The scanning of Java objects is done in heapShared.cpp. Please see calls into the HeapShared class +// Note2: The scanning of Java objects is done in heapShared.cpp. Please see calls into the AOTHeap class // from AOTArtifactFinder. class AOTArtifactFinder : AllStatic { diff --git a/src/hotspot/share/cds/aotCacheAccess.cpp b/src/hotspot/share/cds/aotCacheAccess.cpp index b8d853792dc7..6ae52d9d03e4 100644 --- a/src/hotspot/share/cds/aotCacheAccess.cpp +++ b/src/hotspot/share/cds/aotCacheAccess.cpp @@ -23,10 +23,10 @@ */ #include "cds/aotCacheAccess.hpp" +#include "cds/aotHeap.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.hpp" #include "classfile/stringTable.hpp" #include "compiler/compiler_globals.hpp" #include "logging/log.hpp" @@ -99,11 +99,11 @@ Metadata* AOTCacheAccess::try_narrow_ptr_to_metadata(narrowPtr narrowp, size_t s #if INCLUDE_CDS_JAVA_HEAP int AOTCacheAccess::get_archived_object_permanent_index(oop obj) { - return HeapShared::get_root_index(obj); // -1 if obj is not a root. + return AOTHeap::get_root_index(obj); // -1 if obj is not a root. } oop AOTCacheAccess::get_archived_object(int permanent_index) { - oop o = HeapShared::get_root(permanent_index); + oop o = AOTHeap::get_root(permanent_index); assert(oopDesc::is_oop_or_null(o), "sanity"); return o; } diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp index 7f39d35bd8d6..62076931e69f 100644 --- a/src/hotspot/share/cds/aotClassInitializer.cpp +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -26,7 +26,6 @@ #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -99,7 +98,7 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { // // When CDSConfig::is_dumping_aot_linked_classes is enabled, // AOTArtifactFinder::find_artifacts() finds the classes of all - // heap objects that are reachable from HeapShared::_run_time_special_subgraph, + // heap objects that are reachable from AOTHeap::_run_time_special_subgraph, // and mark these classes as aot-inited. This preserves the initialized // mirrors of these classes, and their methods are NOT executed // at runtime. See aotArtifactFinder.hpp for more info. diff --git a/src/hotspot/share/cds/aotClassLinker.cpp b/src/hotspot/share/cds/aotClassLinker.cpp index 0ed17dd67461..94e97820e58c 100644 --- a/src/hotspot/share/cds/aotClassLinker.cpp +++ b/src/hotspot/share/cds/aotClassLinker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,11 +24,11 @@ #include "cds/aotClassLinker.hpp" #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLinkedClassTable.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.inline.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/dictionary.hpp" @@ -145,7 +145,7 @@ bool AOTClassLinker::try_add_candidate(InstanceKlass* ik) { if (!CDSConfig::is_dumping_method_handles()) { return false; } - if (HeapShared::is_lambda_proxy_klass(ik)) { + if (AOTHeap::is_lambda_proxy_klass(ik)) { InstanceKlass* nest_host = ik->nest_host_not_null(); if (!try_add_candidate(nest_host)) { ResourceMark rm; diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index 4add165e1434..a13785057a4e 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -24,9 +24,9 @@ #include "cds/aotClassLinker.hpp" #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.inline.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -165,7 +165,7 @@ void AOTConstantPoolResolver::preresolve_string_cp_entries(InstanceKlass* ik, TR if (LambdaFormInvokers::may_be_regenerated_class(ik->name())) { eager_resolve = true; } - if (ik->is_hidden() && HeapShared::is_archivable_hidden_klass(ik)) { + if (ik->is_hidden() && AOTHeap::is_archivable_hidden_klass(ik)) { eager_resolve = true; } diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/aotHeap.cpp similarity index 89% rename from src/hotspot/share/cds/heapShared.cpp rename to src/hotspot/share/cds/aotHeap.cpp index b486b2f17cb2..2737e0f2165a 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/aotHeap.cpp @@ -26,6 +26,7 @@ #include "cds/aotClassInitializer.hpp" #include "cds/aotClassLocation.hpp" #include "cds/aotCompressedPointers.hpp" +#include "cds/aotHeap.inline.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/aotMappedHeapWriter.hpp" @@ -40,7 +41,6 @@ #include "cds/cdsConfig.hpp" #include "cds/cdsEnumKlass.hpp" #include "cds/cdsHeapVerifier.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/javaClasses.inline.hpp" @@ -100,13 +100,13 @@ struct ArchivableStaticFieldInfo { } }; -HeapArchiveMode HeapShared::_heap_load_mode = HeapArchiveMode::_uninitialized; -HeapArchiveMode HeapShared::_heap_write_mode = HeapArchiveMode::_uninitialized; +HeapArchiveMode AOTHeap::_heap_load_mode = HeapArchiveMode::_uninitialized; +HeapArchiveMode AOTHeap::_heap_write_mode = HeapArchiveMode::_uninitialized; -size_t HeapShared::_alloc_count[HeapShared::ALLOC_STAT_SLOTS]; -size_t HeapShared::_alloc_size[HeapShared::ALLOC_STAT_SLOTS]; -size_t HeapShared::_total_obj_count; -size_t HeapShared::_total_obj_size; +size_t AOTHeap::_alloc_count[AOTHeap::ALLOC_STAT_SLOTS]; +size_t AOTHeap::_alloc_size[AOTHeap::ALLOC_STAT_SLOTS]; +size_t AOTHeap::_total_obj_count; +size_t AOTHeap::_total_obj_size; #ifdef ASSERT // All classes that have at least one instance in the cached heap. @@ -140,11 +140,11 @@ static ArchivableStaticFieldInfo archive_subgraph_entry_fields[] = { {nullptr, nullptr}, }; -KlassSubGraphInfo* HeapShared::_dump_time_special_subgraph; -ArchivedKlassSubGraphInfoRecord* HeapShared::_run_time_special_subgraph; -GrowableArrayCHeap* HeapShared::_pending_roots = nullptr; -OopHandle HeapShared::_scratch_basic_type_mirrors[T_VOID+1]; -MetaspaceObjToOopHandleTable* HeapShared::_scratch_objects_table = nullptr; +KlassSubGraphInfo* AOTHeap::_dump_time_special_subgraph; +ArchivedKlassSubGraphInfoRecord* AOTHeap::_run_time_special_subgraph; +GrowableArrayCHeap* AOTHeap::_pending_roots = nullptr; +OopHandle AOTHeap::_scratch_basic_type_mirrors[T_VOID+1]; +MetaspaceObjToOopHandleTable* AOTHeap::_scratch_objects_table = nullptr; static GrowableArray* _dumptime_resolved_methods = nullptr; static Array* _runtime_resolved_methods = nullptr; @@ -157,7 +157,7 @@ static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], Instan return false; } -bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) { +bool AOTHeap::is_subgraph_root_class(InstanceKlass* ik) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); if (CDSConfig::is_dumping_klass_subgraphs()) { // Legacy CDS archive support (to be deprecated) @@ -167,7 +167,7 @@ bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) { } } -oop HeapShared::CachedOopInfo::orig_referrer() const { +oop AOTHeap::CachedOopInfo::orig_referrer() const { return _orig_referrer.resolve(); } @@ -175,7 +175,7 @@ oop HeapShared::CachedOopInfo::orig_referrer() const { // while copying the oops into the AOT heap region. We don't want to // have any side effects during the copying, so we avoid calling // p->identity_hash() which can update the object header. -unsigned HeapShared::oop_address_hash(oop const& p) { +unsigned AOTHeap::oop_address_hash(oop const& p) { assert(SafepointSynchronize::is_at_safepoint() || JavaThread::current()->is_in_no_safepoint_scope(), "sanity"); return primitive_hash(cast_from_oop(p)); @@ -188,7 +188,7 @@ unsigned HeapShared::oop_address_hash(oop const& p) { // copying the objects into the cache. This will allow the hashcode to be // dynamically and randomly computed in each production, which generally // desirable to make the hashcodes more random between runs. -unsigned HeapShared::archived_object_cache_hash(OopHandle const& oh) { +unsigned AOTHeap::archived_object_cache_hash(OopHandle const& oh) { oop o = oh.resolve(); if (o == nullptr) { return 0; @@ -208,7 +208,7 @@ unsigned HeapShared::archived_object_cache_hash(OopHandle const& oh) { } } -bool HeapShared::oop_handle_equals(const OopHandle& a, const OopHandle& b) { +bool AOTHeap::oop_handle_equals(const OopHandle& a, const OopHandle& b) { return a.resolve() == b.resolve(); } @@ -234,7 +234,7 @@ static void reset_states(oop obj, TRAPS) { } } -void HeapShared::reset_archived_object_states(TRAPS) { +void AOTHeap::reset_archived_object_states(TRAPS) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); log_debug(aot)("Resetting platform loader"); reset_states(SystemDictionary::java_platform_loader(), CHECK); @@ -259,7 +259,7 @@ void HeapShared::reset_archived_object_states(TRAPS) { reset_states(boot_loader(), CHECK); } -void HeapShared::ensure_determinism(TRAPS) { +void AOTHeap::ensure_determinism(TRAPS) { TempNewSymbol class_name = SymbolTable::new_symbol("jdk/internal/util/WeakReferenceKey"); TempNewSymbol method_name = SymbolTable::new_symbol("ensureDeterministicAOTCache"); @@ -276,22 +276,22 @@ void HeapShared::ensure_determinism(TRAPS) { assert(result.get_jboolean() == false, "sanity"); } -void HeapShared::prepare_for_archiving(TRAPS) { +void AOTHeap::prepare_for_archiving(TRAPS) { reset_archived_object_states(CHECK); ensure_determinism(CHECK); } -HeapShared::ArchivedObjectCache* HeapShared::_archived_object_cache = nullptr; +AOTHeap::ArchivedObjectCache* AOTHeap::_archived_object_cache = nullptr; // Controls the hashing method for the _archived_object_cache. // Changes from false to true once, after all objects are copied, // inside make_archived_object_cache_gc_safe(). // See archived_object_cache_hash() for more details. -bool HeapShared::_use_identity_hash_for_archived_object_cache = false; +bool AOTHeap::_use_identity_hash_for_archived_object_cache = false; -bool HeapShared::is_archived_heap_in_use() { - if (HeapShared::is_loading()) { - if (HeapShared::is_loading_streaming_mode()) { +bool AOTHeap::is_archived_heap_in_use() { + if (AOTHeap::is_loading()) { + if (AOTHeap::is_loading_streaming_mode()) { return AOTStreamedHeapLoader::is_in_use(); } else { return AOTMappedHeapLoader::is_in_use(); @@ -301,7 +301,7 @@ bool HeapShared::is_archived_heap_in_use() { return false; } -bool HeapShared::can_use_archived_heap() { +bool AOTHeap::can_use_archived_heap() { FileMapInfo* static_mapinfo = FileMapInfo::current_info(); if (static_mapinfo == nullptr) { return false; @@ -319,34 +319,34 @@ bool HeapShared::can_use_archived_heap() { return true; } -bool HeapShared::is_too_large_to_archive(size_t size) { - if (HeapShared::is_writing_streaming_mode()) { +bool AOTHeap::is_too_large_to_archive(size_t size) { + if (AOTHeap::is_writing_streaming_mode()) { return false; } else { return AOTMappedHeapWriter::is_too_large_to_archive(size); } } -bool HeapShared::is_too_large_to_archive(oop obj) { - if (HeapShared::is_writing_streaming_mode()) { +bool AOTHeap::is_too_large_to_archive(oop obj) { + if (AOTHeap::is_writing_streaming_mode()) { return false; } else { return AOTMappedHeapWriter::is_too_large_to_archive(obj); } } -bool HeapShared::is_string_too_large_to_archive(oop string) { +bool AOTHeap::is_string_too_large_to_archive(oop string) { typeArrayOop value = java_lang_String::value_no_keepalive(string); return is_too_large_to_archive(value); } -void HeapShared::initialize_loading_mode(HeapArchiveMode mode) { +void AOTHeap::initialize_loading_mode(HeapArchiveMode mode) { assert(_heap_load_mode == HeapArchiveMode::_uninitialized, "already set?"); assert(mode != HeapArchiveMode::_uninitialized, "sanity"); _heap_load_mode = mode; }; -void HeapShared::initialize_writing_mode() { +void AOTHeap::initialize_writing_mode() { assert(!FLAG_IS_ERGO(AOTStreamableObjects), "Should not have been ergonomically set yet"); if (!CDSConfig::is_dumping_archive()) { @@ -383,14 +383,14 @@ void HeapShared::initialize_writing_mode() { } } -void HeapShared::initialize_streaming() { +void AOTHeap::initialize_streaming() { assert(is_loading_streaming_mode(), "shouldn't call this"); if (can_use_archived_heap()) { AOTStreamedHeapLoader::initialize(); } } -void HeapShared::enable_gc() { +void AOTHeap::enable_gc() { #ifdef ASSERT // At this point, a GC may start and will be able to see some or all // of the cached oops. The class of each oop seen by the GC must have @@ -411,26 +411,26 @@ void HeapShared::enable_gc() { } } -void HeapShared::materialize_thread_object() { +void AOTHeap::materialize_thread_object() { if (AOTStreamedHeapLoader::is_in_use()) { AOTStreamedHeapLoader::materialize_thread_object(); } } -void HeapShared::archive_interned_string(oop string) { - assert(HeapShared::is_writing_mapping_mode(), "Only used by this mode"); +void AOTHeap::archive_interned_string(oop string) { + assert(AOTHeap::is_writing_mapping_mode(), "Only used by this mode"); bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, string); assert(success, "shared strings array must not point to arrays or strings that are too large to archive"); } -void HeapShared::update_scratch_mirror_field(oop scratch_m, int field_offset, oop new_field_obj) { +void AOTHeap::update_scratch_mirror_field(oop scratch_m, int field_offset, oop new_field_obj) { archive_updated_mirror_field(scratch_m->obj_field(field_offset), new_field_obj); scratch_m->obj_field_put(field_offset, new_field_obj); } // It's possible for a scratch mirror to be scanned before one of its oop fields (such as acmp_maps) is // updated. When this happens, we must ensure that the new oop is also archived. -void HeapShared::archive_updated_mirror_field(oop old_field_obj, oop new_field_obj) { +void AOTHeap::archive_updated_mirror_field(oop old_field_obj, oop new_field_obj) { if (old_field_obj != new_field_obj) { assert(old_field_obj == nullptr, "cannot update a non-null oop field that might have already been scanned"); assert(new_field_obj != nullptr, "must be"); @@ -439,9 +439,9 @@ void HeapShared::archive_updated_mirror_field(oop old_field_obj, oop new_field_o } } -void HeapShared::finalize_initialization(FileMapInfo* static_mapinfo) { - if (HeapShared::is_loading()) { - if (HeapShared::is_loading_streaming_mode()) { +void AOTHeap::finalize_initialization(FileMapInfo* static_mapinfo) { + if (AOTHeap::is_loading()) { + if (AOTHeap::is_loading_streaming_mode()) { // Heap initialization can be done only after vtables are initialized by ReadClosure. AOTStreamedHeapLoader::finish_initialization(static_mapinfo); } else { @@ -452,7 +452,7 @@ void HeapShared::finalize_initialization(FileMapInfo* static_mapinfo) { } } -void HeapShared::make_archived_object_cache_gc_safe() { +void AOTHeap::make_archived_object_cache_gc_safe() { ArchivedObjectCache* new_cache = new (mtClass)ArchivedObjectCache(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE); // It's safe to change the behavior of the hash function now, because iterate_all() @@ -487,19 +487,19 @@ void HeapShared::make_archived_object_cache_gc_safe() { _archived_object_cache = new_cache; } -HeapShared::CachedOopInfo* HeapShared::get_cached_oop_info(oop obj) { +AOTHeap::CachedOopInfo* AOTHeap::get_cached_oop_info(oop obj) { OopHandle oh(Universe::vm_global(), obj); CachedOopInfo* result = _archived_object_cache->get(oh); oh.release(Universe::vm_global()); return result; } -bool HeapShared::has_been_archived(oop obj) { +bool AOTHeap::has_been_archived(oop obj) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); return get_cached_oop_info(obj) != nullptr; } -int HeapShared::append_root(oop obj) { +int AOTHeap::append_root(oop obj) { assert(SafepointSynchronize::is_at_safepoint(), "sanity"); assert(CDSConfig::is_dumping_heap(), "dump-time only"); assert(_pending_roots != nullptr, "sanity"); @@ -527,7 +527,7 @@ int HeapShared::append_root(oop obj) { } } -int HeapShared::add_to_pending_roots(oop obj) { +int AOTHeap::add_to_pending_roots(oop obj) { if (_pending_roots->length() == (int)max_jint) { // This should never happen, but just in case we have HUGE caches ... AOTMetaspace::unrecoverable_writing_error("too many pending roots"); @@ -536,7 +536,7 @@ int HeapShared::add_to_pending_roots(oop obj) { return _pending_roots->append(obj); } -int HeapShared::get_root_index(oop obj) { +int AOTHeap::get_root_index(oop obj) { if (java_lang_Class::is_instance(obj)) { obj = scratch_java_mirror(obj); } @@ -558,15 +558,15 @@ int HeapShared::get_root_index(oop obj) { return -1; } -oop HeapShared::get_root(int index, bool clear) { +oop AOTHeap::get_root(int index, bool clear) { assert(index >= 0, "sanity"); assert(is_archived_heap_in_use(), "getting roots into heap that is not used"); oop result; - if (HeapShared::is_loading_streaming_mode()) { + if (AOTHeap::is_loading_streaming_mode()) { result = AOTStreamedHeapLoader::get_root(index); } else { - assert(HeapShared::is_loading_mapping_mode(), "must be"); + assert(AOTHeap::is_loading_mapping_mode(), "must be"); result = AOTMappedHeapLoader::get_root(index); } @@ -577,13 +577,13 @@ oop HeapShared::get_root(int index, bool clear) { return result; } -void HeapShared::finish_materialize_objects() { +void AOTHeap::finish_materialize_objects() { if (AOTStreamedHeapLoader::is_in_use()) { AOTStreamedHeapLoader::finish_materialize_objects(); } } -void HeapShared::clear_root(int index) { +void AOTHeap::clear_root(int index) { if (CDSConfig::is_using_aot_linked_classes()) { // When AOT linked classes are in use, all roots will be in use all // the time, there's no benefit for clearing the roots. Also, we @@ -597,16 +597,16 @@ void HeapShared::clear_root(int index) { if (log_is_enabled(Debug, aot, heap)) { log_debug(aot, heap)("Clearing root %d: was %zu", index, p2i(get_root(index, false /* clear */))); } - if (HeapShared::is_loading_streaming_mode()) { + if (AOTHeap::is_loading_streaming_mode()) { AOTStreamedHeapLoader::clear_root(index); } else { - assert(HeapShared::is_loading_mapping_mode(), "must be"); + assert(AOTHeap::is_loading_mapping_mode(), "must be"); AOTMappedHeapLoader::clear_root(index); } } } -bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgraph_info) { +bool AOTHeap::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgraph_info) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); assert(!obj->is_stackChunk(), "do not archive stack chunks"); @@ -626,7 +626,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra count_allocation(obj->size()); DEBUG_ONLY(_dumptime_classes_with_cached_oops->add(obj->klass())); - if (HeapShared::is_writing_streaming_mode()) { + if (AOTHeap::is_writing_streaming_mode()) { AOTStreamedHeapWriter::add_source_obj(obj); } else { AOTMappedHeapWriter::add_source_obj(obj); @@ -635,7 +635,6 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra OopHandle oh(Universe::vm_global(), obj); CachedOopInfo info = make_cached_oop_info(obj, referrer); archived_object_cache()->put_when_absent(oh, info); - archived_object_cache()->maybe_grow(); Klass* k = obj->klass(); if (k->is_instance_klass()) { @@ -654,7 +653,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra // For the other subgraphs, we have a weaker mechanism to ensure that // all classes in a subgraph are initialized before the subgraph is programmatically // returned from jdk.internal.misc.CDS::initializeFromArchive(). - // See HeapShared::initialize_from_archived_subgraph(). + // See AOTHeap::initialize_from_archived_subgraph(). { AOTArtifactFinder::add_aot_inited_class(InstanceKlass::cast(k)); } @@ -670,7 +669,7 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra m = RegeneratedClasses::maybe_get_regenerated_object(m); InstanceKlass* method_holder = m->method_holder(); AOTArtifactFinder::add_cached_class(method_holder); - _dumptime_resolved_methods->append(HeapShared::append_root(obj)); + _dumptime_resolved_methods->append(AOTHeap::append_root(obj)); } } else if (AOTCodeCache::is_dumping_code() && (java_lang_invoke_MethodHandle::is_instance(obj) || is_interned_string(obj))) { @@ -728,24 +727,24 @@ class MetaspaceObjToOopHandleTable: public HashTablepool_holder()->class_loader_data())) { _scratch_objects_table->set_oop(src, dest); } } -refArrayOop HeapShared::scratch_resolved_references(ConstantPool* src) { +refArrayOop AOTHeap::scratch_resolved_references(ConstantPool* src) { oop rr = _scratch_objects_table->get_oop(src); return rr == nullptr ? nullptr : oop_cast(rr); } -void HeapShared::remove_scratch_resolved_references(ConstantPool* src) { +void AOTHeap::remove_scratch_resolved_references(ConstantPool* src) { if (CDSConfig::is_dumping_heap()) { _scratch_objects_table->remove_oop(src); } } -void HeapShared::init_dumping() { +void AOTHeap::init_dumping() { _scratch_objects_table = new (mtClass)MetaspaceObjToOopHandleTable(); _dumptime_resolved_methods = new (mtClassShared) GrowableArray(100, mtClassShared); _pending_roots = new GrowableArrayCHeap(500); @@ -753,7 +752,7 @@ void HeapShared::init_dumping() { DEBUG_ONLY(_dumptime_classes_with_cached_oops = new (mtClassShared)ArchivableKlassTable()); } -void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) { +void AOTHeap::init_scratch_objects_for_basic_type_mirrors(TRAPS) { for (int i = T_BOOLEAN; i < T_VOID+1; i++) { BasicType bt = (BasicType)i; if (!is_reference_type(bt)) { @@ -768,7 +767,7 @@ void HeapShared::init_scratch_objects_for_basic_type_mirrors(TRAPS) { // that java_mirror will be returned if the mirror is already a scratch mirror. // // See java_lang_Class::create_scratch_mirror() for more info. -oop HeapShared::scratch_java_mirror(oop java_mirror) { +oop AOTHeap::scratch_java_mirror(oop java_mirror) { assert(java_lang_Class::is_instance(java_mirror), "must be"); for (int i = T_BOOLEAN; i < T_VOID+1; i++) { @@ -787,21 +786,21 @@ oop HeapShared::scratch_java_mirror(oop java_mirror) { } } -oop HeapShared::scratch_java_mirror(BasicType t) { +oop AOTHeap::scratch_java_mirror(BasicType t) { assert((uint)t < T_VOID+1, "range check"); assert(!is_reference_type(t), "sanity"); return _scratch_basic_type_mirrors[t].resolve(); } -oop HeapShared::scratch_java_mirror(Klass* k) { +oop AOTHeap::scratch_java_mirror(Klass* k) { return _scratch_objects_table->get_oop(k); } -void HeapShared::set_scratch_java_mirror(Klass* k, oop mirror) { +void AOTHeap::set_scratch_java_mirror(Klass* k, oop mirror) { _scratch_objects_table->set_oop(k, mirror); } -void HeapShared::remove_scratch_objects(Klass* k) { +void AOTHeap::remove_scratch_objects(Klass* k) { // Klass is being deallocated. Java mirror can still be alive, and it should not // point to dead klass. We need to break the link from mirror to the Klass. // See how InstanceKlass::deallocate_contents does it for normal mirrors. @@ -821,24 +820,24 @@ void HeapShared::remove_scratch_objects(Klass* k) { //string compares on names is fragile and will break as soon as somebody //changes the names in the JDK code. See discussion in JDK-8342481 for //related ideas about marking AOT-related classes. -bool HeapShared::is_lambda_form_klass(InstanceKlass* ik) { +bool AOTHeap::is_lambda_form_klass(InstanceKlass* ik) { return ik->is_hidden() && ik->name()->starts_with("java/lang/invoke/LambdaForm$"); } -bool HeapShared::is_lambda_proxy_klass(InstanceKlass* ik) { +bool AOTHeap::is_lambda_proxy_klass(InstanceKlass* ik) { return ik->is_hidden() && (ik->name()->index_of_at(0, "$$Lambda+", 9) > 0); } -bool HeapShared::is_string_concat_klass(InstanceKlass* ik) { +bool AOTHeap::is_string_concat_klass(InstanceKlass* ik) { return ik->is_hidden() && ik->name()->starts_with("java/lang/String$$StringConcat"); } -bool HeapShared::is_archivable_hidden_klass(InstanceKlass* ik) { +bool AOTHeap::is_archivable_hidden_klass(InstanceKlass* ik) { return CDSConfig::is_dumping_method_handles() && (is_lambda_form_klass(ik) || is_lambda_proxy_klass(ik) || is_string_concat_klass(ik)); } -void HeapShared::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) { +void AOTHeap::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) { ik->set_has_aot_initialized_mirror(); oop orig_mirror; @@ -922,7 +921,7 @@ void HeapShared::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) { } } -void HeapShared::copy_java_mirror(oop orig_mirror, oop scratch_m) { +void AOTHeap::copy_java_mirror(oop orig_mirror, oop scratch_m) { // We need to retain the identity_hash, because it may have been used by some hashtables // in the shared heap. if (orig_mirror->has_identity_hash()) { @@ -964,20 +963,20 @@ void HeapShared::copy_java_mirror(oop orig_mirror, oop scratch_m) { static objArrayOop get_archived_resolved_references(InstanceKlass* src_ik) { if (SystemDictionaryShared::is_builtin_loader(src_ik->class_loader_data())) { objArrayOop rr = src_ik->constants()->resolved_references_or_null(); - if (rr != nullptr && !HeapShared::is_too_large_to_archive(rr)) { - return HeapShared::scratch_resolved_references(src_ik->constants()); + if (rr != nullptr && !AOTHeap::is_too_large_to_archive(rr)) { + return AOTHeap::scratch_resolved_references(src_ik->constants()); } } return nullptr; } -int HeapShared::archive_exception_instance(oop exception) { +int AOTHeap::archive_exception_instance(oop exception) { bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, exception); assert(success, "sanity"); return append_root(exception); } -void HeapShared::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has_native_pointers) { +void AOTHeap::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has_native_pointers) { OopHandle oh(&src_obj); CachedOopInfo* info = archived_object_cache()->get(oh); assert(info != nullptr, "must be"); @@ -985,7 +984,7 @@ void HeapShared::get_pointer_info(oop src_obj, bool& has_oop_pointers, bool& has has_native_pointers = info->has_native_pointers(); } -void HeapShared::set_has_native_pointers(oop src_obj) { +void AOTHeap::set_has_native_pointers(oop src_obj) { OopHandle oh(&src_obj); CachedOopInfo* info = archived_object_cache()->get(oh); assert(info != nullptr, "must be"); @@ -994,7 +993,7 @@ void HeapShared::set_has_native_pointers(oop src_obj) { // Between start_scanning_for_oops() and end_scanning_for_oops(), we discover all Java heap objects that // should be stored in the AOT cache. The scanning is coordinated by AOTArtifactFinder. -void HeapShared::start_scanning_for_oops() { +void AOTHeap::start_scanning_for_oops() { { NoSafepointVerifier nsv; @@ -1005,7 +1004,7 @@ void HeapShared::start_scanning_for_oops() { // Cache for recording where the archived objects are copied to create_archived_object_cache(); - if (HeapShared::is_writing_mapping_mode() && (UseG1GC || UseCompressedOops)) { + if (AOTHeap::is_writing_mapping_mode() && (UseG1GC || UseCompressedOops)) { aot_log_info(aot)("Heap range = [" PTR_FORMAT " - " PTR_FORMAT "]", UseCompressedOops ? p2i(CompressedOops::begin()) : G1GC_ONLY(UseG1GC ? p2i((address)G1CollectedHeap::heap()->reserved().start()) :) 0L, @@ -1020,25 +1019,25 @@ void HeapShared::start_scanning_for_oops() { Universe::archive_exception_instances(); } -void HeapShared::end_scanning_for_oops() { +void AOTHeap::end_scanning_for_oops() { if (is_writing_mapping_mode()) { StringTable::init_shared_table(); } delete_seen_objects_table(); } -void HeapShared::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeapInfo* streamed_heap_info) { +void AOTHeap::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeapInfo* streamed_heap_info) { { NoSafepointVerifier nsv; CDSHeapVerifier::verify(); check_special_subgraph_classes(); } - if (HeapShared::is_writing_mapping_mode()) { + if (AOTHeap::is_writing_mapping_mode()) { StringTable::write_shared_table(); AOTMappedHeapWriter::write(_pending_roots, mapped_heap_info); } else { - assert(HeapShared::is_writing_streaming_mode(), "are there more modes?"); + assert(AOTHeap::is_writing_streaming_mode(), "are there more modes?"); AOTStreamedHeapWriter::write(_pending_roots, streamed_heap_info); } @@ -1057,7 +1056,7 @@ void HeapShared::write_heap(AOTMappedHeapInfo* mapped_heap_info, AOTStreamedHeap make_archived_object_cache_gc_safe(); } -void HeapShared::scan_java_mirror(oop orig_mirror) { +void AOTHeap::scan_java_mirror(oop orig_mirror) { oop m = scratch_java_mirror(orig_mirror); if (m != nullptr) { // nullptr if for custom class loader copy_java_mirror(orig_mirror, m); @@ -1066,7 +1065,7 @@ void HeapShared::scan_java_mirror(oop orig_mirror) { } } -void HeapShared::scan_java_class(Klass* orig_k) { +void AOTHeap::scan_java_class(Klass* orig_k) { scan_java_mirror(orig_k->java_mirror()); if (orig_k->is_instance_klass()) { @@ -1074,13 +1073,13 @@ void HeapShared::scan_java_class(Klass* orig_k) { orig_ik->constants()->prepare_resolved_references_for_archiving(); objArrayOop rr = get_archived_resolved_references(orig_ik); if (rr != nullptr) { - bool success = HeapShared::archive_reachable_objects_from(1, _dump_time_special_subgraph, rr); + bool success = AOTHeap::archive_reachable_objects_from(1, _dump_time_special_subgraph, rr); assert(success, "must be"); } } } -void HeapShared::archive_subgraphs() { +void AOTHeap::archive_subgraphs() { assert(CDSConfig::is_dumping_heap(), "must be"); if (CDSConfig::is_dumping_klass_subgraphs()) { @@ -1091,13 +1090,13 @@ void HeapShared::archive_subgraphs() { // // Subgraph archiving support // -HeapShared::DumpTimeKlassSubGraphInfoTable* HeapShared::_dump_time_subgraph_info_table = nullptr; -HeapShared::RunTimeKlassSubGraphInfoTable HeapShared::_run_time_subgraph_info_table; +AOTHeap::DumpTimeKlassSubGraphInfoTable* AOTHeap::_dump_time_subgraph_info_table = nullptr; +AOTHeap::RunTimeKlassSubGraphInfoTable AOTHeap::_run_time_subgraph_info_table; // Get the subgraph_info for Klass k. A new subgraph_info is created if // there is no existing one for k. The subgraph_info records the "buffered" // address of the class. -KlassSubGraphInfo* HeapShared::init_subgraph_info(Klass* k) { +KlassSubGraphInfo* AOTHeap::init_subgraph_info(Klass* k) { assert(CDSConfig::is_dumping_heap(), "dump time only"); bool created; KlassSubGraphInfo* info = @@ -1106,7 +1105,7 @@ KlassSubGraphInfo* HeapShared::init_subgraph_info(Klass* k) { return info; } -KlassSubGraphInfo* HeapShared::get_subgraph_info(Klass* k) { +KlassSubGraphInfo* AOTHeap::get_subgraph_info(Klass* k) { assert(CDSConfig::is_dumping_heap(), "dump time only"); KlassSubGraphInfo* info = _dump_time_subgraph_info_table->get(k); assert(info != nullptr, "must have been initialized"); @@ -1124,7 +1123,7 @@ void KlassSubGraphInfo::add_subgraph_entry_field(int static_field_offset, oop v) if (v == nullptr) { _subgraph_entry_fields->append(-1); } else { - _subgraph_entry_fields->append(HeapShared::append_root(v)); + _subgraph_entry_fields->append(AOTHeap::append_root(v)); } } @@ -1151,7 +1150,7 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) { // -XX:AOTInitTestClass must be used carefully in regression tests to // include only classes that are safe to aot-initialize. assert(ik->class_loader() == nullptr || - HeapShared::is_lambda_proxy_klass(ik) || + AOTHeap::is_lambda_proxy_klass(ik) || AOTClassInitializer::has_test_class(), "we can archive only instances of boot classes or lambda proxy classes"); } else { @@ -1219,7 +1218,7 @@ void KlassSubGraphInfo::check_allowed_klass(InstanceKlass* ik) { const char* lambda_msg = ""; if (CDSConfig::is_dumping_method_handles()) { lambda_msg = ", or a lambda proxy class"; - if (HeapShared::is_lambda_proxy_klass(ik) && + if (AOTHeap::is_lambda_proxy_klass(ik) && (ik->class_loader() == nullptr || ik->class_loader() == SystemDictionary::java_platform_loader() || ik->class_loader() == SystemDictionary::java_system_loader())) { @@ -1294,14 +1293,14 @@ void ArchivedKlassSubGraphInfoRecord::init(KlassSubGraphInfo* info) { ArchivePtrMarker::mark_pointer(&_subgraph_object_klasses); } -class HeapShared::CopyKlassSubGraphInfoToArchive : StackObj { +class AOTHeap::CopyKlassSubGraphInfoToArchive : StackObj { CompactHashtableWriter* _writer; public: CopyKlassSubGraphInfoToArchive(CompactHashtableWriter* writer) : _writer(writer) {} bool do_entry(Klass* klass, KlassSubGraphInfo& info) { if (info.subgraph_object_klasses() != nullptr || info.subgraph_entry_fields() != nullptr) { - ArchivedKlassSubGraphInfoRecord* record = HeapShared::archive_subgraph_info(&info); + ArchivedKlassSubGraphInfoRecord* record = AOTHeap::archive_subgraph_info(&info); Klass* buffered_k = ArchiveBuilder::get_buffered_klass(klass); unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary((address)buffered_k); _writer->add(hash, AOTCompressedPointers::encode_not_null(record)); @@ -1310,7 +1309,7 @@ class HeapShared::CopyKlassSubGraphInfoToArchive : StackObj { } }; -ArchivedKlassSubGraphInfoRecord* HeapShared::archive_subgraph_info(KlassSubGraphInfo* info) { +ArchivedKlassSubGraphInfoRecord* AOTHeap::archive_subgraph_info(KlassSubGraphInfo* info) { ArchivedKlassSubGraphInfoRecord* record = (ArchivedKlassSubGraphInfoRecord*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedKlassSubGraphInfoRecord)); record->init(info); @@ -1327,7 +1326,7 @@ ArchivedKlassSubGraphInfoRecord* HeapShared::archive_subgraph_info(KlassSubGraph // info. The value is stored back to the corresponding field at runtime. // - A list of klasses that need to be loaded/initialized before archived // java object sub-graph can be accessed at runtime. -void HeapShared::write_subgraph_info_table() { +void AOTHeap::write_subgraph_info_table() { // Allocate the contents of the hashtable(s) inside the RO region of the CDS archive. DumpTimeKlassSubGraphInfoTable* d_table = _dump_time_subgraph_info_table; CompactHashtableStats stats; @@ -1344,14 +1343,14 @@ void HeapShared::write_subgraph_info_table() { } } -void HeapShared::serialize_tables(SerializeClosure* soc) { +void AOTHeap::serialize_tables(SerializeClosure* soc) { _run_time_subgraph_info_table.serialize_header(soc); soc->do_ptr(&_run_time_special_subgraph); soc->do_ptr(&_runtime_resolved_methods); DEBUG_ONLY(soc->do_ptr(&_runtime_classes_with_cached_oops)); } -void HeapShared::load_cached_resolved_methods() { +void AOTHeap::load_cached_resolved_methods() { precond(CDSConfig::is_using_aot_linked_classes()); if (_runtime_resolved_methods != nullptr) { JavaThread* current = JavaThread::current(); @@ -1395,16 +1394,16 @@ static void verify_the_heap(Klass* k, const char* which) { } } -// Before GC can execute, we must ensure that all oops reachable from HeapShared::roots() +// Before GC can execute, we must ensure that all oops reachable from AOTHeap::roots() // have a valid klass. I.e., oopDesc::klass() must have already been resolved. -void HeapShared::resolve_classes(JavaThread* current) { +void AOTHeap::resolve_classes(JavaThread* current) { assert(CDSConfig::is_using_archive(), "runtime only!"); if (CDSConfig::is_using_klass_subgraphs()) { resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields); } } -void HeapShared::resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]) { +void AOTHeap::resolve_classes_for_subgraphs(JavaThread* current, ArchivableStaticFieldInfo fields[]) { for (int i = 0; fields[i].valid(); i++) { ArchivableStaticFieldInfo* info = &fields[i]; TempNewSymbol klass_name = SymbolTable::new_symbol(info->klass_name); @@ -1414,7 +1413,7 @@ void HeapShared::resolve_classes_for_subgraphs(JavaThread* current, ArchivableSt } } -void HeapShared::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) { +void AOTHeap::resolve_classes_for_subgraph_of(JavaThread* current, Klass* k) { JavaThread* THREAD = current; ExceptionMark em(THREAD); const ArchivedKlassSubGraphInfoRecord* record = @@ -1437,7 +1436,7 @@ static const char* java_lang_invoke_core_klasses[] = { "java/lang/invoke/BoundMethodHandle$Species_L", }; -void HeapShared::initialize_java_lang_invoke(TRAPS) { +void AOTHeap::initialize_java_lang_invoke(TRAPS) { if (CDSConfig::is_using_aot_linked_classes() || CDSConfig::is_dumping_method_handles()) { int len = sizeof(java_lang_invoke_core_klasses)/sizeof(char*); for (int i = 0; i < len; i++) { @@ -1446,7 +1445,7 @@ void HeapShared::initialize_java_lang_invoke(TRAPS) { } } -bool HeapShared::is_core_java_lang_invoke_klass(InstanceKlass* klass) { +bool AOTHeap::is_core_java_lang_invoke_klass(InstanceKlass* klass) { ResourceMark rm; char* s2 = klass->name()->as_C_string(); int len = sizeof(java_lang_invoke_core_klasses)/sizeof(char*); @@ -1474,7 +1473,7 @@ bool HeapShared::is_core_java_lang_invoke_klass(InstanceKlass* klass) { // the aot-initialized mirror of Fruit has a static field that references HashSet, which // should be initialized before any Java code can access the Fruit class. Note that // HashSet itself doesn't necessary need to be an aot-initialized class. -void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { +void AOTHeap::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { if (!is_archived_heap_in_use()) { return; } @@ -1505,7 +1504,7 @@ void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { } } -void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k) { +void AOTHeap::initialize_from_archived_subgraph(JavaThread* current, Klass* k) { JavaThread* THREAD = current; if (!CDSConfig::is_using_klass_subgraphs()) { return; // nothing to do @@ -1541,7 +1540,7 @@ void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k } const ArchivedKlassSubGraphInfoRecord* -HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS) { +AOTHeap::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAPS) { assert(!CDSConfig::is_dumping_heap(), "Should not be called when dumping heap"); if (!k->in_aot_cache()) { @@ -1597,7 +1596,7 @@ HeapShared::resolve_or_init_classes_for_subgraph_of(Klass* k, bool do_init, TRAP return record; } -void HeapShared::resolve_or_init(const char* klass_name, bool do_init, TRAPS) { +void AOTHeap::resolve_or_init(const char* klass_name, bool do_init, TRAPS) { TempNewSymbol klass_name_sym = SymbolTable::new_symbol(klass_name); InstanceKlass* k = SystemDictionaryShared::find_builtin_class(klass_name_sym); if (k == nullptr) { @@ -1610,7 +1609,7 @@ void HeapShared::resolve_or_init(const char* klass_name, bool do_init, TRAPS) { } } -void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { +void AOTHeap::resolve_or_init(Klass* k, bool do_init, TRAPS) { if (!do_init) { if (k->class_loader_data() == nullptr) { Klass* resolved_k = SystemDictionary::resolve_or_null(k->name(), CHECK); @@ -1621,7 +1620,7 @@ void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { } } } else { - assert(k->class_loader_data() != nullptr, "must have been resolved by HeapShared::resolve_classes"); + assert(k->class_loader_data() != nullptr, "must have been resolved by AOTHeap::resolve_classes"); if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); ik->initialize(CHECK); @@ -1632,7 +1631,7 @@ void HeapShared::resolve_or_init(Klass* k, bool do_init, TRAPS) { } } -void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphInfoRecord* record) { +void AOTHeap::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphInfoRecord* record) { verify_the_heap(k, "before"); Array* entry_field_records = record->entry_field_records(); @@ -1672,7 +1671,7 @@ void HeapShared::init_archived_fields_for(Klass* k, const ArchivedKlassSubGraphI verify_the_heap(k, "after "); } -void HeapShared::clear_archived_roots_of(Klass* k) { +void AOTHeap::clear_archived_roots_of(Klass* k) { unsigned int hash = SystemDictionaryShared::hash_for_shared_dictionary_quick(k); const ArchivedKlassSubGraphInfoRecord* record = _run_time_subgraph_info_table.lookup(k, hash, 0); if (record != nullptr) { @@ -1690,7 +1689,7 @@ void HeapShared::clear_archived_roots_of(Klass* k) { // Push all oop fields (or oop array elemenets in case of an objArray) in // _referencing_obj onto the _stack. -class HeapShared::OopFieldPusher: public BasicOopIterateClosure { +class AOTHeap::OopFieldPusher: public BasicOopIterateClosure { PendingOopStack* _stack; GrowableArray _found_oop_fields; int _level; @@ -1771,13 +1770,13 @@ class PointsToOopsChecker : public BasicOopIterateClosure { bool result() { return _result; } }; -HeapShared::CachedOopInfo HeapShared::make_cached_oop_info(oop obj, oop referrer) { +AOTHeap::CachedOopInfo AOTHeap::make_cached_oop_info(oop obj, oop referrer) { PointsToOopsChecker points_to_oops_checker; obj->oop_iterate(&points_to_oops_checker); return CachedOopInfo(OopHandle(Universe::vm_global(), referrer), points_to_oops_checker.result()); } -void HeapShared::init_box_classes(TRAPS) { +void AOTHeap::init_box_classes(TRAPS) { if (is_archived_heap_in_use()) { vmClasses::Boolean_klass()->initialize(CHECK); vmClasses::Character_klass()->initialize(CHECK); @@ -1791,8 +1790,8 @@ void HeapShared::init_box_classes(TRAPS) { } } -// Used by HeapShared::find_flat_field_klasses(). -class HeapShared::FlatFieldKlassFinder : public FieldClosure { +// Used by AOTHeap::find_flat_field_klasses(). +class AOTHeap::FlatFieldKlassFinder : public FieldClosure { KlassSubGraphInfo* _subgraph_info; oop _obj; const ValuePayloadContext* _vpc; @@ -1822,7 +1821,7 @@ class HeapShared::FlatFieldKlassFinder : public FieldClosure { } }; -void HeapShared::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKlass* k) { +void AOTHeap::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKlass* k) { subgraph_info->add_subgraph_object_klass(k); if (InstanceKlass::cast(k)->is_enum_subclass() || (subgraph_info == _dump_time_special_subgraph)) { @@ -1840,12 +1839,12 @@ void HeapShared::add_flat_field_klass(KlassSubGraphInfo* subgraph_info, ValueKla // @NullRestricted Point p1; // @NullRestricted Point p2; ... } // -// Klasses of non-flat fields are already recorded by HeapShared::archive_object(). +// Klasses of non-flat fields are already recorded by AOTHeap::archive_object(). // -// If only a single instance of Line is archived, HeapShared::archive_object() would +// If only a single instance of Line is archived, AOTHeap::archive_object() would // have never visited a heap oop instance of Point, but we must store Point in // AOT-initialized state. This function finds Point. -void HeapShared::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop orig_obj) { +void AOTHeap::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop orig_obj) { Klass* klass = orig_obj->klass(); if (klass->is_flatArray_klass()) { @@ -1885,7 +1884,7 @@ void HeapShared::find_flat_field_klasses(KlassSubGraphInfo* subgraph_info, oop o // trace all objects that are reachable from it, and make sure these objects are archived. // (3) Record the klasses of all objects that are reachable from orig_obj (including those that // were already archived when this function is called) -bool HeapShared::archive_reachable_objects_from(int level, +bool AOTHeap::archive_reachable_objects_from(int level, KlassSubGraphInfo* subgraph_info, oop orig_obj) { assert(orig_obj != nullptr, "must be"); @@ -1909,7 +1908,7 @@ bool HeapShared::archive_reachable_objects_from(int level, return true; } -bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGraphInfo* subgraph_info, +bool AOTHeap::walk_one_object(PendingOopStack* stack, int level, KlassSubGraphInfo* subgraph_info, oop orig_obj, oop referrer) { assert(orig_obj != nullptr, "must be"); if (!JavaClasses::is_supported_for_archiving(orig_obj)) { @@ -1949,7 +1948,7 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap } else if (java_lang_Class::is_instance(orig_obj) && subgraph_info != _dump_time_special_subgraph) { // Without CDSConfig::is_dumping_aot_linked_classes(), we only allow archived objects to // point to the mirrors of (1) j.l.Object, (2) primitive classes, and (3) box classes. These are initialized - // very early by HeapShared::init_box_classes(). + // very early by AOTHeap::init_box_classes(). if (orig_obj == vmClasses::Object_klass()->java_mirror() || java_lang_Class::is_primitive(orig_obj) || orig_obj == vmClasses::Boolean_klass()->java_mirror() @@ -2064,7 +2063,7 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap // for loading and initializing before any object in the archived graph can // be accessed at runtime. // -void HeapShared::archive_reachable_objects_from_static_field(InstanceKlass *k, +void AOTHeap::archive_reachable_objects_from_static_field(InstanceKlass *k, const char* klass_name, int field_offset, const char* field_name) { @@ -2113,12 +2112,12 @@ class VerifySharedOopClosure: public BasicOopIterateClosure { template void do_oop_work(T *p) { oop obj = HeapAccess<>::oop_load(p); if (obj != nullptr) { - HeapShared::verify_reachable_objects_from(obj); + AOTHeap::verify_reachable_objects_from(obj); } } }; -void HeapShared::verify_subgraph_from_static_field(InstanceKlass* k, int field_offset) { +void AOTHeap::verify_subgraph_from_static_field(InstanceKlass* k, int field_offset) { precond(CDSConfig::is_dumping_klass_subgraphs()); assert(k->defined_by_boot_loader(), "must be boot class"); @@ -2129,7 +2128,7 @@ void HeapShared::verify_subgraph_from_static_field(InstanceKlass* k, int field_o } } -void HeapShared::verify_subgraph_from(oop orig_obj) { +void AOTHeap::verify_subgraph_from(oop orig_obj) { if (!has_been_archived(orig_obj)) { // It's OK for the root of a subgraph to be not archived. See comments in // archive_reachable_objects_from(). @@ -2142,7 +2141,7 @@ void HeapShared::verify_subgraph_from(oop orig_obj) { delete_seen_objects_table(); } -void HeapShared::verify_reachable_objects_from(oop obj) { +void AOTHeap::verify_reachable_objects_from(oop obj) { _num_total_verifications ++; if (java_lang_Class::is_instance(obj)) { obj = RegeneratedClasses::maybe_get_regenerated_mirror(obj); @@ -2158,7 +2157,7 @@ void HeapShared::verify_reachable_objects_from(oop obj) { } #endif -void HeapShared::check_special_subgraph_classes() { +void AOTHeap::check_special_subgraph_classes() { if (CDSConfig::is_dumping_aot_linked_classes()) { // We can have aot-initialized classes (such as Enums) that can reference objects // of arbitrary types. Currently, we trust the JEP 483 implementation to only @@ -2192,30 +2191,29 @@ void HeapShared::check_special_subgraph_classes() { } } -HeapShared::SeenObjectsTable* HeapShared::_seen_objects_table = nullptr; -HeapShared::PendingOop HeapShared::_object_being_archived; -size_t HeapShared::_num_new_walked_objs; -size_t HeapShared::_num_new_archived_objs; -size_t HeapShared::_num_old_recorded_klasses; +AOTHeap::SeenObjectsTable* AOTHeap::_seen_objects_table = nullptr; +AOTHeap::PendingOop AOTHeap::_object_being_archived; +size_t AOTHeap::_num_new_walked_objs; +size_t AOTHeap::_num_new_archived_objs; +size_t AOTHeap::_num_old_recorded_klasses; -size_t HeapShared::_num_total_subgraph_recordings = 0; -size_t HeapShared::_num_total_walked_objs = 0; -size_t HeapShared::_num_total_archived_objs = 0; -size_t HeapShared::_num_total_recorded_klasses = 0; -size_t HeapShared::_num_total_verifications = 0; +size_t AOTHeap::_num_total_subgraph_recordings = 0; +size_t AOTHeap::_num_total_walked_objs = 0; +size_t AOTHeap::_num_total_archived_objs = 0; +size_t AOTHeap::_num_total_recorded_klasses = 0; +size_t AOTHeap::_num_total_verifications = 0; -bool HeapShared::has_been_seen_during_subgraph_recording(oop obj) { +bool AOTHeap::has_been_seen_during_subgraph_recording(oop obj) { return _seen_objects_table->get(obj) != nullptr; } -void HeapShared::set_has_been_seen_during_subgraph_recording(oop obj) { +void AOTHeap::set_has_been_seen_during_subgraph_recording(oop obj) { assert(!has_been_seen_during_subgraph_recording(obj), "sanity"); _seen_objects_table->put_when_absent(obj, true); - _seen_objects_table->maybe_grow(); ++ _num_new_walked_objs; } -void HeapShared::start_recording_subgraph(InstanceKlass *k, const char* class_name) { +void AOTHeap::start_recording_subgraph(InstanceKlass *k, const char* class_name) { log_info(aot, heap)("Start recording subgraph(s) for archived fields in %s", class_name); init_subgraph_info(k); init_seen_objects_table(); @@ -2224,7 +2222,7 @@ void HeapShared::start_recording_subgraph(InstanceKlass *k, const char* class_na _num_old_recorded_klasses = get_subgraph_info(k)->num_subgraph_object_klasses(); } -void HeapShared::done_recording_subgraph(InstanceKlass *k, const char* class_name) { +void AOTHeap::done_recording_subgraph(InstanceKlass *k, const char* class_name) { size_t num_new_recorded_klasses = get_subgraph_info(k)->num_subgraph_object_klasses() - _num_old_recorded_klasses; log_info(aot, heap)("Done recording subgraph(s) for archived fields in %s: " @@ -2262,7 +2260,7 @@ class ArchivableStaticFieldFinder: public FieldClosure { int offset() { return _offset; } }; -void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], +void AOTHeap::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], TRAPS) { for (int i = 0; fields[i].valid(); i++) { ArchivableStaticFieldInfo* info = &fields[i]; @@ -2312,7 +2310,7 @@ void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], } } -void HeapShared::init_subgraph_entry_fields(TRAPS) { +void AOTHeap::init_subgraph_entry_fields(TRAPS) { assert(CDSConfig::is_dumping_heap(), "must be"); _dump_time_subgraph_info_table = new (mtClass)DumpTimeKlassSubGraphInfoTable(); if (CDSConfig::is_dumping_klass_subgraphs()) { @@ -2320,21 +2318,21 @@ void HeapShared::init_subgraph_entry_fields(TRAPS) { } } -void HeapShared::init_for_dumping(TRAPS) { +void AOTHeap::init_for_dumping(TRAPS) { if (CDSConfig::is_dumping_heap()) { init_subgraph_entry_fields(CHECK); } } -void HeapShared::init_heap_writer() { - if (HeapShared::is_writing_streaming_mode()) { +void AOTHeap::init_heap_writer() { + if (AOTHeap::is_writing_streaming_mode()) { AOTStreamedHeapWriter::init(); } else { AOTMappedHeapWriter::init(); } } -void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) { +void AOTHeap::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) { _num_total_subgraph_recordings = 0; _num_total_walked_objs = 0; _num_total_archived_objs = 0; @@ -2346,7 +2344,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) { // [2] Create a list of all the class of the objects that can be reached // by any of these static fields. // At runtime, these classes are initialized before X's archived fields - // are restored by HeapShared::initialize_from_archived_subgraph(). + // are restored by AOTHeap::initialize_from_archived_subgraph(). for (int i = 0; fields[i].valid(); ) { ArchivableStaticFieldInfo* info = &fields[i]; const char* klass_name = info->klass_name; @@ -2383,7 +2381,7 @@ void HeapShared::archive_object_subgraphs(ArchivableStaticFieldInfo fields[]) { #endif } -bool HeapShared::is_interned_string(oop obj) { +bool AOTHeap::is_interned_string(oop obj) { if (!java_lang_String::is_instance(obj)) { return false; } @@ -2397,14 +2395,14 @@ bool HeapShared::is_interned_string(oop obj) { return StringTable::lookup(name, len) == obj; } -bool HeapShared::is_dumped_interned_string(oop o) { +bool AOTHeap::is_dumped_interned_string(oop o) { return is_interned_string(o) && has_been_archived(o); } // These tables should be used only within the CDS safepoint, so // delete them before we exit the safepoint. Otherwise the table will // contain bad oops after a GC. -void HeapShared::delete_tables_with_raw_oops() { +void AOTHeap::delete_tables_with_raw_oops() { assert(_seen_objects_table == nullptr, "should have been deleted"); if (is_writing_mapping_mode()) { @@ -2415,7 +2413,7 @@ void HeapShared::delete_tables_with_raw_oops() { } } -void HeapShared::debug_trace() { +void AOTHeap::debug_trace() { ResourceMark rm; oop referrer = _object_being_archived.referrer(); if (referrer != nullptr) { @@ -2464,7 +2462,7 @@ class FindEmbeddedNonNullPointers: public BasicOopIterateClosure { }; #endif -void HeapShared::count_allocation(size_t size) { +void AOTHeap::count_allocation(size_t size) { _total_obj_count ++; _total_obj_size += size; for (int i = 0; i < ALLOC_STAT_SLOTS; i++) { @@ -2484,7 +2482,7 @@ static double avg_size(size_t size, size_t count) { return avg; } -void HeapShared::print_stats() { +void AOTHeap::print_stats() { size_t huge_count = _total_obj_count; size_t huge_size = _total_obj_size; @@ -2509,7 +2507,7 @@ void HeapShared::print_stats() { avg_size(_total_obj_size, _total_obj_count)); } -bool HeapShared::is_metadata_field(oop src_obj, int offset) { +bool AOTHeap::is_metadata_field(oop src_obj, int offset) { bool result = false; do_metadata_offsets(src_obj, [&](int metadata_offset) { if (metadata_offset == offset) { @@ -2519,7 +2517,7 @@ bool HeapShared::is_metadata_field(oop src_obj, int offset) { return result; } -void HeapShared::remap_dumped_metadata(oop src_obj, address archived_object) { +void AOTHeap::remap_dumped_metadata(oop src_obj, address archived_object) { do_metadata_offsets(src_obj, [&](int offset) { Metadata** buffered_field_addr = (Metadata**)(archived_object + offset); Metadata* native_ptr = *buffered_field_addr; @@ -2536,7 +2534,7 @@ void HeapShared::remap_dumped_metadata(oop src_obj, address archived_object) { }); } -bool HeapShared::is_archived_boot_layer_available(JavaThread* current) { +bool AOTHeap::is_archived_boot_layer_available(JavaThread* current) { TempNewSymbol klass_name = SymbolTable::new_symbol(ARCHIVED_BOOT_LAYER_CLASS); InstanceKlass* k = SystemDictionary::find_instance_klass(current, klass_name, Handle()); if (k == nullptr) { diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/aotHeap.hpp similarity index 97% rename from src/hotspot/share/cds/heapShared.hpp rename to src/hotspot/share/cds/aotHeap.hpp index 78f493d17c8e..a6379a815852 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/aotHeap.hpp @@ -22,8 +22,8 @@ * */ -#ifndef SHARE_CDS_HEAPSHARED_HPP -#define SHARE_CDS_HEAPSHARED_HPP +#ifndef SHARE_CDS_AOTHEAP_HPP +#define SHARE_CDS_AOTHEAP_HPP #include "cds/aotMetaspace.hpp" #include "cds/dumpTimeClassInfo.hpp" @@ -128,7 +128,7 @@ enum class HeapArchiveMode { _streaming }; -class HeapShared: AllStatic { +class AOTHeap: AllStatic { friend class VerifySharedOopClosure; public: @@ -230,8 +230,8 @@ class HeapShared: AllStatic { typedef ResizeableHashTable ArchivedObjectCache; + AOTHeap::archived_object_cache_hash, + AOTHeap::oop_handle_equals> ArchivedObjectCache; static ArchivedObjectCache* _archived_object_cache; class DumpTimeKlassSubGraphInfoTable @@ -289,7 +289,7 @@ class HeapShared: AllStatic { typedef ResizeableHashTable SeenObjectsTable; + AOTHeap::oop_address_hash> SeenObjectsTable; static SeenObjectsTable *_seen_objects_table; @@ -416,7 +416,7 @@ class HeapShared: AllStatic { static void set_has_native_pointers(oop src_obj); static uintptr_t archive_location(oop src_obj); - // We use the HeapShared::roots() array to make sure that objects stored in the + // We use the AOTHeap::roots() array to make sure that objects stored in the // archived heap region are not prematurely collected. These roots include: // // - mirrors of classes that have not yet been loaded. @@ -425,7 +425,7 @@ class HeapShared: AllStatic { // - java.lang.Module objects that have not yet been added to the module graph // // When a mirror M becomes referenced by a newly loaded class K, M will be removed - // from HeapShared::roots() via clear_root(), and K will be responsible for + // from AOTHeap::roots() via clear_root(), and K will be responsible for // keeping M alive. // // Other types of roots are also cleared similarly when they become referenced. @@ -498,4 +498,4 @@ class HeapShared: AllStatic { static void log_oop_details(oop source_oop, address buffered_addr); }; -#endif // SHARE_CDS_HEAPSHARED_HPP +#endif // SHARE_CDS_AOTHEAP_HPP diff --git a/src/hotspot/share/cds/heapShared.inline.hpp b/src/hotspot/share/cds/aotHeap.inline.hpp similarity index 82% rename from src/hotspot/share/cds/heapShared.inline.hpp rename to src/hotspot/share/cds/aotHeap.inline.hpp index 52ec5182ec45..33e9a866121c 100644 --- a/src/hotspot/share/cds/heapShared.inline.hpp +++ b/src/hotspot/share/cds/aotHeap.inline.hpp @@ -22,10 +22,10 @@ * */ -#ifndef SHARE_CDS_HEAPSHARED_INLINE_HPP -#define SHARE_CDS_HEAPSHARED_INLINE_HPP +#ifndef SHARE_CDS_AOTHEAP_INLINE_HPP +#define SHARE_CDS_AOTHEAP_INLINE_HPP -#include "cds/heapShared.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/regeneratedClasses.hpp" @@ -34,37 +34,37 @@ #if INCLUDE_CDS_JAVA_HEAP -inline bool HeapShared::is_loading() { +inline bool AOTHeap::is_loading() { return _heap_load_mode != HeapArchiveMode::_uninitialized; } -inline bool HeapShared::is_loading_streaming_mode() { +inline bool AOTHeap::is_loading_streaming_mode() { assert(_heap_load_mode != HeapArchiveMode::_uninitialized, "not initialized yet"); return _heap_load_mode == HeapArchiveMode::_streaming; } -inline bool HeapShared::is_loading_mapping_mode() { +inline bool AOTHeap::is_loading_mapping_mode() { assert(_heap_load_mode != HeapArchiveMode::_uninitialized, "not initialized yet"); return _heap_load_mode == HeapArchiveMode::_mapping; } -inline bool HeapShared::is_writing() { +inline bool AOTHeap::is_writing() { return _heap_write_mode != HeapArchiveMode::_uninitialized; } -inline bool HeapShared::is_writing_streaming_mode() { +inline bool AOTHeap::is_writing_streaming_mode() { assert(_heap_write_mode != HeapArchiveMode::_uninitialized, "not initialized yet"); return _heap_write_mode == HeapArchiveMode::_streaming; } -inline bool HeapShared::is_writing_mapping_mode() { +inline bool AOTHeap::is_writing_mapping_mode() { assert(_heap_write_mode != HeapArchiveMode::_uninitialized, "not initialized yet"); return _heap_write_mode == HeapArchiveMode::_mapping; } // Keep the knowledge about which objects have what metadata in one single place template -void HeapShared::do_metadata_offsets(oop src_obj, T callback) { +void AOTHeap::do_metadata_offsets(oop src_obj, T callback) { if (java_lang_Class::is_instance(src_obj)) { assert(java_lang_Class::klass_offset() < java_lang_Class::array_klass_offset(), "metadata offsets must be sorted"); @@ -75,7 +75,7 @@ void HeapShared::do_metadata_offsets(oop src_obj, T callback) { } } -inline void HeapShared::remap_loaded_metadata(oop src_obj) { +inline void AOTHeap::remap_loaded_metadata(oop src_obj) { do_metadata_offsets(src_obj, [&](int offset) { Metadata* metadata = src_obj->metadata_field(offset); if (metadata != nullptr) { @@ -85,7 +85,7 @@ inline void HeapShared::remap_loaded_metadata(oop src_obj) { }); } -inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_offset, oop referent) { +inline oop AOTHeap::maybe_remap_referent(bool is_java_lang_ref, size_t field_offset, oop referent) { if (referent == nullptr) { return nullptr; } @@ -99,7 +99,7 @@ inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_ // When the source object points to a "real" mirror, the buffered object should point // to the "scratch" mirror, which has all unarchivable fields scrubbed (to be reinstated // at run time). - referent = HeapShared::scratch_java_mirror(referent); + referent = AOTHeap::scratch_java_mirror(referent); assert(referent != nullptr, "must be"); } @@ -108,4 +108,4 @@ inline oop HeapShared::maybe_remap_referent(bool is_java_lang_ref, size_t field_ #endif // INCLUDE_CDS_JAVA_HEAP -#endif // SHARE_CDS_HEAPSHARED_INLINE_HPP +#endif // SHARE_CDS_AOTHEAP_INLINE_HPP diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp index c7911294e992..438587281815 100644 --- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp @@ -24,11 +24,11 @@ #include "cds/aotClassInitializer.hpp" #include "cds/aotClassLinker.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotLinkedClassTable.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaStackTraceClasses.hpp" @@ -87,7 +87,7 @@ void AOTLinkedClassBulkLoader::preload_classes_impl(TRAPS) { preload_classes_in_table(table->app(), "app", h_system_loader, CHECK); // Do this after all boot/platform/app classes are loaded, but before bytecode execution. - HeapShared::load_cached_resolved_methods(); + AOTHeap::load_cached_resolved_methods(); } void AOTLinkedClassBulkLoader::preload_classes_in_table(Array* classes, @@ -424,7 +424,7 @@ void AOTLinkedClassBulkLoader::init_classes_for_loader(Handle class_loader, Arra } if (!early_only) { - HeapShared::init_classes_for_special_subgraph(class_loader, CHECK); + AOTHeap::init_classes_for_special_subgraph(class_loader, CHECK); } } diff --git a/src/hotspot/share/cds/aotMappedHeapLoader.cpp b/src/hotspot/share/cds/aotMappedHeapLoader.cpp index b2552bea744c..ab2fc7dc474d 100644 --- a/src/hotspot/share/cds/aotMappedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotMappedHeapLoader.cpp @@ -29,7 +29,6 @@ #include "cds/aotMetaspace.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/stringTable.hpp" #include "classfile/systemDictionaryShared.hpp" #include "gc/shared/collectedHeap.hpp" diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp index ed35c8d09d18..e40ea832baf6 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp @@ -22,13 +22,13 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMappedHeap.hpp" #include "cds/aotMappedHeapWriter.hpp" #include "cds/aotModules.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/javaClasses.hpp" #include "classfile/modules.hpp" @@ -197,7 +197,7 @@ oop AOTMappedHeapWriter::requested_obj_from_buffer_offset(size_t offset) { oop AOTMappedHeapWriter::source_obj_to_requested_obj(oop src_obj) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); - HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj); if (p != nullptr) { return requested_obj_from_buffer_offset(p->buffer_offset()); } else { @@ -355,7 +355,7 @@ void AOTMappedHeapWriter::copy_roots_to_buffer(GrowableArrayCHeaplength(); i++) { int src_obj_index = _source_objs_order->at(i)._index; oop src_obj = _source_objs->at(src_obj_index); - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj); assert(info != nullptr, "must be"); size_t buffer_offset = copy_one_source_obj_to_buffer(src_obj); info->set_buffer_offset(buffer_offset); @@ -417,7 +417,6 @@ void AOTMappedHeapWriter::copy_source_objs_to_buffer(GrowableArrayCHeapput_when_absent(buffer_offset, handle); - _buffer_offset_to_source_obj_table->maybe_grow(); if (java_lang_Module::is_instance(src_obj)) { AOTModules::check_archived_module_oop(src_obj); @@ -770,7 +769,7 @@ class AOTMappedHeapWriter::EmbeddedOopRelocator: public BasicOopIterateClosure { int field_offset = pointer_delta_as_int((char*)p, cast_from_oop(_src_obj)); T* field_addr = (T*)(_buffered_obj + field_offset); oop referent = load_source_oop_from_buffer(field_addr); - referent = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, referent); + referent = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, referent); AOTMappedHeapWriter::relocate_field_in_buffer(field_addr, referent, _oopmap); } }; @@ -796,7 +795,7 @@ void AOTMappedHeapWriter::relocate_embedded_oops(GrowableArrayCHeaplength(); i++) { int src_obj_index = _source_objs_order->at(i)._index; oop src_obj = _source_objs->at(src_obj_index); - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj); assert(info != nullptr, "must be"); oop requested_obj = requested_obj_from_buffer_offset(info->buffer_offset()); update_header_for_requested_obj(requested_obj, src_obj, src_obj->klass()); @@ -806,7 +805,7 @@ void AOTMappedHeapWriter::relocate_embedded_oops(GrowableArrayCHeap::oop_load(elem_addr); - obj = HeapShared::maybe_remap_referent(false /* is_reference_field */, elem_offset, obj); + obj = AOTHeap::maybe_remap_referent(false /* is_reference_field */, elem_offset, obj); if (UseCompressedOops) { relocate_field_in_buffer((narrowOop*)elem_addr, obj, heap_info->oopmap()); } else { @@ -847,13 +846,13 @@ void AOTMappedHeapWriter::mark_native_pointer(oop src_obj, int field_offset) { info._src_obj = src_obj; info._field_offset = field_offset; _native_pointers->append(info); - HeapShared::set_has_native_pointers(src_obj); + AOTHeap::set_has_native_pointers(src_obj); _num_native_ptrs ++; } } void AOTMappedHeapWriter::mark_native_pointers(oop orig_obj) { - HeapShared::do_metadata_offsets(orig_obj, [&](int offset) { + AOTHeap::do_metadata_offsets(orig_obj, [&](int offset) { mark_native_pointer(orig_obj, offset); }); } @@ -869,7 +868,7 @@ void AOTMappedHeapWriter::compute_ptrmap(AOTMappedHeapInfo* heap_info) { NativePointerInfo info = _native_pointers->at(i); oop src_obj = info._src_obj; int field_offset = info._field_offset; - HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj); // requested_field_addr = the address of this field in the requested space oop requested_obj = requested_obj_from_buffer_offset(p->buffer_offset()); Metadata** requested_field_addr = (Metadata**)(cast_from_oop
(requested_obj) + field_offset); diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.hpp b/src/hotspot/share/cds/aotMappedHeapWriter.hpp index 2420e68d9fea..5dc37ae350f3 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.hpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.hpp @@ -25,8 +25,8 @@ #ifndef SHARE_CDS_AOTMAPPEDHEAPWRITER_HPP #define SHARE_CDS_AOTMAPPEDHEAPWRITER_HPP +#include "cds/aotHeap.hpp" #include "cds/aotMapLogger.hpp" -#include "cds/heapShared.hpp" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" #include "oops/compressedOops.hpp" @@ -41,7 +41,7 @@ class MemRegion; #if INCLUDE_CDS_JAVA_HEAP class AOTMappedHeapWriter : AllStatic { - friend class HeapShared; + friend class AOTHeap; friend class AOTMappedHeapLoader; // AOTMappedHeapWriter manipulates three types of addresses: // @@ -53,9 +53,9 @@ class AOTMappedHeapWriter : AllStatic { // - "source objects" are regular Java objects allocated during the execution // of "java -Xshare:dump". They can be used as regular oops. // - // Between HeapShared::start_scanning_for_oops() and HeapShared::end_scanning_for_oops(), + // Between AOTHeap::start_scanning_for_oops() and AOTHeap::end_scanning_for_oops(), // we recursively search for the oops that need to be stored into the CDS archive. - // These are entered into HeapShared::archived_object_cache(). + // These are entered into AOTHeap::archived_object_cache(). // // - "buffered objects" are copies of the "source objects", and are stored in into // AOTMappedHeapWriter::_buffer, which is a GrowableArray that sits outside of diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index cd102e4eee73..d43c9c297467 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -28,6 +28,7 @@ #include "cds/aotClassLinker.hpp" #include "cds/aotClassLocation.hpp" #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.inline.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" @@ -46,7 +47,6 @@ #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" #include "cds/finalImageRecipes.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "classfile/classLoaderDataGraph.hpp" @@ -142,7 +142,7 @@ FileMapInfo* AOTMetaspace::_output_mapinfo = nullptr; // [5] SymbolTable, StringTable, SystemDictionary, and a few other read-only data // are copied into the ro region as read-only tables. // -// The heap region is written by HeapShared::write_heap(). +// The heap region is written by AOTHeap::write_heap(). // // The bitmap region is used to relocate the ro/rw/hp regions. @@ -195,9 +195,6 @@ class DumpClassListCLDClosure : public CLDClosure { if (!created) { return; } - if (_dumped_classes.maybe_grow()) { - log_info(aot, hashtables)("Expanded _dumped_classes table to %d", _dumped_classes.table_size()); - } if (ik->super()) { dump(ik->super()); } @@ -363,7 +360,7 @@ void AOTMetaspace::post_initialize(TRAPS) { // Close any open file descriptors. However, mmap'ed pages will remain in memory. static_mapinfo->close(); - if (!(HeapShared::is_loading() && HeapShared::is_loading_streaming_mode())) { + if (!(AOTHeap::is_loading() && AOTHeap::is_loading_streaming_mode())) { // Streaming mode will unmap the bm region later in AOTStreamedHeapLoader::cleanup() static_mapinfo->unmap_region(AOTMetaspace::bm); } @@ -422,7 +419,7 @@ void AOTMetaspace::read_extra_data(JavaThread* current, const char* filename) { CLEAR_PENDING_EXCEPTION; } else { #if INCLUDE_CDS_JAVA_HEAP - if (HeapShared::is_string_too_large_to_archive(str)) { + if (AOTHeap::is_string_too_large_to_archive(str)) { log_warning(aot, heap)("[line %d] extra interned string ignored; size too large: %d", reader.last_line_no(), utf8_length); continue; @@ -528,7 +525,7 @@ void AOTMetaspace::serialize(SerializeClosure* soc) { // Dump/restore the symbol/string/subgraph_info tables SymbolTable::serialize_shared_table_header(soc); StringTable::serialize_shared_table_header(soc); - HeapShared::serialize_tables(soc); + AOTHeap::serialize_tables(soc); SystemDictionaryShared::serialize_dictionary_headers(soc); AOTLinkedClassBulkLoader::serialize(soc); FinalImageRecipes::serialize(soc); @@ -846,7 +843,7 @@ void VM_PopulateDumpSharedSpace::doit() { _map_info->set_cloned_vtables(CppVtables::vtables_serialized_base()); _map_info->header()->set_class_location_config(cl_config); - HeapShared::delete_tables_with_raw_oops(); + AOTHeap::delete_tables_with_raw_oops(); CDSConfig::set_is_at_aot_safepoint(false); } @@ -1145,12 +1142,12 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS // determinism is not needed. assert(CDSConfig::allow_only_single_java_thread(), "Required"); } - if (!HeapShared::is_archived_boot_layer_available(THREAD)) { + if (!AOTHeap::is_archived_boot_layer_available(THREAD)) { report_loading_error("archivedBootLayer not available, disabling full module graph"); CDSConfig::stop_dumping_full_module_graph(); } // Do this before link_shared_classes(), as the following line may load new classes. - HeapShared::init_for_dumping(CHECK); + AOTHeap::init_for_dumping(CHECK); } #endif @@ -1179,11 +1176,11 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::init_heap_writer(); + AOTHeap::init_heap_writer(); if (CDSConfig::is_dumping_full_module_graph()) { AOTModules::ensure_module_entry_tables_exist(); AOTModules::build_tables(CHECK); - HeapShared::prepare_for_archiving(CHECK); + AOTHeap::prepare_for_archiving(CHECK); } AOTReferenceObjSupport::initialize(CHECK); @@ -1437,7 +1434,7 @@ bool AOTMetaspace::try_link_class(JavaThread* current, InstanceKlass* ik) { void VM_PopulateDumpSharedSpace::dump_java_heap_objects() { if (CDSConfig::is_dumping_heap()) { - HeapShared::write_heap(&_mapped_heap_info, &_streamed_heap_info); + AOTHeap::write_heap(&_mapped_heap_info, &_streamed_heap_info); } else if (!CDSConfig::is_dumping_preimage_static_archive()) { CDSConfig::log_reasons_for_not_dumping_heap(); } @@ -1880,12 +1877,12 @@ MapArchiveResult AOTMetaspace::map_archives(FileMapInfo* static_mapinfo, FileMap if (static_mapinfo->can_use_heap_region()) { if (static_mapinfo->object_streaming_mode()) { - HeapShared::initialize_loading_mode(HeapArchiveMode::_streaming); + AOTHeap::initialize_loading_mode(HeapArchiveMode::_streaming); } else { // map_or_load_heap_region() compares the current narrow oop and klass encodings // with the archived ones, so it must be done after all encodings are determined. static_mapinfo->map_or_load_heap_region(); - HeapShared::initialize_loading_mode(HeapArchiveMode::_mapping); + AOTHeap::initialize_loading_mode(HeapArchiveMode::_mapping); } } else { FileMapRegion* r = static_mapinfo->region_at(AOTMetaspace::hp); @@ -2218,7 +2215,7 @@ void AOTMetaspace::initialize_shared_spaces() { // Finish initializing the heap dump mode used in the archive // Heap initialization can be done only after vtables are initialized by ReadClosure. - HeapShared::finalize_initialization(static_mapinfo); + AOTHeap::finalize_initialization(static_mapinfo); Universe::load_archived_object_instances(); AOTCodeCache::initialize(); @@ -2272,7 +2269,7 @@ void AOTMetaspace::initialize_shared_spaces() { CountSharedSymbols cl; SymbolTable::shared_symbols_do(&cl); tty->print_cr("Number of shared symbols: %zu", cl.total()); - if (HeapShared::is_loading() && HeapShared::is_loading_mapping_mode()) { + if (AOTHeap::is_loading() && AOTHeap::is_loading_mapping_mode()) { tty->print_cr("Number of shared strings: %zu", StringTable::shared_entry_count()); } tty->print_cr("VM version: %s\r\n", static_mapinfo->vm_version()); diff --git a/src/hotspot/share/cds/aotModules.cpp b/src/hotspot/share/cds/aotModules.cpp index 69eb41d343dd..3bf43d0f5176 100644 --- a/src/hotspot/share/cds/aotModules.cpp +++ b/src/hotspot/share/cds/aotModules.cpp @@ -22,10 +22,10 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotLogging.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/serializeClosure.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/moduleEntry.hpp" @@ -476,13 +476,13 @@ void AOTModules::init_archived_modules(JavaThread* current, Handle h_platform_lo void AOTModules::load_archived_platform_and_system_class_loaders() { // The streaming object loader prefers loading the class loader related objects before // the CLD constructor which has a NoSafepointVerifier. - if (!HeapShared::is_loading_streaming_mode()) { + if (!AOTHeap::is_loading_streaming_mode()) { return; } // Ensure these class loaders are eagerly materialized before their CLDs are created. - HeapShared::get_root(_platform_loader_root_index, false /* clear */); - HeapShared::get_root(_system_loader_root_index, false /* clear */); + AOTHeap::get_root(_platform_loader_root_index, false /* clear */); + AOTHeap::get_root(_system_loader_root_index, false /* clear */); if (Universe::is_module_initialized() || !CDSConfig::is_using_full_module_graph()) { return; @@ -551,8 +551,8 @@ void AOTModules::remove_unshareable_info() { _archived_javabase_moduleEntry = ArchiveBuilder::current()->get_buffered_addr(ModuleEntryTable::javabase_moduleEntry()); - _platform_loader_root_index = HeapShared::append_root(SystemDictionary::java_platform_loader()); - _system_loader_root_index = HeapShared::append_root(SystemDictionary::java_system_loader()); + _platform_loader_root_index = AOTHeap::append_root(SystemDictionary::java_platform_loader()); + _system_loader_root_index = AOTHeap::append_root(SystemDictionary::java_system_loader()); } void AOTModules::serialize(SerializeClosure* f) { @@ -579,9 +579,9 @@ ModuleEntry* AOTModules::archived_unnamed_module(ClassLoaderData* loader_data) { precond(_platform_loader_root_index >= 0); precond(_system_loader_root_index >= 0); - if (loader_data->class_loader() == HeapShared::get_root(_platform_loader_root_index)) { + if (loader_data->class_loader() == AOTHeap::get_root(_platform_loader_root_index)) { archived_module = _archived_platform_loader_data.unnamed_module(); - } else if (loader_data->class_loader() == HeapShared::get_root(_system_loader_root_index)) { + } else if (loader_data->class_loader() == AOTHeap::get_root(_system_loader_root_index)) { archived_module = _archived_system_loader_data.unnamed_module(); } } @@ -595,8 +595,8 @@ void AOTModules::clear_archived_oops() { _archived_platform_loader_data.clear_archived_oops(); _archived_system_loader_data.clear_archived_oops(); if (_platform_loader_root_index >= 0) { - HeapShared::clear_root(_platform_loader_root_index); - HeapShared::clear_root(_system_loader_root_index); + AOTHeap::clear_root(_platform_loader_root_index); + AOTHeap::clear_root(_system_loader_root_index); } } @@ -634,8 +634,8 @@ void AOTModules::restore_archived_modules_for_preloading_classes(JavaThread* cur precond(_platform_loader_root_index >= 0); precond(_system_loader_root_index >= 0); - Handle h_platform_loader(current, HeapShared::get_root(_platform_loader_root_index)); - Handle h_system_loader(current, HeapShared::get_root(_system_loader_root_index)); + Handle h_platform_loader(current, AOTHeap::get_root(_platform_loader_root_index)); + Handle h_system_loader(current, AOTHeap::get_root(_system_loader_root_index)); init_archived_modules(current, h_platform_loader, h_system_loader); } diff --git a/src/hotspot/share/cds/aotOopChecker.cpp b/src/hotspot/share/cds/aotOopChecker.cpp index 3c0a142a059f..befc341f7f7e 100644 --- a/src/hotspot/share/cds/aotOopChecker.cpp +++ b/src/hotspot/share/cds/aotOopChecker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,9 +22,9 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotOopChecker.hpp" -#include "cds/heapShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/vmClasses.hpp" @@ -71,7 +71,7 @@ void AOTOopChecker::check(oop obj) { ResourceMark rm; log_error(aot)("Must cache only URLs with jrt/file protocols but got: %s", java_lang_String::as_quoted_ascii(protocol)); - HeapShared::debug_trace(); + AOTHeap::debug_trace(); AOTMetaspace::unrecoverable_writing_error(); } } diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp index 9af1e836391f..082c116edb91 100644 --- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp +++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp @@ -22,8 +22,8 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotReferenceObjSupport.hpp" -#include "cds/heapShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/symbolTable.hpp" #include "classfile/systemDictionary.hpp" @@ -96,7 +96,7 @@ class KeepAliveObjectsTable : public HashTable {}; + AOTHeap::oop_address_hash> {}; static KeepAliveObjectsTable* _keep_alive_objs_table; static OopHandle _keep_alive_objs_array; @@ -226,7 +226,7 @@ bool AOTReferenceObjSupport::check_if_ref_obj(oop obj) { PTR_FORMAT ")", p2i(_null_queue.resolve())); log_error(aot, heap)("%s", (referent == nullptr) ? "referent cannot be null" : "referent is not registered with CDS.keepAlive()"); - HeapShared::debug_trace(); + AOTHeap::debug_trace(); AOTMetaspace::unrecoverable_writing_error(); } diff --git a/src/hotspot/share/cds/aotStreamedHeap.hpp b/src/hotspot/share/cds/aotStreamedHeap.hpp index f06b1bcb4c62..1f6cb7c16863 100644 --- a/src/hotspot/share/cds/aotStreamedHeap.hpp +++ b/src/hotspot/share/cds/aotStreamedHeap.hpp @@ -58,7 +58,7 @@ class AOTStreamedHeapHeader { class AOTStreamedHeapInfo { MemRegion _buffer_region; // Contains the archived objects to be written into the CDS archive. CHeapBitMap _oopmap; - size_t _roots_offset; // Offset of the HeapShared::roots() object, from the bottom + size_t _roots_offset; // Offset of the AOTHeap::roots() object, from the bottom // of the archived heap objects, in bytes. size_t _num_roots; diff --git a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp index 528c0f149793..012dc9eccdab 100644 --- a/src/hotspot/share/cds/aotStreamedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapLoader.cpp @@ -22,13 +22,13 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotModules.hpp" #include "cds/aotStreamedHeapLoader.hpp" #include "cds/aotThread.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/stringTable.hpp" #include "classfile/vmClasses.hpp" @@ -329,7 +329,7 @@ void AOTStreamedHeapLoader::copy_object_impl(oopDesc* archive_object, // A concurrent GC thread might racingly read the object payload after GC is enabled. InPlaceLinkingOopClosure cl(heap_object, linker); heap_object->oop_iterate(&cl); - HeapShared::remap_loaded_metadata(heap_object); + AOTHeap::remap_loaded_metadata(heap_object); return; } @@ -353,7 +353,7 @@ void AOTStreamedHeapLoader::copy_object_impl(oopDesc* archive_object, // shady metadata fields from the archive that do not point at real metadata. // We deal with this by explicitly reading the requested address from the // archive and fixing it to real Metadata before writing it into the heap object. - HeapShared::do_metadata_offsets(heap_object, [&](int metadata_offset) { + AOTHeap::do_metadata_offsets(heap_object, [&](int metadata_offset) { BitMap::idx_t metadata_field_idx = header_bit + (size_t)metadata_offset / sizeof(RawElementT); BitMap::idx_t skip = word_scale; assert(metadata_field_idx >= start_bit && metadata_field_idx + skip <= end_bit, diff --git a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp index 007a0ae4c6dc..3781aded1458 100644 --- a/src/hotspot/share/cds/aotStreamedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotStreamedHeapWriter.cpp @@ -22,12 +22,12 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotModules.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/aotStreamedHeapWriter.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/modules.hpp" #include "classfile/stringTable.hpp" @@ -105,7 +105,7 @@ class FollowOopIterateClosure: public BasicOopIterateClosure { private: template void do_oop_work(T *p) { size_t field_offset = pointer_delta(p, _src_obj, sizeof(char)); - oop obj = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p)); + oop obj = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p)); if (obj != nullptr) { _dfs_stack->push(obj); } @@ -127,7 +127,6 @@ void AOTStreamedHeapWriter::order_source_objs(GrowableArrayCHeaplength(); ++i) { oop obj = _source_objs->at(i); _dfs_order_table->put(cast_from_oop(obj), -1); - _dfs_order_table->maybe_grow(); } int dfs_order = 0; @@ -152,7 +151,6 @@ void AOTStreamedHeapWriter::order_source_objs(GrowableArrayCHeapput(cast_from_oop(obj), ++dfs_order); - _dfs_order_table->maybe_grow(); FollowOopIterateClosure cl(&dfs_stack, obj, AOTReferenceObjSupport::check_if_ref_obj(obj)); obj->oop_iterate(&cl); @@ -255,14 +253,13 @@ static BitMap::idx_t bit_idx_for_buffer_offset(size_t buffer_offset) { void AOTStreamedHeapWriter::copy_source_objs_to_buffer(GrowableArrayCHeap* roots) { for (int i = 0; i < _source_objs->length(); i++) { oop src_obj = _source_objs->at(i); - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj); assert(info != nullptr, "must be"); size_t buffer_offset = copy_one_source_obj_to_buffer(src_obj); info->set_buffer_offset(buffer_offset); OopHandle handle(Universe::vm_global(), src_obj); _buffer_offset_to_source_obj_table->put_when_absent(buffer_offset, handle); - _buffer_offset_to_source_obj_table->maybe_grow(); int dfs_order = i + 1; _dfs_to_archive_object_table[dfs_order] = buffer_offset; @@ -309,7 +306,7 @@ size_t AOTStreamedHeapWriter::copy_one_source_obj_to_buffer(oop src_obj) { ensure_buffer_space(new_used); - if (HeapShared::is_interned_string(src_obj)) { + if (AOTHeap::is_interned_string(src_obj)) { java_lang_String::hash_code(src_obj); // Sets the hash code field(s) java_lang_String::set_deduplication_forbidden(src_obj); // Allows faster interning at runtime assert(java_lang_String::hash_is_set(src_obj), "hash must be set"); @@ -385,7 +382,7 @@ void AOTStreamedHeapWriter::update_header_for_buffered_addr(address buffered_add mw = mw.copy_set_hash(src_hash); } - if (HeapShared::is_interned_string(src_obj)) { + if (AOTHeap::is_interned_string(src_obj)) { // Mark the mark word of interned string so the loader knows to link these to // the string table at runtime. mw = mw.set_marked(); @@ -419,7 +416,7 @@ class AOTStreamedHeapWriter::EmbeddedOopMapper: public BasicOopIterateClosure { template void do_oop_work(T *p) { size_t field_offset = pointer_delta(p, _src_obj, sizeof(char)); - oop obj = HeapShared::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p)); + oop obj = AOTHeap::maybe_remap_referent(_is_java_lang_ref, field_offset, HeapAccess<>::oop_load(p)); AOTStreamedHeapWriter::map_oop_field_in_buffer(obj, (T*)(_buffered_obj + field_offset), _oopmap); } }; @@ -443,7 +440,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) { for (int i = 0; i < _source_objs->length(); i++) { oop src_obj = _source_objs->at(i); - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(src_obj); assert(info != nullptr, "must be"); address buffered_obj = offset_to_buffered_address
(info->buffer_offset()); @@ -451,7 +448,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) { EmbeddedOopMapper mapper(src_obj, buffered_obj, heap_info->oopmap()); src_obj->oop_iterate(&mapper); - HeapShared::remap_dumped_metadata(src_obj, buffered_obj); + AOTHeap::remap_dumped_metadata(src_obj, buffered_obj); }; size_t total_bytes = (size_t)_buffer->length(); @@ -459,7 +456,7 @@ void AOTStreamedHeapWriter::map_embedded_oops(AOTStreamedHeapInfo* heap_info) { } size_t AOTStreamedHeapWriter::source_obj_to_buffered_offset(oop src_obj) { - HeapShared::CachedOopInfo* p = HeapShared::get_cached_oop_info(src_obj); + AOTHeap::CachedOopInfo* p = AOTHeap::get_cached_oop_info(src_obj); return p->buffer_offset(); } @@ -489,7 +486,7 @@ void AOTStreamedHeapWriter::populate_archive_heap_info(AOTStreamedHeapInfo* info info->set_buffer_region(MemRegion(offset_to_buffered_address(0), offset_to_buffered_address(_buffer_used))); info->set_roots_offset(_roots_offset); - info->set_num_roots((size_t)HeapShared::pending_roots()->length()); + info->set_num_roots((size_t)AOTHeap::pending_roots()->length()); info->set_forwarding_offset(_forwarding_offset); info->set_root_highest_object_index_table_offset(_root_highest_object_index_table_offset); info->set_num_archived_objects((size_t)_source_objs->length()); diff --git a/src/hotspot/share/cds/aotStreamedHeapWriter.hpp b/src/hotspot/share/cds/aotStreamedHeapWriter.hpp index c3cc9f2c0921..9690d9d1d283 100644 --- a/src/hotspot/share/cds/aotStreamedHeapWriter.hpp +++ b/src/hotspot/share/cds/aotStreamedHeapWriter.hpp @@ -26,7 +26,6 @@ #define SHARE_CDS_AOTSTREAMEDHEAPWRITER_HPP #include "cds/aotMapLogger.hpp" -#include "cds/heapShared.hpp" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" #include "oops/oopHandle.hpp" diff --git a/src/hotspot/share/cds/aotThread.cpp b/src/hotspot/share/cds/aotThread.cpp index f3382a6f7e4c..7f9304dd5554 100644 --- a/src/hotspot/share/cds/aotThread.cpp +++ b/src/hotspot/share/cds/aotThread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,6 @@ #include "cds/aotStreamedHeapLoader.hpp" #include "cds/aotThread.hpp" -#include "cds/heapShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaThreadStatus.hpp" #include "classfile/vmClasses.hpp" diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 57ab0b316639..0cbc08319312 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -25,6 +25,7 @@ #include "cds/aotArtifactFinder.hpp" #include "cds/aotClassLinker.hpp" #include "cds/aotCompressedPointers.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" #include "cds/aotMetaspace.hpp" @@ -35,7 +36,6 @@ #include "cds/cppVtables.hpp" #include "cds/dumpAllocStats.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.hpp" #include "cds/regeneratedClasses.hpp" #include "classfile/classLoader.hpp" #include "classfile/javaClasses.hpp" @@ -437,11 +437,6 @@ bool ArchiveBuilder::gather_one_source_obj(MetaspaceClosure::Ref* ref, bool read SourceObjInfo src_info(ref, read_only, follow_mode); bool created; SourceObjInfo* p = _src_obj_table.put_if_absent(src_obj, src_info, &created); - if (created) { - if (_src_obj_table.maybe_grow()) { - log_info(aot, hashtables)("Expanded _src_obj_table table to %d", _src_obj_table.table_size()); - } - } #ifdef ASSERT if (ref->type() == MetaspaceClosureType::MethodType) { @@ -659,9 +654,6 @@ void ArchiveBuilder::make_shallow_copy(DumpRegion *dump_region, SourceObjInfo* s bool created; _buffered_to_src_table.put_if_absent((address)dest, src, &created); assert(created, "must be"); - if (_buffered_to_src_table.maybe_grow()) { - log_info(aot, hashtables)("Expanded _buffered_to_src_table table to %d", _buffered_to_src_table.table_size()); - } } intptr_t* archived_vtable = CppVtables::get_archived_vtable(src_info->type(), (address)dest); @@ -852,10 +844,10 @@ void ArchiveBuilder::make_klasses_shareable() { assert(0, "shouldn't happen"); } if (CDSConfig::is_dumping_method_handles()) { - assert(HeapShared::is_archivable_hidden_klass(ik), "sanity"); + assert(AOTHeap::is_archivable_hidden_klass(ik), "sanity"); } else { // Legacy CDS support for lambda proxies - CDS_JAVA_HEAP_ONLY(assert(HeapShared::is_lambda_proxy_klass(ik), "sanity");) + CDS_JAVA_HEAP_ONLY(assert(AOTHeap::is_lambda_proxy_klass(ik), "sanity");) } } else if (ik->defined_by_boot_loader()) { type = "boot"; @@ -1178,7 +1170,7 @@ void ArchiveBuilder::write_archive(FileMapInfo* mapinfo, AOTMappedHeapInfo* mapp if (log_is_enabled(Info, aot, map)) { AOTMapLogger::dumptime_log(this, mapinfo, mapped_heap_info, streamed_heap_info, bitmap, bitmap_size_in_bytes); } - CDS_JAVA_HEAP_ONLY(HeapShared::destroy_archived_object_cache()); + CDS_JAVA_HEAP_ONLY(AOTHeap::destroy_archived_object_cache()); FREE_C_HEAP_ARRAY(bitmap); } diff --git a/src/hotspot/share/cds/archiveUtils.cpp b/src/hotspot/share/cds/archiveUtils.cpp index 3d4d36ab486b..9dbd8ce1cfa7 100644 --- a/src/hotspot/share/cds/archiveUtils.cpp +++ b/src/hotspot/share/cds/archiveUtils.cpp @@ -33,7 +33,6 @@ #include "cds/dumpAllocStats.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 278c7397f12b..4faaa6329874 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -22,6 +22,7 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMapLogger.hpp" #include "cds/aotMetaspace.hpp" @@ -30,7 +31,6 @@ #include "cds/cdsConfig.hpp" #include "cds/classListWriter.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/moduleEntry.hpp" #include "code/aotCodeCache.hpp" #include "compiler/compilerDefinitions.inline.hpp" @@ -88,8 +88,8 @@ void CDSConfig::ergo_initialize() { if (is_dumping_static_archive() && !is_dumping_final_static_archive()) { // Note: -Xshare and -XX:AOTMode flags are mutually exclusive. - // - Classic workflow: -Xshare:on and -Xshare:dump cannot take effect at the same time. - // - JEP 483 workflow: -XX:AOTMode:record and -XX:AOTMode=on cannot take effect at the same time. + // - Classic CDS workflow: -Xshare:on and -Xshare:dump cannot take effect at the same time. + // - AOT workflow: -XX:AOTMode:record and -XX:AOTMode=required cannot take effect at the same time. // So we can never come to here with RequireSharedSpaces==true. assert(!RequireSharedSpaces, "sanity"); @@ -122,7 +122,7 @@ void CDSConfig::ergo_initialize() { precond(allow_only_single_java_thread()); // The AttachListenerThread may execute Java code or load new classes. It might see - // unexpected results after HeapShared::prepare_for_archiving(). + // unexpected results after AOTHeap::prepare_for_archiving(). // // We disable all new incoming attach requests, so you can't use jcmd, etc, on this JVM. // Since we are not running any application code in this JVM and only executed a very @@ -515,6 +515,11 @@ void CDSConfig::check_aotmode_off() { } void CDSConfig::check_aotmode_auto_or_on() { + if (AOTMode != nullptr && strcmp(AOTMode, "on") == 0) { + warning("Option -XX:AOTMode-on was deprecated in version 28 and will likely be " + "removed in a future release. Use option -XX:AOTMode=required instead."); + } + if (!FLAG_IS_DEFAULT(AOTConfiguration)) { vm_exit_during_initialization(err_msg("AOTConfiguration can only be used with when AOTMode is record or create (selected AOTMode = %s)", FLAG_IS_DEFAULT(AOTMode) ? "auto" : AOTMode)); @@ -1020,7 +1025,7 @@ bool CDSConfig::is_dumping_heap() { } bool CDSConfig::is_loading_heap() { - return HeapShared::is_archived_heap_in_use(); + return AOTHeap::is_archived_heap_in_use(); } bool CDSConfig::is_dumping_klass_subgraphs() { @@ -1068,7 +1073,7 @@ bool CDSConfig::is_using_full_module_graph() { return false; } - if (is_using_archive() && HeapShared::can_use_archived_heap()) { + if (is_using_archive() && AOTHeap::can_use_archived_heap()) { // Classes used by the archived full module graph are loaded in JVMTI early phase. assert(!(JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()), "CDS should be disabled if early class hooks are enabled"); diff --git a/src/hotspot/share/cds/cdsEnumKlass.cpp b/src/hotspot/share/cds/cdsEnumKlass.cpp index 7684e33e7b8c..4fb82b67bc40 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.cpp +++ b/src/hotspot/share/cds/cdsEnumKlass.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,8 +22,8 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/cdsEnumKlass.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" #include "memory/resourceArea.hpp" @@ -58,7 +58,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) { // ModuleLayer::EMPTY_LAYER, for example). // // Instead, we archive all static field of such Enum classes. At runtime, -// HeapShared::initialize_enum_klass() skips the method and instead pulls +// AOTHeap::initialize_enum_klass() skips the method and instead pulls // the static fields out of the archived heap. void CDSEnumKlass::handle_enum_obj(int level, KlassSubGraphInfo* subgraph_info, @@ -101,9 +101,9 @@ void CDSEnumKlass::archive_static_field(int level, KlassSubGraphInfo* subgraph_i guarantee(false, "static field %s::%s is of the wrong type", ik->external_name(), fd.name()->as_C_string()); } - bool success = HeapShared::archive_reachable_objects_from(level, subgraph_info, oop_field); + bool success = AOTHeap::archive_reachable_objects_from(level, subgraph_info, oop_field); assert(success, "VM should have exited with unarchivable objects for _level > 1"); - int root_index = HeapShared::append_root(oop_field); + int root_index = AOTHeap::append_root(oop_field); log_info(aot, heap)("Archived enum obj @%d %s::%s (" INTPTR_FORMAT ")", root_index, ik->external_name(), fd.name()->as_C_string(), p2i((oopDesc*)oop_field)); @@ -111,7 +111,7 @@ void CDSEnumKlass::archive_static_field(int level, KlassSubGraphInfo* subgraph_i } bool CDSEnumKlass::initialize_enum_klass(InstanceKlass* k, TRAPS) { - if (!HeapShared::is_archived_heap_in_use()) { + if (!AOTHeap::is_archived_heap_in_use()) { return false; } @@ -129,7 +129,7 @@ bool CDSEnumKlass::initialize_enum_klass(InstanceKlass* k, TRAPS) { int root_index = info->enum_klass_static_field_root_index_at(i++); fieldDescriptor& fd = fs.field_descriptor(); assert(fd.field_type() == T_OBJECT || fd.field_type() == T_ARRAY, "must be"); - oop root_object = HeapShared::get_root(root_index, /*clear=*/true); + oop root_object = AOTHeap::get_root(root_index, /*clear=*/true); k->java_mirror()->obj_field_put(fd.offset(), root_object); } } diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index b0aaede89c1f..ee4ccea84a9c 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -375,7 +375,7 @@ void CDSHeapVerifier::do_klass(Klass* k) { if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - if (HeapShared::is_subgraph_root_class(ik)) { + if (AOTHeap::is_subgraph_root_class(ik)) { // ik is inside one of the ArchivableStaticFieldInfo tables // in heapShared.cpp. We assume such classes are programmed to // update their static fields correctly at runtime. @@ -400,11 +400,11 @@ void CDSHeapVerifier::add_static_obj_field(InstanceKlass* ik, oop field, Symbol* // This function is called once for every archived heap object. Warn if this object is referenced by // a static field of a class that's not aot-initialized. -inline bool CDSHeapVerifier::do_entry(OopHandle& orig_obj_handle, HeapShared::CachedOopInfo& value) { +inline bool CDSHeapVerifier::do_entry(OopHandle& orig_obj_handle, AOTHeap::CachedOopInfo& value) { oop orig_obj = orig_obj_handle.resolve(); _archived_objs++; - if (java_lang_String::is_instance(orig_obj) && HeapShared::is_dumped_interned_string(orig_obj)) { + if (java_lang_String::is_instance(orig_obj) && AOTHeap::is_dumped_interned_string(orig_obj)) { // It's quite often for static fields to have interned strings. These are most likely not // problematic (and are hard to filter). So we will ignore them. return true; @@ -451,7 +451,7 @@ class CDSHeapVerifier::TraceFields : public FieldClosure { // Call this function (from gdb, etc) if you want to know why an object is archived. void CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj) { - HeapShared::CachedOopInfo* info = HeapShared::get_cached_oop_info(orig_obj); + AOTHeap::CachedOopInfo* info = AOTHeap::get_cached_oop_info(orig_obj); if (info != nullptr) { trace_to_root(st, orig_obj, nullptr, info); } else { @@ -482,10 +482,10 @@ const char* static_field_name(oop mirror, oop field) { return ""; } -int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* info) { +int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_field, AOTHeap::CachedOopInfo* info) { int level = 0; if (info->orig_referrer() != nullptr) { - HeapShared::CachedOopInfo* ref = HeapShared::get_cached_oop_info(info->orig_referrer()); + AOTHeap::CachedOopInfo* ref = AOTHeap::get_cached_oop_info(info->orig_referrer()); assert(ref != nullptr, "sanity"); level = trace_to_root(st, info->orig_referrer(), orig_obj, ref) + 1; } else if (java_lang_String::is_instance(orig_obj)) { @@ -521,7 +521,7 @@ int CDSHeapVerifier::trace_to_root(outputStream* st, oop orig_obj, oop orig_fiel void CDSHeapVerifier::verify() { CDSHeapVerifier verf; - HeapShared::archived_object_cache()->iterate(&verf); + AOTHeap::archived_object_cache()->iterate(&verf); } #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/cdsHeapVerifier.hpp b/src/hotspot/share/cds/cdsHeapVerifier.hpp index f8e090801bbc..33de9e8dfbaa 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.hpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,7 @@ #ifndef SHARE_CDS_CDSHEAPVERIFIER_HPP #define SHARE_CDS_CDSHEAPVERIFIER_HPP -#include "cds/heapShared.hpp" +#include "cds/aotHeap.hpp" #include "memory/iterator.hpp" #include "oops/oopHandle.hpp" #include "utilities/growableArray.hpp" @@ -53,7 +53,7 @@ class CDSHeapVerifier : public KlassClosure { 15889, // prime number AnyObj::C_HEAP, mtClassShared, - HeapShared::oop_address_hash> _table; + AOTHeap::oop_address_hash> _table; GrowableArray _exclusions; GrowableArray _shared_secret_accessors; @@ -88,7 +88,7 @@ class CDSHeapVerifier : public KlassClosure { return false; } - static int trace_to_root(outputStream* st, oop orig_obj, oop orig_field, HeapShared::CachedOopInfo* p); + static int trace_to_root(outputStream* st, oop orig_obj, oop orig_field, AOTHeap::CachedOopInfo* p); CDSHeapVerifier(); ~CDSHeapVerifier(); @@ -99,7 +99,7 @@ class CDSHeapVerifier : public KlassClosure { virtual void do_klass(Klass* k); // For HashTable::iterate() - inline bool do_entry(OopHandle& orig_obj, HeapShared::CachedOopInfo& value); + inline bool do_entry(OopHandle& orig_obj, AOTHeap::CachedOopInfo& value); static void verify(); diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index 17405b6c0796..d0898a39b301 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -105,7 +105,8 @@ \ product(ccstr, AOTMode, nullptr, \ "Specifies how AOTCache should be created or used. Valid values " \ - "are: off, record, create, auto, on; the default is auto") \ + "are: off, record, create, auto, required, and (deprecated) on; " \ + "the default is auto") \ constraint(AOTModeConstraintFunc, AtParse) \ \ product(ccstr, AOTConfiguration, nullptr, \ diff --git a/src/hotspot/share/cds/classListParser.cpp b/src/hotspot/share/cds/classListParser.cpp index 836d018a714c..24cd9454e088 100644 --- a/src/hotspot/share/cds/classListParser.cpp +++ b/src/hotspot/share/cds/classListParser.cpp @@ -774,9 +774,6 @@ Klass* ClassListParser::load_current_class(Symbol* class_name_symbol, TRAPS) { if (!created) { error("Duplicated ID %d for class %s", id, _class_name); } - if (id2klass_table()->maybe_grow()) { - log_info(aot, hashtables)("Expanded id2klass_table() to %d", id2klass_table()->table_size()); - } } return klass; diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index b8adbba22f1f..30afc2c2a5a1 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -33,7 +33,6 @@ #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "cds/regeneratedClasses.hpp" diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index d0788871221d..d0feb197ef28 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -23,6 +23,7 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotHeap.inline.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/aotMappedHeapWriter.hpp" @@ -33,7 +34,6 @@ #include "cds/cdsConfig.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/altHashing.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" @@ -293,7 +293,7 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, _compact_headers = UseCompactObjectHeaders; #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - _object_streaming_mode = HeapShared::is_writing_streaming_mode(); + _object_streaming_mode = AOTHeap::is_writing_streaming_mode(); _narrow_oop_mode = AOTMappedHeapWriter::narrow_oop_mode(); _narrow_oop_base = AOTMappedHeapWriter::narrow_oop_base(); _narrow_oop_shift = AOTMappedHeapWriter::narrow_oop_shift(); @@ -875,7 +875,7 @@ size_t FileMapRegion::used_aligned() const { void FileMapRegion::init(int region_index, size_t mapping_offset, size_t size, bool read_only, bool allow_exec, int crc) { - _is_heap_region = HeapShared::is_heap_region(region_index); + _is_heap_region = AOTHeap::is_heap_region(region_index); _is_bitmap_region = (region_index == AOTMetaspace::bm); _mapping_offset = mapping_offset; _used = size; @@ -977,11 +977,11 @@ void FileMapInfo::write_region(int region, char* base, size_t size, } else if (size == 0) { // This is an unused region (e.g., a heap region when !INCLUDE_CDS_JAVA_HEAP) requested_base = nullptr; - } else if (HeapShared::is_heap_region(region)) { + } else if (AOTHeap::is_heap_region(region)) { assert(CDSConfig::is_dumping_heap(), "sanity"); #if INCLUDE_CDS_JAVA_HEAP assert(!CDSConfig::is_dumping_dynamic_archive(), "must be"); - if (HeapShared::is_writing_mapping_mode()) { + if (AOTHeap::is_writing_mapping_mode()) { requested_base = (char*)AOTMappedHeapWriter::requested_address(); if (UseCompressedOops) { mapping_offset = (size_t)((address)requested_base - AOTMappedHeapWriter::narrow_oop_base()); @@ -1055,7 +1055,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, if (mapped_heap_info != nullptr && mapped_heap_info->is_used()) { // Remove leading and trailing zeros - assert(HeapShared::is_writing_mapping_mode(), "unexpected dumping mode"); + assert(AOTHeap::is_writing_mapping_mode(), "unexpected dumping mode"); size_t removed_oop_leading_zeros = remove_bitmap_zeros(mapped_heap_info->oopmap()); size_t removed_ptr_leading_zeros = remove_bitmap_zeros(mapped_heap_info->ptrmap()); mapped_heap_info->set_oopmap_start_pos(removed_oop_leading_zeros); @@ -1064,7 +1064,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, size_in_bytes += mapped_heap_info->oopmap()->size_in_bytes(); size_in_bytes += mapped_heap_info->ptrmap()->size_in_bytes(); } else if (streamed_heap_info != nullptr && streamed_heap_info->is_used()) { - assert(HeapShared::is_writing_streaming_mode(), "unexpected dumping mode"); + assert(AOTHeap::is_writing_streaming_mode(), "unexpected dumping mode"); size_in_bytes += streamed_heap_info->oopmap()->size_in_bytes(); } @@ -1090,7 +1090,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, } if (mapped_heap_info != nullptr && mapped_heap_info->is_used()) { - assert(HeapShared::is_writing_mapping_mode(), "unexpected dumping mode"); + assert(AOTHeap::is_writing_mapping_mode(), "unexpected dumping mode"); FileMapRegion* r = region_at(AOTMetaspace::hp); r->init_oopmap(written, mapped_heap_info->oopmap()->size()); @@ -1099,7 +1099,7 @@ char* FileMapInfo::write_bitmap_region(CHeapBitMap* rw_ptrmap, r->init_ptrmap(written, mapped_heap_info->ptrmap()->size()); written = write_bitmap(mapped_heap_info->ptrmap(), buffer, written); } else if (streamed_heap_info != nullptr && streamed_heap_info->is_used()) { - assert(HeapShared::is_writing_streaming_mode(), "unexpected dumping mode"); + assert(AOTHeap::is_writing_streaming_mode(), "unexpected dumping mode"); FileMapRegion* r = region_at(AOTMetaspace::hp); r->init_oopmap(written, streamed_heap_info->oopmap()->size()); @@ -1320,7 +1320,7 @@ bool FileMapInfo::read_region(int i, char* base, size_t size, bool do_commit) { } MapArchiveResult FileMapInfo::map_region(int i, intx addr_delta, char* mapped_base_address, ReservedSpace rs) { - assert(!HeapShared::is_heap_region(i), "sanity"); + assert(!AOTHeap::is_heap_region(i), "sanity"); FileMapRegion* r = region_at(i); size_t size = r->used_aligned(); char *requested_addr = mapped_base_address + r->mapping_offset(); @@ -1660,7 +1660,7 @@ void FileMapInfo::stream_heap_region() { assert(object_streaming_mode(), "This should only be done for the streaming approach"); if (map_auxiliary_region(AOTMetaspace::hp, /*readonly=*/true) != nullptr) { - HeapShared::initialize_streaming(); + AOTHeap::initialize_streaming(); } else { on_heap_region_loading_error(); } @@ -1723,11 +1723,11 @@ bool FileMapInfo::can_use_heap_region() { // If JvmtiExport::has_early_class_hook_env() is false, the classes of some objects // in the archived subgraphs may be replaced by the ClassFileLoadHook. But that's OK // because we won't install an archived object subgraph if the klass of any of the - // referenced objects are replaced. See HeapShared::initialize_from_archived_subgraph(). + // referenced objects are replaced. See AOTHeap::initialize_from_archived_subgraph(). } // We pre-compute narrow Klass IDs with the runtime mapping start intended to be the base, and a shift of - // HeapShared::precomputed_narrow_klass_shift. We enforce this encoding at runtime (see + // AOTHeap::precomputed_narrow_klass_shift. We enforce this encoding at runtime (see // CompressedKlassPointers::initialize_for_given_encoding()). Therefore, the following assertions must // hold: address archive_narrow_klass_base = (address)header()->mapped_base_address(); diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 5987e6d52d3c..a92397db85f3 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -30,7 +30,6 @@ #include "cds/aotMetaspace.hpp" #include "cds/aotStreamedHeap.hpp" #include "cds/archiveUtils.hpp" -#include "cds/heapShared.hpp" #include "include/cds.h" #include "logging/logLevel.hpp" #include "memory/allocation.hpp" diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index bd0e723c0c27..4b649c5dd482 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -24,7 +24,7 @@ #include "cds/aotCacheAccess.hpp" #include "cds/aotConstantPoolResolver.hpp" -#include "cds/heapShared.hpp" +#include "cds/aotHeap.hpp" #include "ci/ciConstant.hpp" #include "ci/ciEnv.hpp" #include "ci/ciField.hpp" @@ -1275,7 +1275,7 @@ void ciEnv::register_method(ciMethod* target, assert(!method->is_synchronized() || nm->has_monitors(), ""); #if INCLUDE_CDS - if (task()->is_aot_compile()) { + if (is_aot_compile()) { #ifdef ASSERT LogStreamHandle(Trace, aot, codecache, nmethod) log; if (log.is_enabled()) { @@ -2009,7 +2009,7 @@ InstanceKlass::ClassState ciEnv::compute_init_state_for_aot_compile(InstanceKlas // are initialized in production run, but they are not recorded as init dependencies. // CI query should report their status as if in production run, otherwise AOT // code would have uncommon traps at invokedynamic calls. - if (HeapShared::is_core_java_lang_invoke_klass(ik)) { + if (AOTHeap::is_core_java_lang_invoke_klass(ik)) { log_trace(aot, compilation)("%d: core_java_lang_invoke: (%s) %s", task()->compile_id(), InstanceKlass::state2name(ik->init_state()), ik->external_name()); return InstanceKlass::ClassState::fully_initialized; } diff --git a/src/hotspot/share/ci/ciValueKlass.cpp b/src/hotspot/share/ci/ciValueKlass.cpp index 0655e18e21e4..df27b679b1a6 100644 --- a/src/hotspot/share/ci/ciValueKlass.cpp +++ b/src/hotspot/share/ci/ciValueKlass.cpp @@ -51,10 +51,9 @@ bool ciValueKlass::can_be_returned_as_fields() const { } bool ciValueKlass::is_empty() { - // Do not use ValueKlass::is_empty_value_type here because it does - // consider the container empty even if fields of empty value types - // are not flat - return nof_declared_nonstatic_fields() == 0; + // Flat fields are recursively expanded, nullable flat fields include + // their null marker and non-flat fields remain as oops. + return nof_nonstatic_fields() == 0; } bool ciValueKlass::is_cloneable() const { diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 00a6442d13c1..fea2a8f9a26f 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -23,11 +23,11 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotHeap.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" #include "cds/cdsProtectionDomain.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.inline.hpp" #include "classfile/classLoaderData.inline.hpp" @@ -1440,10 +1440,10 @@ void ClassLoader::record_hidden_class(InstanceKlass* ik) { return; } - if (HeapShared::is_lambda_proxy_klass(ik)) { + if (AOTHeap::is_lambda_proxy_klass(ik)) { InstanceKlass* nest_host = ik->nest_host_not_null(); ik->set_shared_classpath_index(nest_host->shared_classpath_index()); - } else if (HeapShared::is_lambda_form_klass(ik)) { + } else if (AOTHeap::is_lambda_form_klass(ik)) { ik->set_shared_classpath_index(0); } else { // Generated invoker classes. diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp index e901e3142be9..51f02de3e755 100644 --- a/src/hotspot/share/classfile/classLoaderData.cpp +++ b/src/hotspot/share/classfile/classLoaderData.cpp @@ -46,7 +46,7 @@ // The bootstrap loader (represented by null) also has a ClassLoaderData, // the singleton class the_null_class_loader_data(). -#include "cds/heapShared.hpp" +#include "cds/aotHeap.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.inline.hpp" #include "classfile/dictionary.hpp" @@ -904,7 +904,7 @@ void ClassLoaderData::free_deallocate_list() { if (m->is_method()) { MetadataFactory::free_metadata(this, (Method*)m); } else if (m->is_constantPool()) { - HeapShared::remove_scratch_resolved_references((ConstantPool*)m); + AOTHeap::remove_scratch_resolved_references((ConstantPool*)m); MetadataFactory::free_metadata(this, (ConstantPool*)m); } else if (m->is_klass()) { JFR_ONLY(Jfr::on_deallocation(static_cast(m));) diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp index d55adf8093f3..643f6f5767f5 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.cpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.cpp @@ -187,19 +187,14 @@ bool LayoutRawBlock::fit(int size, int alignment) { FieldGroup::FieldGroup(int contended_group) : _next(nullptr), - _small_primitive_fields(nullptr), - _big_primitive_fields(nullptr), + _non_oop_fields(nullptr), _oop_fields(nullptr), _contended_group(contended_group) {} // -1 means no contended group, 0 means default contended group void FieldGroup::add_primitive_field(int idx, BasicType type) { int size = type2aelembytes(type); LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::REGULAR, size, size /* alignment == size for primitive types */); - if (size >= heapOopSize) { - add_to_big_primitive_list(block); - } else { - add_to_small_primitive_list(block); - } + add_to_non_oop_list(block); } void FieldGroup::add_oop_field(int idx) { @@ -218,35 +213,20 @@ void FieldGroup::add_flat_field(int idx, ValueKlass* vk, LayoutKind lk) { LayoutRawBlock* block = new LayoutRawBlock(idx, LayoutRawBlock::FLAT, size, alignment); block->set_value_klass(vk); block->set_layout_kind(lk); - if (block->size() >= heapOopSize) { - add_to_big_primitive_list(block); - } else { - assert(!vk->contains_oops(), "Size of Value klass with oops should be >= heapOopSize"); - add_to_small_primitive_list(block); - } + add_to_non_oop_list(block); } void FieldGroup::sort_by_size() { - if (_small_primitive_fields != nullptr) { - _small_primitive_fields->sort(LayoutRawBlock::compare_size_inverted); - } - if (_big_primitive_fields != nullptr) { - _big_primitive_fields->sort(LayoutRawBlock::compare_size_inverted); + if (_non_oop_fields != nullptr) { + _non_oop_fields->sort(LayoutRawBlock::compare_size_inverted); } } -void FieldGroup::add_to_small_primitive_list(LayoutRawBlock* block) { - if (_small_primitive_fields == nullptr) { - _small_primitive_fields = new GrowableArray(INITIAL_LIST_SIZE); +void FieldGroup::add_to_non_oop_list(LayoutRawBlock* block) { + if (_non_oop_fields == nullptr) { + _non_oop_fields = new GrowableArray(INITIAL_LIST_SIZE); } - _small_primitive_fields->append(block); -} - -void FieldGroup::add_to_big_primitive_list(LayoutRawBlock* block) { - if (_big_primitive_fields == nullptr) { - _big_primitive_fields = new GrowableArray(INITIAL_LIST_SIZE); - } - _big_primitive_fields->append(block); + _non_oop_fields->append(block); } FieldLayout::FieldLayout(GrowableArray* field_info, Array* value_field_info_array, ConstantPool* cp) : @@ -310,18 +290,41 @@ LayoutRawBlock* FieldLayout::first_field_block() { return block; } -// Insert a set of fields into a layout. -// For each field, search for an empty slot able to fit the field +// Return the first non-oop field smaller than a heap oop. +int FieldGroup::first_small_field_index() const { + if (_non_oop_fields == nullptr) { + return 0; + } + + int index = 0; + while (index < _non_oop_fields->length() && _non_oop_fields->at(index)->size() >= heapOopSize) { + index++; + } + return index; +} + +// Insert fields from a list into a layout. add() inserts the whole list, +// while add_range() inserts a half-open range of the list. +// For each selected field, search for an empty slot able to fit the field // (satisfying both size and alignment requirements), if none is found, // add the field at the end of the layout. // Fields cannot be inserted before the block specified in the "start" argument void FieldLayout::add(GrowableArray* list, LayoutRawBlock* start) { - if (list == nullptr) return; - if (start == nullptr) start = this->_start; + if (list == nullptr) { + return; + } + add_range(list, 0, list->length(), start); +} + +void FieldLayout::add_range(GrowableArray* list, int from, int to, LayoutRawBlock* start) { + assert(list != nullptr, "sanity"); + assert(0 <= from && from <= to && to <= list->length(), "sanity"); + + if (start == nullptr) start = _start; bool last_search_success = false; int last_size = 0; int last_alignment = 0; - for (int i = 0; i < list->length(); i ++) { + for (int i = from; i < to; i ++) { LayoutRawBlock* b = list->at(i); LayoutRawBlock* cursor = nullptr; LayoutRawBlock* candidate = nullptr; @@ -983,14 +986,14 @@ LayoutRawBlock* FieldLayoutBuilder::insert_contended_padding(LayoutRawBlock* slo // Computation of regular classes layout is an evolution of the previous default layout // (FieldAllocationStyle 1): -// - primitive fields (both primitive types and flat value types) are allocated +// - non-oop fields (both primitive types and flat value types) are allocated // first (from the biggest to the smallest) // - oop fields are allocated, either in existing gaps or at the end of // the layout. We allocate oops in a single block to have a single oop map entry. // - if the super class ended with an oop, we lead with oops. That will cause the // trailing oop map entry of the super class and the oop map entry of this class // to be folded into a single entry later. Correspondingly, if the super class -// ends with a primitive field, we gain nothing by leading with oops; therefore +// ends with a non-oop field, we gain nothing by leading with oops; therefore // we let oop fields trail, thus giving future derived classes the chance to apply // the same trick. void FieldLayoutBuilder::compute_regular_layout() { @@ -1011,11 +1014,9 @@ void FieldLayoutBuilder::compute_regular_layout() { if (_super_ends_with_oop) { _layout->add(_root_group->oop_fields()); - _layout->add(_root_group->big_primitive_fields()); - _layout->add(_root_group->small_primitive_fields()); + _layout->add(_root_group->non_oop_fields()); } else { - _layout->add(_root_group->big_primitive_fields()); - _layout->add(_root_group->small_primitive_fields()); + _layout->add(_root_group->non_oop_fields()); _layout->add(_root_group->oop_fields()); } @@ -1030,8 +1031,7 @@ void FieldLayoutBuilder::compute_regular_layout() { start = padding; } - _layout->add(cg->big_primitive_fields(), start); - _layout->add(cg->small_primitive_fields(), start); + _layout->add(cg->non_oop_fields(), start); _layout->add(cg->oop_fields(), start); need_tail_padding = true; } @@ -1041,10 +1041,9 @@ void FieldLayoutBuilder::compute_regular_layout() { insert_contended_padding(_layout->last_block()); } - // Warning: IntanceMirrorKlass expects static oops to be allocated first + // InstanceMirrorKlass expects static oop fields to be allocated first. _static_layout->add_contiguously(_static_fields->oop_fields()); - _static_layout->add(_static_fields->big_primitive_fields()); - _static_layout->add(_static_fields->small_primitive_fields()); + _static_layout->add(_static_fields->non_oop_fields()); epilogue(); } @@ -1056,10 +1055,10 @@ void FieldLayoutBuilder::compute_regular_layout() { // of value classes is to be embedded into other containers, it is critical // to keep their size as small as possible. For this reason, the allocation // strategy is: -// - big primitive fields (primitive types and flat value types larger -// than an oop) are allocated first (from the biggest to the smallest) +// - non-oop fields at least as large as a heap oop are allocated first +// (from the biggest to the smallest) // - then oop fields -// - then small primitive fields (from the biggest to the smallest) +// - then non-oop fields smaller than a heap oop (from the biggest to the smallest) // void FieldLayoutBuilder::compute_value_class_layout() { @@ -1149,9 +1148,21 @@ void FieldLayoutBuilder::compute_value_class_layout() { } } - _layout->add(_root_group->big_primitive_fields()); + // The non-oop fields are sorted in decreasing size order. Split the list around + // heapOopSize so oop fields can be placed between large and small fields. + GrowableArray* non_oop_fields = _root_group->non_oop_fields(); + int split_index = 0; + + if (non_oop_fields != nullptr) { + split_index = _root_group->first_small_field_index(); + _layout->add_range(non_oop_fields, 0, split_index); + } + _layout->add(_root_group->oop_fields()); - _layout->add(_root_group->small_primitive_fields()); + + if (non_oop_fields != nullptr) { + _layout->add_range(non_oop_fields, split_index, non_oop_fields->length()); + } LayoutRawBlock* first_field = _layout->first_field_block(); if (first_field != nullptr) { @@ -1309,10 +1320,9 @@ void FieldLayoutBuilder::compute_value_class_layout() { _payload_size_in_bytes = null_free_atomic_layout_size_in_bytes(); } } - // Warning:: InstanceMirrorKlass expects static oops to be allocated first + // InstanceMirrorKlass expects static oop fields to be allocated first. _static_layout->add_contiguously(_static_fields->oop_fields()); - _static_layout->add(_static_fields->big_primitive_fields()); - _static_layout->add(_static_fields->small_primitive_fields()); + _static_layout->add(_static_fields->non_oop_fields()); generate_acmp_maps(); epilogue(); @@ -1352,7 +1362,8 @@ void FieldLayoutBuilder::register_embedded_oops(OopMapBlocksBuilder* nonstatic_o nonstatic_oop_maps->add(b->offset(), 1); } } - register_embedded_oops_from_list(nonstatic_oop_maps, group->big_primitive_fields()); + // A non-oop block can be a flat field that contains embedded oops. + register_embedded_oops_from_list(nonstatic_oop_maps, group->non_oop_fields()); } static int insert_segment(GrowableArray* map, int offset, int size, int last_idx) { diff --git a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp index e1594255515d..3f4c868da8be 100644 --- a/src/hotspot/share/classfile/fieldLayoutBuilder.hpp +++ b/src/hotspot/share/classfile/fieldLayoutBuilder.hpp @@ -132,16 +132,16 @@ class LayoutRawBlock : public ResourceObj { // A Field group represents a set of fields that have to be allocated together, // this is the way the @Contended annotation is supported. -// Inside a FieldGroup, fields are sorted based on their kind: primitive, -// oop, or flat. +// Fields are separated into oop fields and non-oop field blocks. +// Non-oop blocks include primitive fields and flat fields. +// Flat fields can themselves contain embedded oops. // class FieldGroup : public ResourceObj { private: FieldGroup* _next; - GrowableArray* _small_primitive_fields; - GrowableArray* _big_primitive_fields; + GrowableArray* _non_oop_fields; GrowableArray* _oop_fields; int _contended_group; static const int INITIAL_LIST_SIZE = 16; @@ -151,10 +151,11 @@ class FieldGroup : public ResourceObj { FieldGroup* next() const { return _next; } void set_next(FieldGroup* next) { _next = next; } - GrowableArray* small_primitive_fields() const { return _small_primitive_fields; } - GrowableArray* big_primitive_fields() const { return _big_primitive_fields; } + GrowableArray* non_oop_fields() const { return _non_oop_fields; } GrowableArray* oop_fields() const { return _oop_fields; } int contended_group() const { return _contended_group; } + // Requires the non-oop fields to be sorted in decreasing size order. + int first_small_field_index() const; void add_primitive_field(int idx, BasicType type); void add_oop_field(int idx); @@ -162,8 +163,7 @@ class FieldGroup : public ResourceObj { void add_block(LayoutRawBlock** list, LayoutRawBlock* block); void sort_by_size(); private: - void add_to_small_primitive_list(LayoutRawBlock* block); - void add_to_big_primitive_list(LayoutRawBlock* block); + void add_to_non_oop_list(LayoutRawBlock* block); }; // The FieldLayout class represents a set of fields organized @@ -232,6 +232,8 @@ class FieldLayout : public ResourceObj { LayoutRawBlock* first_field_block(); void add(GrowableArray* list, LayoutRawBlock* start = nullptr); + // Add the fields in the half-open range [from, to). + void add_range(GrowableArray* list, int from, int to, LayoutRawBlock* start = nullptr); void add_field_at_offset(LayoutRawBlock* blocks, int offset, LayoutRawBlock* start = nullptr); void add_contiguously(GrowableArray* list, LayoutRawBlock* start = nullptr); LayoutRawBlock* insert_field_block(LayoutRawBlock* slot, LayoutRawBlock* block); diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index c399d9e0a9b8..6f73e4a794aa 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -22,10 +22,10 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/javaClassesImpl.hpp" @@ -963,7 +963,7 @@ void java_lang_Class::fixup_mirror(Klass* k, TRAPS) { } if (k->in_aot_cache() && k->has_archived_mirror_index()) { - if (HeapShared::is_archived_heap_in_use()) { + if (AOTHeap::is_archived_heap_in_use()) { bool present = restore_archived_mirror(k, Handle(), Handle(), Handle(), CHECK); assert(present, "Missing archived mirror for %s", k->external_name()); return; @@ -1092,7 +1092,7 @@ void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protecti if (k->is_typeArray_klass()) { BasicType type = TypeArrayKlass::cast(k)->element_type(); if (is_scratch) { - comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(type)); + comp_mirror = Handle(THREAD, AOTHeap::scratch_java_mirror(type)); } else { comp_mirror = Handle(THREAD, Universe::java_mirror(type)); } @@ -1101,7 +1101,7 @@ void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protecti Klass* element_klass = ObjArrayKlass::cast(k)->element_klass(); assert(element_klass != nullptr, "Must have an element klass"); if (is_scratch) { - comp_mirror = Handle(THREAD, HeapShared::scratch_java_mirror(element_klass)); + comp_mirror = Handle(THREAD, AOTHeap::scratch_java_mirror(element_klass)); } else { comp_mirror = Handle(THREAD, element_klass->java_mirror()); } @@ -1208,7 +1208,7 @@ void java_lang_Class::create_scratch_mirror(Klass* k, TRAPS) { release_set_array_klass(comp_mirror(), k); } - HeapShared::set_scratch_java_mirror(k, mirror()); + AOTHeap::set_scratch_java_mirror(k, mirror()); } // Returns true if the mirror is updated, false if no archived mirror @@ -1499,7 +1499,7 @@ BasicType java_lang_Class::primitive_type(oop java_class) { #ifdef ASSERT if (CDSConfig::is_dumping_heap()) { oop mirror = Universe::java_mirror(type); - oop scratch_mirror = HeapShared::scratch_java_mirror(type); + oop scratch_mirror = AOTHeap::scratch_java_mirror(type); assert(java_class == mirror || java_class == scratch_mirror, "must be consistent"); } else { assert(Universe::java_mirror(type) == java_class, "must be consistent"); diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index 5b6b923bc7ad..56862016db49 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -234,7 +234,7 @@ class java_lang_String : AllStatic { class java_lang_Class : AllStatic { friend class VMStructs; - friend class HeapShared; + friend class AOTHeap; private: diff --git a/src/hotspot/share/classfile/loaderConstraints.cpp b/src/hotspot/share/classfile/loaderConstraints.cpp index 13c1ff7319dd..bebe7c53e551 100644 --- a/src/hotspot/share/classfile/loaderConstraints.cpp +++ b/src/hotspot/share/classfile/loaderConstraints.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -442,22 +442,16 @@ InstanceKlass* LoaderConstraintTable::find_constrained_klass(Symbol* name, return nullptr; } -// Removes a class that was added to the table then class loading subsequently failed for this class, -// so we don't have a dangling pointer to InstanceKlass in the LoaderConstraintTable. -void LoaderConstraintTable::remove_failed_loaded_klass(InstanceKlass* klass, - ClassLoaderData* loader) { +// Checks that a class that failed to load wasn't added to the table. +void LoaderConstraintTable::check_failed_loaded_klass(InstanceKlass* klass, + ClassLoaderData* loader) { +#ifdef ASSERT MutexLocker ml(SystemDictionary_lock); Symbol* name = klass->name(); - LoaderConstraint *p = find_loader_constraint(name, loader); - if (p != nullptr && p->klass() != nullptr && p->klass() == klass) { - // If this is the klass in the constraint, the error was OOM from the ClassLoader.addClass() call. - // Other errors during loading (eg. constraint violations) will not have added this klass. - log_info(class, loader, constraints)("removing klass %s: failed to load", name->as_C_string()); - // We only null out the class, since the constraint for the class name for this loader is still valid as - // it was added when checking signature loaders for a method or field resolution. - p->set_klass(nullptr); - } + LoaderConstraint* p = find_loader_constraint(name, loader); + assert (p == nullptr || p->klass() != klass, "pointer to class that failed to load"); +#endif } void LoaderConstraintTable::merge_loader_constraints(Symbol* class_name, diff --git a/src/hotspot/share/classfile/loaderConstraints.hpp b/src/hotspot/share/classfile/loaderConstraints.hpp index 8153d9c28fd7..0e40781f7764 100644 --- a/src/hotspot/share/classfile/loaderConstraints.hpp +++ b/src/hotspot/share/classfile/loaderConstraints.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,7 +59,7 @@ class LoaderConstraintTable : public AllStatic { // Class loader constraints static bool check_or_update(InstanceKlass* k, ClassLoaderData* loader, Symbol* name); - static void remove_failed_loaded_klass(InstanceKlass* k, ClassLoaderData* loader); + static void check_failed_loaded_klass(InstanceKlass* k, ClassLoaderData* loader); static void purge_loader_constraints(); static void print_table_statistics(outputStream* st); diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index e36348c59321..8a644f027aee 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -23,11 +23,11 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotModules.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/javaClasses.inline.hpp" @@ -413,7 +413,7 @@ bool ModuleEntry::should_be_archived() const { } void ModuleEntry::remove_unshareable_info() { - _archived_module_index = HeapShared::append_root(module_oop()); + _archived_module_index = AOTHeap::append_root(module_oop()); if (_reads != nullptr) { _reads->set_in_aot_cache(); @@ -449,12 +449,12 @@ void ModuleEntry::load_from_archive(ClassLoaderData* loader_data) { } void ModuleEntry::preload_archived_oops() { - (void)HeapShared::get_root(_archived_module_index, false /* clear */); + (void)AOTHeap::get_root(_archived_module_index, false /* clear */); } void ModuleEntry::restore_archived_oops(ClassLoaderData* loader_data) { assert(CDSConfig::is_using_full_module_graph(), "runtime only"); - Handle module_handle(Thread::current(), HeapShared::get_root(_archived_module_index, /*clear=*/true)); + Handle module_handle(Thread::current(), AOTHeap::get_root(_archived_module_index, /*clear=*/true)); assert(module_handle.not_null(), "huh"); set_module_handle(loader_data->add_handle(module_handle)); @@ -475,7 +475,7 @@ void ModuleEntry::restore_archived_oops(ClassLoaderData* loader_data) { void ModuleEntry::clear_archived_oops() { assert(CDSConfig::is_using_archive() && !CDSConfig::is_using_full_module_graph(), "runtime only"); - HeapShared::clear_root(_archived_module_index); + AOTHeap::clear_root(_archived_module_index); } static int compare_module_by_name(ModuleEntry* a, ModuleEntry* b) { diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index a1e55229bb50..91c2384ee7fe 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -22,10 +22,10 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/altHashing.hpp" #include "classfile/compactHashtable.hpp" #include "classfile/javaClasses.inline.hpp" @@ -76,7 +76,7 @@ const double CLEAN_DEAD_HIGH_WATER_MARK = 0.5; #if INCLUDE_CDS_JAVA_HEAP inline oop StringTable::read_string_from_compact_hashtable(address base_address, u4 index) { assert(AOTMappedHeapLoader::is_in_use(), "sanity"); - oop s = HeapShared::get_root((int)index, false); + oop s = AOTHeap::get_root((int)index, false); assert(java_lang_String::is_instance(s), "must be"); return s; } @@ -909,7 +909,7 @@ void StringtableDCmd::execute(DCmdSource source, TRAPS) { // Sharing #if INCLUDE_CDS_JAVA_HEAP size_t StringTable::shared_entry_count() { - assert(HeapShared::is_loading_mapping_mode(), "should not reach here"); + assert(AOTHeap::is_loading_mapping_mode(), "should not reach here"); return _shared_table.entry_count(); } @@ -935,18 +935,18 @@ oop StringTable::lookup_shared(const jchar* name, int len) { void StringTable::init_shared_table() { assert(SafepointSynchronize::is_at_safepoint(), "inside AOT safepoint"); precond(CDSConfig::is_dumping_heap()); - assert(HeapShared::is_writing_mapping_mode(), "not used for streamed oops"); + assert(AOTHeap::is_writing_mapping_mode(), "not used for streamed oops"); int n = 0; auto copy_into_aot_heap = [&] (WeakHandle* val) { oop string = val->peek(); - if (string != nullptr && !HeapShared::is_string_too_large_to_archive(string)) { + if (string != nullptr && !AOTHeap::is_string_too_large_to_archive(string)) { // If string is too large, don't put it into the string table. // - If there are no other references to it, it won't be stored into the archive, // so we are all good. - // - If there's a reference to it, we will report an error inside HeapShared.cpp and + // - If there's a reference to it, we will report an error inside AOTHeap.cpp and // dumping will fail. - HeapShared::archive_interned_string(string); + AOTHeap::archive_interned_string(string); } n++; return true; @@ -959,16 +959,16 @@ void StringTable::init_shared_table() { void StringTable::write_shared_table() { assert(SafepointSynchronize::is_at_safepoint(), "inside AOT safepoint"); precond(CDSConfig::is_dumping_heap()); - assert(HeapShared::is_writing_mapping_mode(), "not used for streamed oops"); + assert(AOTHeap::is_writing_mapping_mode(), "not used for streamed oops"); _shared_table_for_dumping.reset(); CompactHashtableWriter writer((int)items_count_acquire(), ArchiveBuilder::string_stats()); auto copy_into_shared_table = [&] (WeakHandle* val) { oop string = val->peek(); - if (string != nullptr && !HeapShared::is_string_too_large_to_archive(string)) { + if (string != nullptr && !AOTHeap::is_string_too_large_to_archive(string)) { unsigned int hash = java_lang_String::hash_code(string); - int root_id = HeapShared::append_root(string); + int root_id = AOTHeap::append_root(string); writer.add(hash, root_id); } return true; diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index d5fc89f9cd0a..65ff8ec57bf1 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -23,8 +23,8 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotHeap.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "classfile/classFileParser.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" @@ -1005,7 +1005,7 @@ bool SystemDictionary::is_shared_class_visible_impl(Symbol* class_name, // At this point, no modules have been defined yet. KlassSubGraphInfo::check_allowed_klass() // has restricted the classes can be loaded at this step to be only: // [1] cs->is_modules_image(): classes in java.base, or, - // [2] HeapShared::is_a_test_class_in_unnamed_module(ik): classes in bootstrap/unnamed module + // [2] AOTHeap::is_a_test_class_in_unnamed_module(ik): classes in bootstrap/unnamed module assert(cl->is_modules_image(), "only these classes can be loaded before the module system is initialized"); assert(class_loader.is_null(), "sanity"); @@ -1564,19 +1564,6 @@ void SystemDictionary::define_instance_class(InstanceKlass* k, Handle class_load // which will require a token to perform the define class check_constraints(k, loader_data, true, CHECK); - // Register class just loaded with class loader (placed in ArrayList) - // Note we do this before updating the dictionary, as this can - // fail with an OutOfMemoryError (if it does, we will *not* put this - // class in the dictionary and will not update the class hierarchy). - // JVMTI FollowReferences needs to find the classes this way. - if (k->class_loader() != nullptr) { - methodHandle m(THREAD, Universe::loader_addClass_method()); - JavaValue result(T_VOID); - JavaCallArguments args(class_loader); - args.push_oop(Handle(THREAD, k->java_mirror())); - JavaCalls::call(&result, m, &args, CHECK); - } - JFR_ONLY(Jfr::on_definition(k, THREAD);) // Add to class hierarchy, and do possible deoptimizations. @@ -1688,8 +1675,8 @@ InstanceKlass* SystemDictionary::find_or_define_instance_class(Symbol* class_nam assert(defined_k != nullptr, "Should have a klass if there's no exception"); k->class_loader_data()->add_to_deallocate_list(k); } else if (HAS_PENDING_EXCEPTION) { - // Remove this InstanceKlass from the LoaderConstraintTable if added. - LoaderConstraintTable::remove_failed_loaded_klass(k, class_loader_data(class_loader)); + // Validate that this InstanceKlass is not present in the LoaderConstraintTable. + LoaderConstraintTable::check_failed_loaded_klass(k, class_loader_data(class_loader)); assert(defined_k == nullptr, "Should not have a klass if there's an exception"); k->class_loader_data()->add_to_deallocate_list(k); } @@ -1773,7 +1760,7 @@ void SystemDictionary::initialize(TRAPS) { vmClasses::resolve_all(CHECK); // Resolve classes used by archived heap objects if (CDSConfig::is_using_archive()) { - HeapShared::resolve_classes(THREAD); + AOTHeap::resolve_classes(THREAD); } } diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index c7e16fce69d7..0d6c345762ce 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -26,6 +26,7 @@ #include "cds/aotClassFilter.hpp" #include "cds/aotClassLocation.hpp" #include "cds/aotCompressedPointers.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" @@ -37,7 +38,6 @@ #include "cds/dumpTimeClassInfo.inline.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.inline.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "cds/runTimeClassInfo.hpp" @@ -752,7 +752,7 @@ void SystemDictionaryShared::initialize() { _dumptime_table = new (mtClass) DumpTimeSharedClassTable; LambdaProxyClassDictionary::dumptime_init(); if (CDSConfig::is_dumping_heap()) { - HeapShared::init_dumping(); + AOTHeap::init_dumping(); } } } diff --git a/src/hotspot/share/classfile/vmClasses.cpp b/src/hotspot/share/classfile/vmClasses.cpp index 21a910486c88..396d7e5b61e4 100644 --- a/src/hotspot/share/classfile/vmClasses.cpp +++ b/src/hotspot/share/classfile/vmClasses.cpp @@ -22,11 +22,11 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotMappedHeapLoader.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/dictionary.hpp" @@ -139,7 +139,7 @@ void vmClasses::resolve_all(TRAPS) { #if INCLUDE_CDS if (CDSConfig::is_using_archive()) { #if INCLUDE_CDS_JAVA_HEAP - if (HeapShared::is_loading() && HeapShared::is_loading_mapping_mode()) { + if (AOTHeap::is_loading() && AOTHeap::is_loading_mapping_mode()) { // It's unsafe to access the archived heap regions before they // are fixed up, so we must do the fixup as early as possible // before the archived java objects are accessed by functions @@ -155,8 +155,8 @@ void vmClasses::resolve_all(TRAPS) { // Mirrors are restored after java.lang.Class is loaded. AOTMappedHeapLoader::fixup_region(); } - if (HeapShared::is_archived_heap_in_use() && !CDSConfig::is_using_full_module_graph()) { - // Need to remove all the archived java.lang.Module objects from HeapShared::roots(). + if (AOTHeap::is_archived_heap_in_use() && !CDSConfig::is_using_full_module_graph()) { + // Need to remove all the archived java.lang.Module objects from AOTHeap::roots(). AOTModules::clear_archived_oops(); } #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 4d1bb8c83095..bec24305db2a 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -29,7 +29,6 @@ #include "cds/aotMetaspace.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "ci/ciEnv.hpp" #include "ci/ciMethod.hpp" #include "ci/ciUtilities.inline.hpp" diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index 47ad8e650100..38e0df7fa468 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -1582,9 +1582,16 @@ void CompilationPolicy::method_back_branch_event(const methodHandle& mh, const m if (is_compilation_enabled()) { CompLevel next_osr_level = loop_event(imh, level, THREAD); CompLevel max_osr_level = (CompLevel)imh->highest_osr_comp_level(); - // At the very least compile the OSR version + + // Try to compile the OSR version, so that it can be switched at this back branch. + // If OSR compilation is not possible, compile normal version instead, and hope + // the back branch bearing method would be re-entered soon. if (!CompileBroker::compilation_is_in_queue(imh) && (next_osr_level != level)) { - compile(imh, bci, next_osr_level, CHECK); + if (can_be_osr_compiled(imh, CompLevel_any)) { + compile(imh, bci, next_osr_level, CHECK); + } else if (comp_level(imh()) < next_osr_level) { + compile(imh, InvocationEntryBci, next_osr_level, CHECK); + } } // Use loop event as an opportunity to also check if there's been diff --git a/src/hotspot/share/compiler/compilerDirectives.hpp b/src/hotspot/share/compiler/compilerDirectives.hpp index ea709c6897ce..5137b31ce76f 100644 --- a/src/hotspot/share/compiler/compilerDirectives.hpp +++ b/src/hotspot/share/compiler/compilerDirectives.hpp @@ -90,7 +90,9 @@ NOT_PRODUCT(cflags(PrintIdeal, bool, PrintIdeal, PrintIdeal)) \ NOT_PRODUCT(cflags(PhasePrintLevel, intx, PrintPhaseLevel, PhasePrintLevel)) \ NOT_PRODUCT(cflags(IGVPrintLevel, intx, PrintIdealGraphLevel, IGVPrintLevel)) \ cflags(IncrementalInlineForceCleanup, bool, IncrementalInlineForceCleanup, IncrementalInlineForceCleanup) \ - cflags(MaxNodeLimit, intx, MaxNodeLimit, MaxNodeLimit) + cflags(MaxNodeLimit, intx, MaxNodeLimit, MaxNodeLimit) \ +NOT_PRODUCT(cflags(PrintIdealNodeCount, bool, PrintIdealNodeCount, PrintIdealNodeCount)) \ + #define compilerdirectives_c2_string_flags(cflags) \ NOT_PRODUCT(cflags(TraceAutoVectorization, ccstrlist, "", TraceAutoVectorization)) \ NOT_PRODUCT(cflags(TraceMergeStores, ccstrlist, "", TraceMergeStores)) \ diff --git a/src/hotspot/share/compiler/compilerOracle.hpp b/src/hotspot/share/compiler/compilerOracle.hpp index 35cceea32cd3..b5b571e58eaa 100644 --- a/src/hotspot/share/compiler/compilerOracle.hpp +++ b/src/hotspot/share/compiler/compilerOracle.hpp @@ -95,6 +95,7 @@ NOT_PRODUCT(option(TraceMergeStores, "TraceMergeStores", Ccstrlist)) \ option(CloneMapDebug, "CloneMapDebug", Bool) \ option(IncrementalInlineForceCleanup, "IncrementalInlineForceCleanup", Bool) \ option(MaxNodeLimit, "MaxNodeLimit", Intx) \ +NOT_PRODUCT(option(PrintIdealNodeCount, "PrintIdealNodeCount", Bool)) \ option(DontAOTCompile, "DontAOTCompile", Bool) \ option(DontPreload, "DontPreload", Bool) \ option(IgnoreAOTCompiled, "IgnoreAOTCompiled", Bool) \ diff --git a/src/hotspot/share/gc/g1/g1CardSet.cpp b/src/hotspot/share/gc/g1/g1CardSet.cpp index 8f84bf59387e..5da3c8096757 100644 --- a/src/hotspot/share/gc/g1/g1CardSet.cpp +++ b/src/hotspot/share/gc/g1/g1CardSet.cpp @@ -686,7 +686,10 @@ void G1CardSet::transfer_cards(G1CardSetHashTableValue* table_entry, ContainerPt assert(container_type(source_container) == ContainerHowl, "must be"); // Need to correct for that the Full remembered set occupies more cards than the // AoCS before. - _num_occupied.add_then_fetch(_config->max_cards_in_region() - table_entry->_num_occupied.load_relaxed(), memory_order_relaxed); + // Use int64_t to represent the difference because due to races this difference can be negative. + const int64_t diff = (int64_t)_config->max_cards_in_region() - table_entry->_num_occupied.load_relaxed(); + + _num_occupied.add_then_fetch((size_t)diff, memory_order_relaxed); } } @@ -702,7 +705,8 @@ void G1CardSet::transfer_cards_in_howl(ContainerPtr parent_container, G1TransferCard iter(this, card_region); iterate_cards_during_transfer(source_container, iter); } else { - uint diff = _config->max_cards_in_howl_bitmap() - container_ptr(source_container)->num_bits_set(); + // Use int64_t to represent the difference because due to races this difference can be negative. + int64_t diff = (int64_t)_config->max_cards_in_howl_bitmap() - container_ptr(source_container)->num_bits_set(); // Need to correct for that the Full remembered set occupies more cards than the // bitmap before. @@ -712,14 +716,14 @@ void G1CardSet::transfer_cards_in_howl(ContainerPtr parent_container, diff -= 1; G1CardSetHowl* howling_array = container_ptr(parent_container); - howling_array->_num_entries.add_then_fetch(diff, memory_order_relaxed); + howling_array->_num_entries.add_then_fetch((uint)diff, memory_order_relaxed); G1CardSetHashTableValue* table_entry = get_container(card_region); assert(table_entry != nullptr, "Table entry not found for transferred cards"); - table_entry->_num_occupied.add_then_fetch(diff, memory_order_relaxed); + table_entry->_num_occupied.add_then_fetch((uint)diff, memory_order_relaxed); - _num_occupied.add_then_fetch(diff, memory_order_relaxed); + _num_occupied.add_then_fetch((size_t)diff, memory_order_relaxed); } } diff --git a/src/hotspot/share/gc/g1/g1CardSetContainers.hpp b/src/hotspot/share/gc/g1/g1CardSetContainers.hpp index 880c02a71ed9..2a5d332f55ce 100644 --- a/src/hotspot/share/gc/g1/g1CardSetContainers.hpp +++ b/src/hotspot/share/gc/g1/g1CardSetContainers.hpp @@ -252,6 +252,8 @@ class G1CardSetBitMap : public G1CardSetContainer { }; class G1CardSetHowl : public G1CardSetContainer { + DEBUG_ONLY(uint _num_buckets;) + public: typedef uint EntryCountType; using ContainerPtr = G1CardSet::ContainerPtr; diff --git a/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp b/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp index c9e7a046ea7a..d45ee59bccc0 100644 --- a/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp @@ -276,7 +276,7 @@ inline size_t G1CardSetBitMap::header_size_in_bytes() { } inline Atomic const* G1CardSetHowl::container_addr(EntryCountType index) const { - assert(index < _num_entries.load_relaxed(), "precondition"); + precond(index < _num_buckets); return buckets() + index; } @@ -295,6 +295,7 @@ inline Atomic const* G1CardSetHowl::buckets() const inline G1CardSetHowl::G1CardSetHowl(EntryCountType card_in_region, G1CardSetConfiguration* config) : G1CardSetContainer(), + DEBUG_ONLY(_num_buckets(config->num_buckets_in_howl()) COMMA) _num_entries((config->max_cards_in_array() + 1)) /* Card Transfer will not increment _num_entries */ { EntryCountType num_buckets = config->num_buckets_in_howl(); EntryCountType bucket = config->howl_bucket_index(card_in_region); diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 448a40c1d2ec..20d8bb8ad9a2 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1311,7 +1311,7 @@ class G1CollectedHeap : public CollectedHeap { // WhiteBox testing support. bool supports_concurrent_gc_breakpoints() const override; - WorkerThreads* safepoint_workers() override { return _workers; } + bool supports_parallel_object_iteration() const override { return true; } // The methods below are here for convenience and dispatch the // appropriate method depending on value of the given VerifyOption diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 9961c49802ab..4b27dce4dd3c 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -1960,7 +1960,7 @@ class G1VerifyNoCollectionSetOops { void operator()(G1TaskQueueEntry task_entry) const { oop obj = task_entry.is_partial_array_state() - ? task_entry.to_partial_array_state()->source() + ? task_entry.to_partial_array_state()->array() : task_entry.to_oop(); guarantee(oopDesc::is_oop(obj), "Non-oop " PTR_FORMAT ", location: %s, index: %d", @@ -2450,26 +2450,25 @@ size_t G1CMTask::start_partial_array_processing(objArrayOop obj) { process_klass(obj->klass()); size_t array_length = obj->length(); - size_t initial_chunk_size = _partial_array_splitter.start(_task_queue, obj, nullptr, array_length, ObjArrayMarkingStride); + PartialArraySplitter::Claim chunk = _partial_array_splitter.start(_task_queue, obj, array_length, ObjArrayMarkingStride); - process_array_chunk(obj, 0, initial_chunk_size); + process_array_chunk(chunk._array, chunk._start, chunk._end); + const size_t chunk_len = chunk._end - chunk._start; // Include object header size if (obj->is_refArray()) { - return refArrayOopDesc::object_size(checked_cast(initial_chunk_size)); + return refArrayOopDesc::object_size(checked_cast(chunk_len)); } else { FlatArrayKlass* fak = FlatArrayKlass::cast(obj->klass()); - return flatArrayOopDesc::object_size(fak->layout_helper(), checked_cast(initial_chunk_size)); + return flatArrayOopDesc::object_size(fak->layout_helper(), checked_cast(chunk_len)); } } size_t G1CMTask::process_partial_array(const G1TaskQueueEntry& task, bool stolen) { PartialArrayState* state = task.to_partial_array_state(); - // Access state before release by claim(). - objArrayOop obj = oop_cast(state->source()); - PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, _task_queue, stolen); + objArrayOop obj = claim._array; process_array_chunk(obj, claim._start, claim._end); diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp index 6e0ef06da7dd..aeab92625a41 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp @@ -53,11 +53,9 @@ G1FullGCMarker::~G1FullGCMarker() { } void G1FullGCMarker::process_partial_array(PartialArrayState* state, bool stolen) { - // Access state before release by claim(). - objArrayOop obj_array = objArrayOop(state->source()); PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, task_queue(), stolen); - process_array_chunk(obj_array, claim._start, claim._end); + process_array_chunk(claim._array, claim._start, claim._end); } static uintx calc_array_stride(uint array_len, uint num_threads) { @@ -79,9 +77,9 @@ void G1FullGCMarker::start_partial_array_processing(objArrayOop obj) { } const uintx stride = calc_array_stride(array_length, _collector->workers()); - const size_t initial_chunk_size = _partial_array_splitter.start(task_queue(), obj, nullptr, array_length, stride); + PartialArraySplitter::Claim chunk = _partial_array_splitter.start(task_queue(), obj, array_length, stride); - process_array_chunk(obj, 0, initial_chunk_size); + process_array_chunk(chunk._array, chunk._start, chunk._end); } void G1FullGCMarker::complete_marking(G1ScannerTasksQueueSet* task_queues, diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp index ff0cfc8b27a0..3762faa2ba78 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp @@ -91,7 +91,7 @@ inline void G1FullGCMarker::process_array_chunk(objArrayOop obj, size_t start, s inline void G1FullGCMarker::dispatch_task(const ScannerTask& task, bool stolen) { if (task.is_partial_array_state()) { - assert(_bitmap->is_marked(task.to_partial_array_state()->source()), "should be marked"); + assert(_bitmap->is_marked(task.to_partial_array_state()->array()), "should be marked"); process_partial_array(task.to_partial_array_state(), stolen); } else { oop obj = task.to_oop(); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp index 83f62fd7bab3..160c93cdbc21 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.cpp @@ -92,21 +92,10 @@ void G1HeapRegionManager::initialize(G1RegionToSpaceMapper* heap_storage, } G1HeapRegion* G1HeapRegionManager::allocate_free_region(G1HeapRegionType type, uint requested_node_index) { - G1HeapRegion* hr = nullptr; bool from_head = !type.is_young(); G1NUMA* numa = G1NUMA::numa(); - if (requested_node_index != G1NUMA::AnyNodeIndex && numa->is_enabled()) { - // Try to allocate with requested node index. - hr = _free_list.remove_region_with_node_index(from_head, requested_node_index); - } - - if (hr == nullptr) { - // If there's a single active node or we did not get a region from our requested node, - // try without requested node index. - hr = _free_list.remove_region(from_head); - } - + G1HeapRegion* hr = _free_list.remove_region(from_head, requested_node_index); if (hr != nullptr) { assert(hr->next() == nullptr, "Single region should not have next"); assert(is_available(hr->hrm_index()), "Must be committed"); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp index 8edfc99e3ef8..5764532e75d8 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionManager.hpp @@ -202,7 +202,8 @@ class G1HeapRegionManager: public CHeapObj { _free_list.add_ordered(list); } - // Allocate a free region with specific node index. If fails allocate with next node index. + // Allocate a free region on a specific node. If that fails, use the next available + // on another node. G1HeapRegion* allocate_free_region(G1HeapRegionType type, uint requested_node_index); // Allocate a humongous object from the free list diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp index f710b64d7f29..d39eed88b262 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.cpp @@ -131,14 +131,9 @@ void G1FreeRegionList::add_list_common_start(G1FreeRegionList* from_list) { } #ifdef ASSERT - G1FreeRegionListIterator iter(from_list); - while (iter.more_available()) { - G1HeapRegion* hr = iter.get_next(); - // In set_containing_set() we check that we either set the value - // from null to non-null or vice versa to catch bugs. So, we have - // to null it first before setting it to the value. - hr->set_containing_set(nullptr); - hr->set_containing_set(this); + for (G1HeapRegion* r = from_list->_head; r != nullptr; r = r->next()) { + r->set_containing_set(nullptr); + r->set_containing_set(this); } #endif // ASSERT } @@ -373,8 +368,8 @@ G1FreeRegionList::~G1FreeRegionList() { } } -G1FreeRegionList::NodeInfo::NodeInfo() : _numa(G1NUMA::numa()), _num_regions_on_node(nullptr), - _num_nodes(_numa->num_active_nodes()) { +G1FreeRegionList::NodeInfo::NodeInfo() : _num_regions_on_node(nullptr), + _num_nodes(G1NUMA::numa()->num_active_nodes()) { assert(UseNUMA, "Invariant"); _num_regions_on_node = NEW_C_HEAP_ARRAY(uint, _num_nodes, mtGC); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp index 6db3001b117f..41b1cf4bb82d 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.hpp @@ -62,7 +62,6 @@ class G1HeapRegionSetChecker : public CHeapObj { // Base class for all the classes that represent heap region sets. It contains the // basic attributes that each set needs to maintain (e.g., number of regions) plus // any shared functionality (e.g., verification). - class G1HeapRegionSetBase { friend class VMStructs; @@ -105,7 +104,6 @@ class G1HeapRegionSetBase { virtual void verify(); void verify_start(); - void verify_next_region(G1HeapRegion* hr); void verify_end(); void verify_optional() { DEBUG_ONLY(verify();) } @@ -117,7 +115,6 @@ class G1HeapRegionSetBase { // explicitly tracked. It's helpful to group regions using such sets // so that we can reason about all the region groups in the heap using // the same interface (namely, the G1HeapRegionSetBase API). - class G1HeapRegionSet : public G1HeapRegionSetBase { public: G1HeapRegionSet(const char* name, G1HeapRegionSetChecker* checker): @@ -133,18 +130,9 @@ class G1HeapRegionSet : public G1HeapRegionSetBase { // sorted list. We should try to avoid doing operations that iterate over // such lists in performance critical paths. Typically we should // add / remove one region at a time or concatenate two lists. - -class G1FreeRegionListIterator; -class G1NUMA; - class G1FreeRegionList : public G1HeapRegionSetBase { - friend class G1FreeRegionListIterator; - -private: - // This class is only initialized if there are multiple active nodes. class NodeInfo : public CHeapObj { - G1NUMA* _numa; uint* _num_regions_on_node; uint _num_nodes; @@ -173,8 +161,8 @@ class G1FreeRegionList : public G1HeapRegionSetBase { static uint _unrealistically_large_num_regions; - inline G1HeapRegion* remove_from_head_impl(); - inline G1HeapRegion* remove_from_tail_impl(); + inline void unlink_from_list(G1HeapRegion* cur); + inline void remove_from_list(G1HeapRegion* cur); inline void increase_num_regions(uint node_index); inline void decrease_num_regions(uint node_index); @@ -184,9 +172,10 @@ class G1FreeRegionList : public G1HeapRegionSetBase { void add_list_common_end(G1FreeRegionList* from_list); void verify_region_to_remove(G1HeapRegion* curr, G1HeapRegion* next) NOT_DEBUG_RETURN; -protected: - // See the comment for G1HeapRegionSetBase::clear() - virtual void clear(); + + void clear(); + + G1HeapRegion* find_region_to_remove(bool from_head, uint requested_node_index); public: G1FreeRegionList(const char* name, G1HeapRegionSetChecker* checker = nullptr); @@ -209,11 +198,11 @@ class G1FreeRegionList : public G1HeapRegionSetBase { // Same restrictions as above, but adds the region last in the list. inline void add_to_tail(G1HeapRegion* region_to_add); - // Removes from head or tail based on the given argument. - G1HeapRegion* remove_region(bool from_head); - - G1HeapRegion* remove_region_with_node_index(bool from_head, - uint requested_node_index); + // Finds and removes the next fitting region from the free list from head or tail. + // If NUMA support is enabled, initially tries to match the result to be on the + // requested node. This search is limited by G1NUMA's maximum search depth; if no + // matching region is found or not using NUMA, just takes the first. + G1HeapRegion* remove_region(bool from_head, uint requested_node_index); // Merge two ordered lists. The result is also ordered. The order is // determined by hrm_index. @@ -234,40 +223,7 @@ class G1FreeRegionList : public G1HeapRegionSetBase { virtual void verify(); - using G1HeapRegionSetBase::num_regions; uint num_regions_on_node(uint node_index) const; }; -// Iterator class that provides a convenient way to iterate over the -// regions of a FreeRegionList. - -class G1FreeRegionListIterator : public StackObj { -private: - G1FreeRegionList* _list; - G1HeapRegion* _curr; - -public: - bool more_available() { - return _curr != nullptr; - } - - G1HeapRegion* get_next() { - assert(more_available(), - "get_next() should be called when more regions are available"); - - // If we are going to introduce a count in the iterator we should - // do the "cycle" check. - - G1HeapRegion* hr = _curr; - _list->verify_region(hr); - _curr = hr->next(); - return hr; - } - - G1FreeRegionListIterator(G1FreeRegionList* list) - : _list(list), - _curr(list->_head) { - } -}; - #endif // SHARE_GC_G1_G1HEAPREGIONSET_HPP diff --git a/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp b/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp index c18ac68e0648..a6074145aa81 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionSet.inline.hpp @@ -120,116 +120,81 @@ inline void G1FreeRegionList::add_ordered(G1HeapRegion* hr) { increase_num_regions(hr->node_index()); } -inline G1HeapRegion* G1FreeRegionList::remove_from_head_impl() { - G1HeapRegion* result = _head; - _head = result->next(); - if (_head == nullptr) { - _tail = nullptr; +inline void G1FreeRegionList::unlink_from_list(G1HeapRegion* r) { + precond(r != nullptr); + // Splice the region out of the list. + G1HeapRegion* prev = r->prev(); + G1HeapRegion* next = r->next(); + if (prev == nullptr) { + _head = next; } else { - _head->set_prev(nullptr); + prev->set_next(next); } - result->set_next(nullptr); - return result; -} - -inline G1HeapRegion* G1FreeRegionList::remove_from_tail_impl() { - G1HeapRegion* result = _tail; - - _tail = result->prev(); - if (_tail == nullptr) { - _head = nullptr; + if (next == nullptr) { + _tail = prev; } else { - _tail->set_next(nullptr); + next->set_prev(prev); } - result->set_prev(nullptr); - return result; + r->set_prev(nullptr); + r->set_next(nullptr); } -inline G1HeapRegion* G1FreeRegionList::remove_region(bool from_head) { - check_mt_safety(); - verify_optional(); +inline void G1FreeRegionList::remove_from_list(G1HeapRegion* r) { + precond(r != nullptr); - if (is_empty()) { - return nullptr; - } - assert_free_region_list(num_regions() > 0 && _head != nullptr && _tail != nullptr, "invariant"); - - G1HeapRegion* hr; - - if (from_head) { - hr = remove_from_head_impl(); - } else { - hr = remove_from_tail_impl(); - } - - if (_last == hr) { + unlink_from_list(r); + if (_last == r) { _last = nullptr; } - // remove() will verify the region and check mt safety. - remove(hr); - - decrease_num_regions(hr->node_index()); - - return hr; + remove(r); + decrease_num_regions(r->node_index()); } -inline G1HeapRegion* G1FreeRegionList::remove_region_with_node_index(bool from_head, - uint requested_node_index) { - assert(UseNUMA, "Invariant"); - - const uint max_search_depth = G1NUMA::numa()->max_search_depth(); - G1HeapRegion* cur; - - // Find the region to use, searching from _head or _tail as requested. - size_t cur_depth = 0; - if (from_head) { - for (cur = _head; - cur != nullptr && cur_depth < max_search_depth; - cur = cur->next(), ++cur_depth) { - if (requested_node_index == cur->node_index()) { - break; +inline G1HeapRegion* G1FreeRegionList::find_region_to_remove(bool from_head, uint requested_node_index) { + G1HeapRegion* cur = nullptr; + + if (G1NUMA::numa()->is_enabled() && requested_node_index != G1NUMA::AnyNodeIndex) { + const uint max_search_depth = G1NUMA::numa()->max_search_depth(); + // Find the region to use, searching from _head or _tail as requested. + uint cur_depth = 0; + if (from_head) { + for (cur = _head; + cur != nullptr && cur_depth < max_search_depth; + cur = cur->next(), ++cur_depth) { + if (requested_node_index == cur->node_index()) { + return cur; + } } - } - } else { - for (cur = _tail; - cur != nullptr && cur_depth < max_search_depth; - cur = cur->prev(), ++cur_depth) { - if (requested_node_index == cur->node_index()) { - break; + } else { + for (cur = _tail; + cur != nullptr && cur_depth < max_search_depth; + cur = cur->prev(), ++cur_depth) { + if (requested_node_index == cur->node_index()) { + return cur; + } } } } + // Either we could not find a region on the correct NUMA node within the search depth, or + // we do not care about the node placement. In either case take the next region from the + // requested end of the list. + cur = from_head ? _head : _tail; + return cur; +} - // Didn't find a region to use. - if (cur == nullptr || cur_depth >= max_search_depth) { - return nullptr; - } - - // Splice the region out of the list. - G1HeapRegion* prev = cur->prev(); - G1HeapRegion* next = cur->next(); - if (prev == nullptr) { - _head = next; - } else { - prev->set_next(next); - } - if (next == nullptr) { - _tail = prev; - } else { - next->set_prev(prev); - } - cur->set_prev(nullptr); - cur->set_next(nullptr); +inline G1HeapRegion* G1FreeRegionList::remove_region(bool from_head, uint requested_node_index) { + check_mt_safety(); + verify_optional(); - if (_last == cur) { - _last = nullptr; + if (is_empty()) { + return nullptr; } + assert_free_region_list(num_regions() > 0 && _head != nullptr && _tail != nullptr, "invariant"); - remove(cur); - decrease_num_regions(cur->node_index()); - - return cur; + G1HeapRegion* r = find_region_to_remove(from_head, requested_node_index); + remove_from_list(r); + return r; } inline void G1FreeRegionList::NodeInfo::increase_num_regions(uint node_index) { diff --git a/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp b/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp index 72a17f563a09..01f8f67c3d66 100644 --- a/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp +++ b/src/hotspot/share/gc/g1/g1MonitoringSupport.cpp @@ -367,6 +367,10 @@ G1MonitoringScope::~G1MonitoringScope() { MemoryService::track_memory_usage(); } +void G1MonitoringScope::set_all_memory_pools_affected() { + _tms.set_all_memory_pools_affected(); +} + G1YoungGCMonitoringScope::G1YoungGCMonitoringScope(G1MonitoringSupport* monitoring_support, bool all_memory_pools_affected) : G1MonitoringScope(monitoring_support, @@ -376,6 +380,14 @@ G1YoungGCMonitoringScope::G1YoungGCMonitoringScope(G1MonitoringSupport* monitori all_memory_pools_affected) { } +G1YoungGCMonitoringScope::~G1YoungGCMonitoringScope() { + G1GCPhaseTimes* phase_times = G1CollectedHeap::heap()->phase_times(); + if (phase_times->sum_thread_work_items(G1GCPhaseTimes::EagerlyReclaimHumongousObjects, + G1GCPhaseTimes::EagerlyReclaimNumReclaimed) != 0) { + set_all_memory_pools_affected(); + } +} + G1FullGCMonitoringScope::G1FullGCMonitoringScope(G1MonitoringSupport* monitoring_support) : G1MonitoringScope(monitoring_support, monitoring_support->_full_collection_counters, diff --git a/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp b/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp index 8a7040e626d8..7cdbad9054fd 100644 --- a/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp +++ b/src/hotspot/share/gc/g1/g1MonitoringSupport.hpp @@ -232,11 +232,13 @@ class G1MonitoringScope : public StackObj { const char* end_message, bool all_memory_pools_affected = true); ~G1MonitoringScope(); + void set_all_memory_pools_affected(); }; class G1YoungGCMonitoringScope : public G1MonitoringScope { public: G1YoungGCMonitoringScope(G1MonitoringSupport* monitoring_support, bool all_memory_pools_affected); + ~G1YoungGCMonitoringScope(); }; class G1FullGCMonitoringScope : public G1MonitoringScope { diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index f120ac320357..f644625cbb42 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -175,9 +175,7 @@ void G1ParScanThreadState::verify_task(oop* task) const { void G1ParScanThreadState::verify_task(PartialArrayState* task) const { assert(task != nullptr, "invariant"); - // Source isn't used for processing, so not recorded in task. - assert(task->source() == nullptr, "invariant"); - oop p = task->destination(); + oop p = task->array(); assert(_g1h->is_in_reserved(p), "task=" PTR_FORMAT " dest=" PTR_FORMAT, p2i(task), p2i(p)); } @@ -238,10 +236,9 @@ void G1ParScanThreadState::process_array_chunk(objArrayOop obj, size_t start, si MAYBE_INLINE_EVACUATION void G1ParScanThreadState::do_partial_array(PartialArrayState* state, bool stolen) { - // Access state before release by claim(). - objArrayOop to_array = objArrayOop(state->destination()); PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, _task_queue, stolen); + objArrayOop to_array = claim._array; G1HeapRegionAttr dest_attr = _g1h->region_attr(to_array); G1SkipCardMarkSetter x(&_scanner, dest_attr.is_new_survivor()); // Process claimed task. @@ -250,21 +247,17 @@ void G1ParScanThreadState::do_partial_array(PartialArrayState* state, bool stole } MAYBE_INLINE_EVACUATION -void G1ParScanThreadState::start_partial_objarray(oop from_obj, - oop to_obj) { - assert(from_obj->is_forwarded(), "precondition"); - assert(from_obj->forwardee() == to_obj, "precondition"); +void G1ParScanThreadState::start_partial_objarray(oop to_obj) { assert(to_obj->is_objArray(), "precondition"); assert(!_scanner.do_metadata(), "precondition"); assert(_scanner.skip_card_mark_set(), "precondition"); objArrayOop to_array = objArrayOop(to_obj); size_t array_length = to_array->length(); - size_t initial_chunk_size = - // The source array is unused when processing states. - _partial_array_splitter.start(_task_queue, nullptr, to_array, array_length, ParGCArrayScanChunk); + PartialArraySplitter::Claim chunk = + _partial_array_splitter.start(_task_queue, to_array, array_length, ParGCArrayScanChunk); - process_array_chunk(to_array, 0, initial_chunk_size); + process_array_chunk(chunk._array, chunk._start, chunk._end); } MAYBE_INLINE_EVACUATION @@ -432,13 +425,16 @@ void G1ParScanThreadState::do_iterate_object(oop const obj, G1HeapRegionAttr const region_attr, G1HeapRegionAttr const dest_attr, uint age) { + assert(old->is_forwarded(), "precondition"); + assert(old->forwardee() == obj, "precondition"); + // Most objects are not arrays, so do one array check rather than // checking for each array category for each object. if (klass->is_array_klass()) { assert(!klass->is_stack_chunk_instance_klass(), "must be"); if (klass->is_objArray_klass()) { - start_partial_objarray(old, obj); + start_partial_objarray(obj); } else { // Nothing needs to be done for typeArrays. Body doesn't contain // any oops to scan, and the type in the klass will already be handled diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp index 65859c5ade7e..a9478c55b9d2 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp @@ -183,7 +183,7 @@ class G1ParScanThreadState : public CHeapObj { private: void do_partial_array(PartialArrayState* state, bool stolen); - void start_partial_objarray(oop from, oop to); + void start_partial_objarray(oop to); void process_array_chunk(objArrayOop obj, size_t start, size_t end); HeapWord* allocate_copy_slow(G1HeapRegionAttr* dest_attr, diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 5fdfdec67e03..dcad3bea8b24 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -47,6 +47,7 @@ #include "logging/log.hpp" #include "runtime/java.hpp" #include "runtime/mutexLocker.hpp" +#include "utilities/checkedCast.hpp" #include "utilities/debug.hpp" #include "utilities/growableArray.hpp" #include "utilities/pair.hpp" @@ -1431,7 +1432,7 @@ bool G1Policy::next_gc_should_be_mixed() const { } size_t G1Policy::allowed_waste_in_collection_set() const { - return G1HeapWastePercent * _g1h->capacity() / 100; + return checked_cast(static_cast(_g1h->capacity()) * G1HeapWastePercent / 100); } bool G1Policy::try_get_available_bytes_estimate(size_t& available_bytes) const { diff --git a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp index df8838bcd974..3f7632d6aa11 100644 --- a/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGenSizer.cpp @@ -28,6 +28,7 @@ #include "gc/shared/gc_globals.hpp" #include "logging/log.hpp" #include "runtime/globals_extension.hpp" +#include "utilities/checkedCast.hpp" G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), _use_adaptive_sizing(true), _min_desired_num_regions(), _max_desired_num_regions(0) { @@ -119,12 +120,12 @@ G1YoungGenSizer::G1YoungGenSizer() : _sizer_kind(SizerDefaults), } uint G1YoungGenSizer::calculate_default_min_num_regions(uint new_num_regions) { - uint default_value = (new_num_regions * G1NewSizePercent) / 100; + uint default_value = checked_cast(((uint64_t)new_num_regions * G1NewSizePercent) / 100); return MAX2(1U, default_value); } uint G1YoungGenSizer::calculate_default_max_num_regions(uint new_num_regions) { - uint default_value = (new_num_regions * G1MaxNewSizePercent) / 100; + uint default_value = checked_cast(((uint64_t)new_num_regions * G1MaxNewSizePercent) / 100); return MAX2(1U, default_value); } diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp index 0cb3887833ae..a28e094572b5 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp @@ -272,7 +272,7 @@ class ParallelScavengeHeap : public CollectedHeap { void print_gc_on(outputStream* st) const override; void gc_threads_do(ThreadClosure* tc) const override; - WorkerThreads* safepoint_workers() override { return &_workers; } + bool supports_parallel_object_iteration() const override { return true; } PreGenGCValues get_pre_gc_values() const; void print_heap_change(const PreGenGCValues& pre_gc_values) const; diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.cpp b/src/hotspot/share/gc/parallel/psCompactionManager.cpp index 9b96db0e5a68..f8f23dabf8cd 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.cpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.cpp @@ -124,17 +124,15 @@ void ParCompactionManager::push_objArray(objArrayOop obj) { _mark_and_push_closure.do_klass(obj->klass()); size_t array_length = obj->length(); - size_t initial_chunk_size = - _partial_array_splitter.start(&_marking_stack, obj, nullptr, array_length, ObjArrayMarkingStride); - follow_array(obj, 0, initial_chunk_size); + PartialArraySplitter::Claim chunk = + _partial_array_splitter.start(&_marking_stack, obj, array_length, ObjArrayMarkingStride); + follow_array(chunk._array, chunk._start, chunk._end); } void ParCompactionManager::process_array_chunk(PartialArrayState* state, bool stolen) { - // Access before release by claim(). - oop obj = state->source(); PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, &_marking_stack, stolen); - follow_array(objArrayOop(obj), claim._start, claim._end); + follow_array(claim._array, claim._start, claim._end); } void ParCompactionManager::follow_marking_stacks() { @@ -172,15 +170,13 @@ void ParCompactionManager::drain_region_stacks() { size_t ParCompactionManager::pop_shadow_region_mt_safe(PSParallelCompact::RegionData* region_ptr) { MonitorLocker ml(_shadow_region_monitor, Mutex::_no_safepoint_check_flag); while (true) { + // If the heap region has no remaining destination dependencies, use it directly. + if (region_ptr->available()) { + return InvalidShadow; + } if (!_shadow_region_array->is_empty()) { return _shadow_region_array->pop(); } - // Check if the corresponding heap region is available now. - // If so, we don't need to get a shadow region anymore, and - // we return InvalidShadow to indicate such a case. - if (region_ptr->claimed()) { - return InvalidShadow; - } ml.wait(1); } } diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp index dc2663f38ffd..ede4c02a0a68 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,6 @@ #include "gc/parallel/psParallelCompact.inline.hpp" #include "gc/parallel/psStringDedup.hpp" #include "gc/shared/partialArrayState.hpp" -#include "gc/shared/partialArrayTaskStepper.inline.hpp" #include "gc/shared/taskqueue.inline.hpp" #include "oops/access.inline.hpp" #include "oops/arrayOop.hpp" @@ -61,7 +60,7 @@ void ParCompactionManager::push_region(size_t index) #ifdef ASSERT const ParallelCompactData& sd = PSParallelCompact::summary_data(); ParallelCompactData::RegionData* const region_ptr = sd.region(index); - assert(region_ptr->claimed(), "must be claimed"); + assert(region_ptr->is_normal(), "must be normal fill work"); assert(region_ptr->_pushed++ == 0, "should only be pushed once"); #endif region_stack()->push(index); @@ -107,7 +106,7 @@ inline void ParCompactionManager::follow_array(objArrayOop obj, size_t start, si inline void ParCompactionManager::follow_contents(const ScannerTask& task, bool stolen) { if (task.is_partial_array_state()) { - assert(PSParallelCompact::mark_bitmap()->is_marked(task.to_partial_array_state()->source()), "should be marked"); + assert(PSParallelCompact::mark_bitmap()->is_marked(task.to_partial_array_state()->array()), "should be marked"); process_array_chunk(task.to_partial_array_state(), stolen); } else { oop obj = task.to_oop(); diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 8913c90a4bd3..bd8b1c3af95c 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -111,31 +111,13 @@ const size_t ParallelCompactData::RegionSizeOffsetMask = RegionSize - 1; const size_t ParallelCompactData::RegionAddrOffsetMask = RegionSizeBytes - 1; const size_t ParallelCompactData::RegionAddrMask = ~RegionAddrOffsetMask; -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_shift = 27; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_mask = ~0U << dc_shift; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_one = 0x1U << dc_shift; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::los_mask = ~dc_mask; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_claimed = 0x8U << dc_shift; - -const ParallelCompactData::RegionData::region_sz_t -ParallelCompactData::RegionData::dc_completed = 0xcU << dc_shift; - bool ParallelCompactData::RegionData::is_clear() { return (_destination == nullptr) && (_source_region == 0) && (_partial_obj_addr == nullptr) && (_partial_obj_size == 0) && - (dc_and_los() == 0) && - (shadow_state() == 0); + (_live_obj_size.load_relaxed() == 0) && + (state() == 0); } #ifdef ASSERT @@ -144,11 +126,97 @@ void ParallelCompactData::RegionData::verify_clear() { assert(_source_region == 0, "inv"); assert(_partial_obj_addr == nullptr, "inv"); assert(_partial_obj_size == 0, "inv"); - assert(dc_and_los() == 0, "inv"); - assert(shadow_state() == 0, "inv"); + assert(_live_obj_size.load_relaxed() == 0, "inv"); + assert(state() == 0, "inv"); } #endif +void ParallelCompactData::RegionData::set_destination_count(uint count) +{ + assert(count <= max_destination_count, "count too large"); + const state_t old_state = state(); + assert(state_status(old_state) == status_init, "cannot reset processed region"); + _state.store_relaxed(with_dest_count(old_state, static_cast(count))); +} + +void ParallelCompactData::RegionData::decrement_destination_count() +{ + state_t old_state = _state.fetch_then_sub(static_cast(1)); + assert(state_dest_count(old_state) > 0, "count would go negative"); +} + +void ParallelCompactData::RegionData::set_completed() +{ + const state_t old_state = state(); + assert(state_status(old_state) == status_normal, "can only complete normal regions"); + _state.store_relaxed(with_status(old_state, status_completed)); +} + +bool ParallelCompactData::RegionData::available_to_normal() +{ + const state_t old_state = state(); + assert(state_status(old_state) == status_init, "initial regions must be unprocessed"); + if (state_dest_count(old_state) == 0) { + _state.store_relaxed(make_state(0, status_normal)); + return true; + } + return false; +} + +bool ParallelCompactData::RegionData::try_mark_normal() +{ + assert(available(), "region must have no remaining destination dependencies"); + return _state.compare_set(make_state(0, status_init), make_state(0, status_normal)); +} + +bool ParallelCompactData::RegionData::try_complete_filled_shadow() +{ + assert(available(), "region must have no remaining destination dependencies"); + return _state.compare_set(make_state(0, status_filled_shadow), make_state(0, status_completed)); +} + +bool ParallelCompactData::RegionData::try_mark_shadow() { + state_t old_state = state(); + while (true) { + if (state_dest_count(old_state) == 0 || + state_status(old_state) != status_init) { + return false; + } + const state_t new_state = with_status(old_state, status_shadow); + if (_state.compare_set(old_state, new_state)) { + return true; + } + old_state = state(); + } +} + +bool ParallelCompactData::RegionData::finish_shadow_fill() { + state_t old_state = state(); + while (true) { + state_t new_state; + bool copy_now = false; + assert(state_status(old_state) == status_shadow, "must be shadow"); + if (state_dest_count(old_state) == 0) { + new_state = with_status(old_state, status_completed); + copy_now = true; + } else { + new_state = with_status(old_state, status_filled_shadow); + } + if (_state.compare_set(old_state, new_state)) { + return copy_now; + } + old_state = state(); + } +} + +void ParallelCompactData::RegionData::shadow_to_normal() { + assert(available(), "region must have no remaining destination dependencies"); + const state_t old_state = make_state(0, status_shadow); + const state_t new_state = make_state(0, status_normal); + bool result = _state.compare_set(old_state, new_state); + assert(result, "Fail to mark the region as normal"); +} + SpaceInfo PSParallelCompact::_space_info[PSParallelCompact::last_space_id]; HeapWord* PSParallelCompact::_old_space_dense_prefix = nullptr; HeapWord* PSParallelCompact::_old_space_new_top = nullptr; @@ -1508,10 +1576,8 @@ void PSParallelCompact::prepare_region_draining_tasks(uint parallel_gc_threads) // Populate with [beg_region, end_region) for (size_t cur = end_region - 1; cur + 1 > beg_region; --cur) { - if (sd.region(cur)->claim_unsafe()) { + if (sd.region(cur)->available_to_normal()) { ParCompactionManager* cm = ParCompactionManager::gc_thread_compaction_manager(worker_id); - bool result = sd.region(cur)->mark_normal(); - assert(result, "Must succeed at this point."); cm->push_region(cur); region_logger.handle(cur); // Assign regions to tasks in round-robin fashion. @@ -1724,15 +1790,15 @@ void PSParallelCompact::verify_regions_after_compaction() { // [beg_region, new_top_region) must be completed (as compaction targets) for (cur_region = beg_region; cur_region < new_top_region; ++cur_region) { const RegionData* const c = sd.region(cur_region); - assert(c->completed(), "region %zu not filled: destination_count=%u", - cur_region, c->destination_count()); + assert(c->completed(), "region %zu not filled: destination_count=%u status=%s", + cur_region, c->destination_count(), c->status_name()); } // [new_top_region, old_top_region) must be empty. for (cur_region = new_top_region; cur_region < old_top_region; ++cur_region) { const RegionData* const c = sd.region(cur_region); - assert(c->available(), "region %zu not empty: destination_count=%u", - cur_region, c->destination_count()); + assert(c->available() && c->is_init(), "region %zu not empty: destination_count=%u status=%s", + cur_region, c->destination_count(), c->status_name()); } } { @@ -1748,8 +1814,8 @@ void PSParallelCompact::verify_regions_after_compaction() { for (size_t cur_region = beg_region; cur_region < end_region; ++cur_region) { const RegionData* const c = sd.region(cur_region); - assert(c->available(), "region %zu not empty: destination_count=%u", - cur_region, c->destination_count()); + assert(c->available() && c->is_init(), "region %zu not empty: destination_count=%u status=%s", + cur_region, c->destination_count(), c->status_name()); } } } @@ -1861,17 +1927,16 @@ void PSParallelCompact::decrement_destination_counts(ParCompactionManager* cm, for (RegionData* cur = beg; cur < end; ++cur) { assert(cur->data_size() > 0, "region must have live data"); cur->decrement_destination_count(); - if (cur < enqueue_end && cur->available() && cur->claim()) { - if (cur->mark_normal()) { + if (cur < enqueue_end && cur->available()) { + if (cur->try_mark_normal()) { cm->push_region(sd.region(cur)); - } else if (cur->mark_copied()) { + } else if (cur->try_complete_filled_shadow()) { // Try to copy the content of the shadow region back to its corresponding // heap region if the shadow region is filled. Otherwise, the GC thread // fills the shadow region will copy the data back (see // MoveAndUpdateShadowClosure::complete_region). copy_back(sd.region_to_addr(cur->shadow_region()), sd.region_to_addr(cur)); ParCompactionManager::push_shadow_region_mt_safe(cur->shadow_region()); - cur->set_completed(); } } } @@ -2144,7 +2209,7 @@ bool PSParallelCompact::steal_unavailable_region(ParCompactionManager* cm, size_ uint active_gc_threads = ParallelScavengeHeap::heap()->workers().active_workers(); while (next < old_new_top) { - if (sd.region(next)->mark_shadow()) { + if (sd.region(next)->try_mark_shadow()) { region_idx = next; return true; } @@ -2205,7 +2270,7 @@ void MoveAndUpdateClosure::copy_partial_obj(size_t partial_obj_size) } void MoveAndUpdateClosure::complete_region(HeapWord* dest_addr, PSParallelCompact::RegionData* region_ptr) { - assert(region_ptr->shadow_state() == ParallelCompactData::RegionData::NormalRegion, "Region should be finished"); + assert(region_ptr->is_normal(), "Region should be normal"); region_ptr->set_completed(); } @@ -2235,18 +2300,16 @@ void MoveAndUpdateClosure::do_addr(HeapWord* addr, size_t words, markWord mark) } void MoveAndUpdateShadowClosure::complete_region(HeapWord* dest_addr, PSParallelCompact::RegionData* region_ptr) { - assert(region_ptr->shadow_state() == ParallelCompactData::RegionData::ShadowRegion, "Region should be shadow"); + assert(region_ptr->is_shadow(), "Region should be shadow"); // Record the shadow region index region_ptr->set_shadow_region(_shadow); - // Mark the shadow region as filled to indicate the data is ready to be - // copied back - region_ptr->mark_filled(); + // Finish shadow filling and determine whether this worker must copy back immediately. + bool copy_now = region_ptr->finish_shadow_fill(); // Try to copy the content of the shadow region back to its corresponding // heap region if available; the GC thread that decreases the destination // count to zero will do the copying otherwise (see // PSParallelCompact::decrement_destination_counts). - if (((region_ptr->available() && region_ptr->claim()) || region_ptr->claimed()) && region_ptr->mark_copied()) { - region_ptr->set_completed(); + if (copy_now) { PSParallelCompact::copy_back(PSParallelCompact::summary_data().region_to_addr(_shadow), dest_addr); ParCompactionManager::push_shadow_region_mt_safe(_shadow); } diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp index 3eb3691c8c0f..c568b26dc04f 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp @@ -107,7 +107,7 @@ class ParallelCompactData // in this region (words). This does not include the partial object // extending onto the region (if any), or the part of an object that extends // onto the next region (if any). - size_t live_obj_size() const { return dc_and_los() & los_mask; } + size_t live_obj_size() const { return _live_obj_size.load_relaxed(); } // Total live data that lies within the region (words). size_t data_size() const { return partial_obj_size() + live_obj_size(); } @@ -117,31 +117,29 @@ class ParallelCompactData // values of destination_count are // // 0 - data from the region will be compacted completely into itself, or the - // region is empty. The region can be claimed and then filled. + // region is empty. The region can be filled immediately. // 1 - data from the region will be compacted into 1 other region; some // data from the region may also be compacted into the region itself. // 2 - data from the region will be copied to 2 other regions. // // During compaction as regions are emptied, the destination_count is - // decremented (atomically) and when it reaches 0, it can be claimed and - // then filled. - // - // A region is claimed for processing by atomically changing the - // destination_count to the claimed value (dc_claimed). After a region has - // been filled, the destination_count should be set to the completed value - // (dc_completed). - inline uint destination_count() const; - inline uint destination_count_raw() const; - - // Whether this region is available to be claimed, has been claimed, or has - // been completed. - // - // Minor subtlety: claimed() returns true if the region is marked - // completed(), which is desirable since a region must be claimed before it - // can be completed. - bool available() const { return dc_and_los() < dc_one; } - bool claimed() const { return dc_and_los() >= dc_claimed; } - bool completed() const { return dc_and_los() >= dc_completed; } + // decremented atomically. When it reaches 0, the region has no remaining + // destination dependencies. + uint destination_count() const { return state_dest_count(state()); } + + // Whether this region has no remaining destination dependencies. + bool available() const { return destination_count() == 0; } + bool completed() const { return state_status(state()) == status_completed; } + const char* status_name() const { + switch (state_status(state())) { + case status_init: return "init"; + case status_normal: return "normal"; + case status_shadow: return "shadow"; + case status_filled_shadow: return "filled_shadow"; + case status_completed: return "completed"; + default: return "unknown"; + } + } // These are not atomic. void set_destination(HeapWord* addr) { _destination = addr; } @@ -152,40 +150,29 @@ class ParallelCompactData _partial_obj_size = (region_sz_t) words; } - inline void set_destination_count(uint count); + void set_destination_count(uint count); - inline void set_completed(); - inline bool claim_unsafe(); + void set_completed(); + bool available_to_normal(); // These are atomic. - inline void add_live_obj(size_t words); - inline void decrement_destination_count(); - inline bool claim(); - - // Possible values of _shadow_state, and transition is as follows - // Normal Path: - // UnusedRegion -> mark_normal() -> NormalRegion - // Shadow Path: - // UnusedRegion -> mark_shadow() -> ShadowRegion -> - // mark_filled() -> FilledShadow -> mark_copied() -> CopiedShadow - static const int UnusedRegion = 0; // The region is not collected yet - static const int ShadowRegion = 1; // Stolen by an idle thread, and a shadow region is created for it - static const int FilledShadow = 2; // Its shadow region has been filled and ready to be copied back - static const int CopiedShadow = 3; // The data of the shadow region has been copied back - static const int NormalRegion = 4; // The region will be collected by the original parallel algorithm - - // Mark the current region as normal or shadow to enter different processing paths - inline bool mark_normal(); - inline bool mark_shadow(); - // Mark the shadow region as filled and ready to be copied back - inline void mark_filled(); - // Mark the shadow region as copied back to avoid double copying. - inline bool mark_copied(); - // Special case: see the comment in PSParallelCompact::fill_and_update_shadow_region. - // Return to the normal path here - inline void shadow_to_normal(); - - int shadow_state() { return _shadow_state.load_relaxed(); } + void add_live_obj(size_t words) { + assert(words <= RegionSize - live_obj_size(), "overflow"); + _live_obj_size.add_then_fetch(static_cast(words)); + } + void decrement_destination_count(); + bool try_mark_normal(); + bool try_complete_filled_shadow(); + + // Mark the current region as shadow to enter the shadow processing path. + bool try_mark_shadow(); + // Finish shadow filling. Returns true if the caller must copy back immediately. + bool finish_shadow_fill(); + // Special case: see PSParallelCompact::fill_and_update_shadow_region. + void shadow_to_normal(); + bool is_init() const { return state_status(state()) == status_init; } + bool is_normal() const { return state_status(state()) == status_normal; } + bool is_shadow() const { return state_status(state()) == status_shadow; } bool is_clear(); @@ -194,25 +181,76 @@ class ParallelCompactData private: // The type used to represent object sizes within a region. typedef uint region_sz_t; + // The type used to represent destination count and compaction state. + typedef uint state_t; - // Constants for manipulating the _dc_and_los field, which holds both the - // destination count and live obj size. The live obj size lives at the - // least significant end so no masking is necessary when adding. - static const region_sz_t dc_shift; // Shift amount. - static const region_sz_t dc_mask; // Mask for destination count. - static const region_sz_t dc_one; // 1, shifted appropriately. - static const region_sz_t dc_claimed; // Region has been claimed. - static const region_sz_t dc_completed; // Region has been completed. - static const region_sz_t los_mask; // Mask for live obj size. + // Layout of _state: + // + // 31-5 4-2 1-0 + // +--------+---------+----------+ + // unused status dest_count + // +--------+---------+----------+ + // | | | | + // | | |* 1-0 dest_count: destination regions still to be copied + // | | | from this source region (0-2) + // | |* 2-4 status: compaction path progress, see StateBits + // |* 5-31 unused + // + // Complete status routes: + // 1. Normal fill, initially or later available: + // status_init -> status_normal -> status_completed + // The first transition happens during initial task setup or when dest_count reaches 0. + // + // 2. Shadow selected, but no shadow region is available when dest_count reaches 0: + // status_init -> status_shadow -> status_normal -> status_completed + // status_shadow with dest_count 0 can fall back to normal filling. + // + // 3. Shadow fill completes before dest_count reaches 0: + // status_init -> status_shadow -> status_filled_shadow -> status_completed + // The last transition claims copy-back when dest_count reaches 0. + // + // 4. Dest_count reaches 0 before shadow fill completes: + // status_init -> status_shadow -> status_completed + // The last transition claims immediate copy-back when shadow fill completes. + enum StateBits : state_t { + max_destination_count = 2, + dest_count_mask = 0x3, + status_shift = 2, + status_mask = 0x7 << status_shift, + + status_init = 0 << status_shift, // Not yet selected for filling. + status_normal = 1 << status_shift, // Selected for direct filling. + status_shadow = 2 << status_shift, // Selected for filling via a shadow region. + status_filled_shadow = 3 << status_shift, // Shadow filled, waiting for destination dependencies. + status_completed = 4 << status_shift // Direct fill done, or shadow copy-back exclusively claimed. + }; + + // The destination count occupies the low status_shift bits, so decrementing the whole + // word in decrement_destination_count() decrements the count alone. + static_assert(dest_count_mask == ((1U << status_shift) - 1), + "dest_count field width must equal status_shift"); + static_assert(max_destination_count <= dest_count_mask, + "max_destination_count must fit in the dest_count field"); HeapWord* _destination; size_t _source_region; HeapWord* _partial_obj_addr; region_sz_t _partial_obj_size; - Atomic _dc_and_los; - Atomic _shadow_state; + Atomic _live_obj_size; + Atomic _state; - region_sz_t dc_and_los() const { return _dc_and_los.load_relaxed(); } + state_t state() const { return _state.load_relaxed(); } + static state_t make_state(state_t dest_count, state_t status) { + return dest_count | status; + } + static state_t state_dest_count(state_t state) { return state & dest_count_mask; } + static state_t state_status(state_t state) { return state & status_mask; } + static state_t with_dest_count(state_t state, state_t dest_count) { + return (state & ~dest_count_mask) | dest_count; + } + static state_t with_status(state_t state, state_t status) { + return (state & ~status_mask) | status; + } #ifdef ASSERT public: uint _pushed; // 0 until region is pushed onto a stack @@ -269,85 +307,6 @@ class ParallelCompactData size_t _region_count; }; -inline uint -ParallelCompactData::RegionData::destination_count_raw() const -{ - return dc_and_los() & dc_mask; -} - -inline uint -ParallelCompactData::RegionData::destination_count() const -{ - return destination_count_raw() >> dc_shift; -} - -inline void -ParallelCompactData::RegionData::set_destination_count(uint count) -{ - assert(count <= (dc_completed >> dc_shift), "count too large"); - const region_sz_t live_sz = (region_sz_t) live_obj_size(); - _dc_and_los.store_relaxed((count << dc_shift) | live_sz); -} - -inline void ParallelCompactData::RegionData::decrement_destination_count() -{ - assert(dc_and_los() < dc_claimed, "already claimed"); - assert(dc_and_los() >= dc_one, "count would go negative"); - _dc_and_los.add_then_fetch(dc_mask); -} - -inline void ParallelCompactData::RegionData::set_completed() -{ - assert(claimed(), "must be claimed first"); - _dc_and_los.store_relaxed(dc_completed | (region_sz_t) live_obj_size()); -} - -// MT-unsafe claiming of a region. Should only be used during single threaded -// execution. -inline bool ParallelCompactData::RegionData::claim_unsafe() -{ - if (available()) { - _dc_and_los.store_relaxed(dc_and_los() | dc_claimed); - return true; - } - return false; -} - -inline void ParallelCompactData::RegionData::add_live_obj(size_t words) -{ - assert(words <= (size_t)los_mask - live_obj_size(), "overflow"); - _dc_and_los.add_then_fetch(static_cast(words)); -} - -inline bool ParallelCompactData::RegionData::claim() -{ - const region_sz_t los = static_cast(live_obj_size()); - return _dc_and_los.compare_set(los, dc_claimed | los); -} - -inline bool ParallelCompactData::RegionData::mark_normal() { - return _shadow_state.compare_set(UnusedRegion, NormalRegion); -} - -inline bool ParallelCompactData::RegionData::mark_shadow() { - if (shadow_state() != UnusedRegion) return false; - return _shadow_state.compare_set(UnusedRegion, ShadowRegion); -} - -inline void ParallelCompactData::RegionData::mark_filled() { - int old = _shadow_state.compare_exchange(ShadowRegion, FilledShadow); - assert(old == ShadowRegion, "Fail to mark the region as filled"); -} - -inline bool ParallelCompactData::RegionData::mark_copied() { - return _shadow_state.compare_set(FilledShadow, CopiedShadow); -} - -void ParallelCompactData::RegionData::shadow_to_normal() { - int old = _shadow_state.compare_exchange(ShadowRegion, NormalRegion); - assert(old == ShadowRegion, "Fail to mark the region as finish"); -} - inline ParallelCompactData::RegionData* ParallelCompactData::region(size_t region_idx) const { diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.cpp b/src/hotspot/share/gc/parallel/psPromotionManager.cpp index 157db5c07ddc..628935ba4d53 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.cpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.cpp @@ -263,28 +263,22 @@ void PSPromotionManager::process_array_chunk(objArrayOop obj, size_t start, size } void PSPromotionManager::process_array_chunk(PartialArrayState* state, bool stolen) { - // Access before release by claim(). - objArrayOop to_array = objArrayOop(state->destination()); - precond(to_array->is_array_with_oops()); - PartialArraySplitter::Claim claim = _partial_array_splitter.claim(state, &_claimed_stack_depth, stolen); + precond(claim._array->is_array_with_oops()); - process_array_chunk(to_array, claim._start, claim._end); + process_array_chunk(claim._array, claim._start, claim._end); } -void PSPromotionManager::push_objArray(oop old_obj, oop new_obj) { - assert(old_obj->is_forwarded(), "precondition"); - assert(old_obj->forwardee() == new_obj, "precondition"); +void PSPromotionManager::push_objArray(oop new_obj) { precond(new_obj->is_array_with_oops()); objArrayOop to_array = objArrayOop(new_obj); size_t array_length = to_array->length(); - size_t initial_chunk_size = - // The source array is unused when processing states. - _partial_array_splitter.start(&_claimed_stack_depth, nullptr, to_array, array_length, ParGCArrayScanChunk); + PartialArraySplitter::Claim chunk = + _partial_array_splitter.start(&_claimed_stack_depth, to_array, array_length, ParGCArrayScanChunk); - process_array_chunk(to_array, 0, initial_chunk_size); + process_array_chunk(chunk._array, chunk._start, chunk._end); } oop PSPromotionManager::oop_promotion_failed(oop obj, markWord obj_mark) { diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.hpp index 287808429d3a..dd91fccd27c7 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.hpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.hpp @@ -100,7 +100,7 @@ class PSPromotionManager { void process_array_chunk(PartialArrayState* state, bool stolen); void process_array_chunk(objArrayOop obj, size_t start, size_t end); - void push_objArray(oop old_obj, oop new_obj); + void push_objArray(oop new_obj); inline void promotion_trace_event(oop new_obj, Klass* klass, size_t obj_size, uint age, bool tenured, diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp index 2f7fb7cd970e..fa2f1809db23 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp @@ -299,7 +299,7 @@ inline oop PSPromotionManager::copy_unmarked_to_survivor_space(oop o, // So, the is_array_with_oops test would be very infrequent. if (new_obj_size > _min_array_size_for_chunking && new_obj->is_array_with_oops()) { - push_objArray(o, new_obj); + push_objArray(new_obj); } else { // we'll just push its contents push_contents(new_obj, klass); diff --git a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp index 7a773640466c..511ef579b543 100644 --- a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp +++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp @@ -747,7 +747,7 @@ void BarrierSetC2::clone(GraphKit* kit, Node* src_base, Node* dst_base, Node* si Node* n = kit->gvn().transform(ac); if (n == ac) { const TypePtr* raw_adr_type = TypeRawPtr::BOTTOM; - ac->set_adr_type(TypeRawPtr::BOTTOM); + ac->set_out_adr_type(TypeRawPtr::BOTTOM); kit->set_predefined_output_for_runtime_call(ac, ac->in(TypeFunc::Memory), raw_adr_type); } else { kit->set_all_memory(n); diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index c1305f961fbb..e153565b59c0 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -402,6 +402,10 @@ class CollectedHeap : public CHeapObj { // Iterate over all objects, calling "cl.do_object" on each. virtual void object_iterate(ObjectClosure* cl) = 0; + // Returns true if the collector implements parallel_object_iterator(), so + // heap dump and heap inspection may iterate the heap with multiple worker threads. + virtual bool supports_parallel_object_iteration() const { return false; } + virtual ParallelObjectIteratorImpl* parallel_object_iterator(uint thread_num) { return nullptr; } @@ -475,17 +479,6 @@ class CollectedHeap : public CHeapObj { // this collector. The default implementation returns false. virtual bool supports_concurrent_gc_breakpoints() const; - // Workers used in non-GC safepoints for parallel safepoint cleanup. If this - // method returns null, cleanup tasks are done serially in the VMThread. See - // `SafepointSynchronize::do_cleanup_tasks` for details. - // GCs using a GC worker thread pool inside GC safepoints may opt to share - // that pool with non-GC safepoints, avoiding creating extraneous threads. - // Such sharing is safe, because GC safepoints and non-GC safepoints never - // overlap. For example, `G1CollectedHeap::workers()` (for GC safepoints) and - // `G1CollectedHeap::safepoint_workers()` (for non-GC safepoints) return the - // same thread-pool. - virtual WorkerThreads* safepoint_workers() { return nullptr; } - // Support for object pinning. This is used by JNI Get*Critical() // and Release*Critical() family of functions. The GC must guarantee // that pinned objects never move and don't get reclaimed as garbage. diff --git a/src/hotspot/share/gc/shared/gcArguments.cpp b/src/hotspot/share/gc/shared/gcArguments.cpp index ec3d758c00a6..569072845f67 100644 --- a/src/hotspot/share/gc/shared/gcArguments.cpp +++ b/src/hotspot/share/gc/shared/gcArguments.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -279,10 +279,6 @@ void GCArguments::initialize_heap_flags_and_sizes() { } } - if (FLAG_IS_DEFAULT(SoftMaxHeapSize)) { - FLAG_SET_ERGO(SoftMaxHeapSize, MaxHeapSize); - } - FLAG_SET_ERGO(MinHeapDeltaBytes, align_up(MinHeapDeltaBytes, SpaceAlignment)); if (checked_cast(ObjectAlignmentInBytes) > GCCardSizeInBytes) { diff --git a/src/hotspot/share/gc/shared/gcVMOperations.cpp b/src/hotspot/share/gc/shared/gcVMOperations.cpp index 6dbfd56b4e97..59209fb8de44 100644 --- a/src/hotspot/share/gc/shared/gcVMOperations.cpp +++ b/src/hotspot/share/gc/shared/gcVMOperations.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -204,16 +204,7 @@ void VM_GC_HeapInspection::doit() { } } HeapInspection inspect; - WorkerThreads* workers = Universe::heap()->safepoint_workers(); - if (workers != nullptr) { - // The GC provided a WorkerThreads to be used during a safepoint. - // Can't run with more threads than provided by the WorkerThreads. - const uint capped_parallel_thread_num = MIN2(_parallel_thread_num, workers->max_workers()); - WithActiveWorkers with_active_workers(workers, capped_parallel_thread_num); - inspect.heap_inspection(_out, workers); - } else { - inspect.heap_inspection(_out, nullptr); - } + inspect.heap_inspection(_out, _parallel_thread_num); } VM_CollectForMetadataAllocation::VM_CollectForMetadataAllocation(ClassLoaderData* loader_data, diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp index 1d2f45397aa7..236388b14dc4 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp @@ -34,6 +34,9 @@ #if INCLUDE_PARALLELGC #include "gc/parallel/jvmFlagConstraintsParallel.hpp" #endif +#if INCLUDE_ZGC +#include "gc/z/jvmFlagConstraintsZ.hpp" +#endif /* * Here we have GC arguments constraints functions, which are called automatically @@ -76,6 +79,7 @@ JVMFlag::Error MaxPLABSizeBounds(const char* name, size_t value, bool verbose); #define GC_CONSTRAINTS(f) \ SHARED_GC_CONSTRAINTS(f) \ G1GC_ONLY(G1_GC_CONSTRAINTS(f)) \ - PARALLELGC_ONLY(PARALLEL_GC_CONSTRAINTS(f)) + PARALLELGC_ONLY(PARALLEL_GC_CONSTRAINTS(f)) \ + ZGC_ONLY(Z_GC_CONSTRAINTS(f)) #endif // SHARE_GC_SHARED_JVMFLAGCONSTRAINTSGC_HPP diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.cpp b/src/hotspot/share/gc/shared/partialArraySplitter.cpp index 04884d5e666c..68f494eb67ec 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.cpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.cpp @@ -22,7 +22,6 @@ * */ -#include "gc/shared/gc_globals.hpp" #include "gc/shared/partialArraySplitter.hpp" #include "gc/shared/partialArrayState.hpp" #include "utilities/macros.hpp" diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.hpp index 340f370d1d5d..5235d2cd11da 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.hpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.hpp @@ -32,16 +32,18 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -class outputStream; - -// Helper class for splitting the processing of a large objArray into multiple -// tasks, to permit multiple threads to work on different pieces of the array -// in parallel. +// Each worker uses its own splitter to scan arrays in parallel. +// Queued tasks share an atomic progress record; each claim returns a +// disjoint, self-contained chunk and decrements the record's ref-count. class PartialArraySplitter { PartialArrayStateAllocator _allocator; PartialArrayTaskStepper _stepper; TASKQUEUE_STATS_ONLY(PartialArrayTaskStats _stats;) + // The state's ref-count must already include these tasks before publication. + template + void enqueue(Queue* queue, PartialArrayState* state, uint count); + public: PartialArraySplitter(PartialArrayStateManager* manager, uint num_workers); @@ -49,53 +51,30 @@ class PartialArraySplitter { NONCOPYABLE(PartialArraySplitter); - // Setup to process an objArray in chunks. - // - // from_array is the array found by the collector that needs processing. It - // may be null if to_array contains everything needed for processing. - // - // to_array is an unprocessed (possibly partial) copy of from_array, or null - // if a copy of from_array is not required. - // - // length is their length in elements. - // - // chunk_size the size of a single chunk. - // - // If t is a ScannerTask, queue->push(t) must be a valid expression. The - // result of that expression is ignored. - // - // Returns the size of the initial chunk that is to be processed by the - // caller. - // - // Adds PartialArrayState ScannerTasks to the queue if needed to process the - // array in chunks. This permits other workers to steal and process them - // even while the caller is processing the initial chunk. If length doesn't - // exceed the chunk size then the result will be length, indicating the - // caller is to process the entire array. In this case, no tasks will have - // been added to the queue. - template - size_t start(Queue* queue, - objArrayOop from_array, - objArrayOop to_array, - size_t length, - size_t chunk_size); - - // Result type for claim(), carrying multiple values. Provides the claimed - // chunk's start and end array indices. + // A chunk remains valid after its progress record has been recycled. It + // does not own or keep the array alive; that is the collector's responsibility. struct Claim { + objArrayOop _array; size_t _start; size_t _end; }; + // Start processing array[0, length). Return the initial chunk and enqueue + // a continuation if any full-sized chunks remain. Process the remainder + // first so every queued task claims exactly chunk_size elements. + template + Claim start(Queue* queue, + objArrayOop array, + size_t length, + size_t chunk_size); + // Claims a chunk from state, returning the index range for that chunk. The // caller is expected to process that chunk. Adds more state-based tasks to // the queue if needed, permitting other workers to steal and process them // even while the caller is processing this claim. // - // Releases the state. Callers must not use state after the call to this - // function. The state may have been recycled and reused. - // - // The queue has the same requirements as for start(). + // Decrements the state's ref-count for the current task. Callers must not + // use state after this call; it may have been recycled and reused. // // stolen indicates whether the state task was obtained from this queue or // stolen from some other queue. diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp index 7679358e2188..dc702072f3f9 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp @@ -36,26 +36,28 @@ #include "utilities/macros.hpp" template -size_t PartialArraySplitter::start(Queue* queue, - objArrayOop source, - objArrayOop destination, - size_t length, - size_t chunk_size) { +void PartialArraySplitter::enqueue(Queue* queue, PartialArrayState* state, uint count) { + TASKQUEUE_STATS_ONLY(_stats.inc_pushed(count);) + for (uint i = 0; i < count; ++i) { + queue->push(ScannerTask(state)); + } +} + +template +PartialArraySplitter::Claim +PartialArraySplitter::start(Queue* queue, + objArrayOop array, + size_t length, + size_t chunk_size) { precond(chunk_size > 0); - PartialArrayTaskStepper::Step step = _stepper.start(length, chunk_size); - // Push initial partial scan tasks. - if (step._ncreate > 0) { - TASKQUEUE_STATS_ONLY(_stats.inc_split();); - TASKQUEUE_STATS_ONLY(_stats.inc_pushed(step._ncreate);) + size_t end = length % chunk_size; + if (end < length) { + TASKQUEUE_STATS_ONLY(_stats.inc_split();) PartialArrayState* state = - _allocator.allocate(source, destination, step._index, length, chunk_size, step._ncreate); - for (uint i = 0; i < step._ncreate; ++i) { - queue->push(ScannerTask(state)); - } - } else { - assert(step._index == length, "invariant"); + _allocator.allocate(array, end, length, chunk_size); + enqueue(queue, state, 1); } - return step._index; + return Claim{array, 0, end}; } template @@ -66,21 +68,20 @@ PartialArraySplitter::claim(PartialArrayState* state, Queue* queue, bool stolen) _stats.inc_processed(); #endif // TASKQUEUE_STATS - // Claim a chunk and get number of additional tasks to enqueue. - PartialArrayTaskStepper::Step step = _stepper.next(state); - // Push additional tasks. - if (step._ncreate > 0) { - TASKQUEUE_STATS_ONLY(_stats.inc_pushed(step._ncreate);) - // Adjust reference count for tasks being added to the queue. - state->add_references(step._ncreate); - for (uint i = 0; i < step._ncreate; ++i) { - queue->push(ScannerTask(state)); - } + size_t start = state->claim_next(); + size_t chunk_size = state->_chunk_size; + uint count = _stepper.continuation_tasks(start, state->_length, chunk_size); + if (count > 0) { + // Increment the ref-count for all new tasks before publication. A thief + // may immediately claim and release any task we push. + state->add_references(count); + enqueue(queue, state, count); } - size_t chunk_size = state->chunk_size(); - // Release state, decrementing refcount, now that we're done with it. + Claim result{state->array(), start, start + chunk_size}; + // Capture everything needed for scanning before decrementing the state's + // ref-count. _allocator.release(state); - return Claim{step._index, step._index + chunk_size}; + return result; } #endif // SHARE_GC_SHARED_PARTIALARRAYSPLITTER_INLINE_HPP diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp index 3ef2927fe9a7..81d80b71ceb0 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.cpp +++ b/src/hotspot/share/gc/shared/partialArrayState.cpp @@ -33,18 +33,18 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" -PartialArrayState::PartialArrayState(oop src, oop dst, +PartialArrayState::PartialArrayState(objArrayOop array, size_t index, size_t length, - size_t chunk_size, - size_t initial_refcount) - : _source(src), - _destination(dst), + size_t chunk_size) + : _array(array), _length(length), _chunk_size(chunk_size), _index(index), - _refcount(initial_refcount) + _refcount(1) { assert(index <= length, "precondition"); + assert(chunk_size > 0, "precondition"); + assert((length - index) % chunk_size == 0, "whole chunks remain"); } void PartialArrayState::add_references(size_t count) { @@ -76,11 +76,10 @@ PartialArrayStateAllocator::~PartialArrayStateAllocator() { _manager->release_allocator(); } -PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst, +PartialArrayState* PartialArrayStateAllocator::allocate(objArrayOop array, size_t index, size_t length, - size_t chunk_size, - size_t initial_refcount) { + size_t chunk_size) { void* p; FreeListEntry* head = _free_list; if (head == nullptr) { @@ -90,7 +89,7 @@ PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst, head->~FreeListEntry(); p = head; } - return ::new (p) PartialArrayState(src, dst, index, length, chunk_size, initial_refcount); + return ::new (p) PartialArrayState(array, index, length, chunk_size); } void PartialArrayStateAllocator::release(PartialArrayState* state) { @@ -118,6 +117,7 @@ PartialArrayStateManager::~PartialArrayStateManager() { } Arena* PartialArrayStateManager::register_allocator() { + assert(_released_allocators.load_relaxed() == 0, "allocator destruction has begun"); uint idx = _registered_allocators.fetch_then_add(1u, memory_order_relaxed); assert(idx < _max_allocators, "exceeded configured max number of allocators"); return ::new (&_arenas[idx]) Arena(mtGC); diff --git a/src/hotspot/share/gc/shared/partialArrayState.hpp b/src/hotspot/share/gc/shared/partialArrayState.hpp index 75e297526aef..302d73ae879c 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.hpp +++ b/src/hotspot/share/gc/shared/partialArrayState.hpp @@ -28,6 +28,7 @@ #include "memory/allocation.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/atomic.hpp" +#include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" @@ -43,33 +44,39 @@ class PartialArrayStateManager; // for later processing, either by the current thread or by some other thread // that steals one of those tasks. // -// Processing a state involves using the state to claim a segment of the -// array, and processing that segment. Claiming is done by atomically -// incrementing the index, thereby claiming the segment from the old to new -// index values. New tasks should also be added as needed to ensure the -// entire array will be processed. A PartialArrayTaskStepper can be used to -// help with this. +// PartialArraySplitter atomically claims disjoint chunks and publishes enough +// continuation tasks for parallel processing. The state is only needed while +// claiming a chunk, not while scanning it. // // States are allocated and released using a PartialArrayStateAllocator. -// States are reference counted to aid in that management. Each task -// referring to a given state that is added to a taskqueue must increase the -// reference count by one. When the processing of a task referring to a state -// is complete, the reference count must be decreased by one. When the -// reference count reaches zero the state is released to the allocator for -// later reuse. +// Each queued or currently claiming task contributes one to the state's +// ref-count. Increment the ref-count before publishing continuation tasks. +// After a task has claimed its chunk and published any continuations, decrement +// the ref-count by one. The worker that decrements it to zero recycles the +// state on its own allocator for later reuse. class PartialArrayState { - oop _source; - oop _destination; + objArrayOop _array; size_t _length; size_t _chunk_size; Atomic _index; Atomic _refcount; friend class PartialArrayStateAllocator; + friend class PartialArraySplitter; - PartialArrayState(oop src, oop dst, + PartialArrayState(objArrayOop array, size_t index, size_t length, - size_t chunk_size, size_t initial_refcount); + size_t chunk_size); + + // Increment the ref-count by count before publishing that many tasks to a queue. + void add_references(size_t count); + + size_t claim_next() { + size_t start = _index.fetch_then_add(_chunk_size, memory_order_relaxed); + assert(start < _length, "every task must have a chunk to claim"); + assert((_length - start) % _chunk_size == 0, "whole chunks remain"); + return start; + } public: // Deleted to require management by allocator object. @@ -77,24 +84,7 @@ class PartialArrayState { NONCOPYABLE(PartialArrayState); - // Add count references, one per referring task being added to a taskqueue. - void add_references(size_t count); - - // The source array oop. - oop source() const { return _source; } - - // The destination array oop. In some circumstances the source and - // destination may be the same. - oop destination() const { return _destination; } - - // The length of the array oop. - size_t length() const { return _length; } - - size_t chunk_size() const { return _chunk_size; } - - // A pointer to the start index for the next segment to process, for atomic - // update. - Atomic* index_addr() { return &_index; } + objArrayOop array() const { return _array; } }; // This class provides memory management for PartialArrayStates. @@ -121,7 +111,7 @@ class PartialArrayStateAllocator : public CHeapObj { PartialArrayStateManager* _manager; FreeListEntry* _free_list; - Arena* _arena; // Obtained from _manager. + Arena* _arena; public: explicit PartialArrayStateAllocator(PartialArrayStateManager* manager); @@ -129,17 +119,12 @@ class PartialArrayStateAllocator : public CHeapObj { NONCOPYABLE(PartialArrayStateAllocator); - // Create a new state, obtaining the memory for it from the free-list or - // from the associated manager. - PartialArrayState* allocate(oop src, oop dst, + PartialArrayState* allocate(objArrayOop array, size_t index, size_t length, - size_t chunk_size, - size_t initial_refcount); + size_t chunk_size); - // Decrement the state's refcount. If the new refcount is zero, add the - // state to the free-list associated with worker_id. The state must have - // been allocated by this allocator, but that allocation doesn't need to - // have been associated with worker_id. + // Decrement the state's ref-count by one. If it reaches zero, recycle on + // this allocator's free-list. The state must belong to the same manager. void release(PartialArrayState* state); }; diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp index 126c6c302201..8a5b70a34b67 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp @@ -28,12 +28,6 @@ #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" -static uint compute_task_limit(uint n_workers) { - // Don't need more than n_workers tasks at a time. But allowing up to - // that maximizes available parallelism. - return n_workers; -} - static uint compute_task_fanout(uint task_limit) { assert(task_limit > 0, "precondition"); // There is a tradeoff between providing parallelism more quickly and @@ -48,8 +42,8 @@ static uint compute_task_fanout(uint task_limit) { return result; } -PartialArrayTaskStepper::PartialArrayTaskStepper(uint n_workers) : - _task_limit(compute_task_limit(n_workers)), +PartialArrayTaskStepper::PartialArrayTaskStepper(uint num_workers) : + _task_limit(num_workers), _task_fanout(compute_task_fanout(_task_limit)) { // Current stepper code only supports up to 2^31 elements in the array. diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp index 594cc7b245a9..fd9706aa9878 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp @@ -25,12 +25,8 @@ #ifndef SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP #define SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP -#include "oops/arrayOop.hpp" -#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" -class PartialArrayState; - // Helper for partial array chunking tasks. // // When an array is large, we want to split it up into chunks that can be @@ -40,36 +36,19 @@ class PartialArrayState; // substantially expand the task queues. class PartialArrayTaskStepper { public: - PartialArrayTaskStepper(uint n_workers); - - struct Step { - size_t _index; // Array index for the step. - uint _ncreate; // Number of new tasks to create. - }; - - // Called with the length of the array to be processed and chunk size. - // Returns a Step with _index being the end of the initial chunk, which the - // caller should process. This is also the starting index for the next chunk to process. - // The _ncreate is the number of tasks to enqueue to continue processing the - // array. If _ncreate is zero then _index will be length. - inline Step start(size_t length, size_t chunk_size) const; + explicit PartialArrayTaskStepper(uint num_workers); - // Atomically increment state's index by chunk_size() to claim the next - // chunk. Returns a Step with _index being the starting index of the - // claimed chunk and _ncreate being the number of additional partial tasks - // to enqueue. - inline Step next(PartialArrayState* state) const; - - class TestSupport; // For unit tests + // Number of continuation tasks to publish after claiming the full-sized + // chunk starting at start. Initially there is one queued task. Every claim + // must publish the count returned here, even if other claims have advanced + // the shared index in the meantime. + inline uint continuation_tasks(size_t start, size_t length, size_t chunk_size) const; private: - // Limit on the number of partial array tasks to create for a given array. + // Limit on pending tasks for one array. uint _task_limit; // Maximum number of new tasks to create when processing an existing task. uint _task_fanout; - - // For unit tests. - inline Step next_impl(size_t length, size_t chunk_size, Atomic* index_addr) const; }; #endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp index fb58400cf271..9d28bb9fbe8f 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp @@ -27,28 +27,11 @@ #include "gc/shared/partialArrayTaskStepper.hpp" -#include "gc/shared/partialArrayState.hpp" #include "utilities/checkedCast.hpp" #include "utilities/debug.hpp" -PartialArrayTaskStepper::Step -PartialArrayTaskStepper::start(size_t length, size_t chunk_size) const { - size_t end = length % chunk_size; // End of initial chunk. - // If the initial chunk is the complete array, then don't need any partial - // tasks. Otherwise, start with just one partial task; see new task - // calculation in next(). - return Step{ end, (length > end) ? 1u : 0u }; -} - -PartialArrayTaskStepper::Step -PartialArrayTaskStepper::next_impl(size_t length, size_t chunk_size, Atomic* index_addr) const { - // The start of the next task is in the state's index. - // Atomically increment by the chunk size to claim the associated chunk. - // Because we limit the number of enqueued tasks to being no more than the - // number of remaining chunks to process, we can use an atomic add for the - // claim, rather than a CAS loop. - size_t start = index_addr->fetch_then_add(chunk_size, memory_order_relaxed); - +uint PartialArrayTaskStepper::continuation_tasks(size_t start, size_t length, size_t chunk_size) const { + precond(chunk_size > 0); assert(start < length, "invariant: start %zu, length %zu", start, length); assert(((length - start) % chunk_size) == 0, "invariant: start %zu, length %zu, chunk size %zu", @@ -80,12 +63,7 @@ PartialArrayTaskStepper::next_impl(size_t length, size_t chunk_size, Atomic(max_pending, remaining_tasks, _task_limit); uint ncreate = MIN2(_task_fanout, MIN2(remaining_tasks, _task_limit + 1) - pending); - return Step{ start, ncreate }; -} - -PartialArrayTaskStepper::Step -PartialArrayTaskStepper::next(PartialArrayState* state) const { - return next_impl(state->length(), state->chunk_size(), state->index_addr()); + return ncreate; } #endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_INLINE_HPP diff --git a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp index e2e78c7bc4a0..e9213eeeca80 100644 --- a/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp +++ b/src/hotspot/share/gc/shared/stringdedup/stringDedupTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,8 +22,8 @@ * */ +#include "cds/aotHeap.inline.hpp" #include "cds/aotMappedHeapLoader.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/altHashing.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/stringTable.hpp" @@ -518,7 +518,7 @@ void StringDedup::Table::install(typeArrayOop obj, uint hash_code) { // access to a String that is incompletely constructed; the value could be // set before the coder. bool StringDedup::Table::try_deduplicate_shared(oop java_string) { - assert(!HeapShared::is_loading_streaming_mode(), "should not reach here"); + assert(!AOTHeap::is_loading_streaming_mode(), "should not reach here"); typeArrayOop value = java_lang_String::value(java_string); assert(value != nullptr, "precondition"); assert(TypeArrayKlass::cast(value->klass())->element_type() == T_BYTE, "precondition"); @@ -564,7 +564,7 @@ bool StringDedup::Table::try_deduplicate_shared(oop java_string) { } bool StringDedup::Table::try_deduplicate_found_shared(oop java_string, oop found) { - assert(!HeapShared::is_loading_streaming_mode(), "should not reach here"); + assert(!AOTHeap::is_loading_streaming_mode(), "should not reach here"); _cur_stat.inc_known_shared(); typeArrayOop found_value = java_lang_String::value(found); if (found_value == java_lang_String::value(java_string)) { diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp index de0b838fe45d..26c4ab99c19e 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp @@ -106,7 +106,7 @@ void ShenandoahBarrierSetC1::enter_if_gc_state(LIRGenerator* gen, int flags, Cod __ branch_destination(slow_stub->continuation()); } -void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators) { +void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info, DecoratorSet decorators) { CodeStub* slow_stub; if (obj == LIR_OprFact::illegalOpr) { // Caller wants us to do the load. @@ -118,10 +118,11 @@ void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, L addr = LIR_OprFact::address(new LIR_Address(addr, T_OBJECT)); } - slow_stub = new ShenandoahKeepaliveBarrierStub(obj, addr); + slow_stub = new ShenandoahKeepaliveBarrierStub(obj, addr, info); } else { // Caller gave us the obj to work with. assert(addr == LIR_OprFact::illegalOpr, "sanity"); + assert(info == nullptr, "sanity"); assert(obj->is_register(), "must be"); assert(obj->type() == T_OBJECT, "must be an object"); @@ -172,7 +173,11 @@ void ShenandoahBarrierSetC1::store_at_resolved(LIRAccess& access, LIR_Opr value) LIRGenerator* gen = access.gen(); if (ShenandoahSATBBarrier && access.is_oop()) { - keepalive_barrier(gen, /* obj = */ LIR_OprFact::illegalOpr, /* addr = */ access.resolved_addr(), decorators); + CodeEmitInfo* info = access.access_emit_info(); + if (info != nullptr) { + info = new CodeEmitInfo(info); + } + keepalive_barrier(gen, /* obj = */ LIR_OprFact::illegalOpr, /* addr = */ access.resolved_addr(), info, decorators); } BarrierSetC1::store_at_resolved(access, value); @@ -225,7 +230,7 @@ void ShenandoahBarrierSetC1::load_at_resolved(LIRAccess& access, LIR_Opr result) Lcont_anonymous = new LabelObj(); generate_referent_check(access, Lcont_anonymous); } - keepalive_barrier(gen, /* obj = */ result, /* addr = */ LIR_OprFact::illegalOpr, decorators); + keepalive_barrier(gen, /* obj = */ result, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators); if (is_anonymous) { __ branch_destination(Lcont_anonymous->label()); } @@ -365,7 +370,7 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LI // Handle the previous value through SATB, as we are about to perform the store. __ load(addr->as_address_ptr(), tmp); if (ShenandoahSATBBarrier) { - keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, decorators); + keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators); } // Perform LRB on location to fix it up for this and all following accesses. @@ -398,7 +403,7 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRIt // Handle the previous value through SATB, as we are about to perform the store. __ load(addr->as_address_ptr(), tmp); if (ShenandoahSATBBarrier) { - keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, decorators); + keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, nullptr, decorators); } // Perform LRB on location to fix it up for this and all following accesses. diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp index ade3d9a79520..62d167aff67c 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp @@ -35,10 +35,11 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub { LIR_Opr _obj; LIR_Opr _addr; bool _do_load; + CodeEmitInfo* _info; public: - ShenandoahKeepaliveBarrierStub(LIR_Opr obj, LIR_Opr addr) : - _obj(obj), _addr(addr), _do_load(true) + ShenandoahKeepaliveBarrierStub(LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info) : + _obj(obj), _addr(addr), _do_load(true), _info(info) { assert(_obj->is_register(), "should be temporary register"); assert(_addr->is_address(), "should be the address of the field"); @@ -47,7 +48,7 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub { } ShenandoahKeepaliveBarrierStub(LIR_Opr obj) : - _obj(obj), _addr(LIR_OprFact::illegalOpr), _do_load(false) + _obj(obj), _addr(LIR_OprFact::illegalOpr), _do_load(false), _info(nullptr) { assert(_obj->is_register(), "should be a register"); FrameMap* f = Compilation::current()->frame_map(); @@ -57,10 +58,15 @@ class ShenandoahKeepaliveBarrierStub: public CodeStub { LIR_Opr addr() const { return _addr; } LIR_Opr obj() const { return _obj; } bool do_load() const { return _do_load; } + CodeEmitInfo* info() const { return _info; } virtual void emit_code(LIR_Assembler* e); virtual void visit(LIR_OpVisitState* visitor) { - visitor->do_slow_case(); + if (_info != nullptr) { + visitor->do_slow_case(_info); + } else { + visitor->do_slow_case(); + } if (_do_load) { visitor->do_input(_addr); visitor->do_temp(_addr); @@ -121,7 +127,7 @@ class ShenandoahBarrierSetC1 : public BarrierSetC1 { void enter_if_gc_state(LIRGenerator* gen, int flags, CodeStub* slow_stub); - void keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators); + void keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, CodeEmitInfo* info, DecoratorSet decorators); void load_reference_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators); void card_barrier(LIRGenerator* gen, LIR_Opr addr, DecoratorSet decorators); diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp index 2b8c12c41a68..d876a5517755 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp @@ -600,7 +600,7 @@ void ShenandoahBarrierSetC2::clone(GraphKit* kit, Node* src_base, Node* dst_base ac->set_clone_array(); Node* n = kit->gvn().transform(ac); if (n == ac) { - ac->set_adr_type(TypeRawPtr::BOTTOM); + ac->set_out_adr_type(TypeRawPtr::BOTTOM); kit->set_predefined_output_for_runtime_call(ac, ac->in(TypeFunc::Memory), TypeRawPtr::BOTTOM); } else { kit->set_all_memory(n); @@ -775,13 +775,13 @@ void ShenandoahBarrierSetC2::verify_gc_barriers(Compile* compile, CompilePhase p const TypePtr* adr_type = nullptr; if (is_Load(opc)) { bd = n->as_Load()->barrier_data(); - adr_type = n->as_Load()->adr_type(); + adr_type = n->as_Load()->in_adr_type(); } else if (is_Store(opc)) { bd = n->as_Store()->barrier_data(); - adr_type = n->as_Store()->adr_type(); + adr_type = n->as_Store()->out_adr_type(); } else if (is_LoadStore(opc)) { bd = n->as_LoadStore()->barrier_data(); - adr_type = n->as_LoadStore()->adr_type(); + adr_type = n->as_LoadStore()->out_adr_type(); } else if (n->is_Mem()) { bd = MemNode::barrier_data(n); verify_gc_barrier_assert(bd == 0, "Other mem nodes should have no barrier data", bd, n); diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp index 55a582cd54c2..43277c11eda4 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp @@ -77,7 +77,7 @@ void ShenandoahAdaptiveHeuristics::post_initialize() { } void ShenandoahAdaptiveHeuristics::compute_headroom_adjustment() { - // The trigger threshold represents mutator available - "head room". + // The trigger threshold represents mutator available - "headroom". // We plan for GC to finish before the amount of allocated memory exceeds trigger threshold. This is the same as saying we // intend to finish GC before the amount of available memory is less than the allocation headroom. Headroom is the planned // safety buffer to allow a small amount of additional allocation to take place in case we were overly optimistic in delaying @@ -152,15 +152,8 @@ void ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(Shenand } } -void ShenandoahAdaptiveHeuristics::add_degenerated_gc_time(double time_at_start, double gc_time) { - // Conservatively add sample into linear model, if this time is above the predicted concurrent gc time - if (_cycles.predict_duration(time_at_start, _margin_of_error_sd) < gc_time) { - _cycles.record_duration(time_at_start, gc_time); - } -} - -void ShenandoahAdaptiveHeuristics::record_success_concurrent() { - ShenandoahHeuristics::record_success_concurrent(); +void ShenandoahAdaptiveHeuristics::record_concurrent_completion() { + ShenandoahHeuristics::record_concurrent_completion(); // We add this time even if it is a shortened cycle. There is a risk that this pulls // the gc time trend down, but it is still a more accurate view than excluding times @@ -213,13 +206,6 @@ void ShenandoahAdaptiveHeuristics::record_success_concurrent() { } } -void ShenandoahAdaptiveHeuristics::record_degenerated(bool is_generational_global) { - ShenandoahHeuristics::record_degenerated(is_generational_global); - if (!is_generational_global) { - add_degenerated_gc_time(_precursor_cycle_start, elapsed_degenerated_cycle_time()); - } -} - bool ShenandoahAdaptiveHeuristics::should_start_gc() { const size_t capacity = ShenandoahHeap::heap()->soft_max_capacity(); const size_t available = _space_info->soft_mutator_available(); @@ -227,7 +213,7 @@ bool ShenandoahAdaptiveHeuristics::should_start_gc() { log_debug(gc, ergo)("should_start_gc calculation: available: " PROPERFMT ", soft_max_capacity: " PROPERFMT, PROPERFMTARGS(available), PROPERFMTARGS(capacity)); - if (_start_gc_is_pending) { + if (_start_gc_is_pending.load_relaxed()) { log_info(gc, ergo)("GC start is already pending"); return true; } @@ -308,7 +294,7 @@ bool ShenandoahAdaptiveHeuristics::trigger_average_allocation_rate(const Shenand // The following trace represents an actual workload, with allocation rates sampled at 10 Hz, the default behavior before // introduction of accelerated allocation rate detection. Though the allocation rate is seen to be increasing at times // 101.907 and 102.007 and 102.108, the newly sampled allocation rate is not enough to trigger GC because the headroom is -// still quite large. In fact, GC is not triggered until time 102.409s, and this GC degenerates. +// still quite large. In fact, GC is not triggered until time 102.409s, and this GC stalls. // // Sample Time (s) Allocation Rate (MB/s) Headroom (GB) // 101.807 0.0 26.93 diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp index b571f81140c4..8705bf86a54a 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp @@ -29,7 +29,6 @@ #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/shenandoahAllocRate.hpp" #include "gc/shenandoah/shenandoahCycleDuration.hpp" -#include "gc/shenandoah/shenandoahPhaseTimings.hpp" #include "utilities/numberSeq.hpp" @@ -37,8 +36,7 @@ * The adaptive heuristic tracks the allocation behavior and average cycle * time of the application. It attempts to start a cycle with enough time * to complete before the available memory is exhausted. It errors on the - * side of starting cycles early to avoid allocation failures (degenerated - * cycles). + * side of starting cycles early to avoid allocation failures. * * This heuristic limits the number of regions for evacuation such that the * evacuation reserve is respected. This helps it avoid allocation failures @@ -56,8 +54,7 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics { // before we need to start the next GC. void start_idle_span() override; - void record_success_concurrent() override; - void record_degenerated(bool is_generational_global) override; + void record_concurrent_completion() override; bool should_start_gc() override; @@ -113,8 +110,6 @@ class ShenandoahAdaptiveHeuristics : public ShenandoahHeuristics { // bytes of headroom at which we should trigger GC size_t _headroom_adjustment; - void add_degenerated_gc_time(double timestamp_at_start, double duration); - // A conservative minimum threshold of free space that we'll try to maintain when possible. // For example, we might trigger a concurrent gc if we are likely to drop below // this threshold, or we might consider this when dynamically resizing generations diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp index 31f32836ceb3..c941d024e2ff 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp @@ -24,6 +24,7 @@ */ #include "gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahAllocRate.inline.hpp" #include "gc/shenandoah/shenandoahCollectionSet.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp index 5c07fcb6b59f..21ac29895538 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp @@ -24,6 +24,7 @@ */ #include "gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp" #include "gc/shenandoah/shenandoahGlobalGeneration.hpp" diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp index 1e96a665704b..da3998f58543 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp @@ -159,15 +159,17 @@ class ShenandoahGlobalCSetBudget { class ShenandoahGlobalHeuristics : public ShenandoahGenerationalHeuristics { public: - ShenandoahGlobalHeuristics(ShenandoahGlobalGeneration* generation); + explicit ShenandoahGlobalHeuristics(ShenandoahGlobalGeneration* generation); + +protected: void select_collection_set_regions(ShenandoahCollectionSet* cset, RegionData* data, size_t size, size_t actual_free) override; private: void choose_global_collection_set(ShenandoahCollectionSet* cset, - const ShenandoahHeuristics::RegionData* data, + const RegionData* data, size_t size, size_t actual_free, size_t cur_young_garbage) const; }; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp index d0a5d0e0cc10..aa2cb70e0442 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp @@ -55,12 +55,10 @@ ShenandoahHeuristics::ShenandoahHeuristics(ShenandoahSpaceInfo* space_info) : _most_recent_planned_sleep_interval(0.0), _start_gc_is_pending(false), _declined_trigger_count(0), - _most_recent_declined_trigger_count(0), _space_info(space_info), _region_data(nullptr), _guaranteed_gc_interval(0), - _precursor_cycle_start(os::elapsedTime()), - _cycle_start(_precursor_cycle_start), + _cycle_start(os::elapsedTime()), _last_cycle_end(0), _gc_times_learned(0), _gc_time_penalties(0), @@ -164,17 +162,10 @@ void ShenandoahHeuristics::start_idle_span() { // do nothing } -void ShenandoahHeuristics::record_degenerated_cycle_start(bool out_of_cycle) { - if (out_of_cycle) { - _precursor_cycle_start = _cycle_start = os::elapsedTime(); - } else { - _precursor_cycle_start = _cycle_start; - _cycle_start = os::elapsedTime(); - } -} - void ShenandoahHeuristics::record_cycle_start() { _cycle_start = os::elapsedTime(); + cancel_trigger_request(); + log_debug(gc, ergo)("Declined trigger count at start: %zu", _declined_trigger_count.load_relaxed()); } void ShenandoahHeuristics::record_cycle_end() { @@ -188,7 +179,7 @@ void ShenandoahHeuristics::record_cycle_end() { } bool ShenandoahHeuristics::should_start_gc() { - if (_start_gc_is_pending) { + if (_start_gc_is_pending.load_relaxed()) { log_info(gc, ergo)("GC start is already pending"); return true; } @@ -213,33 +204,15 @@ bool ShenandoahHeuristics::should_start_gc() { return false; } -bool ShenandoahHeuristics::should_degenerate_cycle() { - return ShenandoahHeap::heap()->shenandoah_policy()->consecutive_degenerated_gc_count() <= ShenandoahFullGCThreshold; -} - void ShenandoahHeuristics::adjust_penalty(intx step) { - assert(0 <= _gc_time_penalties && _gc_time_penalties <= 100, - "In range before adjustment: %zd", _gc_time_penalties); - if ((_most_recent_declined_trigger_count <= Penalty_Free_Declinations) && (step > 0)) { - // Don't penalize if heuristics are not responsible for a negative outcome. Allow Penalty_Free_Declinations following - // previous GC for self calibration without penalty. - step = 0; - } - - intx new_val = _gc_time_penalties + step; - if (new_val < 0) { - new_val = 0; + const intx adjusted = clamp(_gc_time_penalties + step, Min_Penalty, Max_Penalty); + if (adjusted != _gc_time_penalties) { + _gc_time_penalties = adjusted; + log_info(gc, ergo)("Adjusted heuristic penalty: %zd", _gc_time_penalties); } - if (new_val > 100) { - new_val = 100; - } - _gc_time_penalties = new_val; - - assert(0 <= _gc_time_penalties && _gc_time_penalties <= 100, - "In range after adjustment: %zd", _gc_time_penalties); } -void ShenandoahHeuristics::log_trigger(const char* fmt, ...) { +void ShenandoahHeuristics::log_trigger(const char* fmt, ...) const { LogTarget(Info, gc) lt; if (lt.is_enabled()) { ResourceMark rm; @@ -257,30 +230,36 @@ void ShenandoahHeuristics::log_trigger(const char* fmt, ...) { } } -void ShenandoahHeuristics::record_success_concurrent() { - _gc_times_learned++; - - adjust_penalty(Concurrent_Adjust); +ShenandoahHeuristics::PenaltyData ShenandoahHeuristics::consume_penalty_data() { + const size_t declines = _declined_trigger_count.exchange(0, memory_order_relaxed); + const bool stalls = _allocation_stalls.exchange(false, memory_order_relaxed); + log_debug(gc, ergo)("Declined trigger count at end: %zu", declines); + return { declines, stalls }; } -void ShenandoahHeuristics::record_degenerated(bool is_generational_global) { - - if (!is_generational_global) { - // We don't penalize generational GC heuristics for global GC because heuristics predict based on assumption of young GC. - _most_recent_declined_trigger_count = _declined_trigger_count; - _declined_trigger_count = 0; - } else { - _most_recent_declined_trigger_count = _declined_trigger_count = 0; +void ShenandoahHeuristics::record_concurrent_completion() { + _gc_times_learned++; + const PenaltyData data = consume_penalty_data(); + if (!data.stalls) { + adjust_penalty(Concurrent_Adjust); + } else if (data.declined_triggers > Penalty_Free_Declinations) { + // There were stalls _and_ the trigger was lazy, penalize it. + adjust_penalty(Stall_Penalty); } - adjust_penalty(Degenerated_Penalty); } -void ShenandoahHeuristics::record_success_full() { - adjust_penalty(Full_Penalty); +void ShenandoahHeuristics::record_allocation_stall() { + _allocation_stalls.store_relaxed(true); } -void ShenandoahHeuristics::record_allocation_failure_gc() { - // Do nothing. +void ShenandoahHeuristics::record_full_gc(GCCause::Cause cause) { + const PenaltyData data = consume_penalty_data(); + if (cause == GCCause::_shenandoah_upgrade_to_full_gc) { + adjust_penalty(Full_Penalty); + } else if (data.stalls && data.declined_triggers > Penalty_Free_Declinations) { + // There were stalls _and_ the trigger was lazy, penalize it. + adjust_penalty(Stall_Penalty); + } } void ShenandoahHeuristics::record_requested_gc() { @@ -309,13 +288,6 @@ double ShenandoahHeuristics::elapsed_cycle_time() const { return os::elapsedTime() - _cycle_start; } - -// Includes the time spent in abandoned concurrent GC cycle that may have triggered this degenerated cycle. -double ShenandoahHeuristics::elapsed_degenerated_cycle_time() const { - double now = os::elapsedTime(); - return now - _precursor_cycle_start; -} - #ifdef ASSERT void ShenandoahHeuristics::assert_humongous_mark_consistency(ShenandoahHeapRegion* region) { assert(region->is_humongous(), "Region %zu must be humongous", region->index()); diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp index 1deb98c3f8a9..21bad3058166 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp @@ -26,11 +26,11 @@ #ifndef SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHHEURISTICS_HPP #define SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHHEURISTICS_HPP +#include "gc/shared/gcCause.hpp" #include "gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" #include "memory/allocation.hpp" #include "runtime/globals_extension.hpp" -#include "utilities/numberSeq.hpp" #define SHENANDOAH_ERGO_DISABLE_FLAG(name) \ do { \ @@ -66,12 +66,14 @@ class ShenandoahHeapRegion; * cycle. */ class ShenandoahHeuristics : public CHeapObj { - static const intx Concurrent_Adjust = -1; // recover from penalties - static const intx Degenerated_Penalty = 10; // how much to penalize average GC duration history on Degenerated GC - static const intx Full_Penalty = 20; // how much to penalize average GC duration history on Full GC + static constexpr intx Concurrent_Adjust = -1; // recover from penalties + static constexpr intx Stall_Penalty = 10; // how much to penalize average GC duration history on allocation stall + static constexpr intx Full_Penalty = 20; // how much to penalize average GC duration history on Full GC + static constexpr intx Min_Penalty = 0; + static constexpr intx Max_Penalty = 100; // How many times can I decline a trigger opportunity without being penalized for excessive idle span before trigger? - static const size_t Penalty_Free_Declinations = 16; + static constexpr size_t Penalty_Free_Declinations = 16; #ifdef ASSERT enum UnionTag { @@ -79,24 +81,34 @@ class ShenandoahHeuristics : public CHeapObj { }; #endif -private: double _most_recent_trigger_evaluation_time; double _most_recent_planned_sleep_interval; protected: static constexpr uint Moving_Average_Samples = 10; // Number of samples to store in moving averages - bool _start_gc_is_pending; // True denotes that GC has been triggered, so no need to trigger again. - size_t _declined_trigger_count; // This counts how many times since previous GC finished that this - // heuristic has answered false to should_start_gc(). - size_t _most_recent_declined_trigger_count; - // This represents the value of _declined_trigger_count as captured at the - // moment the most recent GC effort was triggered. In case the most recent - // concurrent GC effort degenerates, the value of this variable allows us to - // differentiate between degeneration because heuristic was overly optimistic - // in delaying the trigger vs. degeneration for other reasons (such as the - // most recent GC triggered "immediately" after previous GC finished, but the - // free headroom has already been depleted). + // True denotes that GC has been triggered, so no need to trigger again. + shenandoah_padding(0); + Atomic _start_gc_is_pending; + + // This counts how many times since previous GC finished that this heuristic has answered false to should_start_gc(). + // If allocations stall during a concurrent gc and this is above Penalty_Free_Declinations at the end of the cycle, + // the heuristics will be 'penalized' in a way that will make them start the next concurrent cycle sooner. Also + // note that once the trigger has been accepted, _start_gc_is_pending will be set and subsequent attempts to evaluate + // the trigger conditions will return early and will not increase _declined_trigger_count. This is written to + // by both the regulator and control thread, read by control thread. + shenandoah_padding(1); + Atomic _declined_trigger_count; + shenandoah_padding(2); + Atomic _allocation_stalls; + shenandoah_padding(3); + + // Snapshot declined trigger count and alloc stalls to compute appropriate penalties (if warranted) + struct PenaltyData { + size_t declined_triggers; + bool stalls; + }; + class RegionData { private: ShenandoahHeapRegion* _region; @@ -110,7 +122,7 @@ class ShenandoahHeuristics : public CHeapObj { public: - inline void clear() { + void clear() { _region = nullptr; _region_union._garbage = 0; #ifdef ASSERT @@ -118,7 +130,7 @@ class ShenandoahHeuristics : public CHeapObj { #endif } - inline void set_region_and_garbage(ShenandoahHeapRegion* region, size_t garbage) { + void set_region_and_garbage(ShenandoahHeapRegion* region, size_t garbage) { _region = region; _region_union._garbage = garbage; #ifdef ASSERT @@ -126,7 +138,7 @@ class ShenandoahHeuristics : public CHeapObj { #endif } - inline void set_region_and_livedata(ShenandoahHeapRegion* region, size_t live) { + void set_region_and_livedata(ShenandoahHeapRegion* region, size_t live) { _region = region; _region_union._live_data = live; #ifdef ASSERT @@ -134,24 +146,24 @@ class ShenandoahHeuristics : public CHeapObj { #endif } - inline void update_livedata(size_t live) { + void update_livedata(size_t live) { _region_union._live_data = live; #ifdef ASSERT _union_tag = is_live_data; #endif } - inline ShenandoahHeapRegion* get_region() const { + ShenandoahHeapRegion* get_region() const { assert(_union_tag != is_uninitialized, "Cannot fetch region from uninitialized RegionData"); return _region; } - inline size_t get_garbage() const { + size_t get_garbage() const { assert(_union_tag == is_garbage, "Invalid union fetch"); return _region_union._garbage; } - inline size_t get_livedata() const { + size_t get_livedata() const { assert(_union_tag == is_live_data, "Invalid union fetch"); return _region_union._live_data; } @@ -176,7 +188,6 @@ class ShenandoahHeuristics : public CHeapObj { size_t _guaranteed_gc_interval; - double _precursor_cycle_start; double _cycle_start; double _last_cycle_end; @@ -198,19 +209,15 @@ class ShenandoahHeuristics : public CHeapObj { virtual void adjust_penalty(intx step); - inline void accept_trigger() { - _start_gc_is_pending = true; - } - - inline void decline_trigger() { - _declined_trigger_count++; + void decline_trigger() { + _declined_trigger_count.add_then_fetch(1UL, memory_order_relaxed); } - inline double get_most_recent_wake_time() const { + double get_most_recent_wake_time() const { return _most_recent_trigger_evaluation_time; } - inline double get_planned_sleep_interval() const { + double get_planned_sleep_interval() const { return _most_recent_planned_sleep_interval; } @@ -236,9 +243,6 @@ class ShenandoahHeuristics : public CHeapObj { } virtual void record_cycle_start(); - - void record_degenerated_cycle_start(bool out_of_cycle); - virtual void record_cycle_end(); void update_should_start_query_times(double now, double planned_sleep_interval) { @@ -246,24 +250,24 @@ class ShenandoahHeuristics : public CHeapObj { _most_recent_planned_sleep_interval = planned_sleep_interval; } - virtual bool should_start_gc(); - - inline void cancel_trigger_request() { - _start_gc_is_pending = false; + void accept_trigger() { + _start_gc_is_pending.store_relaxed(true); } - virtual bool should_degenerate_cycle(); - - virtual void record_success_concurrent(); + void cancel_trigger_request() { + _start_gc_is_pending.store_relaxed(false); + } - virtual void record_degenerated(bool is_generational_global); + virtual bool should_start_gc(); - virtual void record_success_full(); + virtual void record_concurrent_completion(); - virtual void record_allocation_failure_gc(); + virtual void record_full_gc(GCCause::Cause cause); virtual void record_requested_gc(); + virtual void record_allocation_stall(); + // Choose the collection set, returning the number of regions that need to be transferred to the old reserve from the young // reserve in order to effectively evacuate the chosen collection set. In non-generational mode, the return value is 0. virtual void choose_collection_set(ShenandoahCollectionSet* collection_set); @@ -281,10 +285,11 @@ class ShenandoahHeuristics : public CHeapObj { virtual void post_initialize(); double elapsed_cycle_time() const; - double elapsed_degenerated_cycle_time() const; // Format prefix and emit log message indicating a GC cycle hs been triggered - void log_trigger(const char* fmt, ...) ATTRIBUTE_PRINTF(2, 3); + void log_trigger(const char* fmt, ...) const ATTRIBUTE_PRINTF(2, 3); + + PenaltyData consume_penalty_data(); DEBUG_ONLY(static void assert_humongous_mark_consistency(ShenandoahHeapRegion* region)); }; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 1b956b6994a3..6ce679555d8c 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -848,22 +848,16 @@ void ShenandoahOldHeuristics::adjust_old_garbage_threshold() { } } -void ShenandoahOldHeuristics::record_success_concurrent() { +void ShenandoahOldHeuristics::record_concurrent_completion() { // Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger. clear_triggers(); - this->ShenandoahHeuristics::record_success_concurrent(); + this->ShenandoahHeuristics::record_concurrent_completion(); } -void ShenandoahOldHeuristics::record_degenerated(bool is_generational_global) { +void ShenandoahOldHeuristics::record_full_gc(GCCause::Cause cause) { // Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger. clear_triggers(); - this->ShenandoahHeuristics::record_degenerated(is_generational_global); -} - -void ShenandoahOldHeuristics::record_success_full() { - // Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger. - clear_triggers(); - this->ShenandoahHeuristics::record_success_full(); + this->ShenandoahHeuristics::record_full_gc(cause); } const char* ShenandoahOldHeuristics::name() { diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp index 37d186a991f9..63a4cbed7069 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp @@ -244,11 +244,9 @@ class ShenandoahOldHeuristics : public ShenandoahHeuristics { // Returns true if the old generation needs to prepare for marking, or continue marking. bool should_resume_old_cycle(); - void record_success_concurrent() override; + void record_concurrent_completion() override; - void record_degenerated(bool is_generational_global) override; - - void record_success_full() override; + void record_full_gc(GCCause::Cause cause) override; const char* name() override; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp index b5e9cc433ea6..f1619c2e3848 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,8 @@ #include "gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp" -#include "gc/shenandoah/shenandoahCollectionSet.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" -#include "logging/log.hpp" -#include "logging/logTag.hpp" ShenandoahPassiveHeuristics::ShenandoahPassiveHeuristics(ShenandoahSpaceInfo* space_info) : ShenandoahHeuristics(space_info) {} @@ -45,35 +42,9 @@ bool ShenandoahPassiveHeuristics::should_unload_classes() { return can_unload_classes(); } -bool ShenandoahPassiveHeuristics::should_degenerate_cycle() { - // Always fail to Degenerated GC, if enabled - return ShenandoahDegeneratedGC; -} - void ShenandoahPassiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, RegionData* data, size_t size, size_t actual_free) { - assert(ShenandoahDegeneratedGC, "This path is only taken for Degenerated GC"); - - // Do not select too large CSet that would overflow the available free space. - // Take at least the entire evacuation reserve, and be free to overflow to free space. - size_t max_capacity = _space_info->max_capacity(); - size_t available = MAX2(max_capacity / 100 * ShenandoahEvacReserve, actual_free); - size_t max_cset = (size_t)(available / ShenandoahEvacWaste); - - log_info(gc, ergo)("CSet Selection. Actual Free: %zu%s, Max CSet: %zu%s", - byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free), - byte_size_in_proper_unit(max_cset), proper_unit_for_byte_size(max_cset)); - - size_t threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100; - - size_t live_cset = 0; - for (size_t idx = 0; idx < size; idx++) { - ShenandoahHeapRegion* r = data[idx].get_region(); - size_t new_cset = live_cset + r->get_live_data_bytes(); - if (new_cset < max_cset && r->garbage() > threshold) { - live_cset = new_cset; - cset->add_region(r); - } - } + // Passive mode only runs full GCs. + ShouldNotReachHere(); } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp index 3cb85f5d05f0..809c8df60036 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp @@ -29,7 +29,7 @@ /* * The passive heuristic is for use only with the passive mode. In - * the passive mode, Shenandoah only performs STW (i.e., degenerated) + * the passive mode, Shenandoah only performs STW (i.e., full) * collections. All the barriers are disabled and there are no concurrent * activities. Therefore, this heuristic _never_ triggers a cycle. It * will select regions for evacuation based on ShenandoahEvacReserve, @@ -44,8 +44,6 @@ class ShenandoahPassiveHeuristics : public ShenandoahHeuristics { bool should_unload_classes() override; - bool should_degenerate_cycle() override; - void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, RegionData* data, size_t data_size, size_t free) override; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp index 723fb631e755..c8304ac1836f 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp @@ -41,6 +41,9 @@ class ShenandoahYoungHeuristics : public ShenandoahGenerationalHeuristics { bool should_start_gc() override; + // Young collections can never unload classes + bool can_unload_classes() override { return false; } + size_t bytes_of_allocation_runway_before_gc_trigger(size_t young_regions_to_be_reclaimed); protected: diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp index a880baccdf7d..481e28e74272 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp @@ -22,26 +22,20 @@ * */ -#include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp" #include "gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp" #include "gc/shenandoah/mode/shenandoahPassiveMode.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" -#include "logging/log.hpp" -#include "logging/logTag.hpp" #include "runtime/globals_extension.hpp" -#include "runtime/java.hpp" void ShenandoahPassiveMode::initialize_flags() const { // Do not allow concurrent cycles. FLAG_SET_DEFAULT(ExplicitGCInvokesConcurrent, false); - // No need for evacuation reserve with Full GC, only for Degenerated GC. - if (!ShenandoahDegeneratedGC) { - if (FLAG_IS_DEFAULT(ShenandoahEvacReserve)) { - log_info(gc)("Heuristics sets -XX:ShenandoahEvacReserve=0"); - FLAG_SET_DEFAULT(ShenandoahEvacReserve, 0); - } + // No need for evacuation reserve with Full GC + if (FLAG_IS_DEFAULT(ShenandoahEvacReserve)) { + log_info(gc)("Heuristics sets -XX:ShenandoahEvacReserve=0"); + FLAG_SET_DEFAULT(ShenandoahEvacReserve, 0); } // Disable known barriers by default. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 3c23dd0bf233..d90100953920 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -44,24 +44,17 @@ ShenandoahAgeCensus::ShenandoahAgeCensus(uint max_workers) } _global_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, MAX_SNAPSHOTS, mtGC); - CENSUS_NOISE(_global_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, MAX_SNAPSHOTS, mtGC);) _tenuring_threshold = NEW_C_HEAP_ARRAY(uint, MAX_SNAPSHOTS, mtGC); - CENSUS_NOISE(_skipped = 0); - NOT_PRODUCT(_counted = 0); - NOT_PRODUCT(_total = 0); for (int i = 0; i < MAX_SNAPSHOTS; i++) { // Note that we don't now get perfdata from age_table _global_age_tables[i] = new AgeTable(false); - CENSUS_NOISE(_global_noise[i].clear();) // Sentinel value _tenuring_threshold[i] = MAX_COHORTS; } _local_age_tables = NEW_C_HEAP_ARRAY(AgeTable*, _max_workers, mtGC); - CENSUS_NOISE(_local_noise = NEW_C_HEAP_ARRAY(ShenandoahNoiseStats, max_workers, mtGC);) for (uint i = 0; i < _max_workers; i++) { _local_age_tables[i] = new AgeTable(false); - CENSUS_NOISE(_local_noise[i].clear();) } _epoch = MAX_SNAPSHOTS - 1; // see prepare_for_census_update() } @@ -72,34 +65,14 @@ ShenandoahAgeCensus::~ShenandoahAgeCensus() { } FREE_C_HEAP_ARRAY(_global_age_tables); FREE_C_HEAP_ARRAY(_tenuring_threshold); - CENSUS_NOISE(FREE_C_HEAP_ARRAY(_global_noise)); if (_local_age_tables) { for (uint i = 0; i < _max_workers; i++) { delete _local_age_tables[i]; } FREE_C_HEAP_ARRAY(_local_age_tables); - CENSUS_NOISE(FREE_C_HEAP_ARRAY(_local_noise)); } } -#ifdef SHENANDOAH_CENSUS_NOISE -void ShenandoahAgeCensus::add_skipped(size_t size, uint worker_id) { - _local_noise[worker_id].skipped += size; -} - -void ShenandoahAgeCensus::add_aged(size_t size, uint worker_id) { - _local_noise[worker_id].aged += size; -} - -void ShenandoahAgeCensus::add_clamped(size_t size, uint worker_id) { - _local_noise[worker_id].clamped += size; -} - -void ShenandoahAgeCensus::add_young(size_t size, uint worker_id) { - _local_noise[worker_id].young += size; -} -#endif // SHENANDOAH_CENSUS_NOISE - // Prepare for a new census update, by clearing appropriate global slots. void ShenandoahAgeCensus::prepare_for_census_update() { assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); @@ -107,7 +80,6 @@ void ShenandoahAgeCensus::prepare_for_census_update() { _epoch=0; } _global_age_tables[_epoch]->clear(); - CENSUS_NOISE(_global_noise[_epoch].clear();) } // Update the census data from appropriate sources, @@ -115,7 +87,6 @@ void ShenandoahAgeCensus::prepare_for_census_update() { void ShenandoahAgeCensus::update_census(size_t age0_pop) { prepare_for_census_update(); assert(_global_age_tables[_epoch]->is_clear(), "Dirty decks"); - CENSUS_NOISE(assert(_global_noise[_epoch].is_clear(), "Dirty decks");) // Seed cohort 0 with population that may have been missed during // regular census. @@ -124,19 +95,11 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop) { // Merge data from local age tables into the global age table for the epoch, // clearing the local tables. for (uint i = 0; i < _max_workers; i++) { - // age stats _global_age_tables[_epoch]->merge(_local_age_tables[i]); _local_age_tables[i]->clear(); // clear for next census - // Merge noise stats - CENSUS_NOISE(_global_noise[_epoch].merge(_local_noise[i]);) - CENSUS_NOISE(_local_noise[i].clear();) } update_tenuring_threshold(); - - // used for checking reasonableness of census coverage, non-product - // only. - NOT_PRODUCT(update_total();) } size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const { @@ -155,7 +118,6 @@ void ShenandoahAgeCensus::reset_global() { assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); for (uint i = 0; i < MAX_SNAPSHOTS; i++) { _global_age_tables[i]->clear(); - CENSUS_NOISE(_global_noise[i].clear();) } _epoch = MAX_SNAPSHOTS; assert(_epoch < MAX_SNAPSHOTS, "Error"); @@ -165,7 +127,6 @@ void ShenandoahAgeCensus::reset_global() { void ShenandoahAgeCensus::reset_local() { for (uint i = 0; i < _max_workers; i++) { _local_age_tables[i]->clear(); - CENSUS_NOISE(_local_noise[i].clear();) } } @@ -174,9 +135,7 @@ void ShenandoahAgeCensus::reset_local() { bool ShenandoahAgeCensus::is_clear_global() { assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); for (uint i = 0; i < MAX_SNAPSHOTS; i++) { - bool clear = _global_age_tables[i]->is_clear(); - CENSUS_NOISE(clear |= _global_noise[i].is_clear();) - if (!clear) { + if (!_global_age_tables[i]->is_clear()) { return false; } } @@ -186,35 +145,12 @@ bool ShenandoahAgeCensus::is_clear_global() { // Is local census information clear? bool ShenandoahAgeCensus::is_clear_local() { for (uint i = 0; i < _max_workers; i++) { - bool clear = _local_age_tables[i]->is_clear(); - CENSUS_NOISE(clear |= _local_noise[i].is_clear();) - if (!clear) { + if (!_local_age_tables[i]->is_clear()) { return false; } } return true; } - -size_t ShenandoahAgeCensus::get_all_ages(uint snap) { - assert(snap < MAX_SNAPSHOTS, "Out of bounds"); - size_t pop = 0; - const AgeTable* pv = _global_age_tables[snap]; - for (uint i = 0; i < MAX_COHORTS; i++) { - pop += pv->sizes[i]; - } - return pop; -} - -size_t ShenandoahAgeCensus::get_skipped(uint snap) { - assert(snap < MAX_SNAPSHOTS, "Out of bounds"); - return _global_noise[snap].skipped; -} - -void ShenandoahAgeCensus::update_total() { - _counted = get_all_ages(_epoch); - _skipped = get_skipped(_epoch); - _total = _counted + _skipped; -} #endif // !PRODUCT void ShenandoahAgeCensus::update_tenuring_threshold() { @@ -332,7 +268,6 @@ void ShenandoahAgeCensus::print() { const uint tt = tenuring_threshold(); - size_t total= 0; for (uint i = 1; i < MAX_COHORTS; i++) { const size_t prev_pop = prev_pv->sizes[i-1]; // (i-1) OK because i >= 1 const size_t cur_pop = cur_pv->sizes[i]; @@ -342,26 +277,9 @@ void ShenandoahAgeCensus::print() { ls.print_cr(" - age %3u: prev %10zu bytes, curr %10zu bytes, mortality %.2f ", i, prev_pop * oopSize, cur_pop * oopSize, mr); } - total += cur_pop; if (i == tt) { // Underline the cohort for tenuring threshold (if < MAX_COHORTS) ls.print_cr("----------------------------------------------------------------------------"); } } - CENSUS_NOISE(_global_noise[cur_epoch].print(ls, total);) -} - -#ifdef SHENANDOAH_CENSUS_NOISE -void ShenandoahNoiseStats::print(LogStream& ls, const size_t total) { - if (total > 0) { - const float f_skipped = (float)skipped/(float)total; - const float f_aged = (float)aged/(float)total; - const float f_clamped = (float)clamped/(float)total; - const float f_young = (float)young/(float)total; - ls.print_cr("Skipped: %10zu (%.2f), R-Aged: %10zu (%.2f), " - "Clamped: %10zu (%.2f), R-Young: %10zu (%.2f)", - skipped*oopSize, f_skipped, aged*oopSize, f_aged, - clamped*oopSize, f_clamped, young*oopSize, f_young); - } } -#endif // SHENANDOAH_CENSUS_NOISE diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 2d79eb559609..6577715f44d3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -27,55 +27,6 @@ #include "gc/shared/ageTable.hpp" -#ifndef PRODUCT -// Enable noise instrumentation -#define SHENANDOAH_CENSUS_NOISE 1 -#endif // PRODUCT - -#ifdef SHENANDOAH_CENSUS_NOISE - -#define CENSUS_NOISE(x) x -#define NO_CENSUS_NOISE(x) - -class LogStream; - -struct ShenandoahNoiseStats { - size_t skipped; // Volume of objects skipped - size_t aged; // Volume of objects from aged regions - size_t clamped; // Volume of objects whose ages were clamped - size_t young; // Volume of (rejuvenated) objects of retrograde age - - ShenandoahNoiseStats() { - clear(); - } - - void clear() { - skipped = 0; - aged = 0; - clamped = 0; - young = 0; - } - -#ifndef PRODUCT - bool is_clear() { - return (skipped + aged + clamped + young) == 0; - } -#endif // !PRODUCT - - void merge(ShenandoahNoiseStats& other) { - skipped += other.skipped; - aged += other.aged; - clamped += other.clamped; - young += other.young; - } - - void print(LogStream& ls, size_t total); -}; -#else // SHENANDOAH_CENSUS_NOISE -#define CENSUS_NOISE(x) -#define NO_CENSUS_NOISE(x) x -#endif // SHENANDOAH_CENSUS_NOISE - // A class for tracking a sequence of cohort population vectors (or, // interchangeably, age tables) for up to C=MAX_COHORTS age cohorts, where a cohort // represents the set of objects allocated during a specific inter-GC epoch. @@ -102,19 +53,6 @@ class ShenandoahAgeCensus: public CHeapObj { AgeTable** _global_age_tables; // Global age tables used for adapting tenuring threshold, one per snapshot AgeTable** _local_age_tables; // Local scratch age tables to track object ages, one per worker -#ifdef SHENANDOAH_CENSUS_NOISE - ShenandoahNoiseStats* _global_noise; // Noise stats, one per snapshot - ShenandoahNoiseStats* _local_noise; // Local scratch table for noise stats, one per worker - - size_t _skipped; // net size of objects encountered, but skipped during census, - // because their age was indeterminate -#endif // SHENANDOAH_CENSUS_NOISE - -#ifndef PRODUCT - size_t _counted; // net size of objects counted in census - size_t _total; // net size of objects encountered (counted or skipped) in census -#endif - uint _epoch; // Current epoch (modulo max age) uint* _tenuring_threshold; // An array of the last N tenuring threshold values we // computed. @@ -156,20 +94,6 @@ class ShenandoahAgeCensus: public CHeapObj { // to force is_tenurable() to be true for every age during WB.fullGC tests. void set_always_tenure(bool always_tenure) { _always_tenure = always_tenure; } -#ifndef PRODUCT - // Return the sum of size of objects of all ages recorded in the - // census at snapshot indexed by snap. - size_t get_all_ages(uint snap); - - // Return the size of all objects that were encountered, but skipped, - // during the census, because their age was indeterminate. - size_t get_skipped(uint snap); - - // Update the total size of objects counted or skipped at the census for - // the most recent epoch. - void update_total(); -#endif // !PRODUCT - public: enum { MAX_COHORTS = AgeTable::table_size, // = markWord::max_age + 1 @@ -204,20 +128,8 @@ class ShenandoahAgeCensus: public CHeapObj { bool is_always_tenure() const { return _always_tenure; } // Update the local age table for worker_id by size for - // given obj_age, region_age, and region_youth - CENSUS_NOISE(inline void add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id);) - NO_CENSUS_NOISE(inline void add(uint obj_age, uint region_age, size_t size, uint worker_id);) - -#ifdef SHENANDOAH_CENSUS_NOISE - // Update the local skip table for worker_id by size - void add_skipped(size_t size, uint worker_id); - // Update the local aged region volume table for worker_id by size - void add_aged(size_t size, uint worker_id); - // Update the local clamped object volume table for worker_id by size - void add_clamped(size_t size, uint worker_id); - // Update the local (rejuvenated) object volume (retrograde age) for worker_id by size - void add_young(size_t size, uint worker_id); -#endif // SHENANDOAH_CENSUS_NOISE + // given obj_age and region_age + inline void add(uint obj_age, uint region_age, size_t size, uint worker_id); // Update the census data, and compute the new tenuring threshold. // This method should be called at the end of each marking cycle to update @@ -245,10 +157,6 @@ class ShenandoahAgeCensus: public CHeapObj { // Check whether census information is clear bool is_clear_global(); bool is_clear_local(); - - // Return the net size of objects encountered (counted or skipped) in census - // at most recent epoch. - size_t get_total() const { return _total; } #endif // !PRODUCT // Print the age census information diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp index 299b9a3f8b7a..704915375fa3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.inline.hpp @@ -27,35 +27,11 @@ #include "gc/shenandoah/shenandoahAgeCensus.hpp" -CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, uint region_youth, size_t size, uint worker_id) {) -NO_CENSUS_NOISE(void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) {) +void ShenandoahAgeCensus::add(uint obj_age, uint region_age, size_t size, uint worker_id) { if (obj_age <= markWord::max_age) { assert(obj_age < MAX_COHORTS && region_age < MAX_COHORTS, "Should have been tenured"); -#ifdef SHENANDOAH_CENSUS_NOISE - // Region ageing is stochastic and non-monotonic; this vitiates mortality - // demographics in ways that might defeat our algorithms. Marking may be a - // time when we might be able to correct this, but we currently do not do - // this. Like skipped statistics further below, we want to track the - // impact of this noise to see if this may be worthwhile. JDK-. - uint age = obj_age; - if (region_age > 0) { - add_aged(size, worker_id); // this tracking is coarse for now - age += region_age; - if (age >= MAX_COHORTS) { - age = (uint)(MAX_COHORTS - 1); // clamp - add_clamped(size, worker_id); - } - } - if (region_youth > 0) { // track object volume with retrograde age - add_young(size, worker_id); - } -#else // SHENANDOAH_CENSUS_NOISE uint age = MIN2(obj_age + region_age, (uint)(MAX_COHORTS - 1)); // clamp -#endif // SHENANDOAH_CENSUS_NOISE get_local_age_table(worker_id)->add(age, size); - } else { - // update skipped statistics - CENSUS_NOISE(add_skipped(size, worker_id);) } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp index f980c1f88c88..8b67bea82488 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp @@ -240,11 +240,17 @@ void ShenandoahArguments::initialize_alignments() { } } +void ShenandoahArguments::initialize_heap_flags_and_sizes() { + GCArguments::initialize_heap_flags_and_sizes(); + + FLAG_SET_ERGO_IF_DEFAULT(SoftMaxHeapSize, MaxHeapSize); +} + CollectedHeap* ShenandoahArguments::create_heap() { if (strcmp(ShenandoahGCMode, "generational") != 0) { // Not generational - return new ShenandoahHeap(new ShenandoahCollectorPolicy()); + return new ShenandoahHeap(); } else { - return new ShenandoahGenerationalHeap(new ShenandoahCollectorPolicy()); + return new ShenandoahGenerationalHeap(); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp index bc73d9a2d128..4dbb0ce561fa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.hpp @@ -32,6 +32,7 @@ class CollectedHeap; class ShenandoahArguments : public GCArguments { private: virtual void initialize_alignments(); + virtual void initialize_heap_flags_and_sizes(); virtual void initialize(); virtual size_t conservative_max_heap_alignment(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp index f8eff1ddf34e..3d43167aeac3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp @@ -107,8 +107,7 @@ void ShenandoahBarrierSet::on_slowpath_allocation_exit(JavaThread* thread, oop n if (ReduceInitialCardMarks && ShenandoahCardBarrier && !ShenandoahHeap::heap()->is_in_young(new_obj)) { log_debug(gc)("Newly allocated object (" PTR_FORMAT ") is not in the young generation", p2i(new_obj)); // This can happen when an object is newly allocated, but we come to a safepoint before returning - // the object. If the safepoint runs a degenerated cycle that is upgraded to a full GC, this object - // will have survived two GC cycles. If the tenuring age is very low (1), this object may be promoted. + // the object. If the safepoint runs a full GC, and the tenuring age is very low (1), this object may be promoted. // In this case, we have an allocated object, but it has received no stores yet. If card marking barriers // have been elided, we could end up with an object in old holding pointers to young that won't be in // the remembered set. The solution here is conservative, but this problem should be rare, and it will diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp index 33823ad1d8f4..acbb189b75e1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp @@ -34,6 +34,7 @@ #include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahCardTable.hpp" #include "gc/shenandoah/shenandoahCollectionSet.inline.hpp" +#include "gc/shenandoah/shenandoahForwarding.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahHeapRegion.hpp" @@ -155,7 +156,7 @@ template inline oop ShenandoahBarrierSet::oop_load_post(DecoratorSet decorators, oop value, T* addr) { assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "Reference strength must be known"); - shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc()); + shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); // Perform LRB to handle evacuation and possibly weak loads. value = load_reference_barrier(decorators, value, addr); @@ -172,9 +173,9 @@ template inline void ShenandoahBarrierSet::oop_store_pre(DecoratorSet decorators, T* addr, oop new_value) { assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "Reference strength must be known"); - shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc()); - shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_forwarded_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc()); + shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); + shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); + shenandoah_assert_not_forwarded_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); shenandoah_assert_marked_if(nullptr, new_value, !CompressedOops::is_null(new_value) && @@ -199,11 +200,11 @@ inline void ShenandoahBarrierSet::oop_cmpxchg_pre(DecoratorSet decorators, T* ad assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "CAS should have resolved ref strength"); assert((decorators & ON_STRONG_OOP_REF) != 0, "CAS only for strong refs"); - shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc()); - shenandoah_assert_not_in_cset_except(nullptr, compare_value, compare_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_forwarded_except(addr, compare_value, compare_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || _heap->cancelled_gc()); + shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); + shenandoah_assert_not_in_cset_except(nullptr, compare_value, compare_value == nullptr || ShenandoahForwarding::is_self_forwarded(compare_value)); + shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); + shenandoah_assert_not_forwarded_except(addr, compare_value, compare_value == nullptr || ShenandoahForwarding::is_self_forwarded(compare_value)); + shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); // Handle the previous value through SATB, as we are about to perform the store. oop prev = RawAccess<>::oop_load(addr); @@ -221,9 +222,9 @@ inline void ShenandoahBarrierSet::oop_xchg_pre(DecoratorSet decorators, T* addr, assert((decorators & ON_UNKNOWN_OOP_REF) == 0, "XCHG should have resolved ref strength"); assert((decorators & ON_STRONG_OOP_REF) != 0, "XCHG only for strong refs"); - shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc()); - shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || _heap->cancelled_gc()); - shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || _heap->cancelled_gc()); + shenandoah_assert_not_in_cset_loc_except(addr, !is_heap_access(decorators) || _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); + shenandoah_assert_not_in_cset_except(nullptr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); + shenandoah_assert_not_forwarded_except(addr, new_value, new_value == nullptr || ShenandoahForwarding::is_self_forwarded(new_value)); // Handle the previous value through SATB, as we are about to perform the store. oop prev = RawAccess<>::oop_load(addr); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp index 58e47841819f..a9d3e5a3d9bf 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp @@ -204,22 +204,8 @@ class ShenandoahCleanUpdateWeakOopsClosure : public OopClosure { // ========= Update References // -template -class ShenandoahMarkUpdateRefsClosure : public ShenandoahMarkRefsSuperClosure { -private: - template - inline void work(T* p); - -public: - ShenandoahMarkUpdateRefsClosure(ShenandoahObjToScanQueue* q, ShenandoahReferenceProcessor* rp, ShenandoahObjToScanQueue* old_q); - - virtual void do_oop(narrowOop* p) { work(p); } - virtual void do_oop(oop* p) { work(p); } -}; - -class ShenandoahUpdateRefsSuperClosure : public ShenandoahSuperClosure {}; - -class ShenandoahNonConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosure { +template +class ShenandoahUpdateRefsClosure : public ShenandoahSuperClosure { private: template inline void work(T* p); @@ -229,17 +215,6 @@ class ShenandoahNonConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosu virtual void do_oop(oop* p) { work(p); } }; -class ShenandoahConcUpdateRefsClosure : public ShenandoahUpdateRefsSuperClosure { -private: - template - inline void work(T* p); - -public: - virtual void do_oop(narrowOop* p) { work(p); } - virtual void do_oop(oop* p) { work(p); } -}; - - class ShenandoahFlushSATB : public ThreadClosure { private: SATBMarkQueueSet& _satb_qset; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp index 16280d35c5d5..0ff970ae9362 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp @@ -201,33 +201,26 @@ void ShenandoahCleanUpdateWeakOopsClosure::do_oo // // ========= Update References // - -template -ShenandoahMarkUpdateRefsClosure::ShenandoahMarkUpdateRefsClosure(ShenandoahObjToScanQueue* q, - ShenandoahReferenceProcessor* rp, - ShenandoahObjToScanQueue* old_q) : - ShenandoahMarkRefsSuperClosure(q, rp, old_q) { - assert(_heap->is_stw_gc_in_progress(), "Can only be used for STW GC"); -} - -template -template -inline void ShenandoahMarkUpdateRefsClosure::work(T* p) { - // Update the location - _heap->non_conc_update_with_forwarded(p); - - // ...then do the usual thing - ShenandoahMarkRefsSuperClosure::work(p); -} - +template template -inline void ShenandoahNonConcUpdateRefsClosure::work(T* p) { - _heap->non_conc_update_with_forwarded(p); -} +void ShenandoahUpdateRefsClosure::work(T* p) { + T o = RawAccess<>::oop_load(p); + if (!CompressedOops::is_null(o)) { + oop obj = CompressedOops::decode_not_null(o); + if (_heap->in_collection_set(obj)) { + shenandoah_assert_forwarded(p, obj); + oop resolved = ShenandoahForwarding::forwardee(obj); + shenandoah_assert_not_in_cset_except(p, resolved, _heap->cancelled_gc() || _heap->has_self_forwarded_objects()); -template -inline void ShenandoahConcUpdateRefsClosure::work(T* p) { - _heap->conc_update_with_forwarded(p); + if (resolved != obj) { + if (CONCURRENT) { + _heap->atomic_update_oop(resolved, p, o); + } else { + RawAccess::oop_store(p, resolved); + } + } + } + } } inline void ShenandoahFlushSATB::do_thread(Thread* thread) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp index d2a489a15bee..de816149faf2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp @@ -187,6 +187,16 @@ ShenandoahHeapRegion* ShenandoahCollectionSet::next() { return nullptr; } +bool ShenandoahCollectionSet::all_regions_claimed() const { + const size_t max = _heap->num_regions(); + for (size_t index = _current_index.load_relaxed(); index < max; index++) { + if (is_in(index)) { + return false; + } + } + return true; +} + void ShenandoahCollectionSet::print_on(outputStream* out) const { out->print_cr("Collection Set: Regions: " "%zu, Garbage: %zu%s, Live: %zu%s, Used: %zu%s", count(), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp index 7722423709d9..eec93f5b0ac2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp @@ -84,6 +84,9 @@ class ShenandoahCollectionSet : public CHeapObj { // Single-thread version ShenandoahHeapRegion* next(); + // Not MT safe, use to test if gc workers processed all regions + bool all_regions_claimed() const; + size_t count() const { return _region_count; } bool is_empty() const { return _region_count == 0; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp index 0eaf91f38fff..4d7d7992111c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp @@ -25,27 +25,29 @@ */ +#include "gc/shared/gc_globals.hpp" +#include "gc/shared/plab.hpp" +#include "gc/shenandoah/mode/shenandoahMode.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" -#include "gc/shenandoah/shenandoahHeap.inline.hpp" -#include "runtime/os.hpp" - -ShenandoahCollectorPolicy::ShenandoahCollectorPolicy() : +#include "gc/shenandoah/shenandoahController.hpp" +#include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" +#include "gc/shenandoah/shenandoahThreadLocalData.hpp" +#include "logging/log.hpp" +#include "utilities/copy.hpp" +#include "utilities/ostream.hpp" + +ShenandoahCollectorPolicy::ShenandoahCollectorPolicy(ShenandoahMode* mode) : _success_concurrent_gcs(0), _abbreviated_concurrent_gcs(0), - _success_degenerated_gcs(0), - _abbreviated_degenerated_gcs(0), + _last_whole_heap_gc_id(0), _success_full_gcs(0), - _consecutive_degenerated_gcs(0), - _consecutive_degenerated_gcs_without_progress(0), _consecutive_young_gcs(0), _mixed_gcs(0), _success_old_gcs(0), _interrupted_old_gcs(0), - _alloc_failure_degenerated(0), - _alloc_failure_degenerated_upgrade_to_full(0), - _alloc_failure_full(0) { - Copy::zero_to_bytes(_degen_point_counts, sizeof(size_t) * ShenandoahGC::_DEGENERATED_LIMIT); + _alloc_failure_full(0), + _mode(mode) { + Copy::zero_to_bytes(_stall_counts, sizeof(size_t) * ShenandoahController::PHASE_LIMIT); Copy::zero_to_bytes(_collection_cause_counts, sizeof(size_t) * GCCause::_last_gc_cause); _tracer = new ShenandoahTracer(); @@ -60,21 +62,19 @@ void ShenandoahCollectorPolicy::record_alloc_failure_to_full() { _alloc_failure_full++; } -void ShenandoahCollectorPolicy::record_alloc_failure_to_degenerated(ShenandoahGC::ShenandoahDegenPoint point) { - assert(point < ShenandoahGC::_DEGENERATED_LIMIT, "sanity"); - _alloc_failure_degenerated++; - _degen_point_counts[point]++; -} - -void ShenandoahCollectorPolicy::record_degenerated_upgrade_to_full() { - reset_consecutive_degenerated_gcs(); - _alloc_failure_degenerated_upgrade_to_full++; +void ShenandoahCollectorPolicy::record_allocation_stall(ShenandoahController::ShenandoahCollectorPhase phase) { + assert(phase < ShenandoahController::PHASE_LIMIT, "Invalid phase: %d", phase); + assert(ShenandoahController::UNSET <= phase, "Invalid phase: %d", phase); + _stall_counts[phase].add_then_fetch(1UL, memory_order_relaxed); } -void ShenandoahCollectorPolicy::record_success_concurrent(bool is_young, bool is_abbreviated) { +void ShenandoahCollectorPolicy::record_success_concurrent(size_t gc_id, bool is_young, bool is_abbreviated) { update_young(is_young); - reset_consecutive_degenerated_gcs(); + if (!is_young) { + _last_whole_heap_gc_id.store_relaxed(gc_id); + } + _success_concurrent_gcs++; if (is_abbreviated) { _abbreviated_concurrent_gcs++; @@ -95,23 +95,6 @@ void ShenandoahCollectorPolicy::record_interrupted_old() { _interrupted_old_gcs++; } -void ShenandoahCollectorPolicy::record_degenerated(bool is_young, bool is_abbreviated, bool progress) { - update_young(is_young); - - _success_degenerated_gcs++; - _consecutive_degenerated_gcs++; - - if (progress) { - _consecutive_degenerated_gcs_without_progress = 0; - } else { - _consecutive_degenerated_gcs_without_progress++; - } - - if (is_abbreviated) { - _abbreviated_degenerated_gcs++; - } -} - void ShenandoahCollectorPolicy::update_young(bool is_young) { if (is_young) { _consecutive_young_gcs++; @@ -120,10 +103,10 @@ void ShenandoahCollectorPolicy::update_young(bool is_young) { } } -void ShenandoahCollectorPolicy::record_success_full() { - reset_consecutive_degenerated_gcs(); +void ShenandoahCollectorPolicy::record_success_full(size_t gc_id) { _consecutive_young_gcs = 0; _success_full_gcs++; + _last_whole_heap_gc_id.store_relaxed(gc_id); } void ShenandoahCollectorPolicy::record_shutdown() { @@ -176,6 +159,11 @@ bool ShenandoahCollectorPolicy::should_run_full_gc(GCCause::Cause cause) { if (!ShenandoahHeap::heap()->mode()->is_concurrent()) { return true; } + + if (cause == GCCause::_shenandoah_upgrade_to_full_gc) { + return true; + } + return is_explicit_gc(cause) && !ExplicitGCInvokesConcurrent; } @@ -188,11 +176,75 @@ bool ShenandoahCollectorPolicy::should_handle_requested_gc(GCCause::Cause cause) return true; } +static PLAB* shenandoah_get_lab(ShenandoahHeapRegion* region) { + if (region->is_young()) { + return ShenandoahThreadLocalData::gclab(Thread::current()); + } + + assert(region->is_old(), "Region in cset(%zu) must be young or old", region->index()); + ShenandoahPLAB* shenandoah_plab = ShenandoahThreadLocalData::shenandoah_plab(Thread::current()); + assert(shenandoah_plab != nullptr, "Must have plab in generational mode"); + return shenandoah_plab->plab(); +} + +bool ShenandoahCollectorPolicy::should_abandon_evacuations(ShenandoahHeapRegion* region) { + if (region->has_self_forwards() && ShenandoahThreadLocalData::has_evac_failures()) { + // This region and this thread are lost. This thread has evacuated all it can. If + // we let it continue on to other regions, it will only fail those as well. We want + // to let other threads try the regions that this thread could not. This isn't as + // precise as it could be. In a mixed collection, we would ideally have a way to + // stop this thread from claiming old or young regions, depending on which lab is + // exhausted. + log_debug(gc, thread)("Region (%zu) has self-forwards and this thread has evacuation failures (LAB words remaining: %zu)", + region->index(), shenandoah_get_lab(region)->words_remaining()); + return true; + } + return false; +} + +// Some causes should not be allowed to preempt others. We must make sure that +// regulator requests and allocation failures do not preempt a shutdown request. +int ShenandoahCollectorPolicy::cause_priority(GCCause::Cause cause) { + if (is_explicit_gc(cause)) { + return 5; + } + + switch (cause) { + case GCCause::_shenandoah_stop_vm: + return 7; + case GCCause::_shenandoah_upgrade_to_full_gc: + return 6; + case GCCause::_shenandoah_humongous_allocation_failure: + return 4; + case GCCause::_shenandoah_allocation_failure_evac: + return 3; + case GCCause::_allocation_failure: + return 2; + case GCCause::_shenandoah_concurrent_gc: + return 1; + case GCCause::_no_gc: + return 0; + default: + // Unanticipated gc causes are treated as an allocation failure and cannot be + // preempted by regulator requests + return 2; + } +} + +template +size_t shenandoah_sum_array(T* a, size_t length) { + size_t sum = 0; + for (size_t i = 0; i < length; i++) { + sum += a[i].load_relaxed(); + } + return sum; +} + void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { - out->print_cr("Under allocation pressure, concurrent cycles may cancel, and either continue cycle"); - out->print_cr("under stop-the-world pause or result in stop-the-world Full GC. Increase heap size,"); - out->print_cr("tune GC heuristics, or lower allocation rate to avoid Degenerated and Full GC cycles."); - out->print_cr("Abbreviated cycles are those which found enough regions with no live objects to skip evacuation."); + out->print_cr("Under allocation pressure, concurrent cycles may stall allocating threads or escalate "); + out->print_cr("to stop-the-world Full GC. Increase heap size, tune GC heuristics, or lower allocation rate"); + out->print_cr("to avoid stalls and Full GC cycles. Abbreviated cycles are those which found enough"); + out->print_cr("regions with no live objects to skip evacuation."); out->cr(); size_t gc_attempts = 0; @@ -200,7 +252,7 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { gc_attempts += _collection_cause_counts[c]; } - size_t completed_gcs = _success_full_gcs + _success_degenerated_gcs + _success_concurrent_gcs + _success_old_gcs; + size_t completed_gcs = _success_full_gcs + _success_concurrent_gcs + _success_old_gcs; out->print_cr("%5zu GC attempts. %zu Completed GCs (%.2f%%).", gc_attempts, completed_gcs, percent_of(completed_gcs, gc_attempts)); @@ -218,27 +270,25 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { out->print_cr(" %5zu abbreviated (%.2f%%)", _abbreviated_concurrent_gcs, percent_of(_abbreviated_concurrent_gcs, _success_concurrent_gcs)); out->cr(); - if (ShenandoahHeap::heap()->mode()->is_generational()) { + if (_mode->is_generational()) { out->print_cr("%5zu Completed Old GCs (%.2f%%)", _success_old_gcs, percent_of(_success_old_gcs, completed_gcs)); out->print_cr(" %5zu mixed", _mixed_gcs); out->print_cr(" %5zu interruptions", _interrupted_old_gcs); out->cr(); } - size_t degenerated_gcs = _alloc_failure_degenerated_upgrade_to_full + _success_degenerated_gcs; - out->print_cr("%5zu Degenerated GCs (%.2f%%)", degenerated_gcs, percent_of(degenerated_gcs, completed_gcs)); - out->print_cr(" %5zu upgraded to Full GC (%.2f%%)", _alloc_failure_degenerated_upgrade_to_full, percent_of(_alloc_failure_degenerated_upgrade_to_full, degenerated_gcs)); - out->print_cr(" %5zu caused by allocation failure (%.2f%%)", _alloc_failure_degenerated, percent_of(_alloc_failure_degenerated, degenerated_gcs)); - out->print_cr(" %5zu abbreviated (%.2f%%)", _abbreviated_degenerated_gcs, percent_of(_abbreviated_degenerated_gcs, degenerated_gcs)); - for (int c = 0; c < ShenandoahGC::_DEGENERATED_LIMIT; c++) { - if (_degen_point_counts[c] > 0) { - const char* desc = ShenandoahGC::degen_point_to_string((ShenandoahGC::ShenandoahDegenPoint)c); - out->print_cr(" %5zu happened at %s", _degen_point_counts[c], desc); + const size_t total_stalls = shenandoah_sum_array(_stall_counts, ShenandoahController::PHASE_LIMIT); + out->print_cr("%5zu Stalls", total_stalls); + for (int c = 0; c < ShenandoahController::PHASE_LIMIT; c++) { + const size_t stall_count = _stall_counts[c].load_relaxed(); + if (stall_count > 0) { + const auto phase = static_cast(c); + const char* desc = ShenandoahController::collector_phase_to_string(phase); + out->print_cr(" %5zu happened at %s", stall_count, desc); } } out->cr(); out->print_cr("%5zu Full GCs (%.2f%%)", _success_full_gcs, percent_of(_success_full_gcs, completed_gcs)); out->print_cr(" %5zu caused by allocation failure (%.2f%%)", _alloc_failure_full, percent_of(_alloc_failure_full, _success_full_gcs)); - out->print_cr(" %5zu upgraded from Degenerated GC (%.2f%%)", _alloc_failure_degenerated_upgrade_to_full, percent_of(_alloc_failure_degenerated_upgrade_to_full, _success_full_gcs)); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp index e265da9d4134..a3931b6714eb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.hpp @@ -27,48 +27,39 @@ #define SHARE_GC_SHENANDOAH_SHENANDOAHCOLLECTORPOLICY_HPP #include "gc/shared/gcTrace.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" +#include "gc/shenandoah/shenandoahController.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" #include "gc/shenandoah/shenandoahTrace.hpp" #include "memory/allocation.hpp" -#include "utilities/ostream.hpp" + +class ShenandoahHeapRegion; +class ShenandoahMode; class ShenandoahCollectorPolicy : public CHeapObj { private: size_t _success_concurrent_gcs; size_t _abbreviated_concurrent_gcs; - size_t _success_degenerated_gcs; - size_t _abbreviated_degenerated_gcs; - // Written by control thread, read by mutators + + // Written by control thread, read by mutators (infrequently) + Atomic _last_whole_heap_gc_id; volatile size_t _success_full_gcs; - uint _consecutive_degenerated_gcs; - uint _consecutive_degenerated_gcs_without_progress; volatile size_t _consecutive_young_gcs; + size_t _mixed_gcs; size_t _success_old_gcs; size_t _interrupted_old_gcs; - size_t _alloc_failure_degenerated; - size_t _alloc_failure_degenerated_upgrade_to_full; size_t _alloc_failure_full; size_t _collection_cause_counts[GCCause::_last_gc_cause]; - size_t _degen_point_counts[ShenandoahGC::_DEGENERATED_LIMIT]; + + Atomic _stall_counts[ShenandoahController::PHASE_LIMIT]; ShenandoahSharedFlag _in_shutdown; ShenandoahTracer* _tracer; - - void reset_consecutive_degenerated_gcs() { - _consecutive_degenerated_gcs = 0; - _consecutive_degenerated_gcs_without_progress = 0; - } + ShenandoahMode* _mode; public: - // The most common scenario for lack of good progress following a degenerated GC is an accumulation of floating - // garbage during the most recently aborted concurrent GC effort. Usually, it is far more effective to - // reclaim this floating garbage with another degenerated cycle (which focuses on young generation and might require - // a pause of 200 ms) rather than a full GC cycle (which may require multiple seconds with a 10 GB old generation). - static constexpr size_t CONSECUTIVE_BAD_DEGEN_PROGRESS_THRESHOLD = 2; - ShenandoahCollectorPolicy(); + ShenandoahCollectorPolicy(ShenandoahMode* mode); void record_mixed_cycle(); void record_success_old(); @@ -77,19 +68,12 @@ class ShenandoahCollectorPolicy : public CHeapObj { // A collection cycle may be "abbreviated" if Shenandoah finds a sufficient percentage // of regions that contain no live objects (ShenandoahImmediateThreshold). These cycles // end after final mark, skipping the evacuation and reference-updating phases. Such - // cycles are very efficient and are worth tracking. Note that both degenerated and - // concurrent cycles can be abbreviated. - void record_success_concurrent(bool is_young, bool is_abbreviated); - - // Record that a degenerated cycle has been completed. Note that such a cycle may or - // may not make "progress". We separately track the total number of degenerated cycles, - // the number of consecutive degenerated cycles and the number of consecutive cycles that - // fail to make good progress. - void record_degenerated(bool is_young, bool is_abbreviated, bool progress); - void record_success_full(); - void record_alloc_failure_to_degenerated(ShenandoahGC::ShenandoahDegenPoint point); + // cycles are very efficient and are worth tracking. + void record_success_concurrent(size_t gc_id, bool is_young, bool is_abbreviated); + + void record_success_full(size_t gc_id); + void record_allocation_stall(ShenandoahController::ShenandoahCollectorPhase phase); void record_alloc_failure_to_full(); - void record_degenerated_upgrade_to_full(); void record_collection_cause(GCCause::Cause cause); void record_shutdown(); @@ -100,19 +84,11 @@ class ShenandoahCollectorPolicy : public CHeapObj { void print_gc_stats(outputStream* out) const; size_t full_gc_count() const { - return _success_full_gcs + _alloc_failure_degenerated_upgrade_to_full; + return _success_full_gcs; } - // If the heuristics find that the number of consecutive degenerated cycles is above - // ShenandoahFullGCThreshold, then they will initiate a Full GC upon an allocation - // failure. - size_t consecutive_degenerated_gc_count() const { - return _consecutive_degenerated_gcs; - } - - // Only upgrade to a full gc after the configured number of futile degenerated cycles. - bool should_upgrade_degenerated_gc() const { - return _consecutive_degenerated_gcs_without_progress >= CONSECUTIVE_BAD_DEGEN_PROGRESS_THRESHOLD; + size_t last_whole_heap_gc_id() const { + return _last_whole_heap_gc_id.load_relaxed(); } static bool is_allocation_failure(GCCause::Cause cause); @@ -121,11 +97,23 @@ class ShenandoahCollectorPolicy : public CHeapObj { static bool should_run_full_gc(GCCause::Cause cause); static bool should_handle_requested_gc(GCCause::Cause cause); + static bool is_higher_priority(GCCause::Cause lhs, GCCause::Cause rhs) { + return cause_priority(lhs) > cause_priority(rhs); + } + + // True if the region has self-forwarded objects and the calling thread has exhausted its LABs. + static bool should_abandon_evacuations(ShenandoahHeapRegion* region); + size_t consecutive_young_gc_count() const { return _consecutive_young_gcs; } + size_t reclaiming_gc_count() const { + return _success_concurrent_gcs + _success_full_gcs + _success_old_gcs; + } + private: + static int cause_priority(GCCause::Cause cause); void update_young(bool is_young); }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index f6a614586971..0b875fa1c2ca 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -28,12 +28,10 @@ #include "code/codeCache.hpp" #include "gc/shared/barrierSetNMethod.hpp" #include "gc/shared/collectorCounters.hpp" -#include "gc/shared/continuationGCSupport.inline.hpp" #include "gc/shenandoah/shenandoahBreakpoint.hpp" #include "gc/shenandoah/shenandoahClosures.inline.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahConcurrentGC.hpp" -#include "gc/shenandoah/shenandoahForwarding.inline.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" @@ -93,16 +91,17 @@ class ShenandoahBreakpointMarkScope : public StackObj { } }; -ShenandoahConcurrentGC::ShenandoahConcurrentGC(ShenandoahGeneration* generation, bool do_old_gc_bootstrap) : +ShenandoahConcurrentGC::ShenandoahConcurrentGC(ShenandoahController* controller, ShenandoahGeneration* generation, bool do_old_gc_bootstrap) : ShenandoahGC(generation), _mark(generation), - _degen_point(ShenandoahDegenPoint::_degenerated_unset), + _controller(controller), _abbreviated(false), _do_old_gc_bootstrap(do_old_gc_bootstrap) { + update_phase(ShenandoahController::INITIALIZING); } -ShenandoahGC::ShenandoahDegenPoint ShenandoahConcurrentGC::degen_point() const { - return _degen_point; +ShenandoahConcurrentGC::~ShenandoahConcurrentGC() { + update_phase(ShenandoahController::UNSET); } void ShenandoahConcurrentGC::entry_concurrent_update_refs_prepare(ShenandoahHeap* const heap) { @@ -163,13 +162,13 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // Concurrent mark roots entry_mark_roots(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_roots)) { + if (check_cancellation_and_abort()) { return false; } // Continue concurrent mark entry_mark(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_mark)) { + if (check_cancellation_and_abort()) { return false; } } @@ -177,12 +176,8 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // Complete marking under STW, and start evacuation vmop_entry_final_mark(); - // If the GC was cancelled before final mark, nothing happens on the safepoint. We are still - // in the marking phase and must resume the degenerated cycle from there. If the GC was cancelled - // after final mark, then we've entered the evacuation phase and must resume the degenerated cycle - // from that phase. if (_generation->is_concurrent_mark_in_progress()) { - bool cancelled = check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_mark); + bool cancelled = check_cancellation_and_abort(); assert(cancelled, "GC must have been cancelled between concurrent and final mark"); return false; } @@ -224,16 +219,29 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // This may be skipped if there is nothing to evacuate. // If so, evac_in_progress would be unset by collection set preparation code. if (heap->is_evacuation_in_progress()) { + // Final mark may have reclaimed some immediate garbage that could satisfy allocation requests. + // We are proceeding with evacuation, so notify alloc waiters (if we are not evacuating, completion + // of the abbreviated cycle would also notify them). + _controller->notify_alloc_waiters(); + // Concurrently evacuate entry_evacuate(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_evac)) { + if (check_cancellation_and_abort()) { return false; } + if (!heap->collection_set()->all_regions_claimed()) { + // Self forward objects stranded in the collection set + entry_self_forward_stranded_objects(); + if (check_cancellation_and_abort()) { + return false; + } + } + // Perform update-refs phase. entry_concurrent_update_refs_prepare(heap); - if (ShenandoahHeap::heap()->mode()->is_generational()) { + if (heap->mode()->is_generational()) { entry_update_card_table(); } @@ -242,13 +250,13 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { } entry_update_refs(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_update_refs)) { + if (check_cancellation_and_abort()) { return false; } // Concurrent update thread roots entry_update_thread_roots(); - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_update_refs)) { + if (check_cancellation_and_abort()) { return false; } @@ -262,12 +270,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { if (heap->mode()->is_generational()) { entry_complete_abbreviated_cycle(); - // If the promote-in-place operation was cancelled, we can have the degenerated - // cycle complete the operation. It will see that no evacuations are in progress, - // and that there are regions wanting promotion. The risk with not handling the - // cancellation would be failing to restore top for these regions and leaving - // them unable to serve allocations for the old generation. - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_evac)) { + if (check_cancellation_and_abort()) { return false; } } @@ -287,7 +290,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // Instead of always resetting immediately before the start of a new GC, we can often reset at the end of the // previous GC. This allows us to start the next GC cycle more quickly after a trigger condition is detected, - // reducing the likelihood that GC will degenerate. + // reducing the likelihood that mutators will experience allocation stalls. entry_reset_after_collect(); return true; @@ -313,11 +316,11 @@ void ShenandoahConcurrentGC::entry_complete_abbreviated_cycle() { if (heap->old_generation()->has_in_place_promotions()) { ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::complete_abbreviated_promote_in_place); ShenandoahGCWorkerPhase worker_phase(ShenandoahPhaseTimings::complete_abbreviated_promote_in_place); - heap->promote_regions_in_place(_generation, true); + heap->promote_regions_in_place(_generation); } // At this point, the cycle is effectively complete. If the cycle has been cancelled here, - // the control thread will detect it on its next iteration and run a degenerated young cycle. + // the control thread will detect it on its next iteration. if (!heap->cancelled_gc() && !_generation->is_old()) { ShenandoahTimingsTracker tracker(ShenandoahPhaseTimings::complete_abbreviated_update_region_ages); heap->update_region_ages(_generation->complete_marking_context()); @@ -497,6 +500,7 @@ void ShenandoahConcurrentGC::entry_mark_roots() { ShenandoahWorkerPolicy::calc_workers_for_conc_marking(), "concurrent marking roots"); + update_phase(ShenandoahController::ROOTS); heap->try_inject_alloc_failure(); op_mark_roots(); } @@ -515,6 +519,7 @@ void ShenandoahConcurrentGC::entry_mark() { ShenandoahWorkerPolicy::calc_workers_for_conc_marking(), "concurrent marking"); + update_phase(ShenandoahController::MARK); heap->try_inject_alloc_failure(); op_mark(); heap->try_inject_pin(); @@ -613,9 +618,9 @@ void ShenandoahConcurrentGC::entry_cleanup_early() { op_cleanup_early(); if (!heap->is_evacuation_in_progress()) { // This is an abbreviated cycle. Rebuild the freeset in order to establish reserves for the next GC cycle. Doing - // the rebuild ASAP also expedites availability of immediate trash, reducing the likelihood that we will degenerate + // the rebuild ASAP also expedites availability of immediate trash, reducing the likelihood that mutators will stall // during promote-in-place processing. - heap->rebuild_free_set(true /*concurrent*/); + heap->rebuild_free_set(); } } @@ -630,11 +635,28 @@ void ShenandoahConcurrentGC::entry_evacuate() { ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), "concurrent evacuation"); + update_phase(ShenandoahController::EVAC); heap->try_inject_alloc_failure(); heap->try_inject_pin(); op_evacuate(); } +void ShenandoahConcurrentGC::entry_self_forward_stranded_objects() { + ShenandoahHeap* const heap = ShenandoahHeap::heap(); + TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); + SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Concurrent self forwarding", ""); + ShenandoahConcurrentSubphase gc_phase(msg, ShenandoahPhaseTimings::conc_self_forward_stranded); + EventMark em("%s", msg); + + ShenandoahWorkerScope scope(heap->workers(), + ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), + "concurrent self forwarding"); + + heap->try_inject_alloc_failure(); + heap->try_inject_pin(); + op_self_forward_stranded_objects(); +} + void ShenandoahConcurrentGC::entry_update_thread_roots() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); @@ -659,6 +681,7 @@ void ShenandoahConcurrentGC::entry_update_refs() { ShenandoahWorkerPolicy::calc_workers_for_conc_update_ref(), "concurrent reference update"); + update_phase(ShenandoahController::UPDATE_REFS); heap->try_inject_alloc_failure(); heap->try_inject_pin(); op_update_refs(); @@ -686,6 +709,12 @@ void ShenandoahConcurrentGC::entry_reset_after_collect() { op_reset_after_collect(); } +void ShenandoahConcurrentGC::update_phase(ShenandoahController::ShenandoahCollectorPhase phase) const { + if (_controller != nullptr) { + _controller->set_phase(phase); + } +} + void ShenandoahConcurrentGC::op_reset() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); @@ -825,7 +854,7 @@ void ShenandoahConcurrentGC::op_final_mark() { // The collection set is chosen by prepare_regions_and_collection_set(). Additionally, certain parameters have been // established to govern the evacuation efforts that are about to begin. Refer to comments on reserve members in // ShenandoahGeneration and ShenandoahOldGeneration for more detail. - _generation->prepare_regions_and_collection_set(true /*concurrent*/); + _generation->prepare_regions_and_collection_set(); // Has to be done after cset selection heap->prepare_concurrent_roots(); @@ -1143,7 +1172,12 @@ void ShenandoahConcurrentGC::op_cleanup_early() { } void ShenandoahConcurrentGC::op_evacuate() { - ShenandoahHeap::heap()->evacuate_collection_set(_generation, true /*concurrent*/); + ShenandoahHeap* heap = ShenandoahHeap::heap(); + heap->evacuate_collection_set(_generation); +} + +void ShenandoahConcurrentGC::op_self_forward_stranded_objects() { + ShenandoahHeap::heap()->self_forward_stranded_objects(); } void ShenandoahConcurrentGC::op_init_update_refs() { @@ -1155,7 +1189,7 @@ void ShenandoahConcurrentGC::op_init_update_refs() { } void ShenandoahConcurrentGC::op_update_refs() { - ShenandoahHeap::heap()->update_heap_references(_generation, true /*concurrent*/); + ShenandoahHeap::heap()->update_heap_references(_generation); } class ShenandoahUpdateThreadHandshakeClosure : public HandshakeClosure { @@ -1163,7 +1197,7 @@ class ShenandoahUpdateThreadHandshakeClosure : public HandshakeClosure { // This closure runs when thread is stopped for handshake, which means // we can use non-concurrent closure here, as long as it only updates // locations modified by the thread itself, i.e. stack locations. - ShenandoahNonConcUpdateRefsClosure _cl; + ShenandoahUpdateRefsClosure _cl; public: ShenandoahUpdateThreadHandshakeClosure(); void do_thread(Thread* thread) override; @@ -1229,7 +1263,7 @@ void ShenandoahConcurrentGC::op_update_thread_roots() { void ShenandoahConcurrentGC::op_final_update_refs() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint"); - assert(!heap->_update_refs_iterator.has_next(), "Should have finished update references"); + assert(heap->is_update_refs_in_progress(), "Should have run update references first"); heap->finish_concurrent_roots(); @@ -1246,21 +1280,23 @@ void ShenandoahConcurrentGC::op_final_update_refs() { // If we are running in generational mode, this will also age active regions that // haven't been used for allocation. - heap->update_heap_region_states(true /*concurrent*/); + heap->update_heap_region_states(); heap->set_update_refs_in_progress(false); heap->set_has_forwarded_objects(false); + heap->unset_has_self_forwarded_objects(); + + if (VerifyAfterGC) { + Universe::verify(); + } + + heap->rebuild_free_set(); if (ShenandoahVerify) { ShenandoahTimingsTracker v(ShenandoahPhaseTimings::final_update_refs_verify); heap->verifier()->verify_after_update_refs(_generation); } - if (VerifyAfterGC) { - Universe::verify(); - } - - heap->rebuild_free_set(true /*concurrent*/); _generation->heuristics()->start_idle_span(); // Final pause: update GC barriers to idle state. @@ -1312,10 +1348,6 @@ void ShenandoahConcurrentGC::op_reset_after_collect() { } } -bool ShenandoahConcurrentGC::check_cancellation_and_abort(ShenandoahDegenPoint point) { - if (ShenandoahHeap::heap()->cancelled_gc()) { - _degen_point = point; - return true; - } - return false; +bool ShenandoahConcurrentGC::check_cancellation_and_abort() { + return ShenandoahHeap::heap()->cancelled_gc(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp index 7b49b6626e23..f3cdede2bc09 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp @@ -28,11 +28,11 @@ #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahConcurrentMark.hpp" +#include "gc/shenandoah/shenandoahController.hpp" #include "gc/shenandoah/shenandoahGC.hpp" #include "gc/shenandoah/shenandoahHeap.hpp" class ShenandoahGeneration; - class VM_ShenandoahInitMark; class VM_ShenandoahFinalMarkStartEvac; class VM_ShenandoahInitUpdateRefs; @@ -50,15 +50,15 @@ class ShenandoahConcurrentGC : public ShenandoahGC { ShenandoahConcurrentMark _mark; private: - ShenandoahDegenPoint _degen_point; - bool _abbreviated; - const bool _do_old_gc_bootstrap; + ShenandoahController* _controller; + bool _abbreviated; + const bool _do_old_gc_bootstrap; public: - ShenandoahConcurrentGC(ShenandoahGeneration* generation, bool do_old_gc_bootstrap); + ShenandoahConcurrentGC(ShenandoahController* controller, ShenandoahGeneration* generation, bool do_old_gc_bootstrap); + ~ShenandoahConcurrentGC(); bool collect(GCCause::Cause cause) override; - ShenandoahDegenPoint degen_point() const; // Return true if this cycle found enough immediate garbage to skip evacuation bool abbreviated() const { return _abbreviated; } @@ -96,6 +96,7 @@ class ShenandoahConcurrentGC : public ShenandoahGC { void entry_cleanup_early(); void entry_complete_abbreviated_cycle(); void entry_evacuate(); + void entry_self_forward_stranded_objects(); void entry_update_thread_roots(); void entry_update_card_table(); void entry_concurrent_update_refs_prepare(ShenandoahHeap* heap); @@ -115,6 +116,7 @@ class ShenandoahConcurrentGC : public ShenandoahGC { void op_strong_roots(); void op_cleanup_early(); void op_evacuate(); + void op_self_forward_stranded_objects(); void op_init_update_refs(); void op_update_refs(); void op_update_thread_roots(); @@ -125,11 +127,12 @@ class ShenandoahConcurrentGC : public ShenandoahGC { void op_reset_after_collect(); // Check GC cancellation and abort concurrent GC - bool check_cancellation_and_abort(ShenandoahDegenPoint point); + bool check_cancellation_and_abort(); // Called when concurrent GC succeeds. void entry_reset_after_collect(); + void update_phase(ShenandoahController::ShenandoahCollectorPhase phase) const; private: void start_mark(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 1eec38027752..3ba9e5e9fac7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -23,12 +23,12 @@ * */ +#include "gc/shared/allocTracer.hpp" #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/mode/shenandoahMode.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahConcurrentGC.hpp" #include "gc/shenandoah/shenandoahControlThread.hpp" -#include "gc/shenandoah/shenandoahDegeneratedGC.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahFullGC.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" @@ -40,66 +40,40 @@ #include "memory/metaspaceStats.hpp" #include "memory/metaspaceUtils.hpp" -ShenandoahControlThread::ShenandoahControlThread() : - ShenandoahController(), - _requested_gc_cause(GCCause::_no_gc), - _degen_point(ShenandoahGC::_degenerated_outside_cycle), - _control_lock(CONTROL_LOCK_RANK, "ShenandoahControl_lock", true) { +ShenandoahControlThread::ShenandoahControlThread() { set_name("ShenControl"); create_and_start(); } void ShenandoahControlThread::run_service() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); - const GCMode default_mode = concurrent_normal; - const GCCause::Cause default_cause = GCCause::_shenandoah_concurrent_gc; - int sleep = ShenandoahControlIntervalMin; + constexpr GCMode default_mode = concurrent_normal; + constexpr GCCause::Cause default_cause = GCCause::_shenandoah_concurrent_gc; + int sleep = ShenandoahControlIntervalMin; double last_sleep_adjust_time = os::elapsedTime(); + double most_recent_wake_time = last_sleep_adjust_time; - ShenandoahCollectorPolicy* const policy = heap->shenandoah_policy(); ShenandoahHeuristics* const heuristics = heap->heuristics(); - double most_recent_wake_time = os::elapsedTime(); while (!should_terminate()) { - const GCCause::Cause cancelled_cause = heap->cancelled_cause(); - if (cancelled_cause == GCCause::_shenandoah_stop_vm) { - break; - } + assert(!heap->cancelled_gc() || should_terminate(), "Can only be cancelled for shutdown"); // Figure out if we have pending requests. - const bool alloc_failure_pending = ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause); - const bool is_gc_requested = _gc_requested.try_unset(); - const GCCause::Cause requested_gc_cause = _requested_gc_cause; + GCCause::Cause cause; + { + MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag); + cause = _requested_gc_cause; + _requested_gc_cause = GCCause::_no_gc; + } // Choose which GC mode to run in. The block below should select a single mode. GCMode mode = none; - GCCause::Cause cause = GCCause::_last_gc_cause; - ShenandoahGC::ShenandoahDegenPoint degen_point = ShenandoahGC::_degenerated_unset; - - if (alloc_failure_pending) { - // Allocation failure takes precedence: we have to deal with it first thing - heuristics->log_trigger("Handle Allocation Failure"); - - cause = GCCause::_allocation_failure; - - // Consume the degen point, and seed it with default value - degen_point = _degen_point; - _degen_point = ShenandoahGC::_degenerated_outside_cycle; - - if (ShenandoahDegeneratedGC && heuristics->should_degenerate_cycle()) { - heuristics->record_allocation_failure_gc(); - policy->record_alloc_failure_to_degenerated(degen_point); - mode = stw_degenerated; - } else { - heuristics->record_allocation_failure_gc(); - policy->record_alloc_failure_to_full(); - mode = stw_full; - } - } else if (is_gc_requested) { - cause = requested_gc_cause; + bool clear_soft_references = ShenandoahAlwaysClearSoftRefs; + if (cause != GCCause::_no_gc) { + // A cycle was requested, clear soft references heuristics->log_trigger("GC Request (%s)", GCCause::to_string(cause)); heuristics->record_requested_gc(); - + clear_soft_references = true; if (ShenandoahCollectorPolicy::should_run_full_gc(cause)) { mode = stw_full; } else { @@ -107,26 +81,19 @@ void ShenandoahControlThread::run_service() { // Unload and clean up everything heap->set_unload_classes(heuristics->can_unload_classes()); } - } else { - // Potential normal cycle: ask heuristics if it wants to act - if (heuristics->should_start_gc()) { - mode = default_mode; - cause = default_cause; - } - + } else if (heuristics->should_start_gc()) { + // Nobody requested a cycle, but heuristics want to run one + mode = default_mode; + cause = default_cause; // Ask policy if this cycle wants to process references or unload classes heap->set_unload_classes(heuristics->should_unload_classes()); } - if (alloc_failure_pending || is_gc_requested || ShenandoahAlwaysClearSoftRefs) { - // Blow away all soft references on this cycle - heap->global_generation()->ref_processor()->set_soft_reference_policy(true); - } + if (mode != none) { + assert(cause != GCCause::_no_gc, "GC cause should be set"); - const bool gc_requested = (mode != none); - assert (!gc_requested || cause != GCCause::_last_gc_cause, "GC cause should be set"); + heap->global_generation()->ref_processor()->set_soft_reference_policy(clear_soft_references); - if (gc_requested) { // Cannot uncommit bitmap slices during concurrent reset ShenandoahNoUncommitMark forbid_region_uncommit(heap); @@ -151,9 +118,6 @@ void ShenandoahControlThread::run_service() { case concurrent_normal: service_concurrent_normal_cycle(cause); break; - case stw_degenerated: - service_stw_degenerated_cycle(cause, degen_point); - break; case stw_full: service_stw_full_cycle(cause); break; @@ -162,15 +126,12 @@ void ShenandoahControlThread::run_service() { } heap->print_after_gc(); - // If this was the requested GC cycle, notify waiters about it - if (is_gc_requested) { - notify_gc_waiters(); - } + // Try to reduce concurrent worker count + decrease_concurrent_worker_count(); - // If this cycle completed without being cancelled, notify waiters about it - if (!heap->cancelled_gc()) { - notify_alloc_failure_waiters(); - } + // Notify waiters that a cycle is completed. They'll decide for themselves to continue waiting or not. + notify_gc_waiters(); + clear_allocation_failure_and_notify_waiters(); // Report current free set state at the end of cycle, whether // it is a normal completion, or the abort. @@ -212,7 +173,7 @@ void ShenandoahControlThread::run_service() { if (ShenandoahUncommit) { if (heap->check_soft_max_changed()) { heap->notify_soft_max_changed(); - } else if (is_gc_requested) { + } else if (ShenandoahCollectorPolicy::is_explicit_gc(cause)) { heap->notify_explicit_gc_requested(); } } @@ -231,59 +192,35 @@ void ShenandoahControlThread::run_service() { ml.wait(sleep); most_recent_wake_time = os::elapsedTime(); // Record a conservative estimate of the longest anticipated sleep duration until we sample again. - double planned_sleep_interval = MIN2(ShenandoahControlIntervalMax, MAX2(1, sleep * 2)) / 1000.0; + const double planned_sleep_interval = MIN2(ShenandoahControlIntervalMax, MAX2(1, sleep * 2)) / 1000.0; heuristics->update_should_start_query_times(most_recent_wake_time, planned_sleep_interval); if (LogTarget(Debug, gc, thread)::is_enabled()) { - double elapsed = most_recent_wake_time - before_sleep_time; - double hiccup = elapsed - double(sleep) / 1000.0; + const double elapsed = most_recent_wake_time - before_sleep_time; + const double hiccup = elapsed - static_cast(sleep) / 1000.0; if (hiccup > 0.001) { log_debug(gc, thread)("Control Thread hiccup time: %.3fs", hiccup); } } } + // In case any threads are waiting for a cycle to happen, notify them so they observe the shutdown. + notify_gc_waiters(); + notify_alloc_waiters(); + // We're done writing GC stats, so print them here. heap->print_gc_stats_at_exit(); } void ShenandoahControlThread::service_concurrent_normal_cycle(GCCause::Cause cause) { // Normal cycle goes via all concurrent phases. If allocation failure (af) happens during - // any of the concurrent phases, it first degrades to Degenerated GC and completes GC there. - // If second allocation failure happens during Degenerated GC cycle (for example, when GC - // tries to evac something and no memory is available), cycle degrades to Full GC. - // - // There are also a shortcut through the normal cycle: immediate garbage shortcut, when - // heuristics says there are no regions to compact, and all the collection comes from immediately - // reclaimable regions. - // - // ................................................................................................ - // - // (immediate garbage shortcut) Concurrent GC - // /-------------------------------------------\ - // | | - // | | - // | | - // | v - // [START] ----> Conc Mark ----o----> Conc Evac --o--> Conc Update-Refs ---o----> [END] - // | | | ^ - // | (af) | (af) | (af) | - // ..................|....................|.................|..............|....................... - // | | | | - // | | | | Degenerated GC - // v v v | - // STW Mark ----------> STW Evac ----> STW Update-Refs ----->o - // | | | ^ - // | (af) | (af) | (af) | - // ..................|....................|.................|..............|....................... - // | | | | - // | v | | Full GC - // \------------------->o<----------------/ | - // | | - // v | - // Full GC --------------------------/ + // any of the concurrent phases, the allocating thread will block until the concurrent + // cycle completes. // + // There is also a shortcut through the normal cycle: immediate garbage shortcut. When + // heuristics say there are no regions to compact, and all the collection comes from immediately + // reclaimable regions, Shenandoah can skip the evacuation phase. ShenandoahHeap* heap = ShenandoahHeap::heap(); - if (check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle)) { + if (heap->cancelled_gc()) { // Need to report at "gc" level to report GC ID proper. log_info(gc)("Cancelled before cycle started"); return; @@ -294,44 +231,22 @@ void ShenandoahControlThread::service_concurrent_normal_cycle(GCCause::Cause cau TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); - ShenandoahConcurrentGC gc(heap->global_generation(), false); + ShenandoahConcurrentGC gc(this, heap->global_generation(), false); if (gc.collect(cause)) { - // Cycle is complete. There were no failed allocation requests and no degeneration, so count this as good progress. - heap->notify_gc_progress(); - heap->global_generation()->heuristics()->record_success_concurrent(); - heap->shenandoah_policy()->record_success_concurrent(false, gc.abbreviated()); + if (alloc_stall_count() == 0) { + heap->notify_gc_progress(); + } + heap->global_generation()->heuristics()->record_concurrent_completion(); + heap->shenandoah_policy()->record_success_concurrent(get_gc_id(), false, gc.abbreviated()); heap->log_heap_status("At end of GC"); } else { assert(heap->cancelled_gc(), "Must have been cancelled"); - check_cancellation_or_degen(gc.degen_point()); heap->log_heap_status("At end of cancelled GC"); } } -bool ShenandoahControlThread::check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point) { - // Only read the cancellation cause once. Other threads may change it. - ShenandoahHeap* heap = ShenandoahHeap::heap(); - const GCCause::Cause cancelled_cause = heap->cancelled_cause(); - if (cancelled_cause == GCCause::_no_gc) { - return false; - } - - if (cancelled_cause == GCCause::_shenandoah_stop_vm) { - return true; - } - - if (ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause)) { - assert (_degen_point == ShenandoahGC::_degenerated_outside_cycle, - "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point)); - _degen_point = point; - return true; - } - - fatal("Unexpected reason for cancellation: %s", GCCause::to_string(cancelled_cause)); -} - void ShenandoahControlThread::stop_service() { - ShenandoahHeap::heap()->cancel_gc(GCCause::_shenandoah_stop_vm); + // Nothing to do here, worker threads will also check should_terminate, by way of shHeap::is_stopping. } void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { @@ -344,69 +259,25 @@ void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) { gc.collect(cause); } -void ShenandoahControlThread::service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point) { - assert (point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set"); - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - ShenandoahGCSession session(cause, heap->global_generation(), true, - point == ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle); - - heap->increment_total_collections(false); - - ShenandoahDegenGC gc(point, heap->global_generation()); - gc.collect(cause); -} - -void ShenandoahControlThread::request_gc(GCCause::Cause cause) { - if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { - handle_requested_gc(cause); +bool ShenandoahControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* ignored) { + MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag); + if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) { + log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause)); + return false; } -} -void ShenandoahControlThread::notify_control_thread(GCCause::Cause cause) { - // Although setting gc request is under _controller_lock, the read side (run_service()) - // does not take the lock. We need to enforce following order, so that read side sees - // latest requested gc cause when the flag is set. - MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag); _requested_gc_cause = cause; - _gc_requested.set(); - controller.notify(); + controller.notify_all(); + return true; } -void ShenandoahControlThread::handle_requested_gc(GCCause::Cause cause) { - if (should_terminate()) { - log_info(gc, phases)("Control thread is terminating, no more GCs"); - return; - } - - // For normal requested GCs (System.gc) we want to block the caller. However, - // for whitebox requested GC, we want to initiate the GC and return immediately. - // The whitebox caller thread will arrange for itself to wait until the GC notifies - // it that has reached the requested breakpoint (phase in the GC). - if (cause == GCCause::_wb_breakpoint) { - notify_control_thread(cause); - return; - } +void ShenandoahControlThread::notify_alloc_stall(GCCause::Cause cause) { + ShenandoahHeap* heap = ShenandoahHeap::heap(); + ShenandoahGeneration* generation = heap->global_generation(); - // Make sure we have at least one complete GC cycle before unblocking - // from the explicit GC request. - // - // This is especially important for weak references cleanup and/or native - // resources (e.g. DirectByteBuffers) machinery: when explicit GC request - // comes very late in the already running cycle, it would miss lots of new - // opportunities for cleanup that were made available before the caller - // requested the GC. - - MonitorLocker ml(&_gc_waiters_lock); - size_t current_gc_id = get_gc_id(); - size_t required_gc_id = current_gc_id + 1; - while (current_gc_id < required_gc_id && !should_terminate()) { - notify_control_thread(cause); - ml.wait(); - current_gc_id = get_gc_id(); - } -} + // Inform global heuristics of allocation stall + generation->heuristics()->record_allocation_stall(); -void ShenandoahControlThread::notify_gc_waiters() { - MonitorLocker ml(&_gc_waiters_lock); - ml.notify_all(); + // Run a global cycle (no other kind for this control thread). + notify_control_thread(cause, generation); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp index c9bb64192013..ac9d6f365a8a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.hpp @@ -25,51 +25,33 @@ #ifndef SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP #define SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP -#include "gc/shared/concurrentGCThread.hpp" #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahController.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" -#include "gc/shenandoah/shenandoahPadding.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" +class ShenandoahGeneration; + class ShenandoahControlThread: public ShenandoahController { -private: typedef enum { none, concurrent_normal, - stw_degenerated, stw_full } GCMode; - ShenandoahSharedFlag _gc_requested; - GCCause::Cause _requested_gc_cause; - ShenandoahGC::ShenandoahDegenPoint _degen_point; - - // This lock is used to coordinate waking up the control thread - Monitor _control_lock; - public: ShenandoahControlThread(); +protected: void run_service() override; void stop_service() override; - void request_gc(GCCause::Cause cause) override; + // Sets the requested cause, flag and notifies the control thread + bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) override; + void notify_alloc_stall(GCCause::Cause cause) override; private: - // Sets the requested cause and flag and notifies the control thread - void notify_control_thread(GCCause::Cause cause); - - bool check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point); void service_concurrent_normal_cycle(GCCause::Cause cause); void service_stw_full_cycle(GCCause::Cause cause); - void service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point); - - void notify_gc_waiters(); - - // Handle GC request. - // Blocks until GC is over. - void handle_requested_gc(GCCause::Cause cause); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLTHREAD_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp index 0096aad2570d..e4eef635b6e6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp @@ -1,6 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,55 +23,173 @@ * */ -#include "gc/shared/allocTracer.hpp" #include "gc/shared/gc_globals.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahController.hpp" #include "gc/shenandoah/shenandoahHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" +ShenandoahController::ShenandoahController(): + _gc_id(0), + _phase(UNSET), + _control_lock(CONTROL_LOCK_RANK, "ShenandoahControl_lock", true), + _gc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahGCWaiters_lock", true), + _alloc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahAllocWaiters_lock", true), + _requested_gc_cause(GCCause::_no_gc), + _alloc_stall_count(0), + _concurrent_worker_count(ConcGCThreads) { } + void ShenandoahController::update_gc_id() { - _gc_id.add_then_fetch((size_t)1); + _gc_id.add_then_fetch(1UL); } -size_t ShenandoahController::get_gc_id() { +size_t ShenandoahController::get_gc_id() const { return _gc_id.load_relaxed(); } -void ShenandoahController::handle_alloc_failure(const ShenandoahAllocRequest& req, bool block) { +void ShenandoahController::handle_alloc_failure(const ShenandoahAllocRequest &req) { assert(current()->is_Java_thread(), "expect Java thread here"); + _alloc_stall_count.add_then_fetch(1UL, memory_order_relaxed); + increase_concurrent_worker_count(); + ShenandoahHeap::heap()->shenandoah_policy()->record_allocation_stall(get_phase()); const bool is_humongous = ShenandoahHeapRegion::requires_humongous(req.size()); const GCCause::Cause cause = is_humongous ? GCCause::_shenandoah_humongous_allocation_failure : GCCause::_allocation_failure; + MonitorLocker ml(&_alloc_waiters_lock); + notify_alloc_stall(cause); + if (!should_terminate()) { + // This is the inner part of a larger retry loop, so just wait here + ml.wait(); + } +} + +void ShenandoahController::handle_alloc_failure_full() { + if (should_terminate()) { + log_info(gc)("Control thread is terminating, no more GCs"); + return; + } + + // Make sure we have at least one full GC cycle before unblocking from the explicit GC request. + ShenandoahHeap* heap = ShenandoahHeap::heap(); + const ShenandoahCollectorPolicy* policy = heap->shenandoah_policy(); + MonitorLocker ml(&_gc_waiters_lock); + size_t full_gc_count = policy->full_gc_count(); + const size_t required_count = full_gc_count + 1; + while (full_gc_count < required_count && !should_terminate()) { + notify_control_thread(GCCause::_shenandoah_upgrade_to_full_gc, heap->global_generation()); + ml.wait(); + full_gc_count = policy->full_gc_count(); + } +} - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - size_t req_byte = req.size() * HeapWordSize; - if (heap->cancel_gc(cause)) { - log_info(gc)("Failed to allocate %s, " PROPERFMT, req.type_string(), PROPERFMTARGS(req_byte)); - request_gc(cause); +void ShenandoahController::request_gc(GCCause::Cause cause) { + if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { + handle_requested_gc(cause); } - AllocTracer::send_allocation_requiring_gc_event(req_byte, checked_cast(get_gc_id())); +} - if (block) { - MonitorLocker ml(&_alloc_failure_waiters_lock); - while (!should_terminate() && ShenandoahCollectorPolicy::is_allocation_failure(heap->cancelled_cause())) { - ml.wait(); +void ShenandoahController::handle_requested_gc(GCCause::Cause cause) { + // Requested gc's always operate on the entire heap + const ShenandoahHeap* heap = ShenandoahHeap::heap(); + const ShenandoahCollectorPolicy* policy = heap->shenandoah_policy(); + ShenandoahGeneration* global = heap->global_generation(); + + // For normal requested GCs (System.gc) we want to block the caller. However, + // for whitebox requested GC, we want to initiate the GC and return immediately. + // The whitebox caller thread will arrange for itself to wait until the GC notifies + // it that has reached the requested breakpoint (phase in the GC). + if (cause == GCCause::_wb_breakpoint) { + notify_control_thread(cause, global); + return; + } + + // Make sure we have at least one complete GC cycle before unblocking + // from the explicit GC request. + // + // This is especially important for weak references cleanup and/or native + // resources (e.g. DirectByteBuffers) machinery: when explicit GC request + // comes very late in the already running cycle, it would miss lots of new + // opportunities for cleanup that were made available before the caller + // requested the GC. + MonitorLocker ml(&_gc_waiters_lock); + const size_t current_gc_id = get_gc_id(); + while (policy->last_whole_heap_gc_id() <= current_gc_id && !should_terminate()) { + notify_control_thread(cause, global); + ml.wait(); + } +} + +void ShenandoahController::clear_allocation_failure_and_notify_waiters() { + MonitorLocker waiters(&_alloc_waiters_lock, Mutex::_no_safepoint_check_flag); + { + MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); + if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) { + // If an allocation failure occurred during this cycle, we'll have threads waiting + // for reclaimed memory. We'll wake them up, and they'll retry their allocation. + // If our waiters cannot allocate, they will signal the control thread again + // to start another cycle. If we didn't clear the request here, the control thread would + // immediately begin another allocation failure cycle. + _requested_gc_cause = GCCause::_no_gc; } } + waiters.notify_all(); } -void ShenandoahController::handle_alloc_failure_evac(size_t words) { +void ShenandoahController::increase_concurrent_worker_count() { + while (true) { + const size_t workers = _concurrent_worker_count.load_relaxed(); + if (workers == ParallelGCThreads) { + break; + } - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - const bool is_humongous = ShenandoahHeapRegion::requires_humongous(words); - const GCCause::Cause cause = is_humongous ? GCCause::_shenandoah_humongous_allocation_failure : GCCause::_shenandoah_allocation_failure_evac; + const size_t new_value = MIN2(workers + 1, checked_cast(ParallelGCThreads)); + if (_concurrent_worker_count.compare_set(workers, new_value, memory_order_relaxed)) { + break; + } + } +} + +void ShenandoahController::decrease_concurrent_worker_count() { + const size_t alloc_stalls = _alloc_stall_count.exchange(0); + const int max_conc_gc_threads = checked_cast(ConcGCThreads); + if (alloc_stalls != 0) { + log_info(gc, ergo)("Allocation stalls: %zu, holding concurrent worker count at: %zu", + alloc_stalls, _concurrent_worker_count.load_relaxed()); + } else { + // There were no stalls during this cycle, try to reduce the concurrent gc workers + while (true) { + const int workers = _concurrent_worker_count.load_relaxed(); + if (workers == max_conc_gc_threads) { + break; + } - if (heap->cancel_gc(cause)) { - log_info(gc)("Failed to allocate " PROPERFMT " for evacuation", PROPERFMTARGS(words * HeapWordSize)); + const size_t new_value = MAX2(workers - 1, max_conc_gc_threads); + if (_concurrent_worker_count.compare_set(workers, new_value, memory_order_relaxed)) { + break; + } + } } } -void ShenandoahController::notify_alloc_failure_waiters() { - MonitorLocker ml(&_alloc_failure_waiters_lock); +void ShenandoahController::notify_gc_waiters() { + MonitorLocker ml(&_gc_waiters_lock); ml.notify_all(); } + +void ShenandoahController::notify_alloc_waiters() { + MonitorLocker ml(&_alloc_waiters_lock); + ml.notify_all(); +} + +const char* ShenandoahController::collector_phase_to_string(ShenandoahCollectorPhase phase) { + switch(phase) { + case UNSET: return "Outside of Cycle"; + case INITIALIZING: return "Initializing"; + case ROOTS: return "Roots"; + case MARK: return "Mark"; + case EVAC: return "Evacuation"; + case UPDATE_REFS: return "Update References"; + default: + ShouldNotReachHere(); + } +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp index 60b41a5fe995..3f462e6b8773 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp @@ -28,56 +28,129 @@ #include "gc/shared/concurrentGCThread.hpp" #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahAllocRequest.hpp" -#include "gc/shenandoah/shenandoahSharedVariables.hpp" +#include "gc/shenandoah/shenandoahPadding.hpp" #include "runtime/atomic.hpp" +class ShenandoahGeneration; + /** * This interface exposes methods necessary for the heap to interact * with the threads responsible for driving the collection cycle. */ class ShenandoahController: public ConcurrentGCThread { +public: + enum ShenandoahCollectorPhase { + UNSET, + INITIALIZING, + ROOTS, + MARK, + EVAC, + UPDATE_REFS, + PHASE_LIMIT + }; + private: shenandoah_padding(0); // A monotonically increasing GC count. Atomic _gc_id; shenandoah_padding(1); + // Written by control thread, read by mutators + Atomic _phase; + protected: const Mutex::Rank WAITERS_LOCK_RANK = Mutex::safepoint - 5; const Mutex::Rank CONTROL_LOCK_RANK = Mutex::nosafepoint - 2; - // While we could have a single lock for these, it may risk unblocking - // GC waiters when alloc failure GC cycle finishes. We want instead - // to make complete explicit cycle for demanding customers. - Monitor _alloc_failure_waiters_lock; + // This lock is used to coordinate waking up the control thread and protecting _requested_gc_cause. + // The generational derived class also uses this lock to protect _gc_mode and _requested_generation + Monitor _control_lock; + + // Threads waiting for a complete gc cycle (full gc or concurrent global) will wait here. Monitor _gc_waiters_lock; + // Threads waiting for the gc to free memory will wait here. Note that immediate garbage may be reclaimed + // midcycle during final mark, so we want to notify alloc waiters independently of cycle waiters. Similarly, + // no threads should be notified when a concurrent old mark increment is interrupted. + Monitor _alloc_waiters_lock; + + // Represents a gc request + GCCause::Cause _requested_gc_cause; + + // The number of stalls experienced during the cycle. Incremented by mutators, reset by control thread. + shenandoah_padding(2); + Atomic _alloc_stall_count; + shenandoah_padding(3); + + // Written by control thread and mutators, clamped between ConcGCThreads and ParallelGCThreads. + Atomic _concurrent_worker_count; + shenandoah_padding(4); + // Increments the internal GC count. void update_gc_id(); + // Increase worker count when a stall is reported. Called from mutator thread. + void increase_concurrent_worker_count(); + + // Decrease worker count if no stalls were detected in a cycle. Called from control thread. + void decrease_concurrent_worker_count(); + + // Returns the total number of allocation stalls during a cycle + size_t alloc_stall_count() const { + return _alloc_stall_count.load_relaxed(); + } + + // Notify the control thread to run a cycle for the given generation + virtual bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) = 0; + + // Notify the control thread about an allocation stall specifically. Implementation + // will decide which generation to use and any other mode specific work that must be done. + virtual void notify_alloc_stall(GCCause::Cause cause) = 0; + public: - ShenandoahController(): - _gc_id(0), - _alloc_failure_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahAllocFailureWaiters_lock", true), - _gc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahGCWaiters_lock", true) - { } + ShenandoahController(); // Request a collection cycle. This handles "explicit" gc requests // like System.gc and "implicit" gc requests, like metaspace oom. - virtual void request_gc(GCCause::Cause cause) = 0; + virtual void request_gc(GCCause::Cause cause); - // This cancels the collection cycle and has an option to block - // until another cycle completes successfully. - void handle_alloc_failure(const ShenandoahAllocRequest& req, bool block); + // Notify threads that the gc has completed a cycle, or it's exiting. + void notify_gc_waiters(); - // Invoked for allocation failures during evacuation. This cancels - // the collection cycle without blocking. - void handle_alloc_failure_evac(size_t words); + // Notify threads that the gc has recovered memory, or it's exiting. + void notify_alloc_waiters(); - // Notify threads waiting for GC to complete. - void notify_alloc_failure_waiters(); + // Inform the control thread that this allocation request failed. Caller will block until memory is reclaimed. + void handle_alloc_failure(const ShenandoahAllocRequest &req); + + // Run a full GC, callers will block until at least one full GC cycle is completed. + void handle_alloc_failure_full(); + + // Return suggested number of concurrent worker threads + size_t concurrent_worker_count() const { + return _concurrent_worker_count.load_relaxed(); + } // Return the value of a monotonic increasing GC count, maintained by the control thread. - size_t get_gc_id(); + size_t get_gc_id() const; + + ShenandoahCollectorPhase get_phase() const { + return _phase.load_relaxed(); + } + + // Record the current phase for the cycle. Used to track where allocation stalls occur. + void set_phase(ShenandoahCollectorPhase phase) { + assert(ShenandoahCollectorPhase::UNSET <= phase, "Phase out of bounds: %d", phase); + assert(phase < ShenandoahCollectorPhase::PHASE_LIMIT, "Phase out of bounds %d", phase); + _phase.store_relaxed(phase); + } + + static const char* collector_phase_to_string(ShenandoahCollectorPhase phase); + +protected: + virtual void handle_requested_gc(GCCause::Cause cause); + + // Used at the end of a cycle to clear allocation failure and notify alloc waiters + virtual void clear_allocation_failure_and_notify_waiters(); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHCONTROLLER_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp deleted file mode 100644 index 071a06d823dd..000000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (c) 2021, Red Hat, Inc. All rights reserved. - * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - - -#include "code/codeCache.hpp" -#include "gc/shared/collectorCounters.hpp" -#include "gc/shenandoah/shenandoahClosures.inline.hpp" -#include "gc/shenandoah/shenandoahCollectorPolicy.hpp" -#include "gc/shenandoah/shenandoahConcurrentMark.hpp" -#include "gc/shenandoah/shenandoahDegeneratedGC.hpp" -#include "gc/shenandoah/shenandoahFullGC.hpp" -#include "gc/shenandoah/shenandoahGeneration.hpp" -#include "gc/shenandoah/shenandoahGenerationalHeap.hpp" -#include "gc/shenandoah/shenandoahHeap.inline.hpp" -#include "gc/shenandoah/shenandoahMetrics.hpp" -#include "gc/shenandoah/shenandoahMonitoringSupport.hpp" -#include "gc/shenandoah/shenandoahOldGeneration.hpp" -#include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" -#include "gc/shenandoah/shenandoahSATBMarkQueueSet.hpp" -#include "gc/shenandoah/shenandoahSTWMark.hpp" -#include "gc/shenandoah/shenandoahUtils.hpp" -#include "gc/shenandoah/shenandoahVerifier.hpp" -#include "gc/shenandoah/shenandoahVMOperations.hpp" -#include "gc/shenandoah/shenandoahWorkerPolicy.hpp" -#include "gc/shenandoah/shenandoahYoungGeneration.hpp" -#include "runtime/threads.hpp" -#include "runtime/vmThread.hpp" -#include "utilities/events.hpp" - -ShenandoahDegenGC::ShenandoahDegenGC(ShenandoahDegenPoint degen_point, ShenandoahGeneration* generation, bool do_old_gc_bootstrap) : - ShenandoahGC(generation), - _degen_point(degen_point), - _abbreviated(false), - _do_old_gc_bootstrap(do_old_gc_bootstrap) { -} - -bool ShenandoahDegenGC::collect(GCCause::Cause cause) { - vmop_degenerated(); - ShenandoahHeap* heap = ShenandoahHeap::heap(); - if (heap->mode()->is_generational()) { - FormatBuffer<32> buf("Degenerated %s GC", _generation->name()); - const char* msg = _do_old_gc_bootstrap ? "Degenerated Bootstrap Old GC" : buf.buffer(); - heap->mmu_tracker()->record_degenerated(GCId::current(), msg); - heap->log_heap_status(FormatBuffer<64>("At end of %s", msg)); - } - return true; -} - -void ShenandoahDegenGC::vmop_degenerated() { - TraceCollectorStats tcs(ShenandoahHeap::heap()->monitoring_support()->full_stw_collection_counters()); - ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::degen_gc_gross); - VM_ShenandoahDegeneratedGC degenerated_gc(this); - VMThread::execute(°enerated_gc); -} - -void ShenandoahDegenGC::entry_degenerated() { - const char* msg = degen_event_message(_degen_point); - ShenandoahPausePhase gc_phase(msg, ShenandoahPhaseTimings::degen_gc, true /* log_heap_usage */); - EventMark em("%s", msg); - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - ShenandoahWorkerScope scope(heap->workers(), - ShenandoahWorkerPolicy::calc_workers_for_stw_degenerated(), - "stw degenerated gc"); - - heap->set_degenerated_gc_in_progress(true); - op_degenerated(); - heap->set_degenerated_gc_in_progress(false); - { - ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::degen_gc_propagate_gc_state); - heap->propagate_gc_state_to_all_threads(); - } -} - -void ShenandoahDegenGC::op_degenerated() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - heap->release_injected_pins(); - // Degenerated GC is STW, but it can also fail. Current mechanics communicates - // GC failure via cancelled_concgc() flag. So, if we detect the failure after - // some phase, we have to upgrade the Degenerate GC to Full GC. - heap->clear_cancelled_gc(); - - // If we degenerated from evacuation or update-refs, some objects in cset may - // have been self-forwarded by the failing thread. Clear those marks now so - // the remainder of this cycle (re-evac, update-refs, verification) sees a - // clean forwarding state. - if (_degen_point == ShenandoahDegenPoint::_degenerated_evac || - _degen_point == ShenandoahDegenPoint::_degenerated_update_refs) { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_un_self_forward); - heap->un_self_forward_cset_regions(); - } - - // If it's passive mode with ShenandoahCardBarrier turned on: clean the write table - // without swapping the tables since no scan happens in passive mode anyway - if (ShenandoahCardBarrier && !heap->mode()->is_generational()) { - heap->old_generation()->card_scan()->mark_write_table_as_clean(); - } - - if (heap->mode()->is_generational()) { - const ShenandoahOldGeneration* old_generation = heap->old_generation(); - if (!heap->is_concurrent_old_mark_in_progress()) { - // If we are not marking the old generation, there should be nothing in the old mark queues - assert(old_generation->task_queues()->is_empty(), "Old gen task queues should be empty"); - } else { - // This is still necessary for degenerated cycles because the degeneration point may occur - // after final mark of the young generation. See ShenandoahConcurrentGC::op_final_update_refs for - // a more detailed explanation. - old_generation->transfer_pointers_from_satb(); - } - - if (_generation->is_global()) { - // If we are in a global cycle, the old generation should not be marking. It is, however, - // allowed to be holding regions for evacuation or coalescing. - assert(old_generation->is_idle() - || old_generation->is_doing_mixed_evacuations() - || old_generation->is_preparing_for_mark(), - "Old generation cannot be in state: %s", old_generation->state_name()); - } - } - - ShenandoahMetricsSnapshot metrics(heap->free_set()); - - switch (_degen_point) { - // The cases below form the Duff's-like device: it describes the actual GC cycle, - // but enters it at different points, depending on which concurrent phase had - // degenerated. - - case _degenerated_outside_cycle: - // We have degenerated from outside the cycle, which means something is bad with - // the heap, most probably heavy humongous fragmentation, or we are very low on free - // space. It makes little sense to wait for Full GC to reclaim as much as it can, when - // we can do the most aggressive degen cycle, which includes processing references and - // class unloading, unless those features are explicitly disabled. - - // Note that we can only do this for "outside-cycle" degens, otherwise we would risk - // changing the cycle parameters mid-cycle during concurrent -> degenerated handover. - heap->set_unload_classes(_generation->heuristics()->can_unload_classes() && - (!heap->mode()->is_generational() || _generation->is_global())); - - if (heap->mode()->is_generational()) { - // Clean the read table before swapping it. The end goal here is to have a clean - // write table, and to have the read table updated with the previous write table. - heap->old_generation()->card_scan()->mark_read_table_as_clean(); - - if (_generation->is_young()) { - // Swap remembered sets for young - _generation->swap_card_tables(); - } - } - - case _degenerated_roots: - // Degenerated from concurrent root mark, reset the flag for STW mark - if (!heap->mode()->is_generational()) { - if (heap->is_concurrent_mark_in_progress()) { - heap->cancel_concurrent_mark(); - } - } else { - if (_generation->is_concurrent_mark_in_progress()) { - // We want to allow old generation marking to be punctuated by young collections - // (even if they have degenerated). If this is a global cycle, we'd have cancelled - // the entire old gc before coming into this switch. Note that cancel_marking on - // the generation does NOT abandon incomplete SATB buffers as cancel_concurrent_mark does. - // We need to separate out the old pointers which is done below. - _generation->cancel_marking(); - } - - if (_degen_point == ShenandoahDegenPoint::_degenerated_roots) { - // We only need this if the concurrent cycle has already swapped the card tables. - // Marking will use the 'read' table, but interesting pointers may have been - // recorded in the 'write' table in the time between the cancelled concurrent cycle - // and this degenerated cycle. These pointers need to be included in the 'read' table - // used to scan the remembered set during the STW mark which follows here. - _generation->merge_write_table(); - } - } - - op_reset(); - - // STW mark - op_mark(); - - case _degenerated_mark: - // No fallthrough. Continue mark, handed over from concurrent mark if - // concurrent mark has yet completed - if (_degen_point == ShenandoahDegenPoint::_degenerated_mark && heap->is_concurrent_mark_in_progress()) { - assert(!ShenandoahBarrierSet::satb_mark_queue_set().get_filter_out_young(), - "Should not be filtering out young pointers when concurrent mark degenerates"); - op_finish_mark(); - } - assert(!heap->cancelled_gc(), "STW mark can not OOM"); - - /* Degen select Collection Set. etc. */ - op_prepare_evacuation(); - - op_cleanup_early(); - - case _degenerated_evac: - // If heuristics thinks we should do the cycle, this flag would be set, - // and we can do evacuation. Otherwise, it would be the shortcut cycle. - if (heap->is_evacuation_in_progress()) { - - if (_degen_point == _degenerated_evac) { - // Degeneration under oom-evac protocol allows the mutator LRB to expose - // references to from-space objects. This is okay, in theory, because we - // will come to the safepoint here to complete the evacuations and update - // the references. However, if the from-space reference is written to a - // region that was EC during final mark or was recycled after final mark - // it will not have TAMS or UWM updated. Such a region is effectively - // skipped during update references which can lead to crashes and corruption - // if the from-space reference is accessed. - if (UseTLAB) { - heap->labs_make_parsable(); - } - - for (size_t i = 0; i < heap->num_regions(); i++) { - ShenandoahHeapRegion* r = heap->get_region(i); - if (r->is_active() && r->top() > r->get_update_watermark()) { - r->set_update_watermark_at_safepoint(r->top()); - } - } - } - - // Degeneration under oom-evac protocol might have left some objects in - // collection set un-evacuated. Restart evacuation from the beginning to - // capture all objects. For all the objects that are already evacuated, - // it would be a simple check, which is supposed to be fast. This is also - // safe to do even without degeneration, as CSet iterator is at beginning - // in preparation for evacuation anyway. - // - // Before doing that, we need to make sure we never had any cset-pinned - // regions. This may happen if allocation failure happened when evacuating - // the about-to-be-pinned object, oom-evac protocol left the object in - // the collection set, and then the pin reached the cset region. If we continue - // the cycle here, we would trash the cset and alive objects in it. To avoid - // it, we fail degeneration right away and slide into Full GC to recover. - - { - heap->sync_pinned_region_status(); - heap->collection_set()->clear_current_index(); - ShenandoahHeapRegion* r; - while ((r = heap->collection_set()->next()) != nullptr) { - if (r->is_pinned()) { - op_degenerated_fail(); - return; - } - } - - heap->collection_set()->clear_current_index(); - } - op_evacuate(); - if (heap->cancelled_gc()) { - op_degenerated_fail(); - return; - } - } else if (has_in_place_promotions(heap)) { - // We have nothing to evacuate, but there are still regions to promote in place. - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_promote_regions); - ShenandoahGenerationalHeap::heap()->promote_regions_in_place(_generation, false /* concurrent*/); - } - - // Update collector state regardless of whether there are forwarded objects - heap->set_evacuation_in_progress(false); - heap->set_concurrent_weak_root_in_progress(false); - heap->set_concurrent_strong_root_in_progress(false); - - // If heuristics thinks we should do the cycle, this flag would be set, - // and we need to do update-refs. Otherwise, it would be the shortcut cycle. - if (heap->has_forwarded_objects()) { - op_init_update_refs(); - assert(!heap->cancelled_gc(), "STW reference update can not OOM"); - } else { - _abbreviated = true; - } - - // labs are retired, walk the old regions and update remembered set - if (ShenandoahHeap::heap()->mode()->is_generational()) { - ShenandoahGenerationalHeap::heap()->old_generation()->update_card_table(); - } - - case _degenerated_update_refs: - if (heap->has_forwarded_objects()) { - op_update_refs(); - op_update_roots(); - assert(!heap->cancelled_gc(), "STW reference update can not OOM"); - } - - op_cleanup_complete(); - - if (heap->mode()->is_generational()) { - ShenandoahGenerationalHeap::heap()->complete_degenerated_cycle(); - } - - break; - default: - ShouldNotReachHere(); - } - - DEBUG_ONLY(heap->assert_no_self_forwards()); - - // Leaving degenerated GC, we need to flip barriers back to idle. - CodeCache::arm_all_nmethods(); - - if (ShenandoahVerify) { - heap->verifier()->verify_after_degenerated(_generation); - } - - if (VerifyAfterGC) { - Universe::verify(); - } - - // Decide if this cycle made good progress, and, if not, should it upgrade to a full GC. - const bool progress = metrics.is_good_progress(); - ShenandoahCollectorPolicy* policy = heap->shenandoah_policy(); - policy->record_degenerated(_generation->is_young(), _abbreviated, progress); - if (progress) { - heap->notify_gc_progress(); - _generation->heuristics()->record_degenerated(heap->mode()->is_generational() && _generation->is_global()); - heap->start_idle_span(); - } else if (policy->should_upgrade_degenerated_gc()) { - // Upgrade to full GC, register full-GC impact on heuristics. - op_degenerated_futile(); - } else { - _generation->heuristics()->record_degenerated(heap->mode()->is_generational() && _generation->is_global()); - } -} - -void ShenandoahDegenGC::op_reset() { - _generation->prepare_gc(); -} - -void ShenandoahDegenGC::op_mark() { - assert(!_generation->is_concurrent_mark_in_progress(), "Should be reset"); - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_mark); - ShenandoahSTWMark mark(_generation, false /*full gc*/); - mark.mark(); -} - -void ShenandoahDegenGC::op_finish_mark() { - ShenandoahConcurrentMark mark(_generation); - mark.finish_mark(); -} - -void ShenandoahDegenGC::op_prepare_evacuation() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - if (ShenandoahVerify) { - heap->verifier()->verify_roots_no_forwarded(_generation); - } - - // STW cleanup weak roots and unload classes - heap->parallel_cleaning(_generation, false /*full gc*/); - - // Prepare regions and collection set - _generation->prepare_regions_and_collection_set(false /*concurrent*/); - - // Retire the TLABs, which will force threads to reacquire their TLABs after the pause. - // This is needed for two reasons. Strong one: new allocations would be with new freeset, - // which would be outside the collection set, so no cset writes would happen there. - // Weaker one: new allocations would happen past update watermark, and so less work would - // be needed for reference updates (would update the large filler instead). - if (UseTLAB) { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_final_manage_labs); - heap->tlabs_retire(false); - } - - if (!heap->collection_set()->is_empty()) { - if (ShenandoahVerify) { - heap->verifier()->verify_before_evacuation(_generation); - } - - heap->set_evacuation_in_progress(true); - heap->set_has_forwarded_objects(true); - } else { - if (ShenandoahVerify) { - if (has_in_place_promotions(heap)) { - heap->verifier()->verify_after_concmark_with_promotions(_generation); - } else { - heap->verifier()->verify_after_concmark(_generation); - } - } - - if (VerifyAfterGC) { - Universe::verify(); - } - } -} - -bool ShenandoahDegenGC::has_in_place_promotions(const ShenandoahHeap* heap) const { - return heap->mode()->is_generational() && heap->old_generation()->has_in_place_promotions(); -} - -void ShenandoahDegenGC::op_cleanup_early() { - ShenandoahHeap::heap()->recycle_trash(); -} - -void ShenandoahDegenGC::op_evacuate() { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_evac); - ShenandoahHeap::heap()->evacuate_collection_set(_generation, false /* concurrent*/); -} - -void ShenandoahDegenGC::op_init_update_refs() { - // Evacuation has completed - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - heap->prepare_update_heap_references(); - heap->set_update_refs_in_progress(true); -} - -void ShenandoahDegenGC::op_update_refs() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_update_refs); - // Handed over from concurrent update references phase - heap->update_heap_references(_generation, false /*concurrent*/); - - heap->set_update_refs_in_progress(false); - heap->set_has_forwarded_objects(false); -} - -void ShenandoahDegenGC::op_update_roots() { - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - - update_roots(false /*full_gc*/); - - heap->update_heap_region_states(false /*concurrent*/); - - if (ShenandoahVerify) { - heap->verifier()->verify_after_update_refs(_generation); - } - - if (VerifyAfterGC) { - Universe::verify(); - } - - heap->rebuild_free_set(false /*concurrent*/); -} - -void ShenandoahDegenGC::op_cleanup_complete() { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_cleanup_complete); - - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - - // If we are degenerating for old mark only, we need to check that there are no accidental - // young pointers in SATB queues. If so, we need to filter them before we perform cleanups, - // otherwise they would dangle. - ShenandoahSATBMarkQueueSet& satb_qs = ShenandoahBarrierSet::satb_mark_queue_set(); - if (heap->is_concurrent_old_mark_in_progress() && !heap->is_concurrent_young_mark_in_progress()) { - assert(satb_qs.get_filter_out_young(), "Must be"); - ShenandoahFlushSATB flush_satb(satb_qs); - Threads::threads_do(&flush_satb); - } else { - assert(!satb_qs.get_filter_out_young(), "Must not be"); - } - - heap->recycle_trash(); -} - -void ShenandoahDegenGC::op_degenerated_fail() { - upgrade_to_full(); -} - -void ShenandoahDegenGC::op_degenerated_futile() { - upgrade_to_full(); -} - -const char* ShenandoahDegenGC::degen_event_message(ShenandoahDegenPoint point) const { - switch (point) { - case _degenerated_unset: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " ()"); - return msg; - } - case _degenerated_outside_cycle: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Outside of Cycle)"); - return msg; - } - case _degenerated_roots: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Roots)"); - return msg; - } - case _degenerated_mark: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Mark)"); - return msg; - } - case _degenerated_evac: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Evacuation)"); - return msg; - } - case _degenerated_update_refs: { - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (Update Refs)"); - return msg; - } - default: { - ShouldNotReachHere(); - SHENANDOAH_EVENT_MESSAGE(msg, _generation->type(), "Pause Degenerated GC", " (?)"); - return msg; - } - } -} - -void ShenandoahDegenGC::upgrade_to_full() { - log_info(gc, phases)("Degenerated GC upgrading to Full GC"); - ShenandoahHeap* heap = ShenandoahHeap::heap(); - heap->cancel_gc(GCCause::_shenandoah_upgrade_to_full_gc); - heap->increment_total_collections(true); - heap->shenandoah_policy()->record_degenerated_upgrade_to_full(); - ShenandoahFullGC full_gc; - full_gc.op_full(GCCause::_shenandoah_upgrade_to_full_gc); -} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp deleted file mode 100644 index f3d033af1288..000000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2021, Red Hat, Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP -#define SHARE_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP - -#include "gc/shenandoah/shenandoahGC.hpp" - -class VM_ShenandoahDegeneratedGC; -class ShenandoahGeneration; - -class ShenandoahDegenGC : public ShenandoahGC { - friend class VM_ShenandoahDegeneratedGC; -private: - const ShenandoahDegenPoint _degen_point; - bool _abbreviated; - const bool _do_old_gc_bootstrap; - -public: - ShenandoahDegenGC(ShenandoahDegenPoint degen_point, ShenandoahGeneration* generation, bool do_old_gc_bootstrap = false); - bool collect(GCCause::Cause cause) override; - -private: - void vmop_degenerated(); - void entry_degenerated(); - void op_degenerated(); - - void op_reset(); - void op_mark(); - void op_finish_mark(); - void op_prepare_evacuation(); - void op_cleanup_early(); - - void op_evacuate(); - void op_init_update_refs(); - void op_update_refs(); - void op_update_roots(); - void op_cleanup_complete(); - - // Fail handling - void op_degenerated_futile(); - void op_degenerated_fail(); - - // Turns this degenerated cycle into a full gc without leaving the safepoint - void upgrade_to_full(); - - const char* degen_event_message(ShenandoahDegenPoint point) const; - - bool has_in_place_promotions(const ShenandoahHeap* heap) const; -}; - -#endif // SHARE_GC_SHENANDOAH_SHENANDOAHDEGENERATEDGC_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp index 72d0773eccdf..aad35f512c40 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp @@ -1,6 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahThreadLocalData.hpp" #include "runtime/thread.hpp" +#include "runtime/threads.hpp" #include "runtime/threadSMR.inline.hpp" ShenandoahEvacuationStats::ShenandoahEvacuations* ShenandoahEvacuationStats::get_category( @@ -120,7 +121,7 @@ ShenandoahCycleStats ShenandoahEvacuationTracker::flush_cycle_to_global() { java_threads_iterator.list()->threads_do(&aggregate_mutators); ShenandoahStatAggregator aggregate_workers(&workers); - ShenandoahHeap::heap()->gc_threads_do(&aggregate_workers); + Threads::non_java_threads_do(&aggregate_workers); _mutators_global.accumulate(&mutators); _workers_global.accumulate(&workers); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp index 11b98a10c12d..a7379170ad52 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp @@ -24,9 +24,10 @@ * */ - +#include "classfile/systemDictionary.hpp" #include "code/codeCache.hpp" #include "compiler/oopMap.hpp" +#include "gc/shared/classUnloadingContext.hpp" #include "gc/shared/continuationGCSupport.hpp" #include "gc/shared/fullGCForwarding.inline.hpp" #include "gc/shared/gcTraceTime.inline.hpp" @@ -50,6 +51,7 @@ #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" #include "gc/shenandoah/shenandoahMetrics.hpp" #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" +#include "gc/shenandoah/shenandoahParallelCleaning.inline.hpp" #include "gc/shenandoah/shenandoahPhaseTimings.hpp" #include "gc/shenandoah/shenandoahReferenceProcessor.hpp" #include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" @@ -58,6 +60,7 @@ #include "gc/shenandoah/shenandoahVerifier.hpp" #include "gc/shenandoah/shenandoahVMOperations.hpp" #include "gc/shenandoah/shenandoahWorkerPolicy.hpp" +#include "gc/shenandoah/shenandoahYoungGeneration.hpp" #include "memory/metaspaceUtils.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.inline.hpp" @@ -111,7 +114,7 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) { ShenandoahMetricsSnapshot metrics(heap->free_set()); // Perform full GC - do_it(cause); + do_it(); if (heap->mode()->is_generational()) { ShenandoahGenerationalFullGC::handle_completion(heap); @@ -125,9 +128,19 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) { heap->notify_gc_no_progress(); } - // Regardless if progress was made, we record that we completed a "successful" full GC. - _generation->heuristics()->record_success_full(); - heap->shenandoah_policy()->record_success_full(); + // Regardless if progress was made, we record that we completed a full GC. + if (heap->mode()->is_generational()) { + // In generational mode, the young heuristics are responsible for this failure + heap->young_generation()->heuristics()->record_full_gc(cause); + } else { + _generation->heuristics()->record_full_gc(cause); + } + + if (cause == GCCause::_shenandoah_upgrade_to_full_gc) { + heap->shenandoah_policy()->record_alloc_failure_to_full(); + } + + heap->shenandoah_policy()->record_success_full(heap->control_thread()->get_gc_id()); // Leaving full GC, we need to flip barriers back to idle. CodeCache::arm_all_nmethods(); @@ -138,23 +151,19 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) { } } -void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { +void ShenandoahFullGC::do_it() { ShenandoahHeap* heap = ShenandoahHeap::heap(); heap->release_injected_pins(); - // A full GC may be entered directly, or as an upgrade from a failed - // degenerated GC. In the latter case, self-forwarded objects may be - // present from the failed evacuation. Drain those marks before any phase - // (verify, update_roots, phase1_mark_heap) walks headers. - { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_un_self_forward); - heap->un_self_forward_cset_regions(); - } - if (heap->mode()->is_generational()) { ShenandoahGenerationalFullGC::prepare(); } + guarantee(heap->is_idle(), "Full GC should not be running from incomplete cycle"); + guarantee(!heap->has_forwarded_objects(), "Concurrent cycle should have finished update references"); + guarantee(!heap->is_concurrent_mark_in_progress(), "Cannot be marking now"); + guarantee(!heap->has_self_forwarded_objects(), "Self forwarded objects should be cleared by concurrent cycle."); + if (ShenandoahVerify) { heap->verifier()->verify_before_fullgc(_generation); } @@ -163,11 +172,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { Universe::verify(); } - // Degenerated GC may carry concurrent root flags when upgrading to - // full GC. We need to reset it before mutators resume. - heap->set_concurrent_strong_root_in_progress(false); - heap->set_concurrent_weak_root_in_progress(false); - heap->set_full_gc_in_progress(true); assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at a safepoint"); @@ -180,39 +184,8 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { { ShenandoahGCPhase prepare_phase(ShenandoahPhaseTimings::full_gc_prepare); - // Full GC is supposed to recover from any GC state: - - // a0. Remember if we have forwarded objects - bool has_forwarded_objects = heap->has_forwarded_objects(); - // a1. Cancel evacuation, if in progress - if (heap->is_evacuation_in_progress()) { - heap->set_evacuation_in_progress(false); - } - assert(!heap->is_evacuation_in_progress(), "sanity"); - - // a2. Cancel update-refs, if in progress - if (heap->is_update_refs_in_progress()) { - heap->set_update_refs_in_progress(false); - } - assert(!heap->is_update_refs_in_progress(), "sanity"); - - // b. Cancel all concurrent marks, if in progress - if (heap->is_concurrent_mark_in_progress()) { - heap->cancel_concurrent_mark(); - } - assert(!heap->is_concurrent_mark_in_progress(), "sanity"); - - // c. Update roots if this full GC is due to evac-oom, which may carry from-space pointers in roots. - if (has_forwarded_objects) { - update_roots(true /*full_gc*/); - } - - // d. Abandon reference discovery and clear all discovered references. - ShenandoahReferenceProcessor* rp = _generation->ref_processor(); - rp->abandon_partial_discovery(); - - // e. Sync pinned region status from the CP marks + // Sync pinned region status from the CP marks heap->sync_pinned_region_status(); if (heap->mode()->is_generational()) { @@ -221,8 +194,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { // The rest of prologue: _preserved_marks->init(heap->workers()->active_workers()); - - assert(heap->has_forwarded_objects() == has_forwarded_objects, "This should not change"); } if (UseTLAB) { @@ -235,11 +206,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { phase1_mark_heap(); - // Once marking is done, which may have fixed up forwarded objects, we can drop it. - // Coming out of Full GC, we would not have any forwarded objects. - // This also prevents resolves with fwdptr from kicking in while adjusting pointers in phase3. - heap->set_has_forwarded_objects(false); - heap->set_full_gc_move_in_progress(true); // Setup workers for the rest @@ -280,8 +246,6 @@ void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { heap->set_full_gc_move_in_progress(false); heap->set_full_gc_in_progress(false); - DEBUG_ONLY(heap->assert_no_self_forwards()); - if (ShenandoahVerify) { heap->verifier()->verify_after_fullgc(_generation); } @@ -312,15 +276,91 @@ void ShenandoahFullGC::phase1_mark_heap() { // enable ("weak") refs discovery rp->set_soft_reference_policy(true); // forcefully purge all soft references - ShenandoahSTWMark mark(_generation, true /*full_gc*/); + ShenandoahSTWMark mark(_generation); mark.mark(); - heap->parallel_cleaning(_generation, true /* full_gc */); + parallel_cleaning(_generation); - if (ShenandoahHeap::heap()->mode()->is_generational()) { + if (heap->mode()->is_generational()) { ShenandoahGenerationalFullGC::log_live_in_old(heap); } } +void ShenandoahFullGC::parallel_cleaning(ShenandoahGeneration* generation) const { + assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint"); + assert(ShenandoahHeap::heap()->is_stw_gc_in_progress(), "Only for Full GC"); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_purge); + stw_weak_refs(generation); + stw_process_weak_roots(); + stw_unload_classes(); +} + +void ShenandoahFullGC::stw_weak_refs(ShenandoahGeneration* generation) { + // Weak refs processing + ShenandoahPhaseTimings::Phase phase = ShenandoahPhaseTimings::full_gc_weakrefs; + ShenandoahTimingsTracker t(phase); + ShenandoahGCWorkerPhase worker_phase(phase); + generation->ref_processor()->process_references(phase, ShenandoahHeap::heap()->workers(), false /* concurrent */); +} + +// Weak roots are either pre-evacuated (final mark) or updated (final update refs), +// so they should not have forwarded oops. +// However, we do need to "null" dead oops in the roots, if it cannot be done +// in concurrent cycles. +void ShenandoahFullGC::stw_process_weak_roots() const { + ShenandoahHeap* heap = ShenandoahHeap::heap(); + uint num_workers = heap->workers()->active_workers(); + ShenandoahPhaseTimings::Phase timing_phase = ShenandoahPhaseTimings::full_gc_purge_weak_par; + ShenandoahGCPhase phase(timing_phase); + ShenandoahGCWorkerPhase worker_phase(timing_phase); + // Cleanup weak roots + ShenandoahIsAliveClosure is_alive; +#ifdef ASSERT + ShenandoahAssertNotForwardedClosure verify_cl; + ShenandoahParallelWeakRootsCleaningTask cleaning_task(timing_phase, &is_alive, &verify_cl, num_workers); +#else + ShenandoahParallelWeakRootsCleaningTask cleaning_task(timing_phase, &is_alive, &do_nothing_cl, num_workers); +#endif + heap->workers()->run_task(&cleaning_task); +} + +void ShenandoahFullGC::stw_unload_classes() const { + ShenandoahHeap* heap = ShenandoahHeap::heap(); + if (!heap->unload_classes()) return; + ClassUnloadingContext ctx(heap->workers()->active_workers(), + true /* unregister_nmethods_during_purge */, + false /* lock_nmethod_free_separately */); + + // Unload classes and purge SystemDictionary. + { + ShenandoahPhaseTimings::Phase phase = ShenandoahPhaseTimings::full_gc_purge_class_unload; + ShenandoahIsAliveSelector is_alive; + { + CodeCache::UnlinkingScope scope(is_alive.is_alive_closure()); + ShenandoahGCPhase gc_phase(phase); + ShenandoahGCWorkerPhase worker_phase(phase); + bool unloading_occurred = SystemDictionary::do_unloading(_gc_timer); + + ShenandoahClassUnloadingTask unlink_task(phase, unloading_occurred); + heap->workers()->run_task(&unlink_task); + } + // Release unloaded nmethods's memory. + ClassUnloadingContext::context()->purge_and_free_nmethods(); + } + + { + ShenandoahGCPhase phase(ShenandoahPhaseTimings::full_gc_purge_cldg); + ClassLoaderDataGraph::purge(true /* at_safepoint */); + } + // Resize and verify metaspace + MetaspaceGC::compute_new_size(); + + if (heap->mode()->is_generational()) { + heap->old_generation()->set_parsable(false); + } + + DEBUG_ONLY(MetaspaceUtils::verify();) +} + class ShenandoahPrepareForCompactionObjectClosure : public ObjectClosure { private: PreservedMarks* const _preserved_marks; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp index c69c8cec8fcb..def0b07c8a4e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.hpp @@ -25,9 +25,7 @@ #ifndef SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP #define SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP -#include "gc/shared/gcTimer.hpp" #include "gc/shenandoah/shenandoahGC.hpp" -#include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegionSet.hpp" /** @@ -50,19 +48,16 @@ * Parallelization is handled by assigning each GC worker the slice of the heap (the set of regions) * where it does sliding compaction, without interfering with other threads. */ - +class GCTimer; class PreservedMarksSet; +class ShenandoahHeap; class VM_ShenandoahFullGC; -class ShenandoahDegenGC; class ShenandoahFullGC : public ShenandoahGC { friend class ShenandoahPrepareForCompactionObjectClosure; friend class VM_ShenandoahFullGC; - friend class ShenandoahDegenGC; -private: GCTimer* _gc_timer; - PreservedMarksSet* _preserved_marks; public: @@ -76,7 +71,7 @@ class ShenandoahFullGC : public ShenandoahGC { void entry_full(GCCause::Cause cause); void op_full(GCCause::Cause cause); - void do_it(GCCause::Cause gc_cause); + void do_it(); void phase1_mark_heap(); void phase2_calculate_target_addresses(ShenandoahHeapRegionSet** worker_slices); @@ -86,6 +81,14 @@ class ShenandoahFullGC : public ShenandoahGC { void distribute_slices(ShenandoahHeapRegionSet** worker_slices); void calculate_target_humongous_objects(); void compact_humongous_objects(); + + // Perform STW class unloading and weak root cleaning + void parallel_cleaning(ShenandoahGeneration* generation) const; + + void stw_unload_classes() const; + void stw_process_weak_roots() const; + + static void stw_weak_refs(ShenandoahGeneration* generation); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHFULLGC_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp deleted file mode 100644 index 6c6207e1618a..000000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahGC.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2021, Red Hat, Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - - -#include "compiler/oopMap.hpp" -#include "gc/shared/workerThread.hpp" -#include "gc/shenandoah/shenandoahClosures.inline.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" -#include "gc/shenandoah/shenandoahHeap.hpp" -#include "gc/shenandoah/shenandoahPhaseTimings.hpp" -#include "gc/shenandoah/shenandoahRootProcessor.inline.hpp" -#include "gc/shenandoah/shenandoahUtils.hpp" - -const char* ShenandoahGC::degen_point_to_string(ShenandoahDegenPoint point) { - switch(point) { - case _degenerated_unset: - return ""; - case _degenerated_outside_cycle: - return "Outside of Cycle"; - case _degenerated_roots: - return "Roots"; - case _degenerated_mark: - return "Mark"; - case _degenerated_evac: - return "Evacuation"; - case _degenerated_update_refs: - return "Update References"; - default: - ShouldNotReachHere(); - return "ERROR"; - } -} - -class ShenandoahUpdateRootsTask : public WorkerTask { -private: - ShenandoahRootUpdater* _root_updater; - bool _check_alive; -public: - ShenandoahUpdateRootsTask(ShenandoahRootUpdater* root_updater, bool check_alive) : - WorkerTask("Shenandoah Update Roots"), - _root_updater(root_updater), - _check_alive(check_alive){ - } - - void work(uint worker_id) { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a safepoint"); - ShenandoahParallelWorkerSession worker_session(worker_id); - - ShenandoahNonConcUpdateRefsClosure cl; - if (_check_alive) { - ShenandoahForwardedIsAliveClosure is_alive; - _root_updater->roots_do(worker_id, &is_alive, &cl); - } else { - AlwaysTrueClosure always_true; - _root_updater->roots_do(worker_id, &always_true, &cl); - } - } -}; - -void ShenandoahGC::update_roots(bool full_gc) { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a safepoint"); - assert(ShenandoahHeap::heap()->is_full_gc_in_progress() || - ShenandoahHeap::heap()->is_degenerated_gc_in_progress(), - "Only for degenerated GC and full GC"); - - bool check_alive = !full_gc; - ShenandoahPhaseTimings::Phase p = full_gc ? - ShenandoahPhaseTimings::full_gc_update_roots : - ShenandoahPhaseTimings::degen_gc_update_roots; - - ShenandoahGCPhase phase(p); -#ifdef COMPILER2 - DerivedPointerTable::clear(); -#endif // COMPILER2 - - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - WorkerThreads* workers = heap->workers(); - uint nworkers = workers->active_workers(); - - ShenandoahRootUpdater root_updater(nworkers, p); - ShenandoahUpdateRootsTask update_roots(&root_updater, check_alive); - workers->run_task(&update_roots); - -#ifdef COMPILER2 - DerivedPointerTable::update_pointers(); -#endif // COMPILER2 -} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp index 7182665f2e3a..670cf6dad5a0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGC.hpp @@ -29,45 +29,27 @@ #include "memory/allocation.hpp" /* - * Base class of three Shenandoah GC modes + * Base class of three Shenandoah GC collections * * The relationship of the GCs: * * ("normal" mode) ----> Concurrent GC ----> (finish) - * | - * | - * v - * ("passive" mode) ---> Degenerated GC ---> (finish) - * | - * | - * v - * Full GC --------> (finish) + * ("old" mode) --> Concurrent GC (bootstrap) --> Old GC --> (finish) + * ("passive" mode) ---> Full GC --------> (finish) */ class ShenandoahGeneration; class ShenandoahGC : public StackObj { public: - // Fail point from concurrent GC - enum ShenandoahDegenPoint { - _degenerated_unset, - _degenerated_outside_cycle, - _degenerated_roots, - _degenerated_mark, - _degenerated_evac, - _degenerated_update_refs, - _DEGENERATED_LIMIT - }; - - explicit ShenandoahGC(ShenandoahGeneration* generation) : _generation(generation) {} + explicit ShenandoahGC(ShenandoahGeneration* generation) + : _generation(generation) { } // Returns false if the collection was cancelled, true otherwise. virtual bool collect(GCCause::Cause cause) = 0; - static const char* degen_point_to_string(ShenandoahDegenPoint point); ShenandoahGeneration* generation() const { return _generation; } protected: - static void update_roots(bool full_gc); ShenandoahGeneration* _generation; }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index fb99825bef93..718b32ee7fc9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -25,6 +25,7 @@ #include "gc/shenandoah/heuristics/shenandoahHeuristics.hpp" #include "gc/shenandoah/shenandoahAffiliation.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" @@ -83,24 +84,6 @@ class ShenandoahResetBitmapClosure final : public ShenandoahHeapRegionClosure { bool is_thread_safe() override { return true; } }; -// Copy the write-version of the card-table into the read-version, clearing the -// write-copy. -class ShenandoahMergeWriteTable: public ShenandoahHeapRegionClosure { -private: - ShenandoahScanRemembered* _scanner; -public: - ShenandoahMergeWriteTable(ShenandoahScanRemembered* scanner) : _scanner(scanner) {} - - void heap_region_do(ShenandoahHeapRegion* r) override { - assert(r->is_old(), "Don't waste time doing this for non-old regions"); - _scanner->merge_write_table(r->bottom(), ShenandoahHeapRegion::region_size_words()); - } - - bool is_thread_safe() override { - return true; - } -}; - // Add [TAMS, top) volume over young regions. Used to correct age 0 cohort census // for adaptive tenuring when census is taken during marking. // In non-product builds, for the purposes of verification, we also collect the total @@ -228,20 +211,6 @@ void ShenandoahGeneration::swap_card_tables() { old_generation->card_scan()->swap_card_tables(); } -// Copy the write-version of the card-table into the read-version, clearing the -// write-version. The work is done at a safepoint and in parallel by the GC -// worker threads. -void ShenandoahGeneration::merge_write_table() { - // This should only happen for degenerated cycles - ShenandoahGenerationalHeap* heap = ShenandoahGenerationalHeap::heap(); - heap->assert_gc_workers(heap->workers()->active_workers()); - shenandoah_assert_safepoint(); - - ShenandoahOldGeneration* old_generation = heap->old_generation(); - ShenandoahMergeWriteTable task(old_generation->card_scan()); - old_generation->parallel_heap_region_iterate(&task); -} - void ShenandoahGeneration::prepare_gc() { reset_mark_bitmap(); } @@ -250,16 +219,15 @@ void ShenandoahGeneration::parallel_heap_region_iterate_free(ShenandoahHeapRegio ShenandoahHeap::heap()->parallel_heap_region_iterate(cl); } -void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { +void ShenandoahGeneration::prepare_regions_and_collection_set() { ShenandoahHeap* heap = ShenandoahHeap::heap(); ShenandoahCollectionSet* collection_set = heap->collection_set(); bool is_generational = heap->mode()->is_generational(); - assert(!heap->is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); + assert(!heap->is_full_gc_in_progress(), "Only for concurrent GC"); assert(!is_old(), "Only YOUNG and GLOBAL GC perform evacuations"); { - ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::final_update_region_states : - ShenandoahPhaseTimings::degen_gc_final_update_region_states); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_region_states); // Update region state for every active region, but only update the liveness data for // the generation we marked. We always need to update the watermark for old regions. // If there are mixed collections pending, we also need to synchronize the pinned status @@ -284,8 +252,7 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { } { - ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::choose_cset : - ShenandoahPhaseTimings::degen_gc_choose_cset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::choose_cset); collection_set->clear(); ShenandoahHeapLocker locker(heap->lock()); @@ -307,10 +274,9 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { } { - ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::final_rebuild_freeset : - ShenandoahPhaseTimings::degen_gc_final_rebuild_freeset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_rebuild_freeset); ShenandoahHeapLocker locker(heap->lock()); - // At start of evacation, we do NOT compute_old_generation_balance() + // At start of evacuation, we do NOT compute_old_generation_balance() size_t young_trashed_regions, old_trashed_regions, first_old, last_old, num_old; _free_set->prepare_to_rebuild(young_trashed_regions, old_trashed_regions, first_old, last_old, num_old); _free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, num_old); @@ -426,8 +392,3 @@ size_t ShenandoahGeneration::available(size_t capacity) const { size_t result = in_use > capacity ? 0 : capacity - in_use; return result; } - -void ShenandoahGeneration::record_success_concurrent(bool abbreviated) { - heuristics()->record_success_concurrent(); - ShenandoahHeap::heap()->shenandoah_policy()->record_success_concurrent(is_young(), abbreviated); -} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp index 0183db7d1b3a..fd6b69990d18 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp @@ -32,6 +32,7 @@ #include "gc/shenandoah/shenandoahLock.hpp" #include "gc/shenandoah/shenandoahMarkingContext.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class ShenandoahCollectionSet; class ShenandoahHeap; @@ -55,6 +56,10 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { // Bytes reserved within this generation to hold evacuated objects from the collection set size_t _evacuation_reserve; + // Read by mutators, written by control thread. This records the id of the last gc cycle + // to collect this generation. + Atomic _completed_gc_id; + protected: ShenandoahFreeSet* _free_set; ShenandoahHeuristics* _heuristics; @@ -68,6 +73,14 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { uint max_workers); ~ShenandoahGeneration(); + void update_completed_gc_id(size_t gc_id) { + _completed_gc_id.store_relaxed(gc_id); + } + + size_t completed_gc_id() const { + return _completed_gc_id.load_relaxed(); + } + inline bool is_young() const { return _type == YOUNG; } inline bool is_old() const { return _type == OLD; } inline bool is_global() const { return _type == GLOBAL || _type == NON_GEN; } @@ -109,12 +122,9 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { template void reset_mark_bitmap(); - // Used by concurrent and degenerated GC to reset remembered set. + // Used by concurrent GC to reset remembered set. void swap_card_tables(); - // Update the read cards with the state of the write table (write table is not cleared). - void merge_write_table(); - // Called before init mark, expected to prepare regions for marking. virtual void prepare_gc(); @@ -135,7 +145,7 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { // been set aside to hold objects evacuated from the young-gen collection set. Conservatively, this value // equals the entire amount of live young-gen memory within the collection set, even though some of this memory // will likely be promoted. - virtual void prepare_regions_and_collection_set(bool concurrent); + virtual void prepare_regions_and_collection_set(); // Cancel marking (used by Full collect and when cancelling cycle). virtual void cancel_marking(); @@ -179,8 +189,6 @@ class ShenandoahGeneration : public CHeapObj, public ShenandoahSpaceInfo { virtual bool is_concurrent_mark_in_progress() = 0; void confirm_heuristics_mode(); - - virtual void record_success_concurrent(bool abbreviated); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHGENERATION_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index d73ba05b44a9..5db519df1efa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -28,7 +28,6 @@ #include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahConcurrentGC.hpp" -#include "gc/shenandoah/shenandoahDegeneratedGC.hpp" #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahFullGC.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" @@ -48,13 +47,9 @@ #include "utilities/events.hpp" ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() : - _control_lock(CONTROL_LOCK_RANK, "ShenandoahGCRequest_lock", true), - _requested_gc_cause(GCCause::_no_gc), _requested_generation(nullptr), _gc_mode(none), - _degen_point(ShenandoahGC::_degenerated_unset), - _heap(ShenandoahGenerationalHeap::heap()), - _do_old_gc_bootstrap(false) { + _heap(ShenandoahGenerationalHeap::heap()) { shenandoah_assert_generational(); set_name("ShenControl"); create_and_start(); @@ -62,10 +57,7 @@ ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() : void ShenandoahGenerationalControlThread::run_service() { - // This is the only instance of request. It is important that request.generation - // does not change between a concurrent cycle failure and the start of a degenerated - // cycle. We initialize it with the young generation to handle the pathological case - // where the very first cycle is degenerated (some tests exercise this path). + // This is the only instance of request. ShenandoahGCRequest request; request.generation = _heap->young_generation(); while (!should_terminate()) { @@ -85,6 +77,7 @@ void ShenandoahGenerationalControlThread::run_service() { // if there was no other cycle requested, cleanup and wait for the next request. MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); if (_requested_gc_cause == GCCause::_no_gc) { + _heap->clear_cancelled_gc(); set_gc_mode(ml, none); ml.wait(); } @@ -92,7 +85,7 @@ void ShenandoahGenerationalControlThread::run_service() { // In case any threads are waiting for a cycle to happen, notify them so they observe the shutdown. notify_gc_waiters(); - notify_alloc_failure_waiters(); + notify_alloc_waiters(); set_gc_mode(stopped); // We're done writing GC stats, so print them here. @@ -102,36 +95,41 @@ void ShenandoahGenerationalControlThread::run_service() { void ShenandoahGenerationalControlThread::stop_service() { log_debug(gc, thread)("Stopping control thread"); MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - _heap->cancel_gc(GCCause::_shenandoah_stop_vm); notify_control_thread(ml, GCCause::_shenandoah_stop_vm); // We can't wait here because it may interfere with the active cycle's ability // to reach a safepoint (this runs on a java thread). } +void ShenandoahGenerationalControlThread::notify_alloc_stall(GCCause::Cause cause) { + ShenandoahGeneration* generation = _heap->young_generation(); + + // Tell the young generation heuristics about the stall. + generation->heuristics()->record_allocation_stall(); + + MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); + notify_control_thread(ml, cause, generation); +} + void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& request) { // Hold the lock while we read request cause and generation MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - log_debug(gc, thread)("cancelled cause: %s, requested cause: %s", - GCCause::to_string(_heap->cancelled_cause()), GCCause::to_string(_requested_gc_cause)); + log_debug(gc, thread)("Requested cause: %s", GCCause::to_string(_requested_gc_cause)); request.cause = _requested_gc_cause; + request.generation = _requested_generation; + + // If the cause was an allocation failure or an explicit request, override the generation if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) { - if (_degen_point == ShenandoahGC::_degenerated_unset) { + if (_heap->old_generation()->is_concurrent_mark_in_progress()) { + // We know we can't allocate in young, and we know we need to collect old. + // We don't really know how much longer it will take to clear out old (probably + // a long time if it keeps getting interrupted by allocation failures). At + // this point, let's run a global cycle. + request.generation = _heap->global_generation(); + } else if (_requested_generation == nullptr) { request.generation = _heap->young_generation(); - _degen_point = ShenandoahGC::_degenerated_outside_cycle; - } else { - assert(request.generation != nullptr, "Must know which generation to use for degenerated cycle"); - } - } else { - if (request.cause == GCCause::_shenandoah_concurrent_gc || ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { - // This is a regulator request or an explicit gc request. Note that an explicit gc request is allowed to - // "upgrade" a regulator request. It is possible that the regulator "canceled" an old mark, so we must - // clear that cancellation here or the explicit gc cycle will erroneously detect it as a cancellation. - // This clear operation will only clear the cancellation if it was set by regulator request. - _heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc); } - request.generation = _requested_generation; } log_debug(gc, thread)("request.cause: %s, request.generation: %s", @@ -139,79 +137,68 @@ void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& _requested_gc_cause = GCCause::_no_gc; _requested_generation = nullptr; + _heap->clear_cancelled_gc(); if (request.cause == GCCause::_no_gc || request.cause == GCCause::_shenandoah_stop_vm) { return; } assert(request.generation != nullptr, "request.generation cannot be null, cause is: %s", GCCause::to_string(request.cause)); + GCMode mode; - if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) { - mode = prepare_for_allocation_failure_gc(request); - } else if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { - mode = prepare_for_explicit_gc(request); + if (ShenandoahCollectorPolicy::should_run_full_gc(request.cause)) { + mode = prepare_for_full_gc(request); } else { mode = prepare_for_concurrent_gc(request); } set_gc_mode(ml, mode); } -ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_allocation_failure_gc(ShenandoahGCRequest &request) { - // Important: not all paths update the request.generation. This is intentional. - // A degenerated cycle must use the same generation carried over from the previous request. - if (request.generation->is_old()) { - // This means we degenerated during the young bootstrap for the old generation - // cycle. The following degenerated cycle should therefore also be young. - request.generation = _heap->young_generation(); +ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_full_gc(ShenandoahGCRequest &request) const { + if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { + prepare_for_explicit_gc(request); + return stw_full; } - ShenandoahHeuristics* heuristics = request.generation->heuristics(); - bool old_gen_evacuation_failed = _heap->old_generation()->clear_failed_evacuation(); - - heuristics->log_trigger("Handle Allocation Failure"); + request.generation = _heap->global_generation(); + request.generation->heuristics()->log_trigger("Handle Full GC: %s", GCCause::to_string(request.cause)); + return stw_full; +} - // Do not bother with degenerated cycle if old generation evacuation failed - if (ShenandoahDegeneratedGC && heuristics->should_degenerate_cycle() && !old_gen_evacuation_failed) { - heuristics->record_allocation_failure_gc(); - _heap->shenandoah_policy()->record_alloc_failure_to_degenerated(_degen_point); - return stw_degenerated; - } else { - heuristics->record_allocation_failure_gc(); - _heap->shenandoah_policy()->record_alloc_failure_to_full(); - request.generation = _heap->global_generation(); - return stw_full; - } +void ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest& request) const { + ShenandoahHeuristics* heuristics = request.generation->heuristics(); + heuristics->record_requested_gc(); + heuristics->log_trigger("GC Request (%s)", GCCause::to_string(request.cause)); + _heap->set_unload_classes(heuristics->can_unload_classes()); } -ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_explicit_gc(ShenandoahGCRequest &request) const { - ShenandoahHeuristics* global_heuristics = _heap->global_generation()->heuristics(); - request.generation = _heap->global_generation(); - global_heuristics->log_trigger("GC Request (%s)", GCCause::to_string(request.cause)); - global_heuristics->record_requested_gc(); +ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_concurrent_gc(ShenandoahGCRequest &request) const { - if (ShenandoahCollectorPolicy::should_run_full_gc(request.cause)) { - return stw_full; - } else { - // Unload and clean up everything. Note that this is an _explicit_ request and so does not use - // the same `should_unload_classes` call as the regulator's concurrent gc request. - _heap->set_unload_classes(global_heuristics->can_unload_classes()); + if (ShenandoahCollectorPolicy::is_explicit_gc(request.cause)) { + prepare_for_explicit_gc(request); return concurrent_normal; } -} - -ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_concurrent_gc(const ShenandoahGCRequest &request) const { - assert(!(request.generation->is_old() && _heap->old_generation()->is_doing_mixed_evacuations()), - "Old heuristic should not request cycles while it waits for mixed evacuations"); if (request.generation->is_global()) { - ShenandoahHeuristics* global_heuristics = _heap->global_generation()->heuristics(); - _heap->set_unload_classes(global_heuristics->should_unload_classes()); + _heap->set_unload_classes(request.generation->heuristics()->should_unload_classes()); } else { _heap->set_unload_classes(false); } + if (request.generation->is_old()) { + switch (_heap->old_generation()->state()) { + case ShenandoahOldGeneration::FILLING: + case ShenandoahOldGeneration::MARKING: + return servicing_old; + case ShenandoahOldGeneration::IDLE: + return bootstrapping_old; + default: + fatal("Should not be getting request for old collection in state: %s", _heap->old_generation()->state_name()); + } + } + // preemption was requested or this is a regular cycle - return request.generation->is_old() ? servicing_old : concurrent_normal; + return concurrent_normal; } void ShenandoahGenerationalControlThread::maybe_print_young_region_ages() const { @@ -234,6 +221,15 @@ void ShenandoahGenerationalControlThread::maybe_print_young_region_ages() const } } +void ShenandoahGenerationalControlThread::clear_allocation_failure_and_notify_waiters() { + ShenandoahController::clear_allocation_failure_and_notify_waiters(); + if (_heap->old_generation()->clear_failed_evacuation()) { + // Immediately run a full GC. + log_info(gc, ergo)("Requesting a full gc because of evacuation failures in old"); + notify_control_thread(GCCause::_shenandoah_upgrade_to_full_gc, _heap->global_generation()); + } +} + void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest& request) { log_debug(gc, thread)("Starting GC (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name()); @@ -275,34 +271,25 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest service_concurrent_normal_cycle(request); break; } - case stw_degenerated: { - service_stw_degenerated_cycle(request); + case bootstrapping_old: + case servicing_old: { + service_concurrent_old_cycle(request); break; } case stw_full: { service_stw_full_cycle(request.cause); break; } - case servicing_old: { - assert(request.generation->is_old(), "Expected old generation here"); - service_concurrent_old_cycle(request); - break; - } default: ShouldNotReachHere(); } _heap->print_after_gc(); } - // If this cycle completed successfully, notify threads waiting for gc - if (!_heap->cancelled_gc()) { - notify_gc_waiters(); - notify_alloc_failure_waiters(); - // Report current free set state at the end of cycle if normal completion. - // Do not report if cancelled, since we may not have rebuilt free set and content is unreliable. - _heap->free_set()->log_status_under_lock(); - } + // Try to reduce concurrent workers + decrease_concurrent_worker_count(); + _heap->free_set()->log_status_under_lock(); // Notify Universe about new heap usage. This has implications for // global soft refs policy, and we better report it every time heap @@ -345,41 +332,23 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest } log_debug(gc, thread)("Completed GC (%s): %s, %s, cancelled: %s", - gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), GCCause::to_string(_heap->cancelled_cause())); + gc_mode_name(gc_mode()), GCCause::to_string(request.cause), request.generation->name(), BOOL_TO_STR(_heap->cancelled_gc())); } -// Young and old concurrent cycles are initiated by the regulator. GC requests are handled by the controller thread and -// always run a global cycle (which is concurrent by default, but may be overridden by command line options). Old cycles -// always degenerate to a global cycle. Young cycles are degenerated to complete the young cycle. Young and old degen -// may upgrade to Full GC. Full GC may also be triggered directly by a System.gc() invocation. -// -// -// +-----+ Idle +-----+-----------+---------------------+ -// | + | | | -// | | | | | -// | | v | | -// | | Bootstrap Old +-- | ------------+ | -// | | + | | | -// | | | | | | -// | v v v v | -// | Resume Old <----------+ Young +--> Young Degen | -// | + + ^ + + | -// v | | | | | | -// Global <-+ | +----------------------------+ | | -// + | | | -// | v v | -// +---> Global Degen +--------------------> Full <----+ -// +// Young and old concurrent cycles are initiated by the regulator. GC requests are +// handled by the controller thread and always run a global cycle (which is concurrent +// by default, but may be overridden by command line options to run on a safepoint). +// If there is an allocation failure during old marking, it will be cancelled. The +// next cycle will be global to complete collection of old and young together. Except for +// old marking, all concurrent cycles run to completion before returning to IDLE to choose +// the next action. void ShenandoahGenerationalControlThread::service_concurrent_normal_cycle(const ShenandoahGCRequest& request) { log_info(gc, ergo)("Start GC cycle (%s)", request.generation->name()); - if (request.generation->is_old()) { - service_concurrent_old_cycle(request); - } else { - service_concurrent_cycle(request.generation, request.cause, false); - } + service_concurrent_cycle(request.generation, request.cause, false); } void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const ShenandoahGCRequest& request) { + log_info(gc, ergo)("Start GC cycle (%s)", request.generation->name()); ShenandoahOldGeneration* old_generation = _heap->old_generation(); ShenandoahYoungGeneration* young_generation = _heap->young_generation(); ShenandoahOldGeneration::State original_state = old_generation->state(); @@ -389,74 +358,65 @@ void ShenandoahGenerationalControlThread::service_concurrent_old_cycle(const She _heap->increment_total_collections(false); switch (original_state) { - case ShenandoahOldGeneration::FILLING: { - ShenandoahGCSession session(request.cause, old_generation); - assert(gc_mode() == servicing_old, "Filling should be servicing old"); - _allow_old_preemption.set(); - old_generation->entry_coalesce_and_fill(); - _allow_old_preemption.unset(); - - // Before bootstrapping begins, we must acknowledge any cancellation request. - // If the gc has not been cancelled, this does nothing. If it has been cancelled, - // this will clear the cancellation request and exit before starting the bootstrap - // phase. This will allow the young GC cycle to proceed normally. If we do not - // acknowledge the cancellation request, the subsequent young cycle will observe - // the request and essentially cancel itself. - if (check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle)) { - // Need to report at "gc" level to report GC ID proper. - log_info(gc)("Preparation for old generation cycle was cancelled"); - return; - } - - // Coalescing threads completed and nothing was cancelled. it is safe to transition from this state. - old_generation->transition_to(ShenandoahOldGeneration::IDLE); - return; - } case ShenandoahOldGeneration::IDLE: old_generation->transition_to(ShenandoahOldGeneration::MARKING); + set_gc_mode(bootstrapping_old); + + // This is the logical gc id for the old collection. The old cycle itself might be + // interrupted many times and each iteration will generate a new gc id, but the one + // we record at the start will span all the interruptions. + old_generation->record_collection_start(get_gc_id()); + // Configure the young generation's concurrent mark to put objects in // old regions into the concurrent mark queues associated with the old // generation. The young cycle will run as normal except that rather than // ignore old references it will mark and enqueue them in the old concurrent - // task queues but it will not traverse them. - set_gc_mode(bootstrapping_old); + // task queues, but it will not traverse them. young_generation->set_old_gen_task_queues(old_generation->task_queues()); service_concurrent_cycle(young_generation, request.cause, true); _heap->process_gc_stats(); - if (_heap->cancelled_gc()) { - // Young generation bootstrap cycle has failed. Concurrent mark for old generation - // is going to resume after degenerated bootstrap cycle completes. - log_info(gc, phases)("Bootstrap cycle for old generation was cancelled"); - return; - } - - assert(_degen_point == ShenandoahGC::_degenerated_unset, "Degen point should not be set if gc wasn't cancelled"); - // From here we will 'resume' the old concurrent mark. This will skip reset - // and init mark for the concurrent mark. All of that work will have been - // done by the bootstrapping young cycle. - set_gc_mode(servicing_old); + // Here we return to the top of the control loop so that we can test + // if memory was exhausted before we start marking old. This will notify alloc + // waiters and give them a chance to resume before old marking begins. If our + // bootstrap cycle did not free enough memory, we will escalate to a global cycle + // and skip old marking. + break; case ShenandoahOldGeneration::MARKING: { ShenandoahGCSession session(request.cause, old_generation); - bool marking_complete = resume_concurrent_old_cycle(old_generation, request.cause); - if (marking_complete) { - assert(old_generation->state() != ShenandoahOldGeneration::MARKING, "Should not still be marking"); - if (original_state == ShenandoahOldGeneration::MARKING) { - _heap->mmu_tracker()->record_old_marking_increment(true); - _heap->log_heap_status("At end of Concurrent Old Marking finishing increment"); - } - } else if (original_state == ShenandoahOldGeneration::MARKING) { + resume_concurrent_old_cycle(old_generation, request.cause); + if (old_generation->state() != ShenandoahOldGeneration::MARKING) { + _heap->mmu_tracker()->record_old_marking_increment(true); + _heap->log_heap_status("At end of Concurrent Old Marking finishing increment"); + } else { _heap->mmu_tracker()->record_old_marking_increment(false); _heap->log_heap_status("At end of Concurrent Old Marking increment"); } break; } + case ShenandoahOldGeneration::FILLING: { + ShenandoahGCSession session(request.cause, old_generation); + _allow_old_preemption.set(); + const bool filling_complete = old_generation->entry_coalesce_and_fill(); + _allow_old_preemption.unset(); + + if (!filling_complete) { + assert(old_generation->state() == ShenandoahOldGeneration::FILLING, "Should still be filling"); + // Need to report at "gc" level to report GC ID proper. + log_info(gc)("Preparation for old generation cycle was cancelled"); + break; + } + + // Coalescing threads completed and nothing was cancelled. it is safe to transition from this state. + old_generation->transition_to(ShenandoahOldGeneration::IDLE); + break; + } default: fatal("Unexpected state for old GC: %s", ShenandoahOldGeneration::state_name(old_generation->state())); } } -bool ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause) { +void ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause) { assert(_heap->is_concurrent_old_mark_in_progress(), "Old mark should be in progress"); log_debug(gc)("Resuming old generation with " UINT32_FORMAT " marking tasks queued", generation->task_queues()->tasks()); @@ -465,61 +425,29 @@ bool ShenandoahGenerationalControlThread::resume_concurrent_old_cycle(Shenandoah // is allowed to cancel a GC. ShenandoahOldGC gc(generation, _allow_old_preemption); if (gc.collect(cause)) { - _heap->notify_gc_progress(); - generation->record_success_concurrent(false); - } - - if (_heap->cancelled_gc()) { - // It's possible the gc cycle was cancelled after the last time the collection checked for cancellation. In which - // case, the old gc cycle is still completed, and we have to deal with this cancellation. We set the degeneration - // point to be outside the cycle because if this is an allocation failure, that is what must be done (there is no - // degenerated old cycle). If the cancellation was due to a heuristic wanting to start a young cycle, then we are - // not actually going to a degenerated cycle, so don't set the degeneration point here. - if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) { - check_cancellation_or_degen(ShenandoahGC::_degenerated_outside_cycle); - } else if (cause == GCCause::_shenandoah_concurrent_gc) { - _heap->shenandoah_policy()->record_interrupted_old(); + if (alloc_stall_count() == 0) { + _heap->notify_gc_progress(); } - return false; + + // For old GC, record that it has completed the gc it started + generation->update_completed_gc_id(generation->started_gc_id()); + generation->heuristics()->record_concurrent_completion(); + _heap->shenandoah_policy()->record_success_old(); + + // Old marking has completed, it will have rebuilt the free set with any immediate garbage. + // Notify alloc waiters. We don't notify cycle waiters because no threads should be waiting + // for an old cycle and because cycle waiters would have requested a global cycle that would + // have cancelled the old mark anyway. + clear_allocation_failure_and_notify_waiters(); + } else if (cause == GCCause::_shenandoah_concurrent_gc) { + _heap->shenandoah_policy()->record_interrupted_old(); } - return true; } -// Normal cycle goes via all concurrent phases. If allocation failure (af) happens during -// any of the concurrent phases, it first degrades to Degenerated GC and completes GC there. -// If second allocation failure happens during Degenerated GC cycle (for example, when GC -// tries to evac something and no memory is available), cycle degrades to Full GC. -// -// There are also a shortcut through the normal cycle: immediate garbage shortcut, when -// heuristics says there are no regions to compact, and all the collection comes from immediately -// reclaimable regions. -// -// ................................................................................................ -// -// (immediate garbage shortcut) Concurrent GC -// /-------------------------------------------\ -// | | -// | | -// | | -// | v -// [START] ----> Conc Mark ----o----> Conc Evac --o--> Conc Update-Refs ---o----> [END] -// | | | ^ -// | (af) | (af) | (af) | -// ..................|....................|.................|..............|....................... -// | | | | -// | | | | Degenerated GC -// v v v | -// STW Mark ----------> STW Evac ----> STW Update-Refs ----->o -// | | | ^ -// | (af) | (af) | (af) | -// ..................|....................|.................|..............|....................... -// | | | | -// | v | | Full GC -// \------------------->o<----------------/ | -// | | -// v | -// Full GC --------------------------/ -// +// Normal cycle goes via all concurrent phases. There is also a shortcut through the normal +// cycle: immediate garbage shortcut. When heuristics say there are no regions to compact, +// and all the collection comes from immediately reclaimable regions, Shenandoah can skip the +// evacuation phase. void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGeneration* generation, GCCause::Cause cause, bool do_old_gc_bootstrap) { @@ -531,17 +459,25 @@ void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGen assert(!generation->is_old(), "Old GC takes a different control path"); - _do_old_gc_bootstrap = do_old_gc_bootstrap; - ShenandoahConcurrentGC gc(generation, do_old_gc_bootstrap); + ShenandoahConcurrentGC gc(this, generation, do_old_gc_bootstrap); _heap->increment_total_collections(false); if (gc.collect(cause)) { // Cycle is complete - _heap->notify_gc_progress(); - generation->record_success_concurrent(gc.abbreviated()); - _do_old_gc_bootstrap = false; + if (alloc_stall_count() == 0) { + _heap->notify_gc_progress(); + } + + // In the generational mode, we don't use global heuristics to trigger global cycles, so + // direct heuristic signals to the young heuristics + generation->update_completed_gc_id(get_gc_id()); + _heap->young_generation()->heuristics()->record_concurrent_completion(); + _heap->shenandoah_policy()->record_success_concurrent(get_gc_id(), generation->is_young(), gc.abbreviated()); + + // Notify threads waiting for GC and/or memory when a young or global cycle completes + notify_gc_waiters(); + clear_allocation_failure_and_notify_waiters(); } else { assert(_heap->cancelled_gc(), "Must have been cancelled"); - check_cancellation_or_degen(gc.degen_point()); } const char* msg; @@ -576,67 +512,16 @@ void ShenandoahGenerationalControlThread::service_concurrent_cycle(ShenandoahGen _heap->log_heap_status(msg); } -bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point) { - // Only read the cancellation cause once. Other threads may change it. - const GCCause::Cause cancelled_cause = _heap->cancelled_cause(); - if (cancelled_cause == GCCause::_no_gc) { - return false; - } - - if (cancelled_cause == GCCause::_shenandoah_stop_vm - || cancelled_cause == GCCause::_shenandoah_concurrent_gc) { - log_debug(gc, thread)("Cancellation detected, reason: %s", GCCause::to_string(cancelled_cause)); - return true; - } - - if (ShenandoahCollectorPolicy::is_allocation_failure(cancelled_cause)) { - assert(_degen_point == ShenandoahGC::_degenerated_unset, - "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point)); - MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - _requested_gc_cause = cancelled_cause; - _degen_point = point; - log_debug(gc, thread)("Cancellation detected:, reason: %s, degen point: %s", - GCCause::to_string(cancelled_cause), - ShenandoahGC::degen_point_to_string(_degen_point)); - return true; - } - - fatal("Cancel GC either for alloc failure GC, or gracefully exiting, or to pause old generation marking"); -} - void ShenandoahGenerationalControlThread::service_stw_full_cycle(GCCause::Cause cause) { _heap->increment_total_collections(true); ShenandoahGCSession session(cause, _heap->global_generation()); ShenandoahFullGC gc; gc.collect(cause); - _degen_point = ShenandoahGC::_degenerated_unset; - _do_old_gc_bootstrap = false; -} - -void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const ShenandoahGCRequest& request) { - assert(_degen_point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set"); - _heap->increment_total_collections(false); - - ShenandoahGCSession session(request.cause, request.generation, true, - _degen_point == ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle); - ShenandoahDegenGC gc(_degen_point, request.generation, _do_old_gc_bootstrap); - gc.collect(request.cause); - _degen_point = ShenandoahGC::_degenerated_unset; - _do_old_gc_bootstrap = false; - - assert(_heap->young_generation()->task_queues()->is_empty(), "Unexpected young generation marking tasks"); - if (request.generation->is_global()) { - assert(_heap->old_generation()->task_queues()->is_empty(), "Unexpected old generation marking tasks"); - assert(_heap->global_generation()->task_queues()->is_empty(), "Unexpected global generation marking tasks"); - } -} -void ShenandoahGenerationalControlThread::request_gc(GCCause::Cause cause) { - if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) { - notify_control_thread(cause); - } else if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { - handle_requested_gc(cause); - } + // Notify threads waiting for GC and/or memory that a full cycle has completed + _heap->global_generation()->update_completed_gc_id(get_gc_id()); + notify_gc_waiters(); + clear_allocation_failure_and_notify_waiters(); } bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGeneration* generation) { @@ -659,7 +544,6 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera // Cancel the old GC and wait for the control thread to start servicing the new request. log_info(gc, phases)("Preempting old generation mark to allow %s GC", generation->name()); while (gc_mode() == servicing_old) { - _heap->cancel_gc(GCCause::_shenandoah_concurrent_gc); notify_control_thread(ml, GCCause::_shenandoah_concurrent_gc, generation); ml.wait(); } @@ -687,40 +571,43 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera return false; } -void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) { +bool ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) { MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - notify_control_thread(ml, cause, generation); + return notify_control_thread(ml, cause, generation); } -void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation) { - assert(_control_lock.is_locked(), "Request lock must be held here"); - if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) { - // We have already observed a request to handle an allocation failure. We cannot allow - // another request (System.gc or regulator) to subvert the degenerated cycle. +bool ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, + ShenandoahGeneration* generation) { + assert(_control_lock.owned_by_self(), "Request lock must be held here"); + if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) { log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause)); - } else { - log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name()); - _requested_gc_cause = cause; - _requested_generation = generation; - ml.notify(); + return false; } + + log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name()); + request_gc_and_interrupt_old(cause, generation); + ml.notify_all(); + return true; } -void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause) { - MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - notify_control_thread(ml, cause); +void ShenandoahGenerationalControlThread::request_gc_and_interrupt_old(GCCause::Cause cause, + ShenandoahGeneration* generation) { + assert(_control_lock.owned_by_self(), "Request lock must be held here"); + _requested_gc_cause = cause; + _requested_generation = generation; + if (gc_mode() == servicing_old && !generation->is_old()) { + _heap->cancel_gc(cause); + } } void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause) { - assert(_control_lock.is_locked(), "Request lock must be held here"); - if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) { - // We have already observed a request to handle an allocation failure. We cannot allow - // another request (System.gc or regulator) to subvert the degenerated cycle. + assert(_control_lock.owned_by_self(), "Request lock must be held here"); + if (ShenandoahCollectorPolicy::is_higher_priority(_requested_gc_cause, cause)) { log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause)); } else { log_debug(gc, thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause)); _requested_gc_cause = cause; - ml.notify(); + ml.notify_all(); } } @@ -728,46 +615,100 @@ bool ShenandoahGenerationalControlThread::preempt_old_marking(ShenandoahGenerati return generation->is_young() && _allow_old_preemption.try_unset(); } -void ShenandoahGenerationalControlThread::handle_requested_gc(GCCause::Cause cause) { - // For normal requested GCs (System.gc) we want to block the caller. However, - // for whitebox requested GC, we want to initiate the GC and return immediately. - // The whitebox caller thread will arrange for itself to wait until the GC notifies - // it that has reached the requested breakpoint (phase in the GC). - if (cause == GCCause::_wb_breakpoint) { - notify_control_thread(cause, ShenandoahHeap::heap()->global_generation()); - return; +bool ShenandoahGenerationalControlThread::wait_for_gc_cycle(GCCause::Cause cause, ShenandoahGeneration* generation, uint timeout_seconds) { + if (generation->is_old()) { + return wait_for_old_gc_cycle(cause, static_cast(generation), timeout_seconds); } // Make sure we have at least one complete GC cycle before unblocking - // from the explicit GC request. + // from the requested GC. // // This is especially important for weak references cleanup and/or native - // resources (e.g. DirectByteBuffers) machinery: when explicit GC request + // resources (e.g. DirectByteBuffers) machinery: when the GC request // comes very late in the already running cycle, it would miss lots of new // opportunities for cleanup that were made available before the caller // requested the GC. - MonitorLocker ml(&_gc_waiters_lock); - size_t current_gc_id = get_gc_id(); - const size_t required_gc_id = current_gc_id + 1; - while (current_gc_id < required_gc_id && !should_terminate()) { - // Make requests to run a global cycle until at least one is completed - notify_control_thread(cause, ShenandoahHeap::heap()->global_generation()); + const size_t entry_gc_id = get_gc_id(); + while (generation->completed_gc_id() <= entry_gc_id && !should_terminate()) { + // Make requests to run cycles until at least one is completed + notify_control_thread(cause, generation); ml.wait(); - current_gc_id = get_gc_id(); } + return generation->completed_gc_id() > entry_gc_id; } -void ShenandoahGenerationalControlThread::notify_gc_waiters() { +bool ShenandoahGenerationalControlThread::wait_for_old_gc_cycle(GCCause::Cause cause, ShenandoahOldGeneration* generation, uint timeout_seconds) { + const double start = os::elapsedTime(); + const double deadline = start + timeout_seconds; MonitorLocker ml(&_gc_waiters_lock); - ml.notify_all(); + const size_t entry_old_gc_id = generation->started_gc_id(); + const size_t entry_gc_id = get_gc_id(); + generation->heuristics()->log_trigger("WhiteBox Old Mark"); + while (generation->completed_gc_id() <= entry_old_gc_id && !should_terminate()) { + // Make requests to run cycles until at least one old mark cycle is completed after this method was called + if (os::elapsedTime() > deadline) { + log_info(gc, thread)("Giving up on running an old gc after: %.3f seconds", os::elapsedTime() - start); + log_info(gc, thread)("Entry gc id: %zu, current id: %zu, entry old gc: %zu, last completed: %zu", + entry_gc_id, get_gc_id(), entry_old_gc_id, generation->completed_gc_id()); + return false; + } + + { + // Take control lock for gc mode. Old gc state is changed only when the gc mode is not none. + MonitorLocker controller(&_control_lock, Mutex::_no_safepoint_check_flag); + + if (gc_mode() == none) { + switch (generation->state()) { + case ShenandoahOldGeneration::IDLE: + // Note: we must only submit a request if the old generation is IDLE + notify_control_thread(controller, cause, generation); + break; + case ShenandoahOldGeneration::MARKING: + case ShenandoahOldGeneration::FILLING: + // do nothing, let the existing cycle finish + break; + case ShenandoahOldGeneration::EVACUATING: + case ShenandoahOldGeneration::EVACUATING_AFTER_GLOBAL: + // Expedite the mixed collections to finish the old cycle + generation->heuristics()->log_trigger("WhiteBox Old Expedite Mixed"); + notify_control_thread(controller, cause, _heap->young_generation()); + break; + default: + ShouldNotReachHere(); + } + } + } + + ml.wait(100); + } + + // check actual condition in case vm was stopped and thread was terminated + return generation->completed_gc_id() > entry_old_gc_id; +} + +void ShenandoahGenerationalControlThread::handle_requested_gc(GCCause::Cause cause) { + // For normal requested GCs (System.gc) we want to block the caller. However, + // for whitebox requested GC, we want to initiate the GC and return immediately. + // The whitebox caller thread will arrange for itself to wait until the GC notifies + // it that has reached the requested breakpoint (phase in the GC). + if (cause == GCCause::_wb_breakpoint) { + notify_control_thread(cause, ShenandoahHeap::heap()->global_generation()); + return; + } + + // We can only run concurrent young GCs. If the request is to run a young gc, but + // explicit invokes concurrent is disabled, you get a global/full GC. + ShenandoahGeneration* generation = cause == GCCause::_wb_young_gc && ExplicitGCInvokesConcurrent + ? ShenandoahHeap::heap()->young_generation() + : ShenandoahHeap::heap()->global_generation(); + wait_for_gc_cycle(cause, generation); } const char* ShenandoahGenerationalControlThread::gc_mode_name(GCMode mode) { switch (mode) { case none: return "idle"; case concurrent_normal: return "normal"; - case stw_degenerated: return "degenerated"; case stw_full: return "full"; case servicing_old: return "old"; case bootstrapping_old: return "bootstrap"; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp index 358299ea4693..51cefa728d5f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp @@ -28,7 +28,6 @@ #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahController.hpp" -#include "gc/shenandoah/shenandoahGC.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" #include "runtime/mutexLocker.hpp" @@ -42,7 +41,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { typedef enum { none, concurrent_normal, - stw_degenerated, stw_full, bootstrapping_old, servicing_old, @@ -57,14 +55,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { }; private: - // This lock is used to coordinate setting the _requested_gc_cause, _requested generation - // and _gc_mode. It is important that these be changed together and have a consistent view. - Monitor _control_lock; - - // Represents a normal (non cancellation) gc request. This can be set by mutators (System.gc, - // whitebox gc, etc.) or by the regulator thread when the heuristics want to start a cycle. - GCCause::Cause _requested_gc_cause; - // This is the generation the request should operate on. ShenandoahGeneration* _requested_generation; @@ -73,10 +63,6 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // before making any state changes (double-checked locking idiom). volatile GCMode _gc_mode; - // Only the control thread knows the correct degeneration point. This is used to have the - // control thread resume a STW cycle from the point where the concurrent cycle was cancelled. - ShenandoahGC::ShenandoahDegenPoint _degen_point; - // A reference to the heap ShenandoahGenerationalHeap* _heap; @@ -84,46 +70,47 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // preparing for mark). ShenandoahSharedFlag _allow_old_preemption; - // True while the current cycle is the bootstrap of an old GC. - bool _do_old_gc_bootstrap; - public: ShenandoahGenerationalControlThread(); - void run_service() override; - void stop_service() override; - - void request_gc(GCCause::Cause cause) override; - // Return true if the request to start a concurrent GC for the given generation succeeded. bool request_concurrent_gc(ShenandoahGeneration* generation); + // Visible for white box API to start an old cycle + bool wait_for_gc_cycle(GCCause::Cause cause, ShenandoahGeneration* generation, uint timeout_seconds = 10); + // Returns the current state of the control thread GCMode gc_mode() const { return _gc_mode; } + +protected: + void run_service() override; + void stop_service() override; + + void notify_alloc_stall(GCCause::Cause cause) override; + bool notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation) override; + + // Will also schedule an immediate full gc if old evacuations failed + void clear_allocation_failure_and_notify_waiters() override; + + // Handle whitebox requests to run old or young cycles + void handle_requested_gc(GCCause::Cause cause) override; + private: - // Returns true if the cycle has been cancelled or degenerated. - bool check_cancellation_or_degen(ShenandoahGC::ShenandoahDegenPoint point); // Executes one GC cycle void run_gc_cycle(const ShenandoahGCRequest& request); // Returns true if the old generation marking completed (i.e., final mark executed for old generation). - bool resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause); + void resume_concurrent_old_cycle(ShenandoahOldGeneration* generation, GCCause::Cause cause); // Various service methods handle different gc cycle types void service_concurrent_cycle(ShenandoahGeneration* generation, GCCause::Cause cause, bool reset_old_bitmap_specially); void service_stw_full_cycle(GCCause::Cause cause); - void service_stw_degenerated_cycle(const ShenandoahGCRequest& request); void service_concurrent_normal_cycle(const ShenandoahGCRequest& request); void service_concurrent_old_cycle(const ShenandoahGCRequest& request); - void notify_gc_waiters(); - - // Blocks until at least one global GC cycle is complete. - void handle_requested_gc(GCCause::Cause cause); - // Returns true if the old generation marking was interrupted to allow a young cycle. bool preempt_old_marking(ShenandoahGeneration* generation); @@ -137,21 +124,25 @@ class ShenandoahGenerationalControlThread: public ShenandoahController { // These notify the control thread after updating _requested_gc_cause and (optionally) _requested_generation. // Updating the requested generation is not necessary for allocation failures nor when stopping the thread. - void notify_control_thread(GCCause::Cause cause); void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause); - void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation); - void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation); + bool notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation); + void request_gc_and_interrupt_old(GCCause::Cause cause, ShenandoahGeneration* generation); // Take the _control_lock and check for a request to run a gc cycle. If a request is found, // the `prepare` methods are used to configure the heap and update heuristics accordingly. void check_for_request(ShenandoahGCRequest& request); - GCMode prepare_for_allocation_failure_gc(ShenandoahGCRequest &request); - GCMode prepare_for_explicit_gc(ShenandoahGCRequest &request) const; - GCMode prepare_for_concurrent_gc(const ShenandoahGCRequest &request) const; + GCMode prepare_for_concurrent_gc(ShenandoahGCRequest &request) const; + GCMode prepare_for_full_gc(ShenandoahGCRequest& request) const; + void prepare_for_explicit_gc(ShenandoahGCRequest &request) const; // Print table for young region ages if log is enabled void maybe_print_young_region_ages() const; + + // Specialized function that waits for one complete old gc cycle. Note that this may invoke + // young cycles in order to complete mixed evacuations of an existent old gc before starting + // another. + bool wait_for_old_gc_cycle(GCCause::Cause cause, ShenandoahOldGeneration* generation, uint timeout_seconds); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHGENERATIONALCONTROLTHREAD_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp index 2d58fbb63ae5..3bf7d8f1fe23 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp @@ -1,6 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ */ #include "gc/shenandoah/shenandoahAsserts.hpp" +#include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahForwarding.inline.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp" @@ -50,28 +51,22 @@ class ShenandoahConcurrentEvacuator : public ObjectClosure { ShenandoahGenerationalEvacuationTask::ShenandoahGenerationalEvacuationTask(ShenandoahGenerationalHeap* heap, ShenandoahGeneration* generation, ShenandoahRegionIterator* iterator, - bool concurrent, bool only_promote_regions) : + bool only_promote_regions) : WorkerTask("Shenandoah Evacuation"), _heap(heap), _generation(generation), _regions(iterator), - _concurrent(concurrent), + _collection_set(_heap->collection_set()), _only_promote_regions(only_promote_regions) { shenandoah_assert_generational(); } void ShenandoahGenerationalEvacuationTask::work(uint worker_id) { - if (_concurrent) { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahConcurrentWorkerSession worker_session(worker_id); - SuspendibleThreadSetJoiner stsj; - do_work(); - } else { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_evac, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahParallelWorkerSession worker_session(worker_id); - do_work(); - } + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner stsj; + do_work(); } void ShenandoahGenerationalEvacuationTask::do_work() { @@ -104,7 +99,7 @@ void ShenandoahGenerationalEvacuationTask::promote_regions() { promoter.maybe_promote_region(r); - if (_heap->check_cancelled_gc_and_yield(_concurrent)) { + if (_heap->check_cancelled_gc_and_yield()) { break; } } @@ -113,25 +108,28 @@ void ShenandoahGenerationalEvacuationTask::promote_regions() { void ShenandoahGenerationalEvacuationTask::evacuate_and_promote_regions() { LogTarget(Debug, gc) lt; ShenandoahConcurrentEvacuator cl(_heap); - ShenandoahInPlacePromoter promoter(_heap); ShenandoahHeapRegion* r; - - while ((r = _regions->next()) != nullptr) { + while ((r = _collection_set->claim_next()) != nullptr) { if (lt.is_enabled()) { LogStream ls(lt); log_region(r, &ls); } - if (r->is_cset()) { - assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); - _heap->marked_object_iterate(r, &cl); - } else { - promoter.maybe_promote_region(r); - } + assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); + _heap->marked_object_iterate(r, &cl); - if (_heap->check_cancelled_gc_and_yield(_concurrent)) { + if (ShenandoahCollectorPolicy::should_abandon_evacuations(r)) { + // No more evacuations for this thread, but it may yet complete in-place promotions break; } + + if (_heap->check_cancelled_gc_and_yield()) { + // GC is cancelled (vm is stopping), no further work + assert(_heap->is_stopping(), "Can only stop evacuation for shutdown"); + return; + } } + + promote_regions(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp index 1ff58b42e8cb..50a67869d6f2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.hpp @@ -31,6 +31,7 @@ class ShenandoahGeneration; class ShenandoahGenerationalHeap; class ShenandoahHeapRegion; class ShenandoahRegionIterator; +class ShenandoahCollectionSet; // Unlike ShenandoahEvacuationTask, this iterates over all regions rather than just the collection set. // This is needed in order to promote humongous start regions if age() >= tenure threshold. @@ -39,22 +40,19 @@ class ShenandoahGenerationalEvacuationTask : public WorkerTask { ShenandoahGenerationalHeap* const _heap; ShenandoahGeneration* const _generation; ShenandoahRegionIterator* _regions; - bool _concurrent; + ShenandoahCollectionSet* _collection_set; bool _only_promote_regions; public: ShenandoahGenerationalEvacuationTask(ShenandoahGenerationalHeap* sh, ShenandoahGeneration* generation, ShenandoahRegionIterator* iterator, - bool concurrent, bool only_promote_regions); + bool only_promote_regions); void work(uint worker_id) override; private: void do_work(); void promote_regions(); void evacuate_and_promote_regions(); - void maybe_promote_region(ShenandoahHeapRegion* region); - void promote_in_place(ShenandoahHeapRegion* region); - void promote_humongous(ShenandoahHeapRegion* region); }; #endif //SHARE_GC_SHENANDOAH_SHENANDOAHGENERATIONALEVACUATIONTASK_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp index 46230c9654f4..5c3ebc47a7fc 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp @@ -26,6 +26,7 @@ #include "gc/shared/fullGCForwarding.inline.hpp" #include "gc/shared/preservedMarks.inline.hpp" #include "gc/shenandoah/shenandoahAffiliation.hpp" +#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahForwarding.inline.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalFullGC.hpp" @@ -55,7 +56,7 @@ void assert_usage_not_more_than_regions_used(ShenandoahGeneration* generation) { void ShenandoahGenerationalFullGC::prepare() { auto heap = ShenandoahGenerationalHeap::heap(); - // Since we may arrive here from degenerated GC failure of either young or old, establish generation as GLOBAL. + // Full GC always operates on the GLOBAL generation. heap->set_active_generation(heap->global_generation()); // Full GC supersedes any marking or coalescing in old generation. @@ -69,6 +70,7 @@ void ShenandoahGenerationalFullGC::handle_completion(ShenandoahHeap* heap) { ShenandoahOldGeneration* old = gen_heap->old_generation(); young->heuristics()->record_cycle_end(); old->heuristics()->record_cycle_end(); + old->clear_failed_evacuation(); gen_heap->mmu_tracker()->record_full(GCId::current()); gen_heap->log_heap_status("At end of Full GC"); @@ -155,7 +157,7 @@ void ShenandoahGenerationalFullGC::maybe_coalesce_and_fill_region(ShenandoahHeap void ShenandoahGenerationalFullGC::compute_balances() { auto heap = ShenandoahGenerationalHeap::heap(); - // In case this Full GC resulted from degeneration, clear the tally on anticipated promotion. + // Reset the tally on anticipated promotion. heap->old_generation()->set_promotion_potential(0); // Invoke this in case we are able to transfer memory from OLD to YOUNG diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index c640fc010460..a54eed890b50 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -78,8 +78,7 @@ size_t ShenandoahGenerationalHeap::unsafe_max_tlab_alloc() const { return MIN2(ShenandoahHeapRegion::max_tlab_size_bytes(), young_generation()->available()); } -ShenandoahGenerationalHeap::ShenandoahGenerationalHeap(ShenandoahCollectorPolicy* policy) : - ShenandoahHeap(policy), +ShenandoahGenerationalHeap::ShenandoahGenerationalHeap() : _age_census(nullptr), _min_plab_size(calculate_min_plab()), _max_plab_size(calculate_max_plab()), @@ -120,6 +119,11 @@ void ShenandoahGenerationalHeap::initialize_heuristics() { _old_generation->initialize_heuristics(mode()); } +bool ShenandoahGenerationalHeap::wait_for_old_collection(uint timeout_seconds) { + const auto gen_ctrl_thread = static_cast(_control_thread); + return gen_ctrl_thread->wait_for_gc_cycle(GCCause::_shenandoah_concurrent_gc, old_generation(), timeout_seconds); +} + void ShenandoahGenerationalHeap::initialize_serviceability() { assert(mode()->is_generational(), "Only for the generational mode"); _young_gen_memory_pool = new ShenandoahYoungGenMemoryPool(this); @@ -178,15 +182,15 @@ bool ShenandoahGenerationalHeap::requires_barriers(stackChunkOop obj) const { return false; } -void ShenandoahGenerationalHeap::evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) { +void ShenandoahGenerationalHeap::evacuate_collection_set(ShenandoahGeneration* generation) { ShenandoahRegionIterator regions; - ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, concurrent, false /* only promote regions */); + ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, false /* only promote regions */); workers()->run_task(&task); } -void ShenandoahGenerationalHeap::promote_regions_in_place(ShenandoahGeneration* generation, bool concurrent) { +void ShenandoahGenerationalHeap::promote_regions_in_place(ShenandoahGeneration* generation) { ShenandoahRegionIterator regions; - ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, concurrent, true /* only promote regions */); + ShenandoahGenerationalEvacuationTask task(this, generation, ®ions, true /* only promote regions */); workers()->run_task(&task); } @@ -195,6 +199,11 @@ oop ShenandoahGenerationalHeap::evacuate_object(oop p, Thread* thread) { ShenandoahHeapRegion* from_region = heap_region_containing(p); assert(!from_region->is_humongous(), "never evacuate humongous objects"); + if (has_self_forwarded_objects() && from_region->has_self_forwards()) { + // We don't want GC threads to evacuate objects in regions that have evacuation failures. We'd + // rather have them concentrate on regions that still have a chance of being completely evacuated. + return ShenandoahForwarding::try_forward_to(p, p); + } // Try to keep the object in the same generation const ShenandoahAffiliation target_gen = from_region->affiliation(); @@ -206,26 +215,27 @@ oop ShenandoahGenerationalHeap::evacuate_object(oop p, Thread* thread) { } if (age_census()->is_tenurable(from_region->age() + ShenandoahForwarding::age(p))) { // If the object is tenurable, try to promote it - oop result = try_evacuate_object(p, thread, from_region->age()); + oop result = try_evacuate_object(p, thread, from_region); // If we failed to promote this aged object, we'll fall through to code below and evacuate to young-gen. if (result != nullptr) { return result; } } - return try_evacuate_object(p, thread, from_region->age()); + return try_evacuate_object(p, thread, from_region); } assert(target_gen == OLD_GENERATION, "Expected evacuation to old"); - return try_evacuate_object(p, thread, from_region->age()); + return try_evacuate_object(p, thread, from_region); } // try_evacuate_object registers the object and dirties the associated remembered set information when evacuating // to OLD_GENERATION. template -oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age) { +oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region) { bool alloc_from_lab = true; bool has_plab = false; + uint from_region_age = from_region->age(); HeapWord* copy = nullptr; size_t size = ShenandoahForwarding::size(p); constexpr bool is_promotion = (TO_GENERATION == OLD_GENERATION) && (FROM_GENERATION == YOUNG_GENERATION); @@ -299,7 +309,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint if (copy == nullptr) { if (TO_GENERATION == OLD_GENERATION) { if (FROM_GENERATION == YOUNG_GENERATION) { - // Signal that promotion failed. Will evacuate this old object somewhere in young gen. + // Signal that promotion failed. We will retry to evacuate this old object somewhere in young gen. old_generation()->handle_failed_promotion(thread, size); return nullptr; } else { @@ -309,16 +319,19 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint } } - control_thread()->handle_alloc_failure_evac(size); - // Install the self-forwarded bit so other evacuators/LRBs see the // object as "already handled, do not try to evacuate". oop fwd = ShenandoahForwarding::try_forward_to(p, p); if (fwd == p) { - // Now self-forwarded. Flag the from-region so the degen/full - // GC entry drain knows to scan it for self_fwd bits to clear. - heap_region_containing(p)->set_has_self_forwards(); + // Now self-forwarded. Flag the from-region so that other threads + // don't waste time evacuating this region. + set_has_self_forwarded_objects(); + from_region->set_has_self_forwards(); } + + // It does not matter if this thread did the self forwarding or not. + // This thread failed to allocate an evacuation copy. + ShenandoahThreadLocalData::set_has_evac_failures(thread, true); return fwd; } @@ -391,9 +404,9 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint return result; } -template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age); -template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age); -template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age); +template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region); +template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region); +template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region); // Call this function at the end of a GC cycle in order to establish proper sizes of young and old reserves, // setting the old-generation balance so that GC can perform the anticipated evacuations. @@ -670,17 +683,12 @@ void ShenandoahGenerationalHeap::coalesce_and_fill_old_regions(bool concurrent) } }; - ShenandoahPhaseTimings::Phase phase = concurrent ? - ShenandoahPhaseTimings::conc_coalesce_and_fill : - ShenandoahPhaseTimings::degen_gc_coalesce_and_fill; - // This is not cancellable - ShenandoahGlobalCoalesceAndFill coalesce(phase); + ShenandoahGlobalCoalesceAndFill coalesce(ShenandoahPhaseTimings::conc_coalesce_and_fill); workers()->run_task(&coalesce); old_generation()->set_parsable(true); } -template class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { private: // For update refs, _generation will be young or global. Mixed collections use the young generation. @@ -704,16 +712,10 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { } void work(uint worker_id) override { - if (CONCURRENT) { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahConcurrentWorkerSession worker_session(worker_id); - SuspendibleThreadSetJoiner stsj; - do_work(worker_id); - } else { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahParallelWorkerSession worker_session(worker_id); - do_work(worker_id); - } + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner stsj; + do_work>(worker_id); } private: @@ -721,7 +723,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { void do_work(uint worker_id) { T cl; - if (CONCURRENT && (worker_id == 0)) { + if (worker_id == 0) { // We ask the first worker to replenish the Mutator free set by moving regions previously reserved to hold the // results of evacuation. These reserves are no longer necessary because evacuation has completed. size_t cset_regions = _heap->collection_set()->count(); @@ -732,7 +734,6 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { // next GC cycle. _heap->free_set()->move_regions_from_collector_to_mutator(cset_regions); } - // If !CONCURRENT, there's no value in expanding Mutator free set ShenandoahHeapRegion* r = _regions->next(); // We update references for global, mixed, and young collections. @@ -744,7 +745,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { assert(update_watermark >= r->bottom(), "sanity"); log_debug(gc)("Update refs worker " UINT32_FORMAT ", looking at region %zu", worker_id, r->index()); - if (r->is_active() && !r->is_cset()) { + if (r->is_update_required()) { if (r->is_young()) { _heap->marked_object_oop_iterate(r, &cl, update_watermark); } else if (r->is_old()) { @@ -770,7 +771,7 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { } } - if (_heap->check_cancelled_gc_and_yield(CONCURRENT)) { + if (_heap->check_cancelled_gc_and_yield()) { return; } @@ -791,13 +792,15 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { template void update_references_in_remembered_set(uint worker_id, T &cl, const ShenandoahMarkingContext* ctx, bool is_mixed) { - struct ShenandoahRegionChunk assignment; + ShenandoahRegionChunk assignment; ShenandoahScanRemembered* scanner = _heap->old_generation()->card_scan(); - while (!_heap->check_cancelled_gc_and_yield(CONCURRENT) && _work_chunks->next(&assignment)) { + while (!_heap->check_cancelled_gc_and_yield() && _work_chunks->next(&assignment)) { // Keep grabbing next work chunk to process until finished, or asked to yield ShenandoahHeapRegion* r = assignment._r; - if (r->is_active() && !r->is_cset() && r->is_old()) { + if (r->is_update_required() && r->is_old()) { + // allocations into old (i.e., promotions or evacuations) do _not_ update references + // when they copy, so we move the UWM up for each such allocation. HeapWord* start_of_range = r->bottom() + assignment._chunk_offset; HeapWord* end_of_range = r->get_update_watermark(); if (end_of_range > start_of_range + assignment._chunk_size) { @@ -831,8 +834,8 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { template void update_references_in_old_region(T &cl, const ShenandoahMarkingContext* ctx, ShenandoahScanRemembered* scanner, - const ShenandoahHeapRegion* r, HeapWord* start_of_range, - HeapWord* end_of_range) const { + const ShenandoahHeapRegion* r, HeapWord* start_of_range, + HeapWord* end_of_range) const { // In case last object in my range spans boundary of my chunk, I may need to scan all the way to top() ShenandoahObjectToOopBoundedClosure objs(&cl, start_of_range, r->top()); @@ -848,7 +851,10 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { while (p < end_of_range) { // p is known to point to the beginning of marked object obj oop obj = cast_to_oop(p); - objs.do_object(obj); + if (!ShenandoahForwarding::is_real_forwarded(obj)) { + // Update references in the object if it was not evacuated + objs.do_object(obj); + } HeapWord* prev_p = p; p += ShenandoahForwarding::size(obj); if (p < tams) { @@ -891,16 +897,12 @@ class ShenandoahGenerationalUpdateHeapRefsTask : public WorkerTask { } }; -void ShenandoahGenerationalHeap::update_heap_references(ShenandoahGeneration* generation, bool concurrent) { - assert(!is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); +void ShenandoahGenerationalHeap::update_heap_references(ShenandoahGeneration* generation) { + assert(!is_full_gc_in_progress(), "Only for concurrent GC"); ShenandoahRegionChunkIterator work_list(this); - if (concurrent) { - ShenandoahGenerationalUpdateHeapRefsTask task(generation, &_update_refs_iterator, &work_list); - workers()->run_task(&task); - } else { - ShenandoahGenerationalUpdateHeapRefsTask task(generation, &_update_refs_iterator, &work_list); - workers()->run_task(&task); - } + ShenandoahRegionIterator update_refs_iterator(this); + ShenandoahGenerationalUpdateHeapRefsTask task(generation, &update_refs_iterator, &work_list); + workers()->run_task(&task); if (ShenandoahEnableCardStats) { // Only do this if we are collecting card stats @@ -979,16 +981,6 @@ void ShenandoahGenerationalHeap::final_update_refs_update_region_states() { parallel_heap_region_iterate(&cl); } -void ShenandoahGenerationalHeap::complete_degenerated_cycle() { - shenandoah_assert_heaplocked_or_safepoint(); - if (!old_generation()->is_parsable()) { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_coalesce_and_fill); - coalesce_and_fill_old_regions(false); - } - - old_generation()->maybe_log_promotion_failure_stats(false); -} - void ShenandoahGenerationalHeap::complete_concurrent_cycle() { if (!old_generation()->is_parsable()) { // Class unloading may render the card offsets unusable, so we must rebuild them before diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp index 6080b465dc13..b7932d32223a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp @@ -38,12 +38,14 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { void stop() override; public: - explicit ShenandoahGenerationalHeap(ShenandoahCollectorPolicy* policy); + ShenandoahGenerationalHeap(); void post_initialize() override; void initialize_generations() override; void initialize_heuristics() override; void post_initialize_heuristics() override; + bool wait_for_old_collection(uint timeout_seconds); + static ShenandoahGenerationalHeap* heap() { assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap"); CollectedHeap* heap = Universe::heap(); @@ -81,12 +83,12 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { oop evacuate_object(oop p, Thread* thread) override; template - oop try_evacuate_object(oop p, Thread* thread, uint from_region_age); + oop try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region); // In the generational mode, we will use these two functions for young, mixed, and global collections. // For young and mixed, the generation argument will be the young generation, otherwise it will be the global generation. - void evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) override; - void promote_regions_in_place(ShenandoahGeneration* generation, bool concurrent); + void evacuate_collection_set(ShenandoahGeneration* generation) override; + void promote_regions_in_place(ShenandoahGeneration* generation); size_t plab_min_size() const { return _min_plab_size; } size_t plab_max_size() const { return _max_plab_size; } @@ -95,7 +97,7 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { // // In the generational mode, we will use this function for young, mixed, and global collections. // For young and mixed, the generation argument will be the young generation, otherwise it will be the global generation. - void update_heap_references(ShenandoahGeneration* generation, bool concurrent) override; + void update_heap_references(ShenandoahGeneration* generation) override; void final_update_refs_update_region_states() override; private: @@ -124,7 +126,6 @@ class ShenandoahGenerationalHeap : public ShenandoahHeap { void compute_old_generation_balance(size_t old_xfer_limit, size_t old_trashed_regions, size_t young_trashed_regions); // Balances generations, coalesces and fills old regions if necessary - void complete_degenerated_cycle(); void complete_concurrent_cycle(); private: void initialize_controller() override; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index b03610066334..d8fef7e37acd 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -26,7 +26,7 @@ #include "cds/aotMappedHeapWriter.hpp" -#include "classfile/systemDictionary.hpp" +#include "gc/shared/allocTracer.hpp" #include "gc/shared/classUnloadingContext.hpp" #include "gc/shared/fullGCForwarding.hpp" #include "gc/shared/gc_globals.hpp" @@ -37,6 +37,7 @@ #include "gc/shared/memAllocator.hpp" #include "gc/shared/plab.hpp" #include "gc/shared/tlab_globals.hpp" +#include "gc/shared/workerUtils.hpp" #include "gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp" #include "gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp" #include "gc/shenandoah/mode/shenandoahGenerationalMode.hpp" @@ -271,11 +272,6 @@ jint ShenandoahHeap::initialize() { _workers->initialize_workers(); } - if (ParallelGCThreads > 1) { - _safepoint_workers = new ShenandoahWorkerThreads("Safepoint Cleanup Thread", ParallelGCThreads); - _safepoint_workers->initialize_workers(); - } - // // Reserve and commit memory for bitmap(s) // @@ -558,7 +554,7 @@ void ShenandoahHeap::initialize_heuristics() { #pragma warning( disable:4355 ) // 'this' : used in base member initializer list #endif -ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : +ShenandoahHeap::ShenandoahHeap() : CollectedHeap(), _active_generation(nullptr), _initial_size(0), @@ -566,7 +562,6 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : _alloc_rate_decay(&_alloc_rate), _max_workers(MAX3(ConcGCThreads, ParallelGCThreads, 1U)), _workers(nullptr), - _safepoint_workers(nullptr), _heap_region_special(false), _num_regions(0), _regions(nullptr), @@ -575,13 +570,11 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : _gc_state_changed(false), _gc_no_progress_count(0), _cancel_requested_time(0), - _update_refs_iterator(this), _global_generation(nullptr), _control_thread(nullptr), _uncommit_thread(nullptr), _young_generation(nullptr), _old_generation(nullptr), - _shenandoah_policy(policy), _gc_mode(nullptr), _free_set(nullptr), _allocator(nullptr), @@ -606,7 +599,8 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : { // Initialize GC mode early, many subsequent initialization procedures depend on it initialize_mode(); - _cancelled_gc.set(GCCause::_no_gc); + _cancelled_gc.unset(); + _shenandoah_policy = new ShenandoahCollectorPolicy(mode()); } #ifdef _MSC_VER @@ -650,7 +644,6 @@ void ShenandoahHeap::print_heap_on(outputStream* st) const { } if (is_evacuation_in_progress()) st->print("evacuating, "); if (is_update_refs_in_progress()) st->print("updating refs, "); - if (is_degenerated_gc_in_progress()) st->print("degenerated gc, "); if (is_full_gc_in_progress()) st->print("full gc, "); if (is_full_gc_move_in_progress()) st->print("full gc move, "); if (is_concurrent_weak_root_in_progress()) st->print("concurrent weak roots, "); @@ -725,13 +718,6 @@ void ShenandoahHeap::post_initialize() { // Now, we will let WorkerThreads to initialize gclab when new worker is created. _workers->set_initialize_gclab(); - // Note that the safepoint workers may require gclabs if the threads are used to create a heap dump - // during a concurrent evacuation phase. - if (_safepoint_workers != nullptr) { - _safepoint_workers->threads_do(&init_gclabs); - _safepoint_workers->set_initialize_gclab(); - } - JFR_ONLY(ShenandoahJFRSupport::register_jfr_type_serializers();) } @@ -960,11 +946,7 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { // Shenandoah will grind along for quite a while allocating one // object at a time using shared (non-tlab) allocations. This check // is testing that the GC overhead limit has not been exceeded. - // This will notify the collector to start a cycle, but will raise - // an OOME to the mutator if the last Full GCs have not made progress. - // gc_no_progress_count is incremented following each degen or full GC that fails to achieve is_good_progress(). - if (result == nullptr && !req.is_lab_alloc() && get_gc_no_progress_count() > ShenandoahNoProgressThreshold) { - control_thread()->handle_alloc_failure(req, false); + if (result == nullptr && !req.is_lab_alloc() && get_gc_no_progress_count() >= ShenandoahNoProgressThreshold) { req.set_actual_size(0); return nullptr; } @@ -972,26 +954,41 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { if (result == nullptr) { // Block until control thread reacted, then retry allocation. // - // It might happen that one of the threads requesting allocation would unblock - // way later after GC happened, only to fail the second allocation, because - // other threads have already depleted the free storage. In this case, a better - // strategy is to try again, until at least one full GC has completed. - // - // Stop retrying and return nullptr to cause OOMError exception if our allocation failed even after: - // a) We experienced a GC that had good progress, or - // b) We experienced at least one Full GC (whether or not it had good progress) - - const size_t original_count = shenandoah_policy()->full_gc_count(); - while (result == nullptr && should_retry_allocation(original_count)) { - control_thread()->handle_alloc_failure(req, true); + // When a thread cannot allocate, it will signal the control thread to run a concurrent cycle. It + // will then wait for the control thread to signal in turn that the cycle has completed. The thread + // then retries the allocation. It will do this ShenandoahFullGCThreshold times. If no full gc has + // yet run, it will request one and retry the allocation one more time. If another thread has caused + // a full GC to run, other threads will not request another full GC. + + const size_t req_byte = req.size() * HeapWordSize; + AllocTracer::send_allocation_requiring_gc_event(req_byte, checked_cast(control_thread()->get_gc_id())); + ShenandoahAllocationStallLogger stall_logger; + + const size_t original_full_count = shenandoah_policy()->full_gc_count(); + const size_t original_count = shenandoah_policy()->reclaiming_gc_count(); + while (result == nullptr && !control_thread()->should_terminate()) { + control_thread()->handle_alloc_failure(req); result = allocate_memory_work(req, in_new_region); + if (result == nullptr) { + const size_t current_count = shenandoah_policy()->reclaiming_gc_count(); + if (current_count - original_count >= ShenandoahFullGCThreshold) { + if (original_full_count == shenandoah_policy()->full_gc_count()) { + // We are not getting what we need from concurrent collections, and no full gcs ran + // while we waited, so request a full gc. Whether this satisfies the allocation or not, + // we are done trying. + control_thread()->handle_alloc_failure_full(); + result = allocate_memory_work(req, in_new_region); + } + break; + } + } } + if (result != nullptr) { // If our allocation request has been satisfied after it initially failed, we count this as good gc progress notify_gc_progress(); } if (log_develop_is_enabled(Debug, gc, alloc)) { - ResourceMark rm; log_debug(gc, alloc)("Thread: %s, Result: " PTR_FORMAT ", Request: %s, Size: %zu" ", Original: %zu, Latest: %zu", Thread::current()->name(), p2i(result), req.type_string(), req.size(), @@ -1001,8 +998,6 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { } else { assert(req.is_gc_alloc(), "Can only accept GC allocs here"); result = allocate_memory_work(req, in_new_region); - // Do not call handle_alloc_failure() here, because we cannot block. - // The allocation failure would be handled by the LRB slowpath with handle_alloc_failure_evac(). } if (in_new_region) { @@ -1025,11 +1020,6 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { return result; } -inline bool ShenandoahHeap::should_retry_allocation(size_t original_full_gc_count) const { - return shenandoah_policy()->full_gc_count() == original_full_gc_count - && !shenandoah_policy()->is_at_shutdown(); -} - HeapWord* ShenandoahHeap::allocate_memory_work(ShenandoahAllocRequest& req, bool& in_new_region) { // Reserve the promotion budget up front so it is enforced atomically without the heap lock. // If the reserve is exhausted, deny the promotion rather than overshoot it; the reservation @@ -1125,28 +1115,19 @@ class ShenandoahEvacuationTask : public WorkerTask { private: ShenandoahHeap* const _sh; ShenandoahCollectionSet* const _cs; - bool _concurrent; public: ShenandoahEvacuationTask(ShenandoahHeap* sh, - ShenandoahCollectionSet* cs, - bool concurrent) : + ShenandoahCollectionSet* cs) : WorkerTask("Shenandoah Evacuation"), _sh(sh), - _cs(cs), - _concurrent(concurrent) - {} + _cs(cs) { + } void work(uint worker_id) { - if (_concurrent) { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahConcurrentWorkerSession worker_session(worker_id); - SuspendibleThreadSetJoiner stsj; - do_work(); - } else { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_evac, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahParallelWorkerSession worker_session(worker_id); - do_work(); - } + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner stsj; + do_work(); } private: @@ -1157,7 +1138,11 @@ class ShenandoahEvacuationTask : public WorkerTask { assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); _sh->marked_object_iterate(r, &cl); - if (_sh->check_cancelled_gc_and_yield(_concurrent)) { + if (ShenandoahCollectorPolicy::should_abandon_evacuations(r)) { + break; + } + + if (_sh->check_cancelled_gc_and_yield()) { break; } } @@ -1177,6 +1162,9 @@ class ShenandoahRetireGCLABClosure : public ThreadClosure { ShenandoahThreadLocalData::set_gclab_size(thread, 0); } + // Clear this for next evacuation phase + ShenandoahThreadLocalData::set_has_evac_failures(thread, false); + if (ShenandoahHeap::heap()->mode()->is_generational()) { ShenandoahPLAB* shenandoah_plab = ShenandoahThreadLocalData::shenandoah_plab(thread); assert(shenandoah_plab != nullptr, "PLAB should be initialized for %s", thread->name()); @@ -1227,12 +1215,68 @@ class ShenandoahPrepareForUpdateRefsHandshakeClosure : public HandshakeClosure { ShenandoahGCStatePropagatorHandshakeClosure _propagator; }; -void ShenandoahHeap::evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent) { +class ShenandoahSelfForwardClosure : public ObjectClosure { + bool _self_forwarded; +public: + ShenandoahSelfForwardClosure() : _self_forwarded(false) {} + + void do_object(oop obj) override { + oop resolved = ShenandoahForwarding::try_forward_to(obj, obj); + if (resolved == obj) { + _self_forwarded = true; + } + } + + bool self_forwarded_objects() const { return _self_forwarded; } +}; + +ShenandoahSelfForwardTask::ShenandoahSelfForwardTask(ShenandoahHeap* heap, ShenandoahCollectionSet* cs) : + WorkerTask("Shenandoah Self-Forward"), + _heap(heap), + _cs(cs) { + // Do not reset claim index, these workers pick up from where the evacuation workers left off + assert(!_cs->all_regions_claimed(), "Only here to self forward objects in unclaimed regions"); + assert(_heap->has_self_forwarded_objects(), "Workers that did not evacuate a region must set this flag"); +} + +void ShenandoahSelfForwardTask::work(uint worker_id) { + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner joiner; + ShenandoahHeapRegion* r; + while ((r = _cs->claim_next()) != nullptr) { + ShenandoahSelfForwardClosure cl; + _heap->marked_object_iterate(r, &cl); + if (cl.self_forwarded_objects()) { + r->set_has_self_forwards(); + } + + if (_heap->check_cancelled_gc_and_yield()) { + break; + } + } +} + +void ShenandoahHeap::evacuate_collection_set(ShenandoahGeneration* generation) { assert(generation->is_global(), "Only global generation expected here"); - ShenandoahEvacuationTask task(this, _collection_set, concurrent); + ShenandoahEvacuationTask task(this, _collection_set); workers()->run_task(&task); } +void ShenandoahHeap::self_forward_stranded_objects() { + // When an object cannot be evacuated, it will be self-forwarded. Regions with self-forwarded objects + // can only be "partially" recycled. We have made a policy/design decision to stop trying to evacuate + // such regions. Instead, the workers will focus on evacuating regions that still have a chance of being + // completely evacuated. However, once a worker itself cannot refill its LABs, it will do nothing more + // beside create more partially evacuated regions. For this reason, such a worker exits the evacuation + // task. This leaves the remaining regions to the remaining workers who, we hope, may yet complete more + // successful evacuations. For the case that all workers exit before all the collection set regions are + // evacuated, we have this step below which simply self forwards all the objects in all the regions that + // were not evacuated out of the collection set. + log_debug(gc)("Cleaning up failed evacuations"); + ShenandoahSelfForwardTask self_forward_task(this, collection_set()); + workers()->run_task(&self_forward_task); +} + class ShenandoahCompleteStackWatermarkHandshakeClosure : public HandshakeClosure { public: ShenandoahCompleteStackWatermarkHandshakeClosure() : HandshakeClosure("Shenandoah Complete stacks handshake") {} @@ -1258,7 +1302,6 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { // by the following handshake. MutexLocker lock(Threads_lock); - // A cancellation at this point means the degenerated cycle must resume from update-refs. set_gc_state_concurrent(EVACUATION, false); set_gc_state_concurrent(UPDATE_REFS, true); } @@ -1271,8 +1314,6 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { // Now retire gclabs and plabs and propagate gc_state for mutator threads Handshake::execute(&prepare_for_update_refs); - - _update_refs_iterator.reset(); } void ShenandoahHeap::op_final_roots() { @@ -1307,16 +1348,20 @@ oop ShenandoahHeap::evacuate_object(oop p, Thread* thread) { assert(thread == Thread::current(), "Expected thread parameter to be current thread."); ShenandoahHeapRegion* r = heap_region_containing(p); - assert(!r->is_humongous(), "never evacuate humongous objects"); + if (has_self_forwarded_objects() && r->has_self_forwards()) { + // We don't want GC threads to evacuate objects in regions that have evacuation failures. We'd + // rather have them concentrate on regions that still have a chance of being completely evacuated. + return ShenandoahForwarding::try_forward_to(p, p); + } - ShenandoahAffiliation target_gen = r->affiliation(); - return try_evacuate_object(p, thread, r, target_gen); + assert(!r->is_humongous(), "never evacuate humongous objects"); + assert(r->is_young(), "Only expect evacuations from young in this mode"); + return try_evacuate_object(p, thread, r, YOUNG_GENERATION); } oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapRegion* from_region, ShenandoahAffiliation target_gen) { assert(target_gen == YOUNG_GENERATION, "Only expect evacuations to young in this mode"); - assert(from_region->is_young(), "Only expect evacuations from young in this mode"); bool alloc_from_lab = true; HeapWord* copy = nullptr; size_t size = ShenandoahForwarding::size(p); @@ -1341,18 +1386,21 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg #endif if (copy == nullptr) { - control_thread()->handle_alloc_failure_evac(size); - // Install the self-forwarded bit on p so other evacuators/LRBs see // the object as "already handled, do not try to evacuate". The CAS // may fail if another thread concurrently installed a real forwardee // (they succeeded where we failed) or self-forwarded first. oop fwd = ShenandoahForwarding::try_forward_to(p, p); if (fwd == p) { - // Now self-forwarded. Flag the region so the degen/full GC - // entry drain knows to scan it for self_fwd bits to clear. + // Now self-forwarded. Flag the region so other threads will not + // try to evacuate objects from here. + set_has_self_forwarded_objects(); from_region->set_has_self_forwards(); } + + // It does not matter if this thread did the self forwarding or not. + // This thread failed to allocate an evacuation copy. + ShenandoahThreadLocalData::set_has_evac_failures(thread, true); return fwd; } @@ -1408,78 +1456,48 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg } } -// Clear the self_fwd bit on a live cset object, if set. Runs at a safepoint, -// so a plain store is sufficient — no concurrent writers to the mark word. -class ShenandoahUnSelfForwardObjectClosure : public ObjectClosure { +class ShenandoahTrashRegionTask : public WorkerTask { + ShenandoahCollectionSet* _collection_set; + Atomic _free_bytes_in_evac_failed_regions; public: - void do_object(oop obj) override { - ShenandoahForwarding::unset_self_forwarded(obj); - } -}; + explicit ShenandoahTrashRegionTask(ShenandoahCollectionSet* collection_set) + : WorkerTask("ShenandoahTrashRegions") + , _collection_set(collection_set), _free_bytes_in_evac_failed_regions(0) {} -// Parallel task over flagged cset regions. Iterates the live objects via the -// mark bitmap (skipping evacuated and never-marked memory), clears self_fwd -// bits, and resets the region flag once done. -class ShenandoahUnSelfForwardTask : public WorkerTask { -private: - ShenandoahHeap* const _heap; - ShenandoahCollectionSet* const _cs; - -public: - ShenandoahUnSelfForwardTask(ShenandoahHeap* heap, ShenandoahCollectionSet* cs) : - WorkerTask("Shenandoah Un-Self-Forward"), - _heap(heap), - _cs(cs) {} + size_t free_bytes_in_evac_failed_regions() const { + return _free_bytes_in_evac_failed_regions.load_relaxed(); + } void work(uint worker_id) override { - ShenandoahParallelWorkerSession worker_session(worker_id); - ShenandoahUnSelfForwardObjectClosure cl; ShenandoahHeapRegion* r; - while ((r = _cs->claim_next()) != nullptr) { + size_t free_bytes = 0; + while ((r = _collection_set->claim_next()) != nullptr) { if (r->has_self_forwards()) { - _heap->marked_object_iterate(r, &cl); - r->clear_has_self_forwards(); + r->partially_recycle(); + free_bytes += r->free(); + } else { + r->make_trash(); } } + _free_bytes_in_evac_failed_regions.add_then_fetch(free_bytes, memory_order_relaxed); } }; -void ShenandoahHeap::un_self_forward_cset_regions() { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint"); - ShenandoahCollectionSet* cs = collection_set(); - if (cs == nullptr || cs->is_empty()) { - return; - } - cs->clear_current_index(); - ShenandoahUnSelfForwardTask task(this, cs); - workers()->run_task(&task); - DEBUG_ONLY(assert_no_self_forwards()); -} - -#ifdef ASSERT -void ShenandoahHeap::assert_no_self_forwards() const { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint"); - ShenandoahCollectionSet* cs = collection_set(); - if (cs == nullptr) return; - cs->clear_current_index(); - ShenandoahHeapRegion* r; - while ((r = cs->next()) != nullptr) { - assert(!r->has_self_forwards(), "region still flagged after drain"); - } - cs->clear_current_index(); -} -#endif - void ShenandoahHeap::trash_cset_regions() { - ShenandoahHeapLocker locker(lock()); - ShenandoahCollectionSet* set = collection_set(); - ShenandoahHeapRegion* r; set->clear_current_index(); - while ((r = set->next()) != nullptr) { - r->make_trash(); + if (has_self_forwarded_objects()) { + ShenandoahTrashRegionTask task(set); + workers()->run_task(&task); + log_debug(gc, free)("Memory available in regions that failed evacuation: " PROPERFMT, + PROPERFMTARGS(task.free_bytes_in_evac_failed_regions())); + } else { + ShenandoahHeapRegion* r; + while ((r = set->next()) != nullptr) { + r->make_trash(); + } } - collection_set()->clear(); + set->clear(); } void ShenandoahHeap::print_heap_regions_on(outputStream* st) const { @@ -1566,10 +1584,6 @@ void ShenandoahHeap::labs_make_parsable() { } workers()->threads_do(&cl); - - if (safepoint_workers() != nullptr) { - safepoint_workers()->threads_do(&cl); - } } void ShenandoahHeap::tlabs_retire(bool resize) { @@ -1606,10 +1620,6 @@ void ShenandoahHeap::gclabs_retire(bool resize) { } workers()->threads_do(&cl); - - if (safepoint_workers() != nullptr) { - safepoint_workers()->threads_do(&cl); - } } // Returns size in bytes @@ -1626,7 +1636,7 @@ size_t ShenandoahHeap::max_tlab_size() const { void ShenandoahHeap::collect_as_vm_thread(GCCause::Cause cause) { // These requests are ignored because we can't easily have Shenandoah jump into - // a synchronous (degenerated or full) cycle while it is in the middle of a concurrent + // a synchronous (full) cycle while it is in the middle of a concurrent // cycle. We _could_ cancel the concurrent cycle and then try to run a cycle directly // on the VM thread, but this would confuse the control thread mightily and doesn't // seem worth the trouble. Instead, we will have the caller thread run (and wait for) a @@ -1684,9 +1694,6 @@ void ShenandoahHeap::gc_threads_do(ThreadClosure* tcl) const { } workers()->threads_do(tcl); - if (_safepoint_workers != nullptr) { - _safepoint_workers->threads_do(tcl); - } } void ShenandoahHeap::print_tracing_info() const { @@ -1726,8 +1733,7 @@ void ShenandoahHeap::set_active_generation(ShenandoahGeneration* generation) { _active_generation = generation; } -void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation, - bool is_degenerated, bool is_out_of_cycle) { +void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation) { shenandoah_policy()->record_collection_cause(cause); const GCCause::Cause current = gc_cause(); @@ -1736,10 +1742,11 @@ void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* set_gc_cause(cause); - if (is_degenerated) { - generation->heuristics()->record_degenerated_cycle_start(is_out_of_cycle); - } else { - generation->heuristics()->record_cycle_start(); + generation->heuristics()->record_cycle_start(); + if (mode()->is_generational() && generation->is_global()) { + // We are starting a global cycle, for heuristic's purposes, inform young and old too + young_generation()->heuristics()->record_cycle_start(); + old_generation()->heuristics()->record_cycle_start(); } } @@ -1833,7 +1840,7 @@ void ShenandoahHeap::object_iterate(ObjectClosure* cl) { ShenandoahScanObjectStack oop_stack; ObjectIterateScanRootClosure oops(&_aux_bit_map, &oop_stack); // Seed the stack with root scan - scan_roots_for_iteration(&oop_stack, &oops); + scan_roots_for_iteration(&oops, 1); // Work through the oop stack to traverse heap while (! oop_stack.is_empty()) { @@ -1862,13 +1869,12 @@ bool ShenandoahHeap::prepare_aux_bitmap_for_iteration() { return true; } -void ShenandoahHeap::scan_roots_for_iteration(ShenandoahScanObjectStack* oop_stack, ObjectIterateScanRootClosure* oops) { +void ShenandoahHeap::scan_roots_for_iteration(ObjectIterateScanRootClosure* oops, uint num_workers) { // Process GC roots according to current GC cycle // This populates the work stack with initial objects // It is important to relinquish the associated locks before diving // into heap dumper - uint n_workers = safepoint_workers() != nullptr ? safepoint_workers()->active_workers() : 1; - ShenandoahHeapIterationRootScanner rp(n_workers); + ShenandoahHeapIterationRootScanner rp(num_workers); rp.roots_do(oops); } @@ -1933,7 +1939,7 @@ class ShenandoahParallelObjectIterator : public ParallelObjectIteratorImpl { } ObjectIterateScanRootClosure oops(_aux_bit_map, &_roots_stack); - _heap->scan_roots_for_iteration(&_roots_stack, &oops); + _heap->scan_roots_for_iteration(&oops, _num_workers); _init_ready = prepare_worker_queues(); } @@ -2130,29 +2136,6 @@ void ShenandoahHeap::do_class_unloading() { } } -void ShenandoahHeap::stw_weak_refs(ShenandoahGeneration* generation, bool full_gc) { - // Weak refs processing - ShenandoahPhaseTimings::Phase phase = full_gc ? ShenandoahPhaseTimings::full_gc_weakrefs - : ShenandoahPhaseTimings::degen_gc_weakrefs; - ShenandoahTimingsTracker t(phase); - ShenandoahGCWorkerPhase worker_phase(phase); - generation->ref_processor()->process_references(phase, workers(), false /* concurrent */); -} - -void ShenandoahHeap::prepare_update_heap_references() { - assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "must be at safepoint"); - - // Evacuation is over, no GCLABs are needed anymore. GCLABs are under URWM, so we need to - // make them parsable for update code to work correctly. Plus, we can compute new sizes - // for future GCLABs here. - if (UseTLAB) { - ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_init_update_refs_manage_gclabs); - gclabs_retire(ResizeTLAB); - } - - _update_refs_iterator.reset(); -} - void ShenandoahHeap::propagate_gc_state_to_all_threads() { assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at Shenandoah safepoint"); if (_gc_state_changed) { @@ -2270,19 +2253,8 @@ size_t ShenandoahHeap::tlab_used() const { return _free_set->used_not_holding_lock(); } -bool ShenandoahHeap::try_cancel_gc(GCCause::Cause cause) { - while (true) { - const GCCause::Cause prev = _cancelled_gc.get(); - if (prev != GCCause::_no_gc && prev != GCCause::_shenandoah_concurrent_gc && cause != GCCause::_shenandoah_stop_vm) { - // Only when the gc has not been cancelled, or it has been cancelled to interrupt an old marking cycle - // do we allow the new cancellation request to happen. We make an exception for stopping the VM. - return false; - } - - if (_cancelled_gc.cmpxchg(cause, prev) == prev) { - return true; - } - } +bool ShenandoahHeap::try_cancel_gc() { + return !is_stopping() && _cancelled_gc.try_set(); } void ShenandoahHeap::cancel_concurrent_mark() { @@ -2297,7 +2269,7 @@ void ShenandoahHeap::cancel_concurrent_mark() { } bool ShenandoahHeap::cancel_gc(GCCause::Cause cause) { - if (try_cancel_gc(cause)) { + if (try_cancel_gc()) { FormatBuffer<> msg("Cancelling GC: %s", GCCause::to_string(cause)); log_info(gc,thread)("%s", msg.buffer()); Events::log(Thread::current(), "%s", msg.buffer()); @@ -2332,90 +2304,6 @@ void ShenandoahHeap::stop() { } } -void ShenandoahHeap::stw_unload_classes(bool full_gc) { - if (!unload_classes()) return; - ClassUnloadingContext ctx(_workers->active_workers(), - true /* unregister_nmethods_during_purge */, - false /* lock_nmethod_free_separately */); - - // Unload classes and purge SystemDictionary. - { - ShenandoahPhaseTimings::Phase phase = full_gc ? - ShenandoahPhaseTimings::full_gc_purge_class_unload : - ShenandoahPhaseTimings::degen_gc_purge_class_unload; - ShenandoahIsAliveSelector is_alive; - { - CodeCache::UnlinkingScope scope(is_alive.is_alive_closure()); - ShenandoahGCPhase gc_phase(phase); - ShenandoahGCWorkerPhase worker_phase(phase); - bool unloading_occurred = SystemDictionary::do_unloading(gc_timer()); - - ShenandoahClassUnloadingTask unlink_task(phase, unloading_occurred); - _workers->run_task(&unlink_task); - } - // Release unloaded nmethods's memory. - ClassUnloadingContext::context()->purge_and_free_nmethods(); - } - - { - ShenandoahGCPhase phase(full_gc ? - ShenandoahPhaseTimings::full_gc_purge_cldg : - ShenandoahPhaseTimings::degen_gc_purge_cldg); - ClassLoaderDataGraph::purge(true /* at_safepoint */); - } - // Resize and verify metaspace - MetaspaceGC::compute_new_size(); - - if (mode()->is_generational()) { - old_generation()->set_parsable(false); - } - - DEBUG_ONLY(MetaspaceUtils::verify();) -} - -// Weak roots are either pre-evacuated (final mark) or updated (final update refs), -// so they should not have forwarded oops. -// However, we do need to "null" dead oops in the roots, if can not be done -// in concurrent cycles. -void ShenandoahHeap::stw_process_weak_roots(bool full_gc) { - uint num_workers = _workers->active_workers(); - ShenandoahPhaseTimings::Phase timing_phase = full_gc ? - ShenandoahPhaseTimings::full_gc_purge_weak_par : - ShenandoahPhaseTimings::degen_gc_purge_weak_par; - ShenandoahGCPhase phase(timing_phase); - ShenandoahGCWorkerPhase worker_phase(timing_phase); - // Cleanup weak roots - if (has_forwarded_objects()) { - ShenandoahForwardedIsAliveClosure is_alive; - ShenandoahNonConcUpdateRefsClosure keep_alive; - ShenandoahParallelWeakRootsCleaningTask - cleaning_task(timing_phase, &is_alive, &keep_alive, num_workers); - _workers->run_task(&cleaning_task); - } else { - ShenandoahIsAliveClosure is_alive; -#ifdef ASSERT - ShenandoahAssertNotForwardedClosure verify_cl; - ShenandoahParallelWeakRootsCleaningTask - cleaning_task(timing_phase, &is_alive, &verify_cl, num_workers); -#else - ShenandoahParallelWeakRootsCleaningTask - cleaning_task(timing_phase, &is_alive, &do_nothing_cl, num_workers); -#endif - _workers->run_task(&cleaning_task); - } -} - -void ShenandoahHeap::parallel_cleaning(ShenandoahGeneration* generation, bool full_gc) { - assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint"); - assert(is_stw_gc_in_progress(), "Only for Degenerated and Full GC"); - ShenandoahGCPhase phase(full_gc ? - ShenandoahPhaseTimings::full_gc_purge : - ShenandoahPhaseTimings::degen_gc_purge); - stw_weak_refs(generation, full_gc); - stw_process_weak_roots(full_gc); - stw_unload_classes(full_gc); -} - void ShenandoahHeap::set_has_forwarded_objects(bool cond) { set_gc_state_at_safepoint(HAS_FORWARDED, cond); } @@ -2434,10 +2322,6 @@ address ShenandoahHeap::in_cset_fast_test_addr() { return (address) heap->collection_set()->biased_map_address(); } -void ShenandoahHeap::set_degenerated_gc_in_progress(bool in_progress) { - _degenerated_gc_in_progress.set_cond(in_progress); -} - void ShenandoahHeap::set_full_gc_in_progress(bool in_progress) { _full_gc_in_progress.set_cond(in_progress); } @@ -2574,10 +2458,6 @@ void ShenandoahHeap::assert_gc_workers(uint nworkers) { // Use ParallelGCThreads inside safepoints assert(nworkers == ParallelGCThreads, "Use ParallelGCThreads (%u) within safepoint, not %u", ParallelGCThreads, nworkers); - } else { - // Use ConcGCThreads outside safepoints - assert(nworkers == ConcGCThreads, "Use ConcGCThreads (%u) outside safepoints, %u", - ConcGCThreads, nworkers); } } #endif @@ -2588,7 +2468,6 @@ ShenandoahVerifier* ShenandoahHeap::verifier() { return _verifier; } -template class ShenandoahUpdateHeapRefsTask : public WorkerTask { private: ShenandoahHeap* _heap; @@ -2601,25 +2480,19 @@ class ShenandoahUpdateHeapRefsTask : public WorkerTask { } void work(uint worker_id) { - if (CONCURRENT) { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahConcurrentWorkerSession worker_session(worker_id); - SuspendibleThreadSetJoiner stsj; - do_work(worker_id); - } else { - ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); - ShenandoahParallelWorkerSession worker_session(worker_id); - do_work(worker_id); - } + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); + ShenandoahConcurrentWorkerSession worker_session(worker_id); + SuspendibleThreadSetJoiner stsj; + do_work>(worker_id); } private: template void do_work(uint worker_id) { - if (CONCURRENT && (worker_id == 0)) { + if (worker_id == 0) { // We ask the first worker to replenish the Mutator free set by moving regions previously reserved to hold the // results of evacuation. These reserves are no longer necessary because evacuation has completed. - size_t cset_regions = _heap->collection_set()->count(); + const size_t cset_regions = _heap->collection_set()->count(); // Now that evacuation is done, we can reassign any regions that had been reserved to hold the results of evacuation // to the mutator free set. At the end of GC, we will have cset_regions newly evacuated fully empty regions from @@ -2627,16 +2500,18 @@ class ShenandoahUpdateHeapRefsTask : public WorkerTask { // next GC cycle. _heap->free_set()->move_regions_from_collector_to_mutator(cset_regions); } - // If !CONCURRENT, there's no value in expanding Mutator free set + T cl; ShenandoahHeapRegion* r = _regions->next(); while (r != nullptr) { + // Regions put into service after final mark will not have an update watermark. Regions with self forwarded objects + // must also have the references in these objects be updated. HeapWord* update_watermark = r->get_update_watermark(); assert (update_watermark >= r->bottom(), "sanity"); - if (r->is_active() && !r->is_cset()) { + if (r->is_update_required()) { _heap->marked_object_oop_iterate(r, &cl, update_watermark); } - if (_heap->check_cancelled_gc_and_yield(CONCURRENT)) { + if (_heap->check_cancelled_gc_and_yield()) { return; } r = _regions->next(); @@ -2644,37 +2519,26 @@ class ShenandoahUpdateHeapRefsTask : public WorkerTask { } }; -void ShenandoahHeap::update_heap_references(ShenandoahGeneration* generation, bool concurrent) { +void ShenandoahHeap::update_heap_references(ShenandoahGeneration* generation) { assert(generation->is_global(), "Should only get global generation here"); - assert(!is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); - - if (concurrent) { - ShenandoahUpdateHeapRefsTask task(&_update_refs_iterator); - workers()->run_task(&task); - } else { - ShenandoahUpdateHeapRefsTask task(&_update_refs_iterator); - workers()->run_task(&task); - } + assert(!is_full_gc_in_progress(), "Only for concurrent GC"); + ShenandoahRegionIterator update_refs_iterator(this); + ShenandoahUpdateHeapRefsTask task(&update_refs_iterator); + workers()->run_task(&task); } -void ShenandoahHeap::update_heap_region_states(bool concurrent) { +void ShenandoahHeap::update_heap_region_states() { assert(SafepointSynchronize::is_at_safepoint(), "Must be at a safepoint"); - assert(!is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); + assert(!is_full_gc_in_progress(), "Only for concurrent GC"); { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_refs_update_region_states : - ShenandoahPhaseTimings::degen_gc_final_update_refs_update_region_states); - + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_refs_update_region_states); final_update_refs_update_region_states(); - assert_pinned_region_status(); } { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_refs_trash_cset : - ShenandoahPhaseTimings::degen_gc_final_update_refs_trash_cset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_refs_trash_cset); trash_cset_regions(); } } @@ -2722,10 +2586,8 @@ void ShenandoahHeap::rebuild_free_set_within_phase() { } } -void ShenandoahHeap::rebuild_free_set(bool concurrent) { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_refs_rebuild_freeset : - ShenandoahPhaseTimings::degen_gc_final_update_refs_rebuild_freeset); +void ShenandoahHeap::rebuild_free_set() { + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_refs_rebuild_freeset); rebuild_free_set_within_phase(); } @@ -2819,10 +2681,6 @@ void ShenandoahHeap::safepoint_synchronize_end() { void ShenandoahHeap::try_inject_alloc_failure() { if (ShenandoahAllocFailureALot && !cancelled_gc() && ((os::random() % 1000) > 950)) { _inject_alloc_failure.set(); - os::naked_short_sleep(1); - if (cancelled_gc()) { - log_info(gc)("Allocation failure was successfully injected"); - } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index 5530af1d6713..d6ed39070c25 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -29,6 +29,7 @@ #include "gc/shared/collectedHeap.hpp" #include "gc/shared/markBitMap.hpp" +#include "gc/shared/workerThread.hpp" #include "gc/shenandoah/mode/shenandoahMode.hpp" #include "gc/shenandoah/shenandoahAllocRate.hpp" #include "gc/shenandoah/shenandoahAllocRequest.hpp" @@ -135,6 +136,15 @@ class ShenandoahHeapLocker : public StackObj { typedef Stack ShenandoahScanObjectStack; +class ShenandoahSelfForwardTask : public WorkerTask { + ShenandoahHeap* const _heap; + ShenandoahCollectionSet* const _cs; + +public: + ShenandoahSelfForwardTask(ShenandoahHeap* heap, ShenandoahCollectionSet* cs); + void work(uint worker_id) override; +}; + // Shenandoah GC is low-pause concurrent GC that uses a load reference barrier // for concurent evacuation and a snapshot-at-the-beginning write barrier for // concurrent marking. See ShenandoahControlThread for GC cycle structure. @@ -150,7 +160,6 @@ class ShenandoahHeap : public CollectedHeap { // Supported GC friend class ShenandoahConcurrentGC; friend class ShenandoahOldGC; - friend class ShenandoahDegenGC; friend class ShenandoahFullGC; friend class ShenandoahUnload; @@ -184,7 +193,7 @@ class ShenandoahHeap : public CollectedHeap { ShenandoahHeuristics* heuristics(); -// ---------- Initialization, termination, identification, printing routines + // ---------- Initialization, termination, identification, printing routines // public: static ShenandoahHeap* heap(); @@ -192,7 +201,7 @@ class ShenandoahHeap : public CollectedHeap { const char* name() const override { return "Shenandoah"; } ShenandoahHeap::Name kind() const override { return CollectedHeap::Shenandoah; } - ShenandoahHeap(ShenandoahCollectorPolicy* policy); + ShenandoahHeap(); jint initialize() override; void post_initialize() override; virtual void initialize_generations(); @@ -216,7 +225,7 @@ class ShenandoahHeap : public CollectedHeap { void prepare_for_verify() override; void verify(VerifyOption vo) override; -// WhiteBox testing support. + // WhiteBox testing support. bool supports_concurrent_gc_breakpoints() const override { return mode()->is_concurrent(); } @@ -270,7 +279,6 @@ class ShenandoahHeap : public CollectedHeap { private: uint _max_workers; ShenandoahWorkerThreads* _workers; - ShenandoahWorkerThreads* _safepoint_workers; virtual void initialize_controller(); @@ -279,7 +287,6 @@ class ShenandoahHeap : public CollectedHeap { void assert_gc_workers(uint nworker) NOT_DEBUG_RETURN; WorkerThreads* workers() const; - WorkerThreads* safepoint_workers() override; void gc_threads_do(ThreadClosure* tcl) const override; @@ -361,7 +368,6 @@ class ShenandoahHeap : public CollectedHeap { bool _gc_state_changed; ShenandoahSharedBitmap _gc_state; ShenandoahSharedFlag _heap_changed; - ShenandoahSharedFlag _degenerated_gc_in_progress; ShenandoahSharedFlag _full_gc_in_progress; ShenandoahSharedFlag _full_gc_move_in_progress; ShenandoahSharedFlag _concurrent_strong_root_in_progress; @@ -411,7 +417,6 @@ class ShenandoahHeap : public CollectedHeap { void set_concurrent_old_mark_in_progress(bool in_progress); void set_evacuation_in_progress(bool in_progress); void set_update_refs_in_progress(bool in_progress); - void set_degenerated_gc_in_progress(bool in_progress); void set_full_gc_in_progress(bool in_progress); void set_full_gc_move_in_progress(bool in_progress); void set_has_forwarded_objects(bool cond); @@ -424,7 +429,6 @@ class ShenandoahHeap : public CollectedHeap { inline bool is_concurrent_old_mark_in_progress() const; inline bool is_update_refs_in_progress() const; inline bool is_evacuation_in_progress() const; - inline bool is_degenerated_gc_in_progress() const; inline bool is_full_gc_in_progress() const; inline bool is_full_gc_move_in_progress() const; inline bool has_forwarded_objects() const; @@ -441,32 +445,25 @@ class ShenandoahHeap : public CollectedHeap { // the responsiveness of the heuristic when starting a cycle. double _cancel_requested_time; - // Indicates the reason the current GC has been cancelled (GCCause::_no_gc means the gc is not cancelled). - ShenandoahSharedEnumFlag _cancelled_gc; + // True when gc threads should stop + ShenandoahSharedFlag _cancelled_gc; // Returns true if cancel request was successfully communicated. // Returns false if some other thread already communicated cancel // request. A true return value does not mean GC has been // cancelled, only that the process of cancelling GC has begun. - bool try_cancel_gc(GCCause::Cause cause); + bool try_cancel_gc(); public: // True if gc has been cancelled inline bool cancelled_gc() const; // Used by workers in the GC cycle to detect cancellation and honor STS requirements - inline bool check_cancelled_gc_and_yield(bool sts_active = true); - - // This indicates the reason the last GC cycle was cancelled. - inline GCCause::Cause cancelled_cause() const; + inline bool check_cancelled_gc_and_yield(); - // Clears the cancellation cause and resets the oom handler + // Clears the cancellation inline void clear_cancelled_gc(); - // Clears the cancellation cause iff the current cancellation reason equals the given - // expected cancellation cause. Does not reset the oom handler. - inline GCCause::Cause clear_cancellation(GCCause::Cause expected); - void cancel_concurrent_mark(); // Returns true if and only if this call caused a gc to be cancelled. @@ -475,25 +472,37 @@ class ShenandoahHeap : public CollectedHeap { // Returns true if the soft maximum heap has been changed using management APIs. bool check_soft_max_changed(); -protected: - // This is shared between shConcurrentGC and shDegenerateGC so that degenerated - // GC can resume update refs from where the concurrent GC was cancelled. It is - // also used in shGenerationalHeap, which uses a different closure for update refs. - ShenandoahRegionIterator _update_refs_iterator; + // True if self forwarded objects exist in the heap + bool has_self_forwarded_objects() const { + return _has_self_forwarded_objects.is_set(); + } + + // Set when self forwarded objects exist in the heap + void set_has_self_forwarded_objects() { + _has_self_forwarded_objects.try_set(); + } + + void unset_has_self_forwarded_objects() { + _has_self_forwarded_objects.unset(); + } private: + ShenandoahSharedFlag _has_self_forwarded_objects; + inline void reset_cancellation_time(); // GC support // Evacuation - virtual void evacuate_collection_set(ShenandoahGeneration* generation, bool concurrent); + virtual void evacuate_collection_set(ShenandoahGeneration* generation); + + // Self forwards objects that were never seen by evacuating threads + void self_forward_stranded_objects(); + // Concurrent root processing void prepare_concurrent_roots(); void finish_concurrent_roots(); // Concurrent class unloading support void do_class_unloading(); - // Reference updating - void prepare_update_heap_references(); // Retires LABs used for evacuation void concurrent_prepare_for_update_refs(); @@ -501,16 +510,16 @@ class ShenandoahHeap : public CollectedHeap { // Turn off weak roots flag void op_final_roots(); - virtual void update_heap_references(ShenandoahGeneration* generation, bool concurrent); + virtual void update_heap_references(ShenandoahGeneration* generation); // Final update region states - void update_heap_region_states(bool concurrent); + void update_heap_region_states(); virtual void final_update_refs_update_region_states(); void rendezvous_threads(const char* name); void recycle_trash(); public: // The following two functions rebuild the free set at the end of GC, in preparation for an idle phase. - void rebuild_free_set(bool concurrent); + void rebuild_free_set(); void rebuild_free_set_within_phase(); void notify_gc_progress(); void notify_gc_no_progress(); @@ -547,6 +556,7 @@ class ShenandoahHeap : public CollectedHeap { public: ShenandoahController* control_thread() const { return _control_thread; } + inline bool is_stopping() const; ShenandoahGeneration* global_generation() const { return _global_generation; } ShenandoahYoungGeneration* young_generation() const { @@ -572,7 +582,7 @@ class ShenandoahHeap : public CollectedHeap { return _evac_tracker; } - void on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation, bool is_degenerated, bool is_out_of_cycle); + void on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation); void on_cycle_end(ShenandoahGeneration* generation); ShenandoahVerifier* verifier(); @@ -608,19 +618,13 @@ class ShenandoahHeap : public CollectedHeap { void set_unload_classes(bool uc); bool unload_classes() const; - // Perform STW class unloading and weak root cleaning - void parallel_cleaning(ShenandoahGeneration* generation, bool full_gc); - private: - void stw_unload_classes(bool full_gc); - void stw_process_weak_roots(bool full_gc); - void stw_weak_refs(ShenandoahGeneration* generation, bool full_gc); inline void assert_lock_for_affiliation(ShenandoahAffiliation orig_affiliation, ShenandoahAffiliation new_affiliation); // Heap iteration support - void scan_roots_for_iteration(ShenandoahScanObjectStack* oop_stack, ObjectIterateScanRootClosure* oops); + void scan_roots_for_iteration(ObjectIterateScanRootClosure* oops, uint num_workers); bool prepare_aux_bitmap_for_iteration(); void reclaim_aux_bitmap_for_iteration(); @@ -680,6 +684,7 @@ class ShenandoahHeap : public CollectedHeap { void object_iterate(ObjectClosure* cl) override; // Parallel heap iteration support ParallelObjectIteratorImpl* parallel_object_iterator(uint workers) override; + bool supports_parallel_object_iteration() const override { return true; } // Keep alive an object that was loaded with AS_NO_KEEPALIVE. void keep_alive(oop obj) override; @@ -731,9 +736,6 @@ class ShenandoahHeap : public CollectedHeap { HeapWord* allocate_from_gclab_slow(Thread* thread, size_t size); HeapWord* allocate_new_gclab(size_t min_size, size_t word_size, size_t* actual_size); - // We want to retry an unsuccessful attempt at allocation until at least a full gc. - bool should_retry_allocation(size_t original_full_gc_count) const; - public: HeapWord* allocate_memory(ShenandoahAllocRequest& request); HeapWord* mem_allocate(size_t size) override; @@ -844,22 +846,9 @@ class ShenandoahHeap : public CollectedHeap { // self-forwarded bit on src, flags src's region, and returns src. virtual oop evacuate_object(oop src, Thread* thread); - // Parallel scan of flagged cset regions to clear self-forwarded bits on live - // objects. Must be called at a safepoint; intended for the degenerated and - // full GC entry paths. - void un_self_forward_cset_regions(); - - DEBUG_ONLY(void assert_no_self_forwards() const;) - // ---------- Helper functions // public: - template - inline void conc_update_with_forwarded(T* p); - - template - inline void non_conc_update_with_forwarded(T* p); - static inline void atomic_update_oop(oop update, oop* addr, oop compare); static inline void atomic_update_oop(oop update, narrowOop* addr, oop compare); static inline void atomic_update_oop(oop update, narrowOop* addr, narrowOop compare); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp index f0723ddfef18..8b4954457a35 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp @@ -72,10 +72,6 @@ inline WorkerThreads* ShenandoahHeap::workers() const { return _workers; } -inline WorkerThreads* ShenandoahHeap::safepoint_workers() { - return _safepoint_workers; -} - inline void ShenandoahHeap::notify_gc_progress() { _gc_no_progress_count.store_relaxed((size_t) 0); @@ -102,53 +98,6 @@ inline ShenandoahHeapRegion* ShenandoahHeap::heap_region_containing(const void* return result; } -template -inline void ShenandoahHeap::non_conc_update_with_forwarded(T* p) { - T o = RawAccess<>::oop_load(p); - if (!CompressedOops::is_null(o)) { - oop obj = CompressedOops::decode_not_null(o); - if (in_collection_set(obj)) { - // Corner case: when evacuation fails, there are objects in collection - // set that are not forwarded, and can still be in cset. - shenandoah_assert_forwarded_except(p, obj, cancelled_gc()); - oop resolved = ShenandoahForwarding::forwardee_or_null(obj); - if (resolved == nullptr) { - resolved = obj; - } - shenandoah_assert_not_in_cset_except(p, resolved, cancelled_gc()); - - if (resolved != obj) { - // Unconditionally store the update: no concurrent updates expected. - RawAccess::oop_store(p, resolved); - } - } - } -} - -template -inline void ShenandoahHeap::conc_update_with_forwarded(T* p) { - T o = RawAccess<>::oop_load(p); - if (!CompressedOops::is_null(o)) { - oop obj = CompressedOops::decode_not_null(o); - if (in_collection_set(obj)) { - // For concurrent update-refs, we cannot reach the state - // with non-forwarded objects in cset. - shenandoah_assert_forwarded(p, obj); - oop resolved = ShenandoahForwarding::forwardee(obj); - shenandoah_assert_not_in_cset(p, resolved); - - // We should not be updating the cset regions themselves. - shenandoah_assert_not_in_cset_loc_except(p, !is_in(p)); - - if (resolved != obj) { - // Either we succeed in updating the reference, or something else gets in our way. - // We don't care if that is another concurrent GC update, or another mutator update. - atomic_update_oop(resolved, p, o); - } - } - } -} - // Atomic updates of heap location. This is only expected to work with updating the same // logical object with its forwardee. The reason why we need stronger-than-relaxed memory // ordering has to do with coordination with GC barriers and mutator accesses. @@ -250,12 +199,16 @@ inline void ShenandoahHeap::atomic_clear_oop(narrowOop* addr, narrowOop compare) AtomicAccess::cmpxchg(addr, compare, narrowOop(), memory_order_relaxed); } +inline bool ShenandoahHeap::is_stopping() const { + return control_thread()->should_terminate(); +} + inline bool ShenandoahHeap::cancelled_gc() const { - return _cancelled_gc.get() != GCCause::_no_gc; + return _cancelled_gc.is_set() || is_stopping(); } -inline bool ShenandoahHeap::check_cancelled_gc_and_yield(bool sts_active) { - if (sts_active && !cancelled_gc()) { +inline bool ShenandoahHeap::check_cancelled_gc_and_yield() { + if (!cancelled_gc()) { if (SuspendibleThreadSet::should_yield()) { SuspendibleThreadSet::yield(); } @@ -263,23 +216,11 @@ inline bool ShenandoahHeap::check_cancelled_gc_and_yield(bool sts_active) { return cancelled_gc(); } -inline GCCause::Cause ShenandoahHeap::cancelled_cause() const { - return _cancelled_gc.get(); -} - inline void ShenandoahHeap::clear_cancelled_gc() { - _cancelled_gc.set(GCCause::_no_gc); + _cancelled_gc.unset(); reset_cancellation_time(); } -inline GCCause::Cause ShenandoahHeap::clear_cancellation(const GCCause::Cause expected) { - const GCCause::Cause cancellation_cause = _cancelled_gc.cmpxchg(GCCause::_no_gc, expected); - if (cancellation_cause == expected) { - reset_cancellation_time(); - } - return cancellation_cause; -} - inline void ShenandoahHeap::reset_cancellation_time() { if (_cancel_requested_time > 0) { log_debug(gc)("GC cancellation took %.3fs", (os::elapsedTime() - _cancel_requested_time)); @@ -292,8 +233,6 @@ inline HeapWord* ShenandoahHeap::allocate_from_gclab(Thread* thread, size_t size PLAB* gclab = ShenandoahThreadLocalData::gclab(thread); if (gclab == nullptr) { - assert(!thread->is_Java_thread() && !thread->is_Worker_thread(), - "Performance: thread should have GCLAB: %s", thread->name()); // No GCLABs in this thread, fallback to shared allocation return nullptr; } @@ -487,10 +426,6 @@ inline bool ShenandoahHeap::is_concurrent_weak_root_in_progress() const { return is_gc_state(WEAK_ROOTS); } -inline bool ShenandoahHeap::is_degenerated_gc_in_progress() const { - return _degenerated_gc_in_progress.is_set(); -} - inline bool ShenandoahHeap::is_full_gc_in_progress() const { return _full_gc_in_progress.is_set(); } @@ -500,7 +435,7 @@ inline bool ShenandoahHeap::is_full_gc_move_in_progress() const { } inline bool ShenandoahHeap::is_stw_gc_in_progress() const { - return is_full_gc_in_progress() || is_degenerated_gc_in_progress(); + return is_full_gc_in_progress(); } inline bool ShenandoahHeap::is_concurrent_strong_root_in_progress() const { @@ -571,7 +506,7 @@ class ShenandoahObjectToOopClosure : public ObjectClosure { public: ShenandoahObjectToOopClosure(T* cl) : _cl(cl) {} - void do_object(oop obj) { + void do_object(oop obj) override { obj->oop_iterate(_cl); } }; @@ -584,11 +519,24 @@ class ShenandoahObjectToOopBoundedClosure : public ObjectClosure { ShenandoahObjectToOopBoundedClosure(T* cl, HeapWord* bottom, HeapWord* top) : _cl(cl), _bounds(bottom, top) {} - void do_object(oop obj) { + void do_object(oop obj) override { obj->oop_iterate(_cl, _bounds); } }; +template +class ShenandoahNonForwardedObjectToOopClosure : public ObjectClosure { + T* _cl; +public: + ShenandoahNonForwardedObjectToOopClosure(T* cl) : _cl(cl) {} + + void do_object(oop obj) override { + if (!ShenandoahForwarding::is_real_forwarded(obj)) { + obj->oop_iterate(_cl); + } + } +}; + template inline void ShenandoahHeap::marked_object_oop_iterate(ShenandoahHeapRegion* region, T* cl, HeapWord* top) { if (region->is_humongous()) { @@ -599,8 +547,15 @@ inline void ShenandoahHeap::marked_object_oop_iterate(ShenandoahHeapRegion* regi marked_object_iterate(region, &objs); } } else { - ShenandoahObjectToOopClosure objs(cl); - marked_object_iterate(region, &objs, top); + if (region->has_self_forwards()) { + assert(region->is_cset(), "Self forwards only allowed in cset region: %zu", region->index()); + ShenandoahNonForwardedObjectToOopClosure objs(cl); + marked_object_iterate(region, &objs, top); + } else { + assert(!region->is_cset(), "Should not be iterating objects in cset region: %zu", region->index()); + ShenandoahObjectToOopClosure objs(cl); + marked_object_iterate(region, &objs, top); + } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 3aac7f21efaf..b86a152c19b6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -74,12 +74,7 @@ ShenandoahHeapRegion::ShenandoahHeapRegion(HeapWord* start, size_t index, bool c _mixed_candidate_garbage_words(0), _update_watermark(start), _age(0), -#ifdef SHENANDOAH_CENSUS_NOISE - _youth(0), -#endif // SHENANDOAH_CENSUS_NOISE - _needs_bitmap_reset(false) - { - + _needs_bitmap_reset(false) { assert(Universe::on_page_boundary(_bottom) && Universe::on_page_boundary(_end), "invalid space boundaries"); if (ZapUnusedHeapArea && committed) { @@ -113,6 +108,21 @@ void ShenandoahHeapRegion::make_regular_allocation(ShenandoahAffiliation affilia } } +void ShenandoahHeapRegion::make_regular_for_partial_recycling() { + shenandoah_assert_heaplocked_or_safepoint(); + assert(has_self_forwards(), "Only for regions holding self forwarded objects"); + switch (state()) { + case _cset: + set_state(_regular); + return; + case _pinned_cset: + set_state(_pinned); + return; + default: + report_illegal_transition("partially recycled"); + } +} + // Change affiliation to YOUNG_GENERATION if _state is not _pinned_cset, _regular, or _pinned. This implements // behavior previously performed as a side effect of make_regular_bypass(). This is used by Full GC in non-generational // modes to transition regions from FREE. Note that all non-free regions in single-generational modes are young. @@ -140,8 +150,7 @@ void ShenandoahHeapRegion::make_affiliated_maybe() { void ShenandoahHeapRegion::make_regular_bypass() { shenandoah_assert_heaplocked(); - assert (ShenandoahHeap::heap()->is_full_gc_in_progress() || - ShenandoahHeap::heap()->is_degenerated_gc_in_progress(), + assert (ShenandoahHeap::heap()->is_full_gc_in_progress(), "Only for STW GC"); reset_age(); switch (state()) { @@ -287,7 +296,8 @@ void ShenandoahHeapRegion::make_cset() { } void ShenandoahHeapRegion::make_trash() { - shenandoah_assert_heaplocked(); + shenandoah_assert_heaplocked_or_safepoint(); + assert(!has_self_forwards(), "Should not have evacuation failures"); reset_age(); switch (state()) { case _humongous_start: @@ -320,7 +330,6 @@ void ShenandoahHeapRegion::make_trash_immediate() { void ShenandoahHeapRegion::make_empty() { reset_age(); - CENSUS_NOISE(clear_youth();) switch (state()) { case _trash: set_state(_empty_committed); @@ -443,6 +452,10 @@ void ShenandoahHeapRegion::print_on(outputStream* st) const { st->print("|S %5zu%1s", byte_size_in_proper_unit(get_shared_allocs()), proper_unit_for_byte_size(get_shared_allocs())); st->print("|L %5zu%1s", byte_size_in_proper_unit(get_live_data_bytes()), proper_unit_for_byte_size(get_live_data_bytes())); st->print("|CP %3zu", pin_count()); + + if (has_self_forwards()) { + st->print("|EF"); + } st->cr(); #undef SHR_PTR_FORMAT @@ -489,10 +502,9 @@ bool ShenandoahHeapRegion::oop_coalesce_and_fill(bool cancellable, bool do_card_ if (do_card_table_updates) { heap->old_generation()->card_scan()->coalesce_objects(obj_addr, fill_size); } else { - // A humongous object allocation failure during evacuation will skip the degenerated cycle and - // jump straight to a full GC. If this region is pinned when the full GC cycle starts, it will - // not be compacted. Therefore, if the region is old, we must fill in any unmarked objects. However, - // promoted objects will not have been registered yet, so we cannot use the card table here. + // If this region is pinned when the full GC cycle starts, it will not be compacted. Therefore, if the region is + // old, we must fill in any unmarked objects. However, promoted objects will not have been registered yet, so we + // cannot use the card table here. assert(heap->is_full_gc_in_progress(), "Can only skip card table updates during a full GC"); } @@ -862,6 +874,97 @@ void ShenandoahHeapRegion::set_affiliation(ShenandoahAffiliation new_affiliation heap->set_affiliation(this, new_affiliation); } +// We would like to reclaim what we can from a partially evacuated region. We may have a mixture of objects that were +// self-forwarded and objects that were successfully evacuated to another region. The simplest thing we can do is slide +// top to the end of the last self forwarded object. This requires no changes on the allocation path. +// +// We must also leave old regions in a walkable state because they could be visited by a remembered set scan. +class ShenandoahReclaimSelfForwarded : public ObjectClosure { + + // This is a nullptr for young regions. For old regions, we use it to patch up the card table. + ShenandoahScanRemembered* _cards; + + // This is the _end_ of the last self-forwarded object the closure encountered. We use this + // to know where to start the filler object and where to set top when the iteration is complete. + HeapWord* _previous; + +public: + explicit ShenandoahReclaimSelfForwarded(ShenandoahScanRemembered* cards, ShenandoahHeapRegion* region) + : _cards(cards), _previous(region->bottom()) { + assert(!region->is_old() || cards != nullptr, "Must have card table reference for old region: %zu", region->index()); + } + + void do_object(oop obj) override { + shenandoah_assert_forwarded(nullptr, obj); + + if (ShenandoahForwarding::is_self_forwarded(obj)) { + HeapWord* current = cast_from_oop(obj); + const size_t object_size = ShenandoahForwarding::size(obj); + + // Clear the self forwarded bit, this is just a regular object in a regular region now. + obj->unset_self_forwarded(); + + if (_cards != nullptr) { + // We will have reset registrations and cards before iterating here, so we need to (conservatively) + // update the card table and object registrations. We don't need a lock because each region will only + // be visited by one thread. + _cards->register_object_without_lock(current); + _cards->mark_range_as_dirty(current, object_size); + + if (_previous != current) { + // We create a filler object to keep the old region parsable. We know there are no pointers in + // these filler objects, so we don't need to dirty cards, but we still need to register the object. + CollectedHeap::fill_with_object(_previous, pointer_delta(current, _previous)); + _cards->register_object_without_lock(_previous); + } + } + _previous = current + object_size; + } + } + + HeapWord* last_self_forwarded_object() const { + return _previous; + } +}; + +void ShenandoahHeapRegion::partially_recycle() { + assert(has_self_forwards(), "Region %zu must have self forwarded objects", index()); + + // This is only allowed for partially evacuated cset regions + make_regular_for_partial_recycling(); + + ShenandoahHeap* heap = ShenandoahHeap::heap(); + ShenandoahScanRemembered* cards = is_old() ? heap->old_generation()->card_scan() : nullptr; + if (cards != nullptr) { + // Some of the objects here may be dead or forwarded, we don't want any stale object registrations or dirty cards. + // We will rebuild the registrations and dirty cards for self-forwarded objects that stay in this region. + cards->mark_range_as_empty(this->bottom(), region_size_words()); + } + + // Clear self-forwarded objects and fill in the gaps between them + ShenandoahReclaimSelfForwarded reclaimer(cards, this); + heap->marked_object_iterate(this, &reclaimer); + + // Reset some of the region states that would be cleared if this region was completely recycled + const HeapWord* old_top = top(); + _top_at_evac_start = _bottom; + _mixed_candidate_garbage_words = 0; + clear_live_data(); + reset_alloc_metadata(); + heap->marking_context()->reset_top_at_mark_start(this); + set_update_watermark(bottom()); + clear_has_self_forwards(); + + // Adjust top to the end of our last encountered self-forwarded object. Everything above this is reusable memory. + set_top(reclaimer.last_self_forwarded_object()); + const size_t reclaimed_bytes = pointer_delta(old_top, top()) * HeapWordSize; + if (reclaimed_bytes > 0) { + // Region could be used for new allocations, and we don't want those to be promoted prematurely + log_debug(gc)("Reclaimed " PROPERFMT " from partially evacuated region (%zu)", PROPERFMTARGS(reclaimed_bytes), index()); + reset_age(); + } +} + void ShenandoahHeapRegion::decrement_humongous_waste() { assert(is_humongous(), "Should only use this for humongous regions"); size_t waste_bytes = free(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index 86e3afae81e3..b4b09f11597c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -29,7 +29,6 @@ #include "gc/shared/gc_globals.hpp" #include "gc/shared/spaceDecorator.hpp" #include "gc/shenandoah/shenandoahAffiliation.hpp" -#include "gc/shenandoah/shenandoahAgeCensus.hpp" #include "gc/shenandoah/shenandoahAllocRequest.hpp" #include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahHeap.hpp" @@ -176,6 +175,7 @@ class ShenandoahHeapRegion { // Allowed transitions from the outside code: void make_regular_allocation(ShenandoahAffiliation affiliation); + void make_regular_for_partial_recycling(); void make_affiliated_maybe(); void make_regular_bypass(); void make_humongous_start(); @@ -220,6 +220,7 @@ class ShenandoahHeapRegion { // Macro-properties: bool is_alloc_allowed() const { auto cur_state = state(); return is_empty_state(cur_state) || cur_state == _regular || cur_state == _pinned; } bool is_stw_move_allowed() const { auto cur_state = state(); return cur_state == _regular || cur_state == _cset || (ShenandoahHumongousMoves && cur_state == _humongous_start); } + bool is_update_required() const { return is_active() && (!is_cset() || has_self_forwards()); } RegionState state() const { return _state.load_acquire(); } int state_ordinal() const { return region_state_to_ordinal(state()); } @@ -271,13 +272,11 @@ class ShenandoahHeapRegion { uint _age; bool _promoted_in_place; - CENSUS_NOISE(uint _youth;) // tracks epochs of retrograde ageing (rejuvenation) ShenandoahSharedFlag _recycling; // Used to indicate that the region is being recycled; see try_recycle*(). // Set when an evacuation failure self-forwarded at least one object in this - // region. The drain at degen/full GC entry scans flagged regions and CAS- - // clears the self_fwd bits. Safety-net reset on region recycle. + // region. The flag is cleared when the region is partially recycled during final-update-refs. ShenandoahSharedFlag _has_self_forwards; // This is only read/written by a gc worker to avoid unnecessary bitmap resets @@ -505,9 +504,8 @@ class ShenandoahHeapRegion { void set_affiliation(ShenandoahAffiliation new_affiliation); - // Region ageing and rejuvenation + // Region ageing uint age() const { return _age; } - CENSUS_NOISE(uint youth() const { return _youth; }) void increment_age() { const uint max_age = markWord::max_age; @@ -519,11 +517,10 @@ class ShenandoahHeapRegion { void reset_age() { assert(get_top_before_promote() == nullptr, "Cannot reset age on region (%zu) scheduled for promotion", index()); - CENSUS_NOISE(_youth += _age;) _age = 0; } - CENSUS_NOISE(void clear_youth() { _youth = 0; }) + void partially_recycle(); inline bool need_bitmap_reset() const { return _needs_bitmap_reset; @@ -539,7 +536,7 @@ class ShenandoahHeapRegion { // Self-forward accounting: set by an evacuating thread after it successfully // installs a self-forward mark on an object in this region. Tested and cleared - // at the drain phase (degen/full GC entry) and again on region recycle. + // when the region is partially recycled in final-update-refs. bool has_self_forwards() const { return _has_self_forwards.is_set(); } void set_has_self_forwards() { _has_self_forwards.try_set(); } void clear_has_self_forwards() { _has_self_forwards.unset(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp index 3e809ea84b9b..2e627031963a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2016, 2020, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -191,9 +191,10 @@ jlong ShenandoahHeapRegionCounters::encode_heap_status(ShenandoahHeap* heap) { generation->name(), phase, BOOL_TO_STR(heap->is_concurrent_old_mark_in_progress()), status); } - if (heap->is_degenerated_gc_in_progress()) { + if (heap->has_self_forwarded_objects()) { status |= (1 << 6); } + if (heap->is_full_gc_in_progress()) { status |= (1 << 7); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.hpp index d50188bf70cc..b8fe55e18c58 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.hpp @@ -47,7 +47,7 @@ * 0 = idle, 1 = marking, 2 = evacuating, 3 = updating refs * * For mode: - * 0 = concurrent, 1 = degenerated, 2 = full + * 0 = concurrent, 1 = evacuation failures, 2 = full * * two variable counters per region, with $max_regions (see above) counters: * - sun.gc.shenandoah.regions.region.$i.data diff --git a/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp b/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp index efea98b61f07..ab7b530628ac 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp @@ -144,9 +144,6 @@ void ShenandoahInPlacePromotionPlanner::complete_planning() const { void ShenandoahInPlacePromoter::maybe_promote_region(ShenandoahHeapRegion* r) const { if (r->is_regular_or_regular_pinned() && (r->get_top_before_promote() != nullptr)) { // This region was scheduled for promotion. The promotion must be completed. - // The 'always_tenure' override flag set by WB.fullGC() is not carried over - // into the degenerated cycle so we cannot rely on is_tenurable again. We checked - // it when we made the plan for this region, that plan is authoritative. assert(r->is_young() && r->is_active(), "Region scheduled for promotion must still be young and active"); promote(r); return; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp index 9c6ede961d4e..80ed946a4811 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp @@ -56,7 +56,7 @@ ShenandoahMark::ShenandoahMark(ShenandoahGeneration* generation) : } template -void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::Requests* const req, bool update_refs) { +void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::Requests* const req) { ShenandoahObjToScanQueueSet* queues = task_queues(); ShenandoahObjToScanQueue* q = get_queue(w); ShenandoahObjToScanQueue* old_q = get_old_queue(w); @@ -66,22 +66,12 @@ void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::R // TODO: We can clean up this if we figure out how to do templated oop closures that // play nice with specialized_oop_iterators. - if (update_refs) { - using Closure = ShenandoahMarkUpdateRefsClosure; - Closure cl(q, rp, old_q); - if (UseCompressedOops) { - mark_loop_work(&cl, ld, w, t, req); - } else { - mark_loop_work(&cl, ld, w, t, req); - } + using Closure = ShenandoahMarkRefsClosure; + Closure cl(q, rp, old_q); + if (UseCompressedOops) { + mark_loop_work(&cl, ld, w, t, req); } else { - using Closure = ShenandoahMarkRefsClosure; - Closure cl(q, rp, old_q); - if (UseCompressedOops) { - mark_loop_work(&cl, ld, w, t, req); - } else { - mark_loop_work(&cl, ld, w, t, req); - } + mark_loop_work(&cl, ld, w, t, req); } heap->flush_liveness_cache(w); @@ -90,24 +80,21 @@ void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::R template void ShenandoahMark::mark_loop(uint worker_id, TaskTerminator* terminator, ShenandoahGenerationType generation_type, StringDedup::Requests* const req) { - bool update_refs = ShenandoahHeap::heap()->has_forwarded_objects(); switch (generation_type) { case YOUNG: - mark_loop_prework(worker_id, terminator, req, update_refs); + mark_loop_prework(worker_id, terminator, req); break; case OLD: - // Old generation collection only performs marking, it should not update references. - mark_loop_prework(worker_id, terminator, req, false); + mark_loop_prework(worker_id, terminator, req); break; case GLOBAL: - mark_loop_prework(worker_id, terminator, req, update_refs); + mark_loop_prework(worker_id, terminator, req); break; case NON_GEN: - mark_loop_prework(worker_id, terminator, req, update_refs); + mark_loop_prework(worker_id, terminator, req); break; default: ShouldNotReachHere(); - break; } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp index cb003bfbaab2..b2d860e144de 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp @@ -103,7 +103,7 @@ class ShenandoahMark: public StackObj { static void dedup_string(oop obj, StringDedup::Requests* const req); template - void mark_loop_prework(uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req, bool update_refs); + void mark_loop_prework(uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req); template NOINLINE // Main hot loop, start inlining from here diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 7a3a5e89b11d..668b951abd1a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -54,7 +54,7 @@ void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveD shenandoah_assert_not_forwarded(nullptr, obj); shenandoah_assert_marked(nullptr, obj); - shenandoah_assert_not_in_cset_except(nullptr, obj, ShenandoahHeap::heap()->cancelled_gc()); + shenandoah_assert_not_in_cset_except(nullptr, obj, ShenandoahHeap::heap()->cancelled_gc() || ShenandoahHeap::heap()->has_self_forwarded_objects()); Klass* klass = obj->klass(); @@ -163,8 +163,7 @@ void ShenandoahMark::count_liveness(ShenandoahLiveData* live_data, oop obj, Klas assert(region->is_young(), "Only for young objects"); const uint age = ShenandoahForwarding::age(obj); ShenandoahAgeCensus* const census = ShenandoahGenerationalHeap::heap()->age_census(); - CENSUS_NOISE(census->add(age, region->age(), region->youth(), size, worker_id);) - NO_CENSUS_NOISE(census->add(age, region->age(), size, worker_id);) + census->add(age, region->age(), size, worker_id); } if (!region->is_humongous_start()) { @@ -393,7 +392,7 @@ void ShenandoahMark::mark_non_generational_ref(T* p, ShenandoahObjToScanQueue* q oop obj = CompressedOops::decode_not_null(o); shenandoah_assert_not_forwarded(p, obj); - shenandoah_assert_not_in_cset_except(p, obj, ShenandoahHeap::heap()->cancelled_gc()); + shenandoah_assert_not_in_cset_except(p, obj, ShenandoahHeap::heap()->cancelled_gc() || ShenandoahHeap::heap()->has_self_forwarded_objects()); mark_ref(q, mark_context, weak, obj); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.cpp index 0b38f6e0ceaf..a8a3772314b1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.cpp @@ -1,6 +1,6 @@ /* * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -86,7 +86,6 @@ void ShenandoahMmuTracker::fetch_cpu_times(double &gc_time, double &mutator_time void ShenandoahMmuTracker::update_utilization(size_t gcid, const char* msg) { double current = os::elapsedTime(); _most_recent_gcid = gcid; - _most_recent_is_full = false; if (gcid == 0) { fetch_cpu_times(_most_recent_gc_time, _most_recent_mutator_time); @@ -140,17 +139,8 @@ void ShenandoahMmuTracker::record_mixed(size_t gcid) { update_utilization(gcid, "Mixed Concurrent GC"); } -void ShenandoahMmuTracker::record_degenerated(size_t gcid, const char* msg) { - if ((gcid == _most_recent_gcid) && _most_recent_is_full) { - // Do nothing. This is a redundant recording for the full gc that just completed. - } else { - update_utilization(gcid, msg); - } -} - void ShenandoahMmuTracker::record_full(size_t gcid) { update_utilization(gcid, "Full GC"); - _most_recent_is_full = true; } void ShenandoahMmuTracker::report() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.hpp index cb2db12c233d..6d1ab0988c67 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMmuTracker.hpp @@ -67,8 +67,6 @@ class ShenandoahMmuTracker { size_t _most_recent_gcid; uint _active_processors; - bool _most_recent_is_full; - ShenandoahMmuTask* _mmu_periodic_task; TruncatedSeq _mmu_average; @@ -85,20 +83,13 @@ class ShenandoahMmuTracker { void initialize(); // At completion of each GC cycle (not including interrupted cycles), we invoke one of the following to record the - // GC utilization during this cycle. Incremental efforts spent in an interrupted GC cycle will be accumulated into - // the CPU time reports for the subsequent completed [degenerated or full] GC cycle. - // - // We may redundantly record degen and full in the case that a degen upgrades to full. When this happens, we will invoke - // both record_full() and record_degenerated() with the same value of gcid. record_full() is called first and the log - // reports such a cycle as a FULL cycle. + // GC utilization during this cycle. void record_young(size_t gcid); void record_global(size_t gcid); void record_bootstrap(size_t gcid); void record_old_marking_increment(bool old_marking_done); void record_mixed(size_t gcid); void record_full(size_t gcid); - // Records GC utilization for a degenerated cycle, where "msg" describes the degeneration type. - void record_degenerated(size_t gcid, const char* msg); // This is called by the periodic task timer. The interval is defined by // GCPauseIntervalMillis and defaults to 5 seconds. This method computes diff --git a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp index b375b0c5d391..851cec261b2c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp @@ -112,8 +112,8 @@ void ShenandoahNMethod::parse(nmethod* nm, GrowableArray& oops, bool& has_ if (value != nullptr) { oop* addr = r->oop_addr(); shenandoah_assert_correct(addr, value); - shenandoah_assert_not_in_cset_except(addr, value, ShenandoahHeap::heap()->cancelled_gc()); - shenandoah_assert_not_forwarded_except(addr, value, ShenandoahHeap::heap()->cancelled_gc()); + shenandoah_assert_not_in_cset_except(addr, value, ShenandoahHeap::heap()->cancelled_gc() || ShenandoahHeap::heap()->has_self_forwarded_objects()); + shenandoah_assert_not_forwarded_except(addr, value, ShenandoahHeap::heap()->cancelled_gc() || ShenandoahHeap::heap()->has_self_forwarded_objects()); // Non-null immediate oop found. null oops can safely be // ignored since the method will be re-registered if they // are later patched to be non-null. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp index 2b813560d6b6..890fff1ca9c7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp @@ -28,17 +28,19 @@ #include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" -#include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" #include "gc/shenandoah/shenandoahOldGC.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" +#include "gc/shenandoah/shenandoahVerifier.hpp" #include "gc/shenandoah/shenandoahYoungGeneration.hpp" #include "prims/jvmtiTagMap.hpp" #include "utilities/events.hpp" -ShenandoahOldGC::ShenandoahOldGC(ShenandoahOldGeneration* generation, ShenandoahSharedFlag& allow_preemption) : - ShenandoahConcurrentGC(generation, false), _old_generation(generation), _allow_preemption(allow_preemption) { +ShenandoahOldGC::ShenandoahOldGC(ShenandoahOldGeneration* generation, ShenandoahSharedFlag& allow_preemption) + : ShenandoahConcurrentGC(nullptr, generation, false) + , _old_generation(generation) + , _allow_preemption(allow_preemption) { } // Final mark for old-gen is different for young than old, so we @@ -65,7 +67,7 @@ void ShenandoahOldGC::op_final_mark() { // We need to do this because weak root cleaning reports the number of dead handles JvmtiTagMap::set_needs_cleaning(); - _generation->prepare_regions_and_collection_set(true); + _generation->prepare_regions_and_collection_set(); heap->set_unload_classes(false); heap->prepare_concurrent_roots(); @@ -84,6 +86,7 @@ void ShenandoahOldGC::op_final_mark() { } } +// Intentionally does not update controller phase. Allocation stalls are always attributed to state of young. bool ShenandoahOldGC::collect(GCCause::Cause cause) { auto heap = ShenandoahGenerationalHeap::heap(); assert(!_old_generation->is_doing_mixed_evacuations(), "Should not start an old gc with pending mixed evacuations"); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.hpp index 2c637f5f72af..e64a422ef6e8 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.hpp @@ -27,8 +27,9 @@ #include "gc/shared/gcCause.hpp" #include "gc/shenandoah/shenandoahConcurrentGC.hpp" -#include "gc/shenandoah/shenandoahVerifier.hpp" +#include "gc/shenandoah/shenandoahSharedVariables.hpp" +class ShenandoahController; class ShenandoahOldGeneration; class ShenandoahOldGC : public ShenandoahConcurrentGC { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index be09113c3b5e..f7686faca9b3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -45,20 +45,6 @@ #include "runtime/threads.hpp" #include "utilities/events.hpp" -class ShenandoahPurgeSATBTask : public WorkerTask { -public: - explicit ShenandoahPurgeSATBTask() : WorkerTask("Purge SATB") { - Threads::change_thread_claim_token(); - } - - void work(uint worker_id) override { - ShenandoahParallelWorkerSession worker_session(worker_id); - ShenandoahSATBMarkQueueSet &satb_queues = ShenandoahBarrierSet::satb_mark_queue_set(); - ShenandoahFlushSATB flusher(satb_queues); - Threads::possibly_parallel_threads_do(true /* is_par */, &flusher); - } -}; - class ShenandoahConcurrentCoalesceAndFillTask : public WorkerTask { private: uint _nworkers; @@ -432,26 +418,16 @@ bool ShenandoahOldGeneration::coalesce_and_fill() { } } -void ShenandoahOldGeneration::transfer_pointers_from_satb() const { - const ShenandoahHeap* heap = ShenandoahHeap::heap(); - assert(heap->is_concurrent_old_mark_in_progress(), "Only necessary during old marking."); - log_debug(gc)("Transfer SATB buffers"); - ShenandoahPurgeSATBTask purge_satb_task; - heap->workers()->run_task(&purge_satb_task); -} - bool ShenandoahOldGeneration::contains(oop obj) const { return ShenandoahHeap::heap()->is_in_old(obj); } -void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent) { +void ShenandoahOldGeneration::prepare_regions_and_collection_set() { ShenandoahHeap* heap = ShenandoahHeap::heap(); - assert(!heap->is_full_gc_in_progress(), "Only for concurrent and degenerated GC"); + assert(!heap->is_full_gc_in_progress(), "Only for concurrent GC"); { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_region_states : - ShenandoahPhaseTimings::degen_gc_final_update_region_states); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_update_region_states); ShenandoahFinalMarkUpdateRegionStateClosure cl(complete_marking_context(), this); parallel_heap_region_iterate(&cl); @@ -461,9 +437,7 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent { // This doesn't actually choose a collection set, but prepares a list of // regions as 'candidates' for inclusion in a mixed collection. - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::choose_cset : - ShenandoahPhaseTimings::degen_gc_choose_cset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::choose_cset); ShenandoahHeapLocker locker(heap->lock()); _old_heuristics->prepare_for_old_collections(); } @@ -471,16 +445,14 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent { // Though we did not choose a collection set above, we still may have // freed up immediate garbage regions so proceed with rebuilding the free set. - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_rebuild_freeset : - ShenandoahPhaseTimings::degen_gc_final_rebuild_freeset); + ShenandoahGCPhase phase(ShenandoahPhaseTimings::final_rebuild_freeset); ShenandoahFreeSet* free_set = heap->free_set(); ShenandoahHeapLocker locker(heap->lock()); // This is completion of old-gen marking. We rebuild in order to reclaim immediate garbage and to // prepare for subsequent mixed evacuations. size_t young_trash_regions, old_trash_regions, first_old, last_old, num_old; - heap->free_set()->prepare_to_rebuild(young_trash_regions, old_trash_regions, first_old, last_old, num_old); + free_set->prepare_to_rebuild(young_trash_regions, old_trash_regions, first_old, last_old, num_old); // At the end of old-gen, we may find that we have reclaimed immediate garbage, allowing a longer allocation runway. // We may also find that we have accumulated canddiate regions for mixed evacuation. If so, we will want to expand // the OldCollector reserve in order to make room for these mixed evacuations. @@ -494,7 +466,7 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent (gen_heap->young_generation()->free_unaffiliated_regions() + young_trash_regions) * ShenandoahHeapRegion::region_size_bytes()); gen_heap->compute_old_generation_balance(max_transfer, old_trash_regions, young_trash_regions); - heap->free_set()->finish_rebuild(young_trash_regions, old_trash_regions, num_old); + free_set->finish_rebuild(young_trash_regions, old_trash_regions, num_old); } } @@ -623,11 +595,6 @@ ShenandoahHeuristics* ShenandoahOldGeneration::initialize_heuristics(ShenandoahM return _heuristics; } -void ShenandoahOldGeneration::record_success_concurrent(bool abbreviated) { - heuristics()->record_success_concurrent(); - ShenandoahHeap::heap()->shenandoah_policy()->record_success_old(); -} - void ShenandoahOldGeneration::handle_failed_evacuation() { if (_failed_evacuation.try_set()) { log_debug(gc)("Old gen evac failure."); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 8f41b77fdc2f..5ac87ffce743 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -31,6 +31,7 @@ #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahPadding.hpp" #include "gc/shenandoah/shenandoahSharedVariables.hpp" +#include "runtime/atomic.hpp" class LogStream; class ShenandoahScanRemembered; @@ -51,7 +52,7 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { ssize_t _region_balance; // Set when evacuation in the old generation fails. When this is set, the control thread will initiate a - // full GC instead of a futile degenerated cycle. + // full GC instead of a futile concurrent cycle. ShenandoahSharedFlag _failed_evacuation; // Bytes reserved within old-gen to hold the results of promotion. This is separate from @@ -84,6 +85,10 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // True if old regions may be safely traversed by the remembered set scan. bool _is_parsable; + // Read by mutators, written by control thread. This is a logical id for an old generation collection. + // It is stable across interruptions by young GCs. + Atomic _started_gc_id; + bool coalesce_and_fill(); public: @@ -161,7 +166,7 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // This will signal the heuristic to trigger an old generation collection void handle_failed_transfer(); - // This will signal the control thread to run a full GC instead of a futile degenerated gc + // This will signal the control thread to run a full GC void handle_failed_evacuation(); // Increment promotion failure counters, optionally log a more detailed message @@ -245,35 +250,12 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { void transition_old_generation_after_global_gc(); void prepare_gc() override; - void prepare_regions_and_collection_set(bool concurrent) override; - void record_success_concurrent(bool abbreviated) override; + void prepare_regions_and_collection_set() override; void cancel_marking() override; // Cancels old gc and transitions to the idle state void cancel_gc(); - // The SATB barrier will be "enabled" until old marking completes. This means it is - // possible for an entire young collection cycle to execute while the SATB barrier is enabled. - // Consider a situation like this, where we have a pointer 'B' at an object 'A' which is in - // the young collection set: - // - // +--Young, CSet------+ +--Young, Regular----+ - // | | | | - // | | | | - // | A <--------------------+ B | - // | | | | - // | | | | - // +-------------------+ +--------------------+ - // - // If a mutator thread overwrites pointer B, the SATB barrier will dutifully enqueue - // object A. However, this object will be trashed when the young cycle completes. We must, - // therefore, filter this object from the SATB buffer before any old mark threads see it. - // We do this with a handshake before final-update-refs (see shenandoahConcurrentGC.cpp). - // - // This method is here only for degenerated cycles. A concurrent cycle may be cancelled before - // we have a chance to execute the handshake to flush the SATB in final-update-refs. - void transfer_pointers_from_satb() const; - // True if there are old regions waiting to be selected for a mixed collection bool has_unprocessed_collection_candidates(); @@ -295,7 +277,6 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { // Abandon any regions waiting for mixed collections void abandon_collection_candidates(); -public: enum State { FILLING, IDLE, MARKING, EVACUATING, EVACUATING_AFTER_GLOBAL }; @@ -356,6 +337,14 @@ class ShenandoahOldGeneration : public ShenandoahGeneration { size_t free_unaffiliated_regions() const override; size_t get_affiliated_region_count() const override; size_t max_capacity() const override; + + void record_collection_start(size_t gc_id) { + _started_gc_id.store_relaxed(gc_id); + } + + size_t started_gc_id() const { + return _started_gc_id.load_relaxed(); + } }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp index 59ed97dd5131..d73c232ae867 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp @@ -188,11 +188,11 @@ void ShenandoahPLAB::retire() { _heap->old_generation()->unexpend_promoted(not_promoted); } - // plab->retire() overwrites unused memory between plab->top() and plab->hard_end() with a dummy object to make memory parsable. - // We do _not_ need to register this remnant object with the card table because all paths where a PLAB object would - // be created are covered by a subsequent phase in the cycle. For the concurrent and degenerated cycles, all PLABs - // are retired in preparation for update-references. All objects in these PLABs will be registered by update-card-tables. - // For a full GC, the entire remembered set will be rebuilt in the final phase. Note also that an empty TLAB will _not_ - // create a filler object when it is retired. + // plab->retire() overwrites unused memory between plab->top() and plab->hard_end() with a dummy object to make memory + // parsable. We do _not_ need to register this remnant object with the card table because all paths where a PLAB + // object would be created are covered by a subsequent phase in the cycle. All PLABs are retired in preparation for + // update-references. For concurrent cycles, all objects in these PLABs will be registered by update-card-tables. + // For a full GC, the entire remembered set will be rebuilt in the final phase. Note also that an empty TLAB will + // _not_ create a filler object when it is retired. _plab->retire(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp index ca418ab45d4a..0879ae922b29 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp @@ -96,7 +96,6 @@ ShenandoahWorkerData* ShenandoahPhaseTimings::worker_data(Phase phase, WorkerPha bool ShenandoahPhaseTimings::is_root_work_phase(Phase phase) { switch (phase) { case finish_mark: - case degen_gc_update_roots: case full_gc_mark: case full_gc_update_roots: case full_gc_adjust_roots: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp index 68c568dcc1dd..a525af801bba 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp @@ -110,6 +110,8 @@ class outputStream; SHENANDOAH_SIMPLE_PHASE_DEF(f, final_roots_propagate_gc_state, " Propagate GC State") \ SHENANDOAH_WORKER_PHASE_DEF(f, conc_evac, "Concurrent Evacuation", \ " CE: ") \ + SHENANDOAH_WORKER_PHASE_DEF(f, conc_self_forward_stranded, "Self Forward Stranded Objects", \ + " SF: ") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, conc_update_card_table, "Concurrent Update Cards") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, complete_abbreviated, "Complete Abbreviated Cycle") \ SHENANDOAH_WORKER_PHASE_DEF(f, complete_abbreviated_promote_in_place, " Promote Regions", \ @@ -136,42 +138,8 @@ class outputStream; SHENANDOAH_WORKER_PHASE_DEF(f, conc_coalesce_and_fill, "Concurrent Coalesce and Fill", \ " CC&F: ") \ \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_gross, "Pause Degenerated GC (G)") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc, "Pause Degenerated GC (N)") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_un_self_forward, " Un-Self-Forward") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_mark, " Mark", \ - " DM: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_purge, " System Purge") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_weakrefs, " Weak References", \ - " WRP: ") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_purge_class_unload, " Unload Classes", \ - " DCU: ") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_purge_weak_par, " Weak Roots", \ - " DWR: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_purge_cldg, " CLDG") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_update_region_states, " Update Region States") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_manage_labs, " Manage GC/TLABs") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_choose_cset, " Choose Collection Set") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_rebuild_freeset, " Rebuild Free Set") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_evac, " Evacuation", \ - " DE: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_init_update_refs_manage_gclabs, " Manage GCLABs") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_update_refs, " Update References", \ - " DUR: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_update_refs_update_region_states, " Update Region States") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_update_refs_trash_cset, " Trash Collection Set") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_final_update_refs_rebuild_freeset, " Rebuild Free Set") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_update_roots, " Degen Update Roots", \ - " DU: ") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_cleanup_complete, " Cleanup") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_promote_regions, " Degen Promote Regions") \ - SHENANDOAH_WORKER_PHASE_DEF(f, degen_gc_coalesce_and_fill, " Degen Coalesce and Fill", \ - " DC&F") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, degen_gc_propagate_gc_state, " Propagate GC State") \ - \ SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc_gross, "Pause Full GC (G)") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc, "Pause Full GC (N)") \ - SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc_un_self_forward, " Un-Self-Forward") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc_heapdump_pre, " Pre Heap Dump") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, full_gc_prepare, " Prepare") \ SHENANDOAH_WORKER_PHASE_DEF(f, full_gc_update_roots, " Update Roots", \ diff --git a/src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp b/src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp index 835d141df950..30636b3d448e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahReferenceProcessor.cpp @@ -504,7 +504,7 @@ void ShenandoahReferenceProcessor::process_references(ShenandoahRefProcThreadLoc // set_oop_field maintains the card mark barrier as this list is constructed. if (!CompressedOops::is_null(*list)) { oop head = lrb(CompressedOops::decode_not_null(*list)); - shenandoah_assert_not_in_cset_except(&head, head, ShenandoahHeap::heap()->cancelled_gc() || !ShenandoahLoadRefBarrier); + shenandoah_assert_not_in_cset_except(&head, head, ShenandoahHeap::heap()->has_self_forwarded_objects() || ShenandoahHeap::heap()->cancelled_gc() || !ShenandoahLoadRefBarrier); oop prev = _pending_list.exchange(head); set_oop_field(p, prev); if (prev == nullptr) { @@ -577,7 +577,7 @@ void ShenandoahReferenceProcessor::process_references(ShenandoahPhaseTimings::Ph void ShenandoahReferenceProcessor::enqueue_references_locked() { // Prepend internal pending list to external pending list - shenandoah_assert_not_in_cset_except(&_pending_list, _pending_list.load_relaxed(), ShenandoahHeap::heap()->cancelled_gc() || !ShenandoahLoadRefBarrier); + shenandoah_assert_not_in_cset_except(&_pending_list, _pending_list.load_relaxed(), ShenandoahHeap::heap()->has_self_forwarded_objects() || ShenandoahHeap::heap()->cancelled_gc() || !ShenandoahLoadRefBarrier); // During reference processing, we maintain a local list of references that are identified by // _pending_list and _pending_list_tail. _pending_list_tail points to the next field of the last Reference object on @@ -604,7 +604,7 @@ void ShenandoahReferenceProcessor::enqueue_references(bool concurrent) { return; } if (!concurrent) { - // When called from mark-compact or degen-GC, the locking is done by the VMOperation, + // When called from STW mark, the locking is done by the VMOperation, enqueue_references_locked(); } else { // Heap_lock protects external pending list diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp index 8823e7e4af74..85c2b6b07979 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp @@ -66,27 +66,21 @@ void ShenandoahRegulatorThread::regulate_young_and_old_cycles() { if (request_concurrent_gc(_heap->global_generation())) { // Some of vmTestbase/metaspace tests depend on following line to count GC cycles _global_heuristics->log_trigger("%s", GCCause::to_string(GCCause::_metadata_GC_threshold)); - _global_heuristics->cancel_trigger_request(); } } else { if (_old_heuristics->should_resume_old_cycle()) { if (request_concurrent_gc(_heap->old_generation())) { - _old_heuristics->cancel_trigger_request(); - log_debug(gc)("Heuristics request to resume old collection accepted"); + log_debug(gc, thread)("Heuristics request to resume old collection accepted"); } } else if (start_old_cycle()) { - log_debug(gc)("Heuristics request for old collection accepted"); - _young_heuristics->cancel_trigger_request(); - _old_heuristics->cancel_trigger_request(); + log_debug(gc, thread)("Heuristics request for old collection accepted"); } else if (start_young_cycle()) { - log_debug(gc)("Heuristics request for young collection accepted"); - _young_heuristics->cancel_trigger_request(); + log_debug(gc, thread)("Heuristics request for young collection accepted"); } } } else if (mode == ShenandoahGenerationalControlThread::servicing_old) { if (start_young_cycle()) { - log_debug(gc)("Heuristics request to interrupt old for young collection accepted"); - _young_heuristics->cancel_trigger_request(); + log_debug(gc, thread)("Heuristics request to interrupt old for young collection accepted"); } } @@ -99,12 +93,13 @@ void ShenandoahRegulatorThread::regulate_young_and_global_cycles() { while (!should_terminate()) { SuspendibleThreadSetJoiner joiner; if (_control_thread->gc_mode() == ShenandoahGenerationalControlThread::none) { - if (start_global_cycle()) { - log_debug(gc)("Heuristics request for global collection accepted."); - _global_heuristics->cancel_trigger_request(); + if (should_start_metaspace_gc() && request_concurrent_gc(_heap->global_generation())) { + // Some of vmTestbase/metaspace tests depend on following line to count GC cycles + _global_heuristics->log_trigger("%s", GCCause::to_string(GCCause::_metadata_GC_threshold)); + } else if (_old_heuristics->should_start_gc() && request_concurrent_gc(_heap->global_generation())) { + log_debug(gc, thread)("Heuristics request for global collection accepted."); } else if (start_young_cycle()) { - log_debug(gc)("Heuristics request for young collection accepted."); - _young_heuristics->cancel_trigger_request(); + log_debug(gc, thread)("Heuristics request for young collection accepted."); } } @@ -145,10 +140,6 @@ bool ShenandoahRegulatorThread::start_young_cycle() const { return _young_heuristics->should_start_gc() && request_concurrent_gc(_heap->young_generation()); } -bool ShenandoahRegulatorThread::start_global_cycle() const { - return _global_heuristics->should_start_gc() && request_concurrent_gc(_heap->global_generation()); -} - bool ShenandoahRegulatorThread::request_concurrent_gc(ShenandoahGeneration* generation) const { double now = os::elapsedTime(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.hpp index 62a5dcce9df6..9111d6f42383 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.hpp @@ -61,7 +61,6 @@ class ShenandoahRegulatorThread: public ConcurrentGCThread { // These return true if a cycle was started. bool start_old_cycle() const; bool start_young_cycle() const; - bool start_global_cycle() const; // The generational mode can only unload classes in a global cycle. The regulator // thread itself will trigger a global cycle if metaspace is out of memory. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp index 1b7d0b75304e..b1cfa32b4051 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp @@ -178,15 +178,6 @@ void ShenandoahConcurrentRootScanner::update_tlab_stats() { } } -ShenandoahRootUpdater::ShenandoahRootUpdater(uint n_workers, ShenandoahPhaseTimings::Phase phase) : - ShenandoahRootProcessor(phase), - _vm_roots(phase), - _cld_roots(phase, n_workers, false /*heap iteration*/), - _thread_roots(phase, n_workers > 1), - _weak_roots(phase), - _code_roots(phase) { -} - ShenandoahRootAdjuster::ShenandoahRootAdjuster(uint n_workers, ShenandoahPhaseTimings::Phase phase) : ShenandoahRootProcessor(phase), _vm_roots(phase), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.hpp index 55367e706a2e..f6c1fb13819f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.hpp @@ -195,22 +195,6 @@ class ShenandoahHeapIterationRootScanner : public ShenandoahRootProcessor { void roots_do(OopClosure* cl); }; -// Update all roots at a safepoint -class ShenandoahRootUpdater : public ShenandoahRootProcessor { -private: - ShenandoahVMRoots _vm_roots; - ShenandoahClassLoaderDataRoots _cld_roots; - ShenandoahThreadRoots _thread_roots; - ShenandoahVMWeakRoots _weak_roots; - ShenandoahCodeCacheRoots _code_roots; - -public: - ShenandoahRootUpdater(uint n_workers, ShenandoahPhaseTimings::Phase phase); - - template - void roots_do(uint worker_id, IsAlive* is_alive, KeepAlive* keep_alive); -}; - // Adjuster all roots at a safepoint during full gc class ShenandoahRootAdjuster : public ShenandoahRootProcessor { private: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp index 13ce0fc20715..773b483ba72a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp @@ -211,19 +211,4 @@ void ShenandoahSTWRootScanner::roots_do(T* oops, uint worker_id) { _vm_roots.oops_do(oops, worker_id); } -template -void ShenandoahRootUpdater::roots_do(uint worker_id, IsAlive* is_alive, KeepAlive* keep_alive) { - NMethodToOopClosure nmethods_cl(keep_alive, NMethodToOopClosure::FixRelocations); - CLDToOopClosure clds(keep_alive, ClassLoaderData::_claim_strong); - - // Process light-weight/limited parallel roots then - _vm_roots.oops_do(keep_alive, worker_id); - _weak_roots.weak_oops_do(is_alive, keep_alive, worker_id); - _cld_roots.cld_do(&clds, worker_id); - - // Process heavy-weight/fully parallel roots the last - _code_roots.nmethods_do(&nmethods_cl, worker_id); - _thread_roots.oops_do(keep_alive, nullptr, worker_id); -} - #endif // SHARE_GC_SHENANDOAH_SHENANDOAHROOTPROCESSOR_INLINE_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp index 290c0f389629..de8bd8674687 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp @@ -62,11 +62,10 @@ void ShenandoahSTWMarkTask::work(uint worker_id) { _mark->finish_mark(worker_id); } -ShenandoahSTWMark::ShenandoahSTWMark(ShenandoahGeneration* generation, bool full_gc) : +ShenandoahSTWMark::ShenandoahSTWMark(ShenandoahGeneration* generation) : ShenandoahMark(generation), - _root_scanner(full_gc ? ShenandoahPhaseTimings::full_gc_mark : ShenandoahPhaseTimings::degen_gc_mark), - _terminator(ShenandoahHeap::heap()->workers()->active_workers(), task_queues()), - _full_gc(full_gc) { + _root_scanner(ShenandoahPhaseTimings::full_gc_mark), + _terminator(ShenandoahHeap::heap()->workers()->active_workers(), task_queues()) { assert(ShenandoahSafepoint::is_at_shenandoah_safepoint(), "Must be at a Shenandoah safepoint"); } @@ -134,13 +133,9 @@ void ShenandoahSTWMark::mark_roots(uint worker_id) { _root_scanner.roots_do(&init_mark, worker_id); break; } - case YOUNG: { - ShenandoahMarkRefsClosure init_mark(queue, rp, nullptr); - _root_scanner.roots_do(&init_mark, worker_id); - break; - } + case YOUNG: case OLD: - // We never exclusively mark the old generation on a safepoint. This would be encompassed + // We never exclusively mark the old or young generation on a safepoint. This would be encompassed // by a 'global' collection. Note that both GLOBAL and NON_GEN mark the entire heap, but // the GLOBAL closure is specialized for the generational mode. default: @@ -149,7 +144,7 @@ void ShenandoahSTWMark::mark_roots(uint worker_id) { } void ShenandoahSTWMark::finish_mark(uint worker_id) { - ShenandoahPhaseTimings::Phase phase = _full_gc ? ShenandoahPhaseTimings::full_gc_mark : ShenandoahPhaseTimings::degen_gc_mark; + ShenandoahPhaseTimings::Phase phase = ShenandoahPhaseTimings::full_gc_mark; ShenandoahWorkerTimingsTracker timer(phase, ShenandoahPhaseTimings::Work, worker_id); mark_loop(worker_id, &_terminator, _generation->type(), false /* not cancellable */); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.hpp index d2066ecafb9d..e3416432780c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.hpp @@ -28,7 +28,6 @@ #include "gc/shenandoah/shenandoahMark.hpp" #include "gc/shenandoah/shenandoahRootProcessor.hpp" -class ShenandoahSTWMarkTask; class ShenandoahGeneration; class ShenandoahSTWMark : public ShenandoahMark { @@ -37,9 +36,8 @@ class ShenandoahSTWMark : public ShenandoahMark { private: ShenandoahSTWRootScanner _root_scanner; TaskTerminator _terminator; - bool _full_gc; public: - ShenandoahSTWMark(ShenandoahGeneration* generation, bool full_gc); + ShenandoahSTWMark(ShenandoahGeneration* generation); void mark(); private: diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index 0affa3e4617b..a594ea70410b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -832,16 +832,6 @@ void ShenandoahScanRememberedTask::do_work(uint worker_id) { } scanner->process_region_slice(region, assignment._chunk_offset, clusters, end_of_range, &cl, false, worker_id); } -#ifdef ENABLE_REMEMBERED_SET_CANCELLATION - // This check is currently disabled to avoid crashes that occur - // when we try to cancel remembered set scanning; it should be re-enabled - // after the issues are fixed, as it would allow more prompt cancellation and - // transition to degenerated / full GCs. Note that work that has been assigned/ - // claimed above must be completed before we return here upon cancellation. - if (heap->check_cancelled_gc_and_yield(_is_concurrent)) { - return; - } -#endif } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp index 59e8db07f8e3..207656532184 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp @@ -706,8 +706,8 @@ class ShenandoahScanRemembered: public CHeapObj { _scc = new ShenandoahCardCluster(rs); // We allocate ParallelGCThreads worth even though we usually only - // use up to ConcGCThreads, because degenerate collections may employ - // ParallelGCThreads for remembered set scanning. + // use up to ConcGCThreads, because allocation stalls may push the + // number of concurrent threads up to the parallel thread count. if (ShenandoahEnableCardStats) { _card_stats = NEW_C_HEAP_ARRAY(HdrSeq*, ParallelGCThreads, mtGC); for (uint i = 0; i < ParallelGCThreads; i++) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp index 2e7e4e0ee9ad..ad3fb3e4738a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.inline.hpp @@ -49,8 +49,7 @@ void ShenandoahScanRemembered::mark_card_as_dirty(HeapWord* p) const { // that start within its range and for Object arrays that start before its range and extend into its assigned range. // // Do not CANCEL within process_clusters. It is assumed that if a worker thread accepts responsibility for processing -// a chunk of work, it will finish the work it starts. Otherwise, the chunk of work will be lost in the transition to -// degenerated execution, leading to dangling references. +// a chunk of work, it will finish the work it starts. template void ShenandoahScanRemembered::process_clusters(size_t first_cluster, size_t count, HeapWord* end_of_range, ClosureType* cl, bool use_write_table, uint worker_id) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp index 1a42e0e7b113..799a69600b05 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp @@ -35,6 +35,7 @@ ShenandoahThreadLocalData::ShenandoahThreadLocalData() : _card_table(nullptr), _gclab(nullptr), _gclab_size(0), + _has_evac_failures(false), _shenandoah_plab(nullptr), _evacuation_stats(new ShenandoahEvacuationStats()), _invisible_root(nullptr), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index 60dcd2528f7f..c5c1dbf8c8fb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -56,6 +56,7 @@ class ShenandoahThreadLocalData { // In generational mode, it is exclusive to the young generation. PLAB* _gclab; size_t _gclab_size; + bool _has_evac_failures; // Thread-local allocation buffer only used in generational mode. // Used both by mutator threads and by GC worker threads @@ -149,6 +150,18 @@ class ShenandoahThreadLocalData { data(thread)->_gclab_size = v; } + static void set_has_evac_failures(Thread* thread, bool value) { + data(thread)->_has_evac_failures = value; + } + + static bool has_evac_failures() { + return has_evac_failures(Thread::current()); + } + + static bool has_evac_failures(Thread* thread) { + return data(thread)->_has_evac_failures; + } + static void begin_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) { data(thread)->_evacuation_stats->begin_evacuation(bytes, from, to); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp b/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp index fb6a77b48ae9..ce4d961ab436 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp @@ -51,14 +51,13 @@ const char* ShenandoahGCSession::cycle_end_message(ShenandoahGenerationType type } } -ShenandoahGCSession::ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation, - bool is_degenerated, bool is_out_of_cycle) : +ShenandoahGCSession::ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation) : _heap(ShenandoahHeap::heap()), _generation(generation), _timer(_heap->gc_timer()), _tracer(_heap->tracer()) { assert(!ShenandoahGCPhase::is_current_phase_valid(), "No current GC phase"); - _heap->on_cycle_start(cause, _generation, is_degenerated, is_out_of_cycle); + _heap->on_cycle_start(cause, _generation); _timer->register_gc_start(); _tracer->report_gc_start(cause, _timer->gc_start()); _heap->trace_heap_before_gc(_tracer); @@ -192,3 +191,20 @@ ShenandoahConcurrentWorkerSession::~ShenandoahConcurrentWorkerSession() { ShenandoahParallelWorkerSession::~ShenandoahParallelWorkerSession() { _event.commit(GCId::current(), WorkerThread::worker_id(), ShenandoahPhaseTimings::phase_desc(ShenandoahGCPhase::current_phase())); } + +ShenandoahAllocationStallLogger::ShenandoahAllocationStallLogger() + : _stall_start_seconds(os::elapsedTime()) { + if (log_is_enabled(Info, gc, start)) { + ResourceMark rm; // for Thread::name() + log_info(gc, start)("Allocation Stall (%s)", Thread::current()->name()); + } +} + +ShenandoahAllocationStallLogger::~ShenandoahAllocationStallLogger() { + if (log_is_enabled(Info, gc)) { + ResourceMark rm; + log_info(gc)("Allocation Stall (%s) %.3fms", + Thread::current()->name(), + (os::elapsedTime() - _stall_start_seconds) * MILLIUNITS); + } +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp b/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp index c37d720983b5..941269611259 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp @@ -79,8 +79,7 @@ class ShenandoahGCSession : public StackObj { static const char* cycle_end_message(ShenandoahGenerationType type); public: - ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation, - bool is_degenerated = false, bool is_out_of_cycle = false); + ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation); ~ShenandoahGCSession(); }; @@ -227,8 +226,7 @@ class ShenandoahSafepoint : public AllStatic { type == VM_Operation::VMOp_ShenandoahInitUpdateRefs || type == VM_Operation::VMOp_ShenandoahFinalUpdateRefs || type == VM_Operation::VMOp_ShenandoahFinalVerify || - type == VM_Operation::VMOp_ShenandoahFullGC || - type == VM_Operation::VMOp_ShenandoahDegeneratedGC; + type == VM_Operation::VMOp_ShenandoahFullGC; } }; @@ -273,6 +271,15 @@ class ShenandoahNoUncommitMark : public StackObj { } }; +// Emit log messages for the beginning and end of an allocation stall. +class ShenandoahAllocationStallLogger : public StackObj { +public: + ShenandoahAllocationStallLogger(); + ~ShenandoahAllocationStallLogger(); +private: + const double _stall_start_seconds; +}; + // Casting a double that cannot be represented as a size_t may result in undefined behavior. // This small function checks if the given double is representable in a size_t and returns // that representation if it is. Otherwise, if the double cannot be safely cast to a size_t diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp index 62ad794edc84..f44641de968e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp @@ -24,7 +24,6 @@ #include "gc/shenandoah/shenandoahConcurrentGC.hpp" -#include "gc/shenandoah/shenandoahDegeneratedGC.hpp" #include "gc/shenandoah/shenandoahFullGC.hpp" #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" @@ -105,16 +104,6 @@ void VM_ShenandoahFullGC::doit() { _full_gc->entry_full(_gc_cause); } -VM_ShenandoahDegeneratedGC::VM_ShenandoahDegeneratedGC(ShenandoahDegenGC* gc) - : VM_ShenandoahReferenceOperation(gc->generation()), _gc(gc) { -} - -void VM_ShenandoahDegeneratedGC::doit() { - ShenandoahGCPauseMark mark(_gc_id, "Degenerated GC", SvcGCMarker::CONCURRENT); - set_active_generation(); - _gc->entry_degenerated(); -} - VM_ShenandoahInitUpdateRefs::VM_ShenandoahInitUpdateRefs(ShenandoahConcurrentGC* gc) : VM_ShenandoahOperation(gc->generation()), _gc(gc) { } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp index d9d3ca90f40b..4530175c0815 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp @@ -28,7 +28,6 @@ #include "gc/shared/gcVMOperations.hpp" class ShenandoahConcurrentGC; -class ShenandoahDegenGC; class ShenandoahFullGC; // VM_operations for the Shenandoah Collector. @@ -42,7 +41,6 @@ class ShenandoahFullGC; // - VM_ShenandoahFinalVerify: final verification at the end of the cycle // - VM_ShenandoahReferenceOperation: // - VM_ShenandoahFullGC: do full GC -// - VM_ShenandoahDegeneratedGC: do STW degenerated GC class VM_ShenandoahOperation : public VM_Operation { protected: @@ -91,15 +89,6 @@ class VM_ShenandoahFinalMarkStartEvac: public VM_ShenandoahOperation { void doit() override; }; -class VM_ShenandoahDegeneratedGC: public VM_ShenandoahReferenceOperation { - ShenandoahDegenGC* const _gc; -public: - explicit VM_ShenandoahDegeneratedGC(ShenandoahDegenGC* gc); - VM_Operation::VMOp_Type type() const override { return VMOp_ShenandoahDegeneratedGC; } - const char* name() const override { return "Shenandoah Degenerated GC"; } - void doit() override; -}; - class VM_ShenandoahFullGC : public VM_ShenandoahReferenceOperation { GCCause::Cause _gc_cause; ShenandoahFullGC* const _full_gc; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp index ddb84f09e595..8f824eab0ce6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp @@ -1196,7 +1196,7 @@ void ShenandoahVerifier::verify_after_update_refs(ShenandoahGeneration* generati _verify_liveness_disable, // no reliable liveness data anymore _verify_regions_nocset, // no cset regions, trash regions have appeared // expect generation and heap sizes to match exactly, including trash - _verify_size_exact_including_trash, + _verify_size_exact, _verify_gcstate_stable // update refs had cleaned up forwarded objects ); } @@ -1213,38 +1213,23 @@ void ShenandoahVerifier::verify_after_gc(ShenandoahGeneration* generation) { _verify_liveness_disable, // no reliable liveness data anymore _verify_regions_nocset, // no cset regions, trash regions have appeared // expect generation and heap sizes to match exactly, including trash - _verify_size_exact_including_trash, + _verify_size_exact, _verify_gcstate_stable // GC state was turned off ); } -void ShenandoahVerifier::verify_after_degenerated(ShenandoahGeneration* generation) { - verify_at_safepoint( - generation, - "After Degenerated GC", - _verify_remembered_disable, // do not verify remembered set - _verify_forwarded_none, // all objects are non-forwarded - _verify_marked_disable, // no need to check unreachable objects, end of cycle - _verify_cset_none, // no cset references - _verify_liveness_disable, // no reliable liveness data anymore - _verify_regions_notrash_nocset, // no trash, no cset - _verify_size_exact, // expect generation and heap sizes to match exactly - _verify_gcstate_stable // degenerated refs had cleaned up forwarded objects - ); -} - void ShenandoahVerifier::verify_before_fullgc(ShenandoahGeneration* generation) { verify_at_safepoint( generation, "Before Full GC", - _verify_remembered_disable, // do not verify remembered set - _verify_forwarded_allow, // can have forwarded objects - _verify_marked_disable, // do not verify marked: lots ot time wasted checking dead allocations - _verify_cset_disable, // cset might be foobared - _verify_liveness_disable, // no reliable liveness data anymore - _verify_regions_disable, // no reliable region data here - _verify_size_disable, // if we degenerate during evacuation, usage not valid: padding and deferred accounting - _verify_gcstate_disable // no reliable gcstate data + _verify_remembered_disable, // do not verify remembered set + _verify_forwarded_none, // cannot have forwarded objects + _verify_marked_disable, // do not verify marked: lots ot time wasted checking dead allocations + _verify_cset_none, // cset should be empty + _verify_liveness_disable, // no reliable liveness data anymore + _verify_regions_notrash_nocset, // no reliable region data here + _verify_size_exact, // expect generation and heap sizes to match exactly + _verify_gcstate_stable // no forwarded objects ); } @@ -1300,12 +1285,12 @@ class ShenandoahVerifyInToSpaceClosure : public BasicOopIterateClosure { "Verify Roots In To-Space", "Should be marked", __FILE__, __LINE__); } - if (heap->in_collection_set(obj)) { + if (heap->in_collection_set(obj) && !ShenandoahForwarding::is_self_forwarded(obj)) { ShenandoahAsserts::print_failure(ShenandoahAsserts::_safe_all, obj, p, nullptr, "Verify Roots In To-Space", "Should not be in collection set", __FILE__, __LINE__); } - if (ShenandoahForwarding::is_forwarded(obj)) { + if (ShenandoahForwarding::is_real_forwarded(obj)) { ShenandoahAsserts::print_failure(ShenandoahAsserts::_safe_all, obj, p, nullptr, "Verify Roots In To-Space", "Should not be forwarded", __FILE__, __LINE__); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp index 0479d5f67ce1..e35ef4c0b973 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp @@ -223,7 +223,6 @@ class ShenandoahVerifier : public CHeapObj { void verify_after_gc(ShenandoahGeneration* generation); void verify_before_fullgc(ShenandoahGeneration* generation); void verify_after_fullgc(ShenandoahGeneration* generation); - void verify_after_degenerated(ShenandoahGeneration* generation); void verify_generic(ShenandoahGeneration* generation, VerifyOption option); // Roots should only contain to-space oops diff --git a/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.cpp index 87095e87406f..5b43f64eb14b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.cpp @@ -24,6 +24,7 @@ #include "gc/shared/gc_globals.hpp" +#include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahWorkerPolicy.hpp" uint ShenandoahWorkerPolicy::calc_workers_for_init_marking() { @@ -31,11 +32,11 @@ uint ShenandoahWorkerPolicy::calc_workers_for_init_marking() { } uint ShenandoahWorkerPolicy::calc_workers_for_conc_marking() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_rs_scanning() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_final_marking() { @@ -43,27 +44,23 @@ uint ShenandoahWorkerPolicy::calc_workers_for_final_marking() { } uint ShenandoahWorkerPolicy::calc_workers_for_conc_refs_processing() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_conc_root_processing() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_conc_evac() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_fullgc() { return ParallelGCThreads; } -uint ShenandoahWorkerPolicy::calc_workers_for_stw_degenerated() { - return ParallelGCThreads; -} - uint ShenandoahWorkerPolicy::calc_workers_for_conc_update_ref() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_final_update_ref() { @@ -71,9 +68,13 @@ uint ShenandoahWorkerPolicy::calc_workers_for_final_update_ref() { } uint ShenandoahWorkerPolicy::calc_workers_for_conc_reset() { - return ConcGCThreads; + return concurrent_workers_count(); } uint ShenandoahWorkerPolicy::calc_workers_for_conc_cleanup() { - return ConcGCThreads; + return concurrent_workers_count(); +} + +uint ShenandoahWorkerPolicy::concurrent_workers_count() { + return checked_cast(ShenandoahHeap::heap()->control_thread()->concurrent_worker_count()); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.hpp b/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.hpp index 0de1cf3664b6..d5a0a4b5ac9f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahWorkerPolicy.hpp @@ -53,9 +53,6 @@ class ShenandoahWorkerPolicy : AllStatic { // Calculate workers for parallel full gc static uint calc_workers_for_fullgc(); - // Calculate workers for parallel degenerated gc - static uint calc_workers_for_stw_degenerated(); - // Calculate workers for concurrent reference update static uint calc_workers_for_conc_update_ref(); @@ -67,6 +64,9 @@ class ShenandoahWorkerPolicy : AllStatic { // Calculate workers for concurrent cleanup static uint calc_workers_for_conc_cleanup(); + +private: + static uint concurrent_workers_count(); }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHWORKERPOLICY_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 7437586f7123..38a290ec5a02 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -179,7 +179,7 @@ "GC mode to use. Among other things, this defines which " \ "barriers are in in use. Possible values are:" \ " satb - snapshot-at-the-beginning concurrent GC (three pass mark-evac-update);" \ - " passive - stop the world GC only (either degenerated or full);" \ + " passive - stop the world full GC only (diagnostic mode);" \ " generational - generational concurrent GC") \ \ product(ccstr, ShenandoahGCHeuristics, "adaptive", \ @@ -233,7 +233,7 @@ "heap or generation, trigger a learning cycle if we are " \ "in learning mode. Learning mode happens during initialization " \ "and following a drastic state change, such as following a " \ - "degenerated or Full GC cycle. In percents of soft max " \ + "System.GC call or a Full GC cycle. In percents of soft max " \ "heap size.") \ range(0,100) \ \ @@ -424,20 +424,13 @@ \ product(uintx, ShenandoahCriticalFreeThreshold, 1, EXPERIMENTAL, \ "How much of the heap needs to be free after recovery cycles, " \ - "either Degenerated or Full GC to be claimed successful. If this "\ - "much space is not available, next recovery step would be " \ - "triggered.") \ + "for a Full GC to be claimed successful. If this much space is " \ + "not available, next recovery step would be triggered.") \ range(0, 100) \ \ - product(bool, ShenandoahDegeneratedGC, true, DIAGNOSTIC, \ - "Enable Degenerated GC as the graceful degradation step. " \ - "Disabling this option leads to degradation to Full GC instead. " \ - "When running in passive mode, this can be toggled to measure " \ - "either Degenerated GC or Full GC costs.") \ - \ - product(uintx, ShenandoahFullGCThreshold, 3, EXPERIMENTAL, \ - "How many back-to-back Degenerated GCs should happen before " \ - "going to a Full GC.") \ + product(uintx, ShenandoahFullGCThreshold, 2, EXPERIMENTAL, \ + "How many consecutive concurrent GCs a thread will wait for " \ + "before going to a Full GC.") \ \ product(uintx, ShenandoahNoProgressThreshold, 5, EXPERIMENTAL, \ "After this number of consecutive Full GCs fail to make " \ diff --git a/src/hotspot/share/gc/z/jvmFlagConstraintsZ.cpp b/src/hotspot/share/gc/z/jvmFlagConstraintsZ.cpp new file mode 100644 index 000000000000..65ec8564da88 --- /dev/null +++ b/src/hotspot/share/gc/z/jvmFlagConstraintsZ.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "gc/shared/gc_globals.hpp" +#include "gc/z/jvmFlagConstraintsZ.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" +#include "runtime/flags/jvmFlag.hpp" + +#include + +JVMFlag::Error ZGCIntensityConstraintFunc(double value, bool verbose) { + if (!UseZGC) { + // There is no constraint when not using ZGC. + return JVMFlag::Error::SUCCESS; + } + + if (!g_isfinite(value)) { + JVMFlag::printError(verbose, + "ZGCIntensityConstraint (%f) must be finite.\n", + value); + return JVMFlag::Error::VIOLATES_CONSTRAINT; + } + + if (ZAdaptiveHeapSizing && value == 0.0) { + JVMFlag::printError(verbose, + "Cannot dynamically switch ZGCIntensity off.\n"); + return JVMFlag::Error::VIOLATES_CONSTRAINT; + } + + if (!ZAdaptiveHeapSizing && value != 0.0) { + JVMFlag::printError(verbose, + "Cannot dynamically switch ZGCIntensity on.\n"); + return JVMFlag::Error::VIOLATES_CONSTRAINT; + } + + return JVMFlag::Error::SUCCESS; +} diff --git a/src/hotspot/share/gc/z/zRuntimeWorkers.cpp b/src/hotspot/share/gc/z/jvmFlagConstraintsZ.hpp similarity index 56% rename from src/hotspot/share/gc/z/zRuntimeWorkers.cpp rename to src/hotspot/share/gc/z/jvmFlagConstraintsZ.hpp index c8c4cde9ba0f..b3d8f1574078 100644 --- a/src/hotspot/share/gc/z/zRuntimeWorkers.cpp +++ b/src/hotspot/share/gc/z/jvmFlagConstraintsZ.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -19,30 +19,22 @@ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. + * */ -#include "gc/shared/gc_globals.hpp" -#include "gc/shared/gcLogPrecious.hpp" -#include "gc/z/zRuntimeWorkers.hpp" -#include "runtime/java.hpp" +#ifndef SHARE_GC_Z_JVMFLAGCONSTRAINTSZ_HPP +#define SHARE_GC_Z_JVMFLAGCONSTRAINTSZ_HPP -ZRuntimeWorkers::ZRuntimeWorkers() - : _workers("RuntimeWorker", ParallelGCThreads) { +#include "runtime/flags/jvmFlag.hpp" +#include "utilities/globalDefinitions.hpp" - log_info_p(gc, init)("Runtime Workers: %u", _workers.max_workers()); +#define Z_GC_CONSTRAINTS(f) \ + \ + /* Adaptive Heap Sizing Constraints */ \ + f(double, ZGCIntensityConstraintFunc) \ + /* */ - // Initialize worker threads - _workers.initialize_workers(); - _workers.set_active_workers(_workers.max_workers()); - if (_workers.active_workers() != _workers.max_workers()) { - vm_exit_during_initialization("Failed to create ZRuntimeWorkers"); - } -} +Z_GC_CONSTRAINTS(DECLARE_CONSTRAINT) -WorkerThreads* ZRuntimeWorkers::workers() { - return &_workers; -} -void ZRuntimeWorkers::threads_do(ThreadClosure* tc) const { - _workers.threads_do(tc); -} +#endif // SHARE_GC_Z_JVMFLAGCONSTRAINTSZ_HPP diff --git a/src/hotspot/share/gc/z/vmStructs_z.hpp b/src/hotspot/share/gc/z/vmStructs_z.hpp index de32b964a512..d8d4818df714 100644 --- a/src/hotspot/share/gc/z/vmStructs_z.hpp +++ b/src/hotspot/share/gc/z/vmStructs_z.hpp @@ -90,7 +90,7 @@ typedef ZValue ZPerNUMAZPartition; nonstatic_field(ZPage, _virtual, const ZVirtualMemory) \ volatile_nonstatic_field(ZPage, _top, zoffset_end) \ \ - nonstatic_field(ZPageAllocator, _max_capacity, const size_t) \ + nonstatic_field(ZPageAllocator, _static_max_capacity, const size_t) \ nonstatic_field(ZPageAllocator, _partitions, ZPerNUMAZPartition) \ \ static_field(ZNUMA, _count, uint32_t) \ diff --git a/src/hotspot/share/gc/z/zAdaptiveHeap.cpp b/src/hotspot/share/gc/z/zAdaptiveHeap.cpp new file mode 100644 index 000000000000..cea3549521d9 --- /dev/null +++ b/src/hotspot/share/gc/z/zAdaptiveHeap.cpp @@ -0,0 +1,1246 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/shared/gc_globals.hpp" +#include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" +#include "gc/z/zDriver.hpp" +#include "gc/z/zGlobals.hpp" +#include "gc/z/zHeap.inline.hpp" +#include "gc/z/zLock.inline.hpp" +#include "gc/z/zStat.hpp" +#include "logging/log.hpp" +#include "runtime/atomicAccess.hpp" +#include "runtime/globals_extension.hpp" +#include "runtime/os.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" + +#include +#include + +bool ZAdaptiveHeap::_explicit_max_capacity; +bool ZAdaptiveHeap::_initialized; +bool ZAdaptiveHeap::_initialized_generation_data; +ZAdaptiveHeap::ZIntensitySmoother ZAdaptiveHeap::_gc_intensities; + +Atomic ZAdaptiveHeap::_young_to_old_gc_time(0.0); +Atomic ZAdaptiveHeap::_accumulated_young_gc_time(0.0); +Atomic ZAdaptiveHeap::_accumulated_mem_worker_time(0.0); +Atomic ZAdaptiveHeap::_avg_mem_worker_cpu_overhead(0.0); +ZAdaptiveHeap::ZGenerationOverhead ZAdaptiveHeap::_young_data; +ZAdaptiveHeap::ZGenerationOverhead ZAdaptiveHeap::_old_data; +Atomic ZAdaptiveHeap::_initial_young_worker_cap; + +// Calculate progression from 0 to 1 going down from a less critical availability to a more critical availability +static double calculate_progression(double availability, double from, double to) { + return 1.0 - (availability - to) / (from - to); +} + +void ZAdaptiveHeap::ZIntensitySmoother::initialize() { + _lock = new ZLock(); +} + +double ZAdaptiveHeap::ZIntensitySmoother::record_and_smooth_gc_intensity(double scaled_gc_intensity) { + precond(_lock != nullptr); + ZLocker locker(_lock); + _gc_intensities.add(scaled_gc_intensity); + return MAX2(_gc_intensities.avg(), scaled_gc_intensity); +} + +void ZAdaptiveHeap::initialize(bool explicit_max_capacity) { + precond(!_initialized); + _explicit_max_capacity = explicit_max_capacity; + if (ZAdaptiveHeapSizing) { + _gc_intensities.initialize(); + } + _initialized = true; +} + +void ZAdaptiveHeap::initialize_generation_data() { + precond(_initialized); + precond(!_initialized_generation_data); + + { // Setup initial machine_elapsed_system_cpu_time + ZSystemCpuTime machine_system_time_now; + const bool has_machine_system_time_now = machine_elapsed_system_cpu_time(machine_system_time_now); + if (has_machine_system_time_now) { + _young_data._last_machine_system_time = machine_system_time_now._elapsed_time; + _old_data._last_machine_system_time = machine_system_time_now._elapsed_time; + _young_data._machine_processor_count = machine_system_time_now._processor_count; + _old_data._machine_processor_count = machine_system_time_now._processor_count; + } + _young_data._has_last_machine_system_time = has_machine_system_time_now; + _old_data._has_last_machine_system_time = has_machine_system_time_now; + } + + { // Setup initial container_elapsed_system_cpu_time + ZSystemCpuTime container_system_time_now; + const bool has_container_system_time = container_elapsed_system_cpu_time(container_system_time_now); + if (has_container_system_time) { + _young_data._last_container_system_time = container_system_time_now._elapsed_time; + _old_data._last_container_system_time = container_system_time_now._elapsed_time; + _young_data._container_processor_count = container_system_time_now._processor_count; + _old_data._container_processor_count = container_system_time_now._processor_count; + } + _young_data._has_last_container_system_time = has_container_system_time; + _old_data._has_last_container_system_time = has_container_system_time; + } + + { // Setup initial os::elapsed_process_cpu_time + double process_time_now; + const bool has_process_time = os::elapsed_process_cpu_time(process_time_now); + if (has_process_time) { + _young_data._last_process_time = process_time_now; + _old_data._last_process_time = process_time_now; + } + _young_data._has_last_process_time = has_process_time; + _old_data._has_last_process_time = has_process_time; + } + + { // Setup initial os::elapsedTime + const double time_now = os::elapsedTime(); + _young_data._last_time = time_now; + _old_data._last_time = time_now; + } + + _initialized_generation_data = true; +} + +ZMachineMemoryInfo ZAdaptiveHeap::machine_memory_info() { + ZMachineMemoryInfo info = {}; + + // Used to "latch" on to whatever validity we initially observe and make sure + // that the same validity is continued to be observed moving forward. Observing + // different validity during execution can lead to really bad heuristics. + struct ZBooleanStabilityRAII { + ZMachineMemoryInfo& _info; + ZBooleanStabilityRAII(ZMachineMemoryInfo& info) : _info(info) {} + ~ZBooleanStabilityRAII() { + static bool is_valid = _info._is_valid; + if (_info._is_valid != is_valid) { + log_warning(gc)("The data source for machine_memory_info is unstable (expected _is_valid to be %s, got %s). " + "This behavior is unsupported for Adaptive Heap Sizing. Please run without Adaptive Heap Sizing using -XX:-ZAdaptiveHeapSizing/-XX:ZGCIntensity=0", + BOOL_TO_STR(is_valid), BOOL_TO_STR(_info._is_valid)); + os::exit(1); + } + } + } is_valid_stability(info); + + pd_machine_memory_info(info); + return info; +} + +physical_memory_size_type ZAdaptiveHeap::machine_physical_memory() { + return machine_memory_info()._physical_memory; +} + +double ZAdaptiveHeap::young_to_old_gc_time() { + return _young_to_old_gc_time.load_relaxed(); +} + +uint ZAdaptiveHeap::initial_young_worker_cap() { + uint capacity = _initial_young_worker_cap.load_relaxed(); + if (capacity == 0) { + // Not yet set; use one - there are barely any objects early on anyway + return 1; + } + + return capacity; +} + +// For large machines we estimate their wait probability as if they have 128 +// processors available. This means that for machines with more than 128 +// available processors we may overestimate the impact of loads above 75%. +static constexpr int ErlangCMaxProcessors = 128; + +static double* generate_factorials() { + static double result[ErlangCMaxProcessors + 1]; + result[0] = 1.0; + for (uint64_t i = 1; i <= ErlangCMaxProcessors; ++i) { + result[i] = result[i - 1] * (double)i; + } + return result; +} + +// Probability to have to wait in a queue given c servers and rho queue utilization +static double erlang_c(int c, double rho) { + // Cap c at ErlangCMaxProcessors to avoid problems with double precision. + c = MIN2(c, ErlangCMaxProcessors); + + static double* const factorials = generate_factorials(); + + const double unutilized_reciprocal = 1.0 / (1.0 - rho); + const double offered_load = rho * c; + const double nominator = unutilized_reciprocal * pow(offered_load, double(c)) / factorials[c]; + + double sum = 0.0; + for (int k = 0; k < c; ++k) { + sum += pow(offered_load, double(k)) / factorials[k]; + } + + const double denominator = nominator + sum; + + return nominator / denominator; +} + +static double cpu_latency_factor(int c, double rho, double unluckyness) { + const double prob_join_queue = erlang_c(c, rho); + + // P99 is approximately 100x more likely to join the queue + const double p99_prob_join_queue = MIN2(prob_join_queue * unluckyness, 1.0); + const double p99_latency_factor = p99_prob_join_queue / (1.0 - rho); + + return 1.0 + p99_latency_factor; +} + +ZMemoryPressureMetrics ZAdaptiveHeap::memory_pressure_metrics() { + precond(ZAdaptiveHeapSizing); + const double unscaled_gc_intensity = clamp(AtomicAccess::load(&ZGCIntensity), MinZGCIntensity, MaxZGCIntensity); + + const ZMachineMemoryInfo machine_memory_info = ZAdaptiveHeap::machine_memory_info(); + + const physical_memory_size_type machine_max_memory = machine_memory_info._physical_memory; + + const physical_memory_size_type machine_used_memory = machine_memory_info._is_valid + ? machine_memory_info._physical_memory - machine_memory_info._available_memory + : os::rss(); // Approximation for faulty os + + // The concerning threshold is after which memory utilization we start trying + // harder to keep the memory down. There are multiple reasons for letting the GC + // run hotter: + // 1) We want to maintain some headroom on the machine so that we can deal with + // spikes without getting allocation stalls. + // 2) It's good to let the OS keep some file system cache memory around + // 3) On systems that compress used memory, using compressed memory is not a + // free lunch as it leads to page faults that compress and decompress memory. + // This is extra painful for a tracing GC to traverse. + const double machine_compression_rate = machine_memory_compression_ratio(machine_used_memory, machine_max_memory); + + // When the machine memory usage is fluctuating and isn't stable, it's good to + // increase the headroom to the memory limits, otherwise there is a higher risk + // of an allocation stall. + const double machine_memory_instability = ZStatSystemMemoryUsage::machine_memory_stability(); + + const double memory_uncertainty = MAX2(machine_memory_instability, machine_compression_rate); + const double machine_concerning_threshold = MIN2(ZMemoryConcerningThreshold + memory_uncertainty, 1.0); + + const double machine_concerning_vs_high_diff = ZMemoryConcerningThreshold - ZMemoryHighThreshold; + const double machine_high_threshold = machine_concerning_threshold - machine_concerning_vs_high_diff; + + const double machine_critical_threshold = ZMemoryCriticalThreshold; + + const double far_avoid = 1.0 - ZMemoryConcerningThreshold; + const double medium_avoid = 1.0 - ZMemoryHighThreshold; + const double near_avoid = 1.0 - ZMemoryCriticalThreshold; + + physical_memory_size_type container_max_memory; + physical_memory_size_type container_used_memory; + double container_concerning_threshold; + double container_high_threshold; + double container_critical_threshold; + + const bool can_read_container_memory = os::is_containerized() && os::Container::used_memory(container_used_memory); + bool has_container_limit = false; + if (can_read_container_memory) { + physical_memory_size_type container_critical_memory; + + // Allocation stalls at critical levels + if (os::Container::memory_limit(container_max_memory)) { + has_container_limit = true; + } else { + container_max_memory = machine_max_memory; + } + + // Exponential increase in pressure up to critical + physical_memory_size_type container_high_memory; + if (os::Container::memory_throttle_limit(container_high_memory)) { + container_max_memory = MIN2(container_max_memory, container_high_memory); + container_critical_memory = container_max_memory; + container_high_memory = physical_memory_size_type(MIN2(container_high_memory, container_critical_memory) * (medium_avoid / near_avoid)); + has_container_limit = true; + } else { + container_critical_memory = physical_memory_size_type(container_max_memory * near_avoid); + container_high_memory = physical_memory_size_type(container_critical_memory * (medium_avoid / near_avoid)); + } + + // Linear increase in pressure up to intensity squared + physical_memory_size_type container_min_memory; + if (os::Container::memory_soft_limit(container_min_memory)) { + container_min_memory = MIN2(container_min_memory, physical_memory_size_type(container_high_memory * (far_avoid / medium_avoid))); + has_container_limit = true; + } else { + container_min_memory = physical_memory_size_type(container_high_memory * (far_avoid / medium_avoid)); + } + + const auto memory_threshold = [](physical_memory_size_type used_memory, physical_memory_size_type max_memory) { + return max_memory == 0 ? 1.0 : 1.0 - (double)used_memory / (double)max_memory; + }; + + container_critical_threshold = memory_threshold(container_critical_memory, container_max_memory); + container_high_threshold = memory_threshold(container_high_memory, container_max_memory); + container_concerning_threshold = memory_threshold(container_min_memory, container_max_memory); + + // Adjust the threshold by how unstable the memory usage in the system is + const double container_memory_instability = ZStatSystemMemoryUsage::container_memory_stability(); + const double instability_adjustment = MIN2(container_memory_instability, 1.0 - container_concerning_threshold); + container_concerning_threshold += instability_adjustment; + container_high_threshold += instability_adjustment; + } + + const bool is_containerized = can_read_container_memory && has_container_limit; + + if (!is_containerized) { + container_max_memory = machine_max_memory; + container_used_memory = machine_used_memory; + container_concerning_threshold = machine_concerning_threshold; + container_high_threshold = machine_high_threshold; + container_critical_threshold = machine_critical_threshold; + } + + return { + unscaled_gc_intensity, + is_containerized, + { + machine_used_memory, + machine_max_memory, + machine_concerning_threshold, + machine_high_threshold, + machine_critical_threshold + }, + { + container_used_memory, + container_max_memory, + container_concerning_threshold, + container_high_threshold, + container_critical_threshold + } + }; +} + +static double system_memory_pressure(const ZSystemMemoryPressureMetrics& metrics, double unscaled_gc_intensity) { + const physical_memory_size_type available_memory = metrics.available_memory(); + + // The remaining memory reserve of the machine + const double availability = metrics.available_fraction(); + + // A number indicating how much the memory pressure should grow as the + // memory unavailability grows + const double pressure_rate = MAX2(unscaled_gc_intensity, 2.0); + + const double concerning = metrics._concerning_threshold; + const double high = metrics._high_threshold; + const double critical = metrics._critical_threshold; + + if (availability < high) { + // When memory pressure is "high", we exponentially scale up memory pressure, + // from the already "high" pressure induced by "concerning" memory pressure. + const double progression = calculate_progression(availability, high, critical); + const double exponent = 1.0 + progression; + + // Returns a value between pressure_rate and pressure_rate^3 (the exact exponent + // depends on the values of high and critical, but will not go above 3) + return pow(pressure_rate, exponent); + } + + if (availability < concerning) { + // When memory pressure is "concerning", we quadratically scale up memory + // pressure to the "high" pressure (i.e., unscaled GC intensity). + const double progression = calculate_progression(availability, concerning, high); + const double progression_squared = progression * progression; + const double pressure_factor = (pressure_rate - 1.0) * progression_squared; + + // Returns a value between 1 and pressure_rate + return 1.0 + pressure_factor; + } + + return 1.0; +} + +double ZAdaptiveHeap::compute_memory_pressure(const ZMemoryPressureMetrics& metrics) { + const double machine_mem_pressure = system_memory_pressure(metrics._machine, metrics._unscaled_gc_intensity); + + if (!metrics._is_containerized) { + return machine_mem_pressure; + } + + const double container_mem_pressure = system_memory_pressure(metrics._container, metrics._unscaled_gc_intensity); + return MAX2(machine_mem_pressure, container_mem_pressure); +} + +static bool is_system_memory_pressure_concerning(const ZSystemMemoryPressureMetrics& metrics) { + const double availability = metrics.available_fraction(); + + return availability < metrics._concerning_threshold; +} + +bool ZAdaptiveHeap::is_memory_pressure_concerning(const ZMemoryPressureMetrics& metrics) { + if (is_system_memory_pressure_concerning(metrics._machine)) { + return true; + } + + if (metrics._is_containerized) { + return is_system_memory_pressure_concerning(metrics._container); + } + + return false; +} + +static bool is_system_memory_pressure_high(const ZSystemMemoryPressureMetrics& metrics) { + const double availability = metrics.available_fraction(); + + return availability < metrics._high_threshold; +} + +bool ZAdaptiveHeap::is_memory_pressure_high(const ZMemoryPressureMetrics& metrics) { + if (is_system_memory_pressure_high(metrics._machine)) { + return true; + } + + if (metrics._is_containerized) { + return is_system_memory_pressure_high(metrics._container); + } + + return false; +} + +static bool is_system_memory_pressure_critical(const ZSystemMemoryPressureMetrics& metrics) { + const double availability = metrics.available_fraction(); + + return availability < metrics._critical_threshold; +} + +bool ZAdaptiveHeap::is_memory_pressure_critical(const ZMemoryPressureMetrics& metrics) { + if (is_system_memory_pressure_critical(metrics._machine)) { + return true; + } + + if (metrics._is_containerized) { + return is_system_memory_pressure_critical(metrics._container); + } + + return false; +} + +void ZAdaptiveHeap::sample_generation_data(ZGenerationOverhead& generation_data) { + + { // Sample machine_elapsed_system_cpu_time + ZSystemCpuTime machine_system_time_now; + const bool has_machine_system_time = machine_elapsed_system_cpu_time(machine_system_time_now); + if (has_machine_system_time) { + if (generation_data._has_last_machine_system_time) { + const double machine_system_time_last = generation_data._last_machine_system_time; + const double machine_system_time = machine_system_time_now._elapsed_time - machine_system_time_last; + generation_data._machine_system_times.add(machine_system_time); + } + + generation_data._last_machine_system_time = machine_system_time_now._elapsed_time; + generation_data._machine_processor_count = machine_system_time_now._processor_count; + } + generation_data._has_last_machine_system_time = has_machine_system_time; + } + + { // Sample container_elapsed_system_cpu_time + ZSystemCpuTime container_system_time_now; + const bool has_container_system_time = container_elapsed_system_cpu_time(container_system_time_now); + if (has_container_system_time) { + if (generation_data._has_last_container_system_time) { + const double container_system_time_last = generation_data._last_container_system_time; + const double container_system_time = container_system_time_now._elapsed_time - container_system_time_last; + generation_data._container_system_times.add(container_system_time); + } + + generation_data._last_container_system_time = container_system_time_now._elapsed_time; + generation_data._container_processor_count = container_system_time_now._processor_count; + } + generation_data._has_last_container_system_time = has_container_system_time; + } + + { // Sample os::elapsed_process_cpu_time + double process_time_now; + const bool has_process_time = os::elapsed_process_cpu_time(process_time_now); + if (has_process_time) { + if (generation_data._has_last_process_time) { + const double process_time_last = generation_data._last_process_time; + const double process_time = process_time_now - process_time_last; + generation_data._process_times.add(process_time); + } + + generation_data._last_process_time = process_time_now; + } + generation_data._has_last_process_time = has_process_time; + } + + { // Sample os::elapsedTime + const double time_now = os::elapsedTime(); + const double time_last = generation_data._last_time; + const double time_since_last = time_now - time_last; + generation_data._last_time = time_now; + generation_data._gc_times_since_last.add(time_since_last); + } +} + +ZCpuPressureMetrics ZAdaptiveHeap::cpu_pressure_metrics(ZGenerationId generation) { + precond(_initialized_generation_data); + const bool is_young = generation == ZGenerationId::young; + ZGenerationOverhead& generation_data = is_young ? _young_data : _old_data; + + // Time metrics + // Note that the system time might have poor accuracy early on; it typically + // has 100 ms granularity. So take it with a large grain of salt early on... + sample_generation_data(generation_data); + + // Processor metrics + const double machine_ncpus = generation_data._has_last_machine_system_time + ? generation_data._machine_processor_count + : double(os::processor_count()); + const bool has_container_cpu_metrics = generation_data._has_last_container_system_time; + const double container_ncpus = generation_data._container_processor_count; + const bool has_container_cpu_capacity_limit = + has_container_cpu_metrics && + container_ncpus < double(machine_ncpus); + + // Processors used by this process + const double ncpus = has_container_cpu_metrics ? container_ncpus : double(machine_ncpus); + + // Cycle metrics + ZStatCycleStats cycle_stats = ZGeneration::generation(generation)->stat_cycle()->stats(); + const double gc_time = cycle_stats._last_total_vtime + (is_young ? 0.0 : _accumulated_young_gc_time.load_relaxed()); + generation_data._gc_times.add(gc_time); + const double mem_worker_time = is_young ? ZStatMemoryWorkers::get_and_reset_vtime() : _accumulated_mem_worker_time.load_relaxed(); + generation_data._mem_worker_times.add(mem_worker_time); + + const double avg_gc_time = generation_data._gc_times.avg(); + const double avg_time_since_last = generation_data._gc_times_since_last.avg(); + const double avg_machine_system_time = generation_data._machine_system_times.avg(); + const double avg_container_system_time = generation_data._container_system_times.avg(); + + // Process times + const double process_time = generation_data._process_times.last(); + const double avg_process_time = generation_data._process_times.avg(); + const double avg_generation_gc_cpu_overhead = percent_of(avg_gc_time, avg_process_time) / 100.0; + const double generation_gc_cpu_overhead = percent_of(gc_time, process_time) / 100.0; + + const double avg_machine_process_cpu_load = clamp((avg_process_time / avg_time_since_last) / machine_ncpus, 0.0, 1.0); + const double avg_machine_system_cpu_load = clamp((avg_machine_system_time / avg_time_since_last) / machine_ncpus, 0.0, 1.0); + const double avg_container_process_cpu_load = has_container_cpu_metrics + ? clamp((avg_process_time / avg_time_since_last) / ncpus, 0.0, 1.0) + : avg_machine_process_cpu_load; + const double avg_container_system_cpu_load = has_container_cpu_metrics + ? clamp((avg_container_system_time / avg_time_since_last) / ncpus, 0.0, 1.0) + : avg_machine_system_cpu_load; + + // Account for the overhead of old generation collections when evaluating + // the heap efficiency for young generation collections. + const double avg_total_gc_cpu_overhead = MIN2(avg_generation_gc_cpu_overhead / (is_young ? _young_to_old_gc_time.load_relaxed() : 1.0), 1.0); + + const double avg_mem_worker_time = generation_data._mem_worker_times.avg(); + const double avg_mem_worker_cpu_overhead = percent_of(avg_mem_worker_time, avg_gc_time); + + return { + has_container_cpu_metrics, + has_container_cpu_capacity_limit, + generation_gc_cpu_overhead, + avg_generation_gc_cpu_overhead, + avg_total_gc_cpu_overhead, + avg_time_since_last, + avg_process_time, + gc_time, + mem_worker_time, + avg_mem_worker_cpu_overhead, + { + machine_ncpus, + avg_machine_process_cpu_load, + avg_machine_system_cpu_load + }, + { + ncpus, + avg_container_process_cpu_load, + avg_container_system_cpu_load + } + }; +} + +static double mem_urgency_scaled_cpu_pressure(const ZSystemMemoryPressureMetrics& mem_metrics, double cpu_pressure) { + if (cpu_pressure >= 1.0) { + return cpu_pressure; + } + + // As memory pressure gets high, heap must be pressed down + if (is_system_memory_pressure_high(mem_metrics)) { + return 1.0; + } + + // As memory pressure gets higher, heap must be pressed down + if (!is_system_memory_pressure_concerning(mem_metrics)) { + return cpu_pressure; + } + + // Calculate concerning progression towards high + const double availability = mem_metrics.available_fraction(); + const double progression = calculate_progression(availability, mem_metrics._concerning_threshold, mem_metrics._high_threshold); + + // Scale back to 1 as we approach high mem pressure + return cpu_pressure + (1.0 - cpu_pressure) * progression; +} + +static double compute_cpu_vs_memory_pressure(const ZSystemMemoryPressureMetrics& mem_metrics, const ZSystemCpuPressureMetrics& cpu_metrics, physical_memory_size_type process_used_memory) { + const double process_memory_usage_ratio = clamp(double(process_used_memory) / double(mem_metrics._used_memory), 0.0, 1.0); + + const double process_cpu_usage_ratio = cpu_metrics._avg_process_load / cpu_metrics._avg_system_load; + + // The GC intensity is scaled by the relationship of how many of the system's + // used bytes belong to this process compared to how many of the used system + // CPU ticks belong to this process. For a single application deployment this + // has effectively no effect, while for a multi process deployment, processes + // that are unproportionately memory bloated compared to other processes will + // rebalance themselves better to provide more memory for other processes. + const double process_cpu_pressure = process_memory_usage_ratio - process_cpu_usage_ratio; + + // The GC intensity is scaled by what portion of system CPU resources are being + // used. As CPU utilization of the machine gets higher, there will be more + // fighting between mutator threads for CPU time, affecting latencies. + // This concern is ignored here - this heuristic is purely reasoning about + // the CPU vs memory resource availabilities. A separate cpu_vs_latency + // calculation estimates the latency impact of too high CPU, and adjusts. + const double system_cpu_usage = cpu_metrics._avg_system_load; + const double system_memory_usage = mem_metrics.used_fraction(); + const double system_cpu_pressure = system_memory_usage - system_cpu_usage; + + // Balance the forces of resource share imbalance across processes with the + // forces of system level resource usage imbalance. + const double cpu_vs_memory_pressure = 1.0 + clamp(process_cpu_pressure + system_cpu_pressure, -0.2, 2.0); + + // Make sure that as memory availability drops, memory pressure starts to + // dominate the overall GC intensity; without memory the JVM dies. + return mem_urgency_scaled_cpu_pressure(mem_metrics, cpu_vs_memory_pressure); +} + +static double compute_cpu_vs_memory_pressure(const ZMemoryPressureMetrics& mem_metrics, const ZCpuPressureMetrics& cpu_metrics, physical_memory_size_type process_used_memory) { + const double machine_cpu_pressure = compute_cpu_vs_memory_pressure(mem_metrics._machine, cpu_metrics._machine, process_used_memory); + + if (!mem_metrics._is_containerized && !cpu_metrics._has_container_cpu_capacity_limit) { + return machine_cpu_pressure; + } + + const double container_cpu_pressure = compute_cpu_vs_memory_pressure(mem_metrics._container, cpu_metrics._container, process_used_memory); + return ::sqrt(container_cpu_pressure * machine_cpu_pressure); +} + +static double compute_cpu_vs_latency_pressure(const ZSystemCpuPressureMetrics& machine_cpu_metrics) { + // Approximate latency risks on the machine level using an M/M/c queue system. + // This allows us to calculate by what factor tail latency will be affected by + // the CPU pressure. Rather than looking at the current CPU pressure, we look + // ahead a bit so we deal with latency problems proactively - before they arise, + // instead of post mortem. + // This type of M/M/c calculations make sense on a system level, but for the + // container level, things work differently, and CPU vs memory is considered + // purely as a resource balancing exercise, which still helps latency as well. + double rho = MIN2(machine_cpu_metrics._avg_system_load + 0.05, 0.99); + const int processor_count = MAX2(1, int(machine_cpu_metrics._processor_count)); + const double p99_response_time_scaling = cpu_latency_factor(processor_count, rho, 100.0); + return 1.0 / MIN2(p99_response_time_scaling, 5.0); +} + +static double compute_cpu_vs_latency_pressure(const ZMemoryPressureMetrics& mem_metrics, const ZCpuPressureMetrics& cpu_metrics) { + const double machine_cpu_vs_latency_pressure = compute_cpu_vs_latency_pressure(cpu_metrics._machine); + + // Make sure that as machine memory availability drops, memory pressure starts to + // dominate the overall GC intensity; without memory the JVM dies. + const double machine_scaled_pressure = mem_urgency_scaled_cpu_pressure(mem_metrics._machine, machine_cpu_vs_latency_pressure); + + if (!mem_metrics._is_containerized && !cpu_metrics._has_container_cpu_capacity_limit) { + return machine_scaled_pressure; + } + + // Make sure that as container availability drops, memory pressure starts to + // dominate the overall GC intensity; without memory the JVM dies. + // Note that using the machine CPU vs latency pressure here is intentional; + // the probability of getting placed in a run queue is on machine level, + // not on the container level. + const double container_scaled_pressure = mem_urgency_scaled_cpu_pressure(mem_metrics._container, machine_cpu_vs_latency_pressure); + + return sqrt(machine_scaled_pressure * container_scaled_pressure); +} + +ZResourcePressure ZAdaptiveHeap::compute_pressures(const ZMemoryPressureMetrics& mem_metrics, const ZCpuPressureMetrics& cpu_metrics, size_t projected_process_used_memory) { + precond(ZAdaptiveHeapSizing); + + const double mem_pressure = compute_memory_pressure(mem_metrics); + const double cpu_vs_memory_pressure = compute_cpu_vs_memory_pressure(mem_metrics, cpu_metrics, projected_process_used_memory); + const double cpu_vs_latency_pressure = compute_cpu_vs_latency_pressure(mem_metrics, cpu_metrics); + + // Use sqrt to compute geometric mean + const double cpu_pressure = ::sqrt(cpu_vs_memory_pressure * cpu_vs_latency_pressure); + + // The combined forces of memory vs CPU. The one force... TO RULE THEM ALL!! + const double resource_pressure = mem_pressure * cpu_pressure; + + const double scaled_gc_intensity = mem_metrics._unscaled_gc_intensity * resource_pressure; + + return { + scaled_gc_intensity, + cpu_pressure, + mem_pressure, + cpu_vs_memory_pressure, + cpu_vs_latency_pressure + }; +} + +double ZAdaptiveHeap::smoothed_gc_intensity(double scaled_gc_intensity) { + precond(_initialized); + return _gc_intensities.record_and_smooth_gc_intensity(scaled_gc_intensity); +} + +bool ZAdaptiveHeap::machine_elapsed_system_cpu_time(ZSystemCpuTime& value) { + return pd_machine_elapsed_system_cpu_time(value); +} + +bool ZAdaptiveHeap::container_elapsed_system_cpu_time(ZSystemCpuTime& value) { + if (os::is_containerized()) { + return pd_container_elapsed_system_cpu_time(value); + } + + return false; +} + +double ZAdaptiveHeap::machine_memory_compression_ratio(physical_memory_size_type machine_used_memory, + physical_memory_size_type machine_max_memory) { + physical_memory_size_type machine_compressed_memory; + if (pd_machine_compressed_memory(machine_compressed_memory)) { + machine_compressed_memory = MIN2(machine_compressed_memory, machine_used_memory); + } else { + machine_compressed_memory = 0; + } + + return double(machine_compressed_memory) / double(machine_max_memory); +} + +static double compute_target_gc_interval(const ZSystemMemoryPressureMetrics& mem_metrics, + double gc_intensity, + size_t process_used_memory) { + // As memory pressure gets high, heap must be pressed down + if (is_system_memory_pressure_critical(mem_metrics)) { + return 0.0; + } + + // High GC frequencies lead to extra overheads such as barrier storms and + // frequent cache invalidation of the heap. + // Therefore, we add a factor that ensures there is at least some social + // distancing between GCs, even when the GC overhead is small. The size of + // the factor scales with the level of load induced on the machine, as well + // as the scaled and unscaled GC intensities. + const double gc_intensity_factor = 5.0 / gc_intensity; + + // As the process starts dominating the available system memory, linearly + // tone down the memory bloating. + const double other_processes_memory = clamp(1.0 - mem_metrics.fraction_of_max(process_used_memory), 0.0, 1.0); + const double process_progression = calculate_progression(other_processes_memory, + 1.0, + mem_metrics._critical_threshold); + + const double gc_interval = gc_intensity_factor * (1.0 - process_progression); + + if (is_system_memory_pressure_concerning(mem_metrics)) { + // As memory availability drops low in the system, we want to further prevent + // memory bloating due to the interval target with another linear factor. If + // the only memory culprit is the current process, then the progression will + // now turn quadratic, which should help reduce the memory bloating further. + + const double system_availability = mem_metrics.available_fraction(); + const double system_progression = calculate_progression(system_availability, + mem_metrics._concerning_threshold, + mem_metrics._critical_threshold); + + return gc_interval * (1.0 - system_progression); + } + + return gc_interval; +} + +static double compute_target_gc_interval(const ZMemoryPressureMetrics& mem_metrics, + double gc_intensity, + size_t process_used_memory) { + const double machine_target_gc_interval = compute_target_gc_interval(mem_metrics._machine, + gc_intensity, + process_used_memory); + + if (!mem_metrics._is_containerized) { + return machine_target_gc_interval; + } + + const double container_target_gc_interval = compute_target_gc_interval(mem_metrics._container, + gc_intensity, + process_used_memory); + return MIN2(machine_target_gc_interval, container_target_gc_interval); +} + +// Logistic function, produces values in the range 0 - 1 in an S shape +static double sigmoid_function(double value) { + return 1.0 / (1.0 + pow(M_E, -value)); +} + +// This function smoothens out measured error signals to make the incremental heap +// sizing converge better. During an initial warmup period, a more aggressive function +// is used, which doesn't try to reduce the error signals. This reduces the number of +// early GCs before the system has had any chance to converge to a stable heap size. +static double smoothing_function(double value, double warmness) { + const double sigmoid = sigmoid_function(value); + const double aggressive = MIN2(MAX2(sigmoid, 0.5 + value), 2.0); + + return sigmoid * warmness + aggressive * (1.0 - warmness); +} + +size_t ZAdaptiveHeap::compute_heap_size(const ZHeapResizeMetrics& heap_metrics, ZGenerationId generation) { + precond(ZAdaptiveHeapSizing); + + const bool is_major = Thread::current() == ZDriver::major(); + const GCCause::Cause cause = is_major ? ZDriver::major()->gc_cause() : ZDriver::minor()->gc_cause(); + const bool is_heap_anti_pressure_gc = cause == GCCause::_z_proactive; + const bool is_heap_pressure_gc = cause == GCCause::_z_allocation_rate || + cause == GCCause::_z_high_usage || + cause == GCCause::_z_warmup; + + if (!is_heap_anti_pressure_gc && !is_heap_pressure_gc) { + // If this GC isn't heuristically triggered, don't resize or learn anything + return heap_metrics._heuristic_max_capacity; + } + + // System memory load + const ZMemoryPressureMetrics mem_metrics = memory_pressure_metrics(); + ZStatSystemMemoryUsage::record(mem_metrics); + + // System CPU load + const ZCpuPressureMetrics cpu_metrics = cpu_pressure_metrics(generation); + if (generation == ZGenerationId::young) { + _avg_mem_worker_cpu_overhead.store_relaxed(cpu_metrics._avg_mem_worker_cpu_overhead * ZAdaptiveHeap::young_to_old_gc_time()); + } else { + _avg_mem_worker_cpu_overhead.store_relaxed(cpu_metrics._avg_mem_worker_cpu_overhead); + } + + // Heap size metrics + const size_t soft_max_capacity = heap_metrics._soft_max_capacity; + const size_t current_max_capacity = heap_metrics._current_max_capacity; + const size_t heuristic_max_capacity = heap_metrics._heuristic_max_capacity; + const size_t capacity = heap_metrics._capacity; + const size_t static_min_capacity = heap_metrics._static_min_capacity; + const size_t used = heap_metrics._used; + + if (is_heap_anti_pressure_gc) { + // The GC is bored. The impact of shrinking should not cost a considerable amount of + // CPU, or we would not get here. + const size_t selected_capacity = MAX2(size_t(double(heuristic_max_capacity) * 0.95), used); + return clamp(align_down(selected_capacity, ZGranuleSize), static_min_capacity, current_max_capacity); + } + + const ZStatCycleStats cycle_stats = ZGeneration::generation(generation)->stat_cycle()->stats(); + + const double warmup_time_seconds = 3.0; + const double warmness = MIN2(os::elapsedTime(), warmup_time_seconds) / warmup_time_seconds; + const double warmness_squared = warmness * warmness; + + const size_t process_used_memory = os::rss(); + const size_t process_non_heap_memory = process_used_memory > capacity ? process_used_memory - capacity : 0; + const size_t projected_process_used_memory = heuristic_max_capacity + process_non_heap_memory; + + // Calculate the GC pressure that scales the rest of the heuristics + ZResourcePressure pressures = compute_pressures(mem_metrics, cpu_metrics, projected_process_used_memory); + + const double mem_pressure = pressures._mem_pressure; + const double avg_time_since_last = cpu_metrics._avg_gc_interval; + const double gc_intensity = smoothed_gc_intensity(pressures._scaled_gc_intensity); + + // Calculate the heuristic lower bound for the heuristic heap + const double alloc_rate = heap_metrics._alloc_rate; + // Since a GC cycle is obviously round, we can estimate the minimum bytes due to + // a particular allocation rate and GC intensity by calculating GC intensity * pi. + const double alloc_rate_window = warmness_squared / (gc_intensity * M_PI); + const size_t alloc_rate_window_bytes = size_t(alloc_rate * alloc_rate_window); + const size_t heuristic_low = align_down(size_t(double(MAX2(size_t(double(used) * 1.1), alloc_rate_window_bytes)) / mem_pressure), ZGranuleSize); + + const size_t upper_bound = MIN2(soft_max_capacity, current_max_capacity); + const size_t lower_bound = clamp(heuristic_low, static_min_capacity, upper_bound); + + // We want strictly more GC activity on systems with proactive memory + // compression (Windows and MacOS). Otherwise, memory bloating leads to + // memory compression and decompression, which wastes more CPU than just + // doing more GC instead. + const double compression_gc_penalty = 1.0 MACOS_ONLY(* 2.0) WINDOWS_ONLY(* 2.0); + + // When GC intensity is 10, the implication is that we want ZConcurrentWorkersCPUShare + // of the process CPU to be spent on doing GC when the process uses 100% of the + // available CPU cores. The ConcGCThreads sizing by default goes up to a maximum + // of ZConcurrentWorkersCPUShare of the available cores. So all ConcGCThreads + // would be running back to back then. + const double normalised_gc_intensity = gc_intensity / 10; + const double target_cpu_overhead = ZConcurrentWorkersCPUShare * normalised_gc_intensity * compression_gc_penalty; + + // Save some breadcrumbs to the director to not use more conc GC threads + // than we need to run back to back GC at the target GC CPU overhead limit. + // It is better to let concurrent heap expansion run. + const double avg_process_time = cpu_metrics._avg_process_time; + const double avg_process_cpus = avg_process_time / avg_time_since_last; + const double high_target_workers = avg_process_cpus * target_cpu_overhead; + const uint initial_young_worker_cap = (uint)clamp(ceil(high_target_workers * 1.5), 1.0, (double)ZYoungGCThreads); + _initial_young_worker_cap.store_relaxed(initial_young_worker_cap); + + const double upper_cpu_overhead = MAX2(cpu_metrics._avg_total_gc_cpu_overhead, cpu_metrics._generation_gc_cpu_overhead); + const double upper_cpu_overhead_error = upper_cpu_overhead - target_cpu_overhead; + + const double lower_cpu_overhead = MIN2(cpu_metrics._avg_total_gc_cpu_overhead, cpu_metrics._generation_gc_cpu_overhead); + const double lower_cpu_overhead_error = lower_cpu_overhead - target_cpu_overhead; + + const double target_gc_interval = compute_target_gc_interval(mem_metrics, gc_intensity, process_used_memory) / compression_gc_penalty; + const double gc_interval_error = MAX2(target_gc_interval - avg_time_since_last, target_gc_interval - cpu_metrics._avg_gc_interval); + + const double upper_error_signal = MAX2(upper_cpu_overhead_error, gc_interval_error); + const double lower_error_signal = MAX2(lower_cpu_overhead_error, gc_interval_error); + + const bool is_young = generation == ZGenerationId::young; + + if (is_young) { + const double accumulated_young_gc_time = _accumulated_young_gc_time.load_relaxed(); + const double new_accumulated_young_gc_time = accumulated_young_gc_time + cpu_metrics._gc_time; + // The only race is with the old collection end's exchange bellow. So if the + // first CAS fails, the second must invariantly succeed. + const bool result = _accumulated_young_gc_time.compare_set(accumulated_young_gc_time, new_accumulated_young_gc_time, memory_order_relaxed) || + _accumulated_young_gc_time.compare_set(0.0, cpu_metrics._gc_time, memory_order_relaxed); + assert(result, "Should have succeded, unexpected _accumulated_young_gc_time mutation, expected: %f or 0.0, current: %f", + accumulated_young_gc_time, _accumulated_young_gc_time.load_relaxed()); + + const double accumulated_mem_worker_time = _accumulated_mem_worker_time.load_relaxed(); + const double new_accumulated_mem_worker_time = accumulated_mem_worker_time + cpu_metrics._mem_worker_time; + const bool result2 = _accumulated_mem_worker_time.compare_set(accumulated_mem_worker_time, new_accumulated_mem_worker_time, memory_order_relaxed) || + _accumulated_mem_worker_time.compare_set(0.0, cpu_metrics._mem_worker_time, memory_order_relaxed); + assert(result2, "Should have succeded, unexpected _accumulated_mem_worker_time mutation, expected: %f or 0.0, current: %f", + accumulated_mem_worker_time, _accumulated_mem_worker_time.load_relaxed()); + } else { + const double accumulated_young_gc_time = _accumulated_young_gc_time.exchange(0.0, memory_order_relaxed); + const double young_to_old_gc_time = accumulated_young_gc_time / (accumulated_young_gc_time + cycle_stats._last_total_vtime); + _young_to_old_gc_time.store_relaxed(young_to_old_gc_time); + + _accumulated_mem_worker_time.exchange(0.0, memory_order_relaxed); + } + + const double upper_smoothened_error = smoothing_function(upper_error_signal, warmness); + const double upper_correction_factor = upper_smoothened_error + 0.5; + + const double lower_smoothened_error = smoothing_function(lower_error_signal, warmness); + const double lower_correction_factor = lower_smoothened_error + 0.5; + + const size_t upper_scaled_capacity = align_up(size_t(double(heuristic_max_capacity) * upper_correction_factor), ZGranuleSize); + const size_t lower_scaled_capacity = align_up(size_t(double(heuristic_max_capacity) * lower_correction_factor), ZGranuleSize); + + // Instead of increasing the heap by an aggressive amount, reduce the memory + // availability by said amount as we get closer to the current max capacity. + // This ensures the heap increase slows down as we approach the max capacity, + // similar to when a space ship is docking at a station. + const size_t remaining_capacity = capacity >= current_max_capacity ? 0 : current_max_capacity - capacity; + const size_t upper_scaled_max_capacity = upper_correction_factor <= 1.0 + ? upper_scaled_capacity + : align_up(heuristic_max_capacity + size_t(double(remaining_capacity) * (upper_correction_factor - 1.0)), ZGranuleSize); + const size_t lower_scaled_max_capacity = lower_correction_factor <= 1.0 + ? lower_scaled_capacity + : align_up(heuristic_max_capacity + size_t(double(remaining_capacity) * (lower_correction_factor - 1.0)), ZGranuleSize); + + const size_t upper_suggested_capacity = MIN2(upper_scaled_capacity, upper_scaled_max_capacity); + const size_t lower_suggested_capacity = MIN2(lower_scaled_capacity, lower_scaled_max_capacity); + + const size_t upper_bounded_capacity = clamp(upper_suggested_capacity, lower_bound, upper_bound); + const size_t lower_bounded_capacity = clamp(lower_suggested_capacity, lower_bound, upper_bound); + + // Grow if we experience short term *and* long term pressure on the heap + const bool should_grow = lower_bounded_capacity > heuristic_max_capacity && upper_bounded_capacity > heuristic_max_capacity; + // Shrink if we experience short term *and* long term reverse pressure on the heap + const bool should_shrink = lower_bounded_capacity < heuristic_max_capacity && upper_bounded_capacity < heuristic_max_capacity; + + const double cpu_pressure = pressures._cpu_pressure; + const double cpu_vs_memory_pressure = pressures._cpu_vs_memory_pressure; + const double cpu_vs_latency_pressure = pressures._cpu_vs_latency_pressure; + + if (mem_metrics._is_containerized) { + log_info(gc, load)("Container: System Memory Load: %.1f%%, Process Memory Load: %.1f%%, Heap Memory Load: %.1f%%", + double(mem_metrics._container._used_memory) / double(mem_metrics._container._max_memory) * 100.0, + double(projected_process_used_memory) / double(mem_metrics._container._max_memory) * 100.0, + double(heuristic_max_capacity) / double(mem_metrics._container._max_memory) * 100.0); + } + + if (cpu_metrics._has_container_cpu_metrics) { + log_info(gc, load)("Container: System CPU Load: %.1f%%, Process CPU Load: %.1f%%, GC CPU Load: %.1f%%", + cpu_metrics._container._avg_system_load * 100.0, cpu_metrics._container._avg_process_load * 100.0, + cpu_metrics._avg_total_gc_cpu_overhead * cpu_metrics._container._avg_process_load * 100.0); + } + + log_info(gc, load)("Machine: System Memory Load: %.1f%%, Process Memory Load: %.1f%%, Heap Memory Load: %.1f%%", + double(mem_metrics._machine._used_memory) / double(mem_metrics._machine._max_memory) * 100.0, + double(projected_process_used_memory) / double(mem_metrics._machine._max_memory) * 100.0, + double(heuristic_max_capacity) / double(mem_metrics._machine._max_memory) * 100.0); + + log_info(gc, load)("Machine: System CPU Load: %.1f%%, Process CPU Load: %.1f%%, GC CPU Load: %.1f%%", + cpu_metrics._machine._avg_system_load * 100.0, cpu_metrics._machine._avg_process_load * 100.0, + cpu_metrics._avg_total_gc_cpu_overhead * cpu_metrics._machine._avg_process_load * 100.0); + + if (ZAdaptiveHeapSizing) { + log_info(gc, heap)("Process GC CPU Overhead: %.1f%%, Target Process GC CPU Overhead: %.1f%%", + cpu_metrics._avg_total_gc_cpu_overhead * 100.0, target_cpu_overhead * 100.0); + log_debug(gc, heap)("Memory Worker CPU Overhead: %.1f%% of GC CPU Time", + cpu_metrics._avg_mem_worker_cpu_overhead); + + log_debug(gc, heap)("System CPU Pressure: %.1f, System Memory Pressure: %.1f", + cpu_pressure, mem_pressure); + log_debug(gc, heap)("System CPU vs Memory Pressure: %.1f, System CPU vs Latency Pressure: %.1f", + cpu_vs_memory_pressure, cpu_vs_latency_pressure); + log_info(gc, heap)("GC Intensity: %.1f, Resource Pressure Scaling: %.1f", + gc_intensity, gc_intensity / mem_metrics._unscaled_gc_intensity); + + log_debug(gc, heap)("GC Interval: %.3fs, Target Minimum: %.3fs", + avg_time_since_last, target_gc_interval); + log_debug(gc, heap)("Target heap lower bound: %zuM, upper bound: %zuM", + lower_bound / M, upper_bound / M); + log_debug(gc, heap)("Suggested capacity range: %zuM - %zuM, heuristic capacity: %zuM", + lower_suggested_capacity / M, upper_suggested_capacity / M, heuristic_max_capacity / M); + } + + if (should_grow) { + if (capacity < heuristic_max_capacity) { + // We have not yet finished growing initiated in a previous GC, because the throughput of GCs + // is higher than the throughput of growing. The control system needs to feel the impact of the + // previous growth request before growing further. Therefore, we decide not to change the + // heuristic max capacity in this case. + return heuristic_max_capacity; + } + + const size_t selected_capacity = MIN2(upper_bounded_capacity, lower_bounded_capacity); + const size_t capacity_resize = selected_capacity - heuristic_max_capacity; + + log_debug(gc, heap)("Updated heuristic max capacity: %zuM (%.3f%%), current capacity: %zuM", + selected_capacity / M, double(selected_capacity) / double(heuristic_max_capacity) * 100.0 - 100.0, capacity / M); + + log_info(gc, heap)("Heap Increase %zuM (%.1f%%)", capacity_resize / M, double(capacity_resize) / double(heuristic_max_capacity) * 100.0); + + return selected_capacity; + } else if (should_shrink) { + // We want to shrink slower than we grow; by splitting the proposed shrinking into a fraction, + // we get a slower tail of shrinking, which avoids unnecessary fluctuations up and down. + const size_t shrinking_fraction = 5; + + const size_t proposed_selected_capacity = MAX2(upper_bounded_capacity, lower_bounded_capacity); + const size_t capacity_resize = align_up((heuristic_max_capacity - proposed_selected_capacity) / shrinking_fraction, ZGranuleSize); + + const size_t selected_capacity = heuristic_max_capacity - capacity_resize; + + log_debug(gc, heap)("Updated heuristic max capacity: %zuM (%.3f%%), current capacity: %zuM", + selected_capacity / M, double(selected_capacity) / double(heuristic_max_capacity) * 100.0 - 100.0, capacity / M); + + log_info(gc, heap)("Heap Decrease %zuM (%.1f%%)", capacity_resize / M, double(capacity_resize) / double(heuristic_max_capacity) * 100.0); + + return selected_capacity; + } + + return heuristic_max_capacity; +} + +uint64_t ZAdaptiveHeap::urgent_uncommit_rate() { + return 64; // MB per second +} + +uint64_t ZAdaptiveHeap::no_uncommit_delay() { + return std::numeric_limits::max(); +} + +uint64_t ZAdaptiveHeap::critical_uncommit_delay() { + return 0; +} + +static uint64_t system_uncommit_delay(const ZSystemMemoryPressureMetrics& metrics, size_t capacity, size_t uncommit_granule, double avg_mem_worker_cpu_overhead) { + const double capacity_scaling = clamp(double(capacity) / double(metrics._used_memory), 0.05, 1.0); + const double worker_scaling = clamp(1.0 - avg_mem_worker_cpu_overhead, 0.05, 1.0); + const double rate_scaling = capacity_scaling * worker_scaling; + + // The remaining memory reserve of the system + const double available_fraction = metrics.available_fraction(); + + // If we are critically low on memory, aggressively free up memory + if (available_fraction <= metrics._critical_threshold) { + return ZAdaptiveHeap::critical_uncommit_delay(); + } + + // If we aren't low on memory, disable timer based uncommit; let + // the GC heuristics guide the heap down instead, as part of the + // natural control system. + if (available_fraction > metrics._concerning_threshold) { + return ZAdaptiveHeap::no_uncommit_delay(); + } + + const double uncommit_granule_mb = double(uncommit_granule) / M; + const double scaled_urgent_rate = ZAdaptiveHeap::urgent_uncommit_rate() * rate_scaling; + const uint64_t urgent_delay = uncommit_granule_mb * MILLIUNITS / scaled_urgent_rate; + + // If we aren't using a high amount memory, uncommit memory rather slowly + // and let the GC heuristics do most of the heavy lifting + if (available_fraction > metrics._high_threshold) { + // The memory pressure is concerning but not high; gradually siphon the + // heap to potential other JVMs that may be under more pressure, allowing + // them to grow. + // Progression until critical uncommitting starts + const double progression = calculate_progression(available_fraction, metrics._concerning_threshold, metrics._high_threshold); + + if (ZUncommitDelay == 0) { + // With no ZUncommitDelay, simply use the urgent delay + return urgent_delay; + } + + // Scale the uncommit interval by memory urgency, so the pace of uncommitting + // ramps up as the machine resources gets exhausted. + const double highest_rate = double(ZAdaptiveHeap::urgent_uncommit_rate()); + const double lowest_rate = highest_rate / ZUncommitDelay; + const double interpolated_rate = lowest_rate * (1.0 - progression) + highest_rate * progression; + const double scaled_rate = interpolated_rate * rate_scaling; + return uncommit_granule_mb * MILLIUNITS / scaled_rate; + } + + // We use a policy where the uncommit delay drops off fairly quickly + // as the memory pressure gets "high" to let uncommitting react before + // the next GC, but still without being brutal. + // When the memory availability becomes critical, more brutal uncommitting + // will commence. + + // Progression until critical uncommitting starts + const double progression = calculate_progression(available_fraction, metrics._high_threshold, metrics._critical_threshold); + + // Scale the uncommit interval by memory urgency, so the pace of uncommitting + // ramps up as the machine resources gets exhausted. + return uint64_t(urgent_delay * (1.0 - progression)); +} + +// How long to wait until it is time to uncommit memory. This goes towards +// infinity when there is no concerning memory pressure, then from 64 MB per +// ZUncommitDelay 64 MB per second when high, and eventually 0 when critically low. +uint64_t ZAdaptiveHeap::uncommit_delay(size_t uncommit_granule) { + precond(ZAdaptiveHeapSizing); + + const ZMemoryPressureMetrics mem_metrics = memory_pressure_metrics(); + ZStatSystemMemoryUsage::record(mem_metrics); + + const size_t capacity = ZHeap::heap()->capacity(); + const double avg_mem_worker_cpu_overhead = _avg_mem_worker_cpu_overhead.load_relaxed() / 100.0; + + const uint64_t machine_uncommit_delay = system_uncommit_delay(mem_metrics._machine, capacity, uncommit_granule, avg_mem_worker_cpu_overhead); + + if (!mem_metrics._is_containerized) { + return machine_uncommit_delay; + } + + const uint64_t container_uncommit_delay = system_uncommit_delay(mem_metrics._container, capacity, uncommit_granule, avg_mem_worker_cpu_overhead); + return MIN2(machine_uncommit_delay, container_uncommit_delay); +} + +uint64_t ZAdaptiveHeap::soft_ref_delay() { + ZStatHeap* const stats = ZGeneration::old()->stat_heap(); + // Young generation should have mostly transient state; + // consider it as basically free. + const size_t old_used_reloc_end = stats->used_generation_at_relocate_end(); + const size_t target_capacity = MAX2(ZHeap::heap()->heuristic_max_capacity(), old_used_reloc_end); + const size_t free_heap = target_capacity - old_used_reloc_end; + + const uint64_t explicit_delay = free_heap / M * uint64_t(SoftRefLRUPolicyMSPerMB); + + if (!ZAdaptiveHeapSizing) { + // Use the good old policy we all know and love so much when adaptive heap + // sizing is not in use. + return explicit_delay; + } + + // With adaptive heap sizing, there is a risk for a feedback loop when the amount + // of free memory decides how long soft references survive. More soft references + // will lead to the heap growing, hence creating more free memory and suddenly + // letting soft references live for longer. In order to cut this feedback loop, + // a more involved policy is used. + // + // The more involved strategy scales the delay with the time it would take for the + // heap to get filled up by old generation allocations multiplied by a scaled + // variation of SoftRefLRUPolicyMSPerMB. The scaling is more aggressive than linear + // by computing the nth root of SoftRefLRUPolicyMSPerMB, where n is some memory + // pressure. + + // Scale the delay by the old generation allocation rate; the faster it fills up, + // the more rapidly we need to prune soft references + const double avg_time_since_last = _old_data._gc_times_since_last.avg(); + const size_t old_live = stats->live_at_mark_end(); + const size_t old_used = ZHeap::heap()->used_old(); + const double old_allocated = old_used - old_live; + const double old_alloc_rate = MAX2(percent_of(old_allocated / M, avg_time_since_last) / 100.0, 1.0); + + const double time_to_old_oom = double(free_heap) / M / old_alloc_rate; + + const double free_ratio = double(target_capacity) / double(free_heap); + + const double mem_pressure = free_ratio; + + // Scale the SoftRefLRUPolicyMSPerMB as an nth root where n is the memory pressure. + // The reason for using the nth root is that it might not necessarily be that + // linearly decreasing the interval with memory pressure yields linearly more + // soft references being cleared. It rather depends on the access frequency. + // If they get accessed very frequently, then it's likely that no soft reference + // get cleared at all, until the interval is made *very* small. Therefore, the + // more aggressive nth root is used. + const uint64_t scaled_interval = uint64_t(pow(SoftRefLRUPolicyMSPerMB, 1.0 / mem_pressure)); + + // Compute the potentially more aggressive delay that cuts the feedback loop + const uint64_t implicit_delay = uint64_t(time_to_old_oom * double(scaled_interval)); + + // If the new policy yields earlier cut off points, then use that. Otherwise, + // we still use the more relaxed policy to cut off soft references when they + // have not been used for unreasonably long. While we could keep them around + // forever, it might also be a bit pointless. + const uint64_t delay = MIN2(implicit_delay, explicit_delay); + + log_info(gc, ref)("Soft ref timeout: %.3fs", double(delay) / MILLIUNITS); + + LogTarget(Debug, gc, ref) lt; + if (lt.is_enabled()) { + LogStream ls(lt); + + ls.print_cr("Soft ref time to old generation OOM: %.3fs", time_to_old_oom); + ls.print_cr("Soft ref explicit timeout: %.3fs", double(explicit_delay) / MILLIUNITS); + ls.print_cr("Soft ref implicit timeout: %.3fs", double(implicit_delay) / MILLIUNITS); + ls.print_cr("Soft ref memory pressure: %.3f", mem_pressure); + } + + return delay; +} + +void ZAdaptiveHeap::print() { + const char* status; + if (!ZAdaptiveHeapSizing) { + status = "Fixed"; + } else { + if (explicit_max_capacity() || MinHeapSize != DefaultMinHeapSize) { + status = "Adaptive (Explicit Boundaries)"; + } else { + status = "Adaptive (Implicit Boundaries)"; + } + } + log_info_p(gc, init)("Heap Sizing: %s", status); +} diff --git a/src/hotspot/share/gc/z/zAdaptiveHeap.hpp b/src/hotspot/share/gc/z/zAdaptiveHeap.hpp new file mode 100644 index 000000000000..b57bfc7bc3b8 --- /dev/null +++ b/src/hotspot/share/gc/z/zAdaptiveHeap.hpp @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SHARE_GC_Z_ZADAPTIVEHEAP_HPP +#define SHARE_GC_Z_ZADAPTIVEHEAP_HPP + +#include "gc/z/zGenerationId.hpp" +#include "gc/z/zStat.hpp" +#include "memory/allocation.hpp" +#include "utilities/numberSeq.hpp" + +struct ZHeapResizeMetrics { + const size_t _soft_max_capacity; + const size_t _current_max_capacity; + const size_t _heuristic_max_capacity; + const size_t _static_min_capacity; + const size_t _capacity; + const size_t _used; + const double _alloc_rate; +}; + +struct ZSystemMemoryPressureMetrics { + const physical_memory_size_type _used_memory; + const physical_memory_size_type _max_memory; + + const double _concerning_threshold; + const double _high_threshold; + const double _critical_threshold; + + double fraction_of_max(physical_memory_size_type memory) const; + physical_memory_size_type available_memory() const; + double used_fraction() const; + double available_fraction() const; +}; + +struct ZMemoryPressureMetrics { + const double _unscaled_gc_intensity; + const bool _is_containerized; + const ZSystemMemoryPressureMetrics _machine; + const ZSystemMemoryPressureMetrics _container; +}; + +struct ZSystemCpuPressureMetrics { + const double _processor_count; + const double _avg_process_load; + const double _avg_system_load; +}; + +struct ZCpuPressureMetrics { + const bool _has_container_cpu_metrics; + const bool _has_container_cpu_capacity_limit; + const double _generation_gc_cpu_overhead; + const double _avg_generation_gc_cpu_overhead; + const double _avg_total_gc_cpu_overhead; + const double _avg_gc_interval; + const double _avg_process_time; + const double _gc_time; + const double _mem_worker_time; + const double _avg_mem_worker_cpu_overhead; + const ZSystemCpuPressureMetrics _machine; + const ZSystemCpuPressureMetrics _container; +}; + +struct ZResourcePressure { + const double _scaled_gc_intensity; + const double _cpu_pressure; + const double _mem_pressure; + const double _cpu_vs_memory_pressure; + const double _cpu_vs_latency_pressure; +}; + +struct ZMachineMemoryInfo { + physical_memory_size_type _physical_memory; + physical_memory_size_type _available_memory; + bool _is_valid; +}; + +struct ZSystemCpuTime { + double _elapsed_time; + double _processor_count; +}; + +class ZAdaptiveHeap : public AllStatic { + friend class ZAdaptiveHeapTest; +private: + // We clamp ZGCIntensity between thes values to avoid precision errors + static constexpr double MinZGCIntensity = 1e-12; + static constexpr double MaxZGCIntensity = 1e12; + + static bool _explicit_max_capacity; + static bool _initialized; + static bool _initialized_generation_data; + + struct ZIntensitySmoother { + private: + ZLock* _lock; + TruncatedSeq _gc_intensities; + + public: + ZIntensitySmoother() + : _lock(nullptr), + _gc_intensities() {} + + void initialize(); + double record_and_smooth_gc_intensity(double scaled_gc_intensity); + }; + + struct ZGenerationOverhead { + double _last_machine_system_time; + double _last_container_system_time; + double _machine_processor_count; + double _container_processor_count; + double _last_process_time; + bool _has_last_machine_system_time; + bool _has_last_container_system_time; + bool _has_last_process_time; + double _last_time; + TruncatedSeq _process_times; + TruncatedSeq _machine_system_times; + TruncatedSeq _container_system_times; + TruncatedSeq _gc_times; + TruncatedSeq _gc_times_since_last; + TruncatedSeq _mem_worker_times; + + ZGenerationOverhead() + : _last_machine_system_time(), + _last_container_system_time(), + _machine_processor_count(), + _container_processor_count(), + _last_process_time(), + _has_last_machine_system_time(), + _has_last_container_system_time(), + _has_last_process_time(), + _process_times(), + _machine_system_times(), + _container_system_times(), + _gc_times(), + _gc_times_since_last(), + _mem_worker_times() {} + }; + + static Atomic _young_to_old_gc_time; + static Atomic _accumulated_young_gc_time; + static Atomic _accumulated_mem_worker_time; + static Atomic _avg_mem_worker_cpu_overhead; + static ZGenerationOverhead _young_data; + static ZGenerationOverhead _old_data; + static Atomic _initial_young_worker_cap; + static ZIntensitySmoother _gc_intensities; + + static void sample_generation_data(ZGenerationOverhead& generation_data); + static ZCpuPressureMetrics cpu_pressure_metrics(ZGenerationId generation); + + static ZResourcePressure compute_pressures(const ZMemoryPressureMetrics& mem_metrics, + const ZCpuPressureMetrics& cpu_metrics, + size_t projected_process_used_memory); + static double compute_memory_pressure(const ZMemoryPressureMetrics& metrics); + static double smoothed_gc_intensity(double scaled_gc_intensity); + + [[nodiscard]] static bool pd_machine_elapsed_system_cpu_time(ZSystemCpuTime& value); + [[nodiscard]] static bool pd_container_elapsed_system_cpu_time(ZSystemCpuTime& value); + [[nodiscard]] static bool machine_elapsed_system_cpu_time(ZSystemCpuTime& value); + [[nodiscard]] static bool container_elapsed_system_cpu_time(ZSystemCpuTime& value); + + static void pd_machine_memory_info(ZMachineMemoryInfo& info); + static bool pd_machine_compressed_memory(physical_memory_size_type& value); + static double machine_memory_compression_ratio(physical_memory_size_type machine_used_memory, + physical_memory_size_type machine_max_memory); + +public: + static void initialize(bool explicit_max_heap_size); + static void initialize_generation_data(); + + static ZMachineMemoryInfo machine_memory_info(); + static physical_memory_size_type machine_physical_memory(); + + static size_t compute_heap_size(const ZHeapResizeMetrics& metrics, ZGenerationId generation); + static double young_to_old_gc_time(); + static uint initial_young_worker_cap(); + + // Uncommit support + static uint64_t no_uncommit_delay(); + static uint64_t critical_uncommit_delay(); + static uint64_t urgent_uncommit_rate(); + static uint64_t uncommit_delay(size_t uncommit_granule); + + static uint64_t soft_ref_delay(); + + // Memory pressure + static ZMemoryPressureMetrics memory_pressure_metrics(); + static bool is_memory_pressure_concerning(const ZMemoryPressureMetrics& metrics); + static bool is_memory_pressure_high(const ZMemoryPressureMetrics& metrics); + static bool is_memory_pressure_critical(const ZMemoryPressureMetrics& metrics); + + // How adaptive are we? + static bool explicit_max_capacity(); + + static constexpr double DefaultGCIntensity = 5.0; + static constexpr double DefaultMaxRAMPercentage = 100.; + static constexpr size_t DefaultMinHeapSize = 2 * M; + + static void print(); +}; + +#endif // SHARE_GC_Z_ZADAPTIVEHEAP_HPP diff --git a/src/hotspot/share/gc/z/zAdaptiveHeap.inline.hpp b/src/hotspot/share/gc/z/zAdaptiveHeap.inline.hpp new file mode 100644 index 000000000000..a3548e8cacac --- /dev/null +++ b/src/hotspot/share/gc/z/zAdaptiveHeap.inline.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SHARE_GC_Z_ZADAPTIVEHEAP_INLINE_HPP +#define SHARE_GC_Z_ZADAPTIVEHEAP_INLINE_HPP + +#include "gc/z/zAdaptiveHeap.hpp" + +#include "utilities/globalDefinitions.hpp" + +inline physical_memory_size_type ZSystemMemoryPressureMetrics::available_memory() const { + return _max_memory - MIN2(_used_memory, _max_memory); +} + +inline double ZSystemMemoryPressureMetrics::fraction_of_max(physical_memory_size_type memory) const { + return percent_of(memory, _max_memory) / 100.0; +} + +inline double ZSystemMemoryPressureMetrics::used_fraction() const { + return fraction_of_max(_used_memory); +} + +inline double ZSystemMemoryPressureMetrics::available_fraction() const { + return fraction_of_max(available_memory()); +} + +inline bool ZAdaptiveHeap::explicit_max_capacity() { + precond(_initialized); + return _explicit_max_capacity; +} + +#endif // SHARE_GC_Z_ZADAPTIVEHEAP_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zArguments.cpp b/src/hotspot/share/gc/z/zArguments.cpp index f2ff35a6a438..989d5af728d7 100644 --- a/src/hotspot/share/gc/z/zArguments.cpp +++ b/src/hotspot/share/gc/z/zArguments.cpp @@ -21,16 +21,22 @@ * questions. */ +#include "gc/shared/gc_globals.hpp" #include "gc/shared/gcArguments.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddressSpaceLimit.hpp" #include "gc/z/zArguments.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zGlobals.hpp" +#include "gc/z/zHeap.hpp" #include "gc/z/zHeuristics.hpp" #include "gc/z/zUtils.inline.hpp" +#include "logging/log.hpp" #include "runtime/globals.hpp" #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" +#include "runtime/os.hpp" +#include "utilities/globalDefinitions.hpp" void ZArguments::initialize_alignments() { SpaceAlignment = ZGranuleSize; @@ -40,14 +46,17 @@ void ZArguments::initialize_alignments() { void ZArguments::initialize_heap_flags_and_sizes() { GCArguments::initialize_heap_flags_and_sizes(); - if (!FLAG_IS_CMDLINE(MaxHeapSize) && - !FLAG_IS_CMDLINE(MaxRAMPercentage) && - !FLAG_IS_CMDLINE(SoftMaxHeapSize)) { + precond(!FLAG_IS_ERGO(SoftMaxHeapSize)); + + if (!ZAdaptiveHeap::explicit_max_capacity() && !ZAdaptiveHeapSizing) { // We are really just guessing how much memory the program needs. // When that is the case, we don't want the soft and hard limits to be the same // as it can cause flakiness in the number of GC threads used, in order to keep // to a random number we just pulled out of thin air. - FLAG_SET_ERGO(SoftMaxHeapSize, MaxHeapSize * 90 / 100); + FLAG_SET_ERGO_IF_DEFAULT(SoftMaxHeapSize, MaxHeapSize * 90 / 100); + } else { + // This denotes there is no soft max heap size set. + FLAG_SET_ERGO_IF_DEFAULT(SoftMaxHeapSize, 0); } } @@ -118,6 +127,98 @@ void ZArguments::select_max_gc_threads() { } } +void ZArguments::set_heap_size() { + // We purposely do not care about MinRAMPercentage as adaptive heap sizing + // will use up to the whole machine, so even if MinRAMPercentage is set to + // 100%, we will still satisfy it. + + const bool gc_intensity_was_zero = ZGCIntensity == 0.0; + const bool adaptive_is_explicit = !FLAG_IS_DEFAULT(ZAdaptiveHeapSizing); + const bool intensity_is_explicit = !FLAG_IS_DEFAULT(ZGCIntensity); + const bool adaptive_matches_intensity = ZAdaptiveHeapSizing != gc_intensity_was_zero; + + if ((adaptive_is_explicit || intensity_is_explicit) && !adaptive_matches_intensity) { + // The user has set ZAdaptiveHeapSizing and/or ZGCIntensity AND their state + // does not match. For example: + // +ZAdaptiveHeapSizing and ZGCIntensity = 0 OR + // -ZAdaptiveHeapSizing and ZGCIntensity != 0 + + if (!adaptive_is_explicit && intensity_is_explicit) { + FLAG_SET_ERGO(ZAdaptiveHeapSizing, !ZAdaptiveHeapSizing); + } else if (adaptive_is_explicit && !intensity_is_explicit) { + FLAG_SET_ERGO(ZGCIntensity, ZAdaptiveHeapSizing ? ZAdaptiveHeap::DefaultGCIntensity : 0.0); + } else { + const double old_intensity = ZGCIntensity; + FLAG_SET_ERGO(ZGCIntensity, ZAdaptiveHeapSizing ? ZAdaptiveHeap::DefaultGCIntensity : 0.0); + + // Both were set, log a warning for mismatching configuration + log_warning(gc)("-XX:%cZAdaptiveHeapSizing does not match value selected for -XX:ZGCIntensity=%f, setting ZGCIntensity to %f", + ZAdaptiveHeapSizing ? '+' : '-', old_intensity, ZGCIntensity); + } + } + + assert(!FLAG_IS_ERGO(MaxHeapSize), "Who set my heap size ergo?"); + assert(!FLAG_IS_ERGO(MaxRAMPercentage), "Who set my heap size ergo?"); + + const bool explicit_max_heap_size = !FLAG_IS_DEFAULT(MaxHeapSize) || + !FLAG_IS_DEFAULT(MaxRAMPercentage); + + ZAdaptiveHeap::initialize(explicit_max_heap_size); + + if (!ZAdaptiveHeapSizing) { + // Let the shared code setup the set the heap size when not adapting + GCArguments::set_heap_size(); + return; + } + + // When adapting, we set the heap size ourselves + assert(!FLAG_IS_ERGO(InitialHeapSize), "Who set my heap size ergo?"); + assert(!FLAG_IS_ERGO(MinHeapSize), "Who set my heap size ergo?"); + assert(!FLAG_IS_ERGO(MinRAMPercentage), "Who set my heap size ergo?"); + + if (!FLAG_IS_DEFAULT(ErgoHeapSizeLimit)) { + log_warning(gc, heap, init)("ZGC does not use ErgoHeapSizeLimit, the value is ignored"); + } + if (!FLAG_IS_DEFAULT(HeapBaseMinAddress)) { + log_warning(gc, heap, init)("ZGC does not use HeapBaseMinAddress, the value is ignored"); + } + + // When the user has specified MaxRAMPercentage explicitly, we dispatch to either + // the container or machine's limit. If not explicitly specified, we use the machine's + // limit as we might need to scale up to most of the underlying memory. + const double max_physical_memory = !FLAG_IS_DEFAULT(MaxRAMPercentage) + ? checked_cast(os::physical_memory()) + : checked_cast(os::Machine::physical_memory()); + const double initial_physical_memory = checked_cast(os::physical_memory()); + + FLAG_SET_ERGO_IF_DEFAULT(MaxRAMPercentage, ZAdaptiveHeap::DefaultMaxRAMPercentage); + FLAG_SET_ERGO_IF_DEFAULT_OR_ZERO(MinHeapSize, ZAdaptiveHeap::DefaultMinHeapSize); + + const size_t max_size = align_down((size_t)(max_physical_memory * (MaxRAMPercentage / 100.)), ZGranuleSize); + // GCArguments::initialize_heap_flags_and_sizes does not reject incompatible + // MaxHeapSize and InitialHeapSize if they originate from a user + // MaxRAMPercentage, so we set the MaxHeapSize to be at least InitialHeapSize + // as well. + assert(!FLAG_IS_DEFAULT(InitialHeapSize) || InitialHeapSize == 0, "Assumed in MaxHeapSize selection"); + FLAG_SET_ERGO_IF_DEFAULT(MaxHeapSize, MAX3(max_size, MinHeapSize, InitialHeapSize)); + + const size_t initial_size = (size_t)(initial_physical_memory * (InitialRAMPercentage / 100.)); + FLAG_SET_ERGO_IF_DEFAULT_OR_ZERO(InitialHeapSize, clamp(initial_size, MIN2(MinHeapSize, MaxHeapSize), MaxHeapSize)); + + // GCArguments::initialize_heap_flags_and_sizes will align up the heap sizes. + if (align_up(MaxHeapSize, ZGranuleSize) == align_up(MinHeapSize, ZGranuleSize)) { + if (!FLAG_IS_DEFAULT(ZAdaptiveHeapSizing)) { + log_warning(gc)("Adaptive heap sizing was enabled, but heap size is fixed. Disabling adaptive heap sizing."); + } + + FLAG_SET_ERGO(ZAdaptiveHeapSizing, false); + FLAG_SET_ERGO(ZGCIntensity, 0.0); + } + + // Enable concurrent heating if ZAdaptiveHeapSizing is turned on + FLAG_SET_ERGO_IF_DEFAULT(ZMemoryHeating, true); +} + void ZArguments::initialize() { GCArguments::initialize(); diff --git a/src/hotspot/share/gc/z/zArguments.hpp b/src/hotspot/share/gc/z/zArguments.hpp index eab2d22ac63a..011915a2abfc 100644 --- a/src/hotspot/share/gc/z/zArguments.hpp +++ b/src/hotspot/share/gc/z/zArguments.hpp @@ -37,6 +37,7 @@ class ZArguments : public GCArguments { static bool is_os_supported(); public: + virtual void set_heap_size(); virtual void initialize_alignments(); virtual void initialize_heap_flags_and_sizes(); virtual void initialize(); diff --git a/src/hotspot/share/gc/z/zCollectedHeap.cpp b/src/hotspot/share/gc/z/zCollectedHeap.cpp index db55fff673a0..5afbd7238a1b 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.cpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,8 +66,7 @@ ZCollectedHeap::ZCollectedHeap() _driver_minor(new ZDriverMinor()), _driver_major(new ZDriverMajor()), _director(new ZDirector()), - _stat(new ZStat()), - _runtime_workers() {} + _stat(new ZStat()) {} CollectedHeap::Name ZCollectedHeap::kind() const { return CollectedHeap::Z; @@ -111,7 +110,7 @@ void ZCollectedHeap::stop() { } size_t ZCollectedHeap::max_capacity() const { - return _heap.max_capacity(); + return _heap.dynamic_max_capacity(); } size_t ZCollectedHeap::capacity() const { @@ -243,7 +242,7 @@ MemoryUsage ZCollectedHeap::memory_usage() { const size_t initial_size = InitialHeapSize; const size_t committed = ZHeap::heap()->capacity(); const size_t used = MIN2(ZHeap::heap()->used(), committed); - const size_t max_size = ZHeap::heap()->max_capacity(); + const size_t max_size = MAX2(ZHeap::heap()->dynamic_max_capacity(), committed); return MemoryUsage(initial_size, used, committed, max_size); } @@ -307,8 +306,8 @@ void ZCollectedHeap::verify_nmethod(nmethod* nm) { // Does nothing } -WorkerThreads* ZCollectedHeap::safepoint_workers() { - return _runtime_workers.workers(); +bool ZCollectedHeap::supports_parallel_object_iteration() const { + return true; } void ZCollectedHeap::gc_threads_do(ThreadClosure* tc) const { @@ -317,7 +316,6 @@ void ZCollectedHeap::gc_threads_do(ThreadClosure* tc) const { tc->do_thread(_driver_minor); tc->do_thread(_stat); _heap.threads_do(tc); - _runtime_workers.threads_do(tc); } VirtualSpaceSummary ZCollectedHeap::create_heap_space_summary() { @@ -326,7 +324,7 @@ VirtualSpaceSummary ZCollectedHeap::create_heap_space_summary() { // Fake values. ZGC does not commit memory contiguously in the reserved // address space, and the reserved space is larger than MaxHeapSize. const uintptr_t committed_end = ZAddressHeapBase + capacity(); - const uintptr_t reserved_end = ZAddressHeapBase + max_capacity(); + const uintptr_t reserved_end = MAX2(ZAddressHeapBase + max_capacity(), committed_end); return VirtualSpaceSummary((HeapWord*)start, (HeapWord*)committed_end, (HeapWord*)reserved_end); } diff --git a/src/hotspot/share/gc/z/zCollectedHeap.hpp b/src/hotspot/share/gc/z/zCollectedHeap.hpp index 19c9ab1bbdf0..44ba3fdaf33c 100644 --- a/src/hotspot/share/gc/z/zCollectedHeap.hpp +++ b/src/hotspot/share/gc/z/zCollectedHeap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,6 @@ #include "gc/z/zBarrierSet.hpp" #include "gc/z/zHeap.hpp" #include "gc/z/zInitialize.hpp" -#include "gc/z/zRuntimeWorkers.hpp" #include "memory/metaspace.hpp" #include "services/memoryUsage.hpp" @@ -48,7 +47,6 @@ class ZCollectedHeap : public CollectedHeap { ZDriverMajor* _driver_major; ZDirector* _director; ZStat* _stat; - ZRuntimeWorkers _runtime_workers; HeapWord* allocate_new_tlab(size_t min_size, size_t requested_size, @@ -101,7 +99,7 @@ class ZCollectedHeap : public CollectedHeap { void unregister_nmethod(nmethod* nm) override; void verify_nmethod(nmethod* nmethod) override; - WorkerThreads* safepoint_workers() override; + bool supports_parallel_object_iteration() const override; void gc_threads_do(ThreadClosure* tc) const override; diff --git a/src/hotspot/share/gc/z/zDirector.cpp b/src/hotspot/share/gc/z/zDirector.cpp index e5a060415884..621187c9958b 100644 --- a/src/hotspot/share/gc/z/zDirector.cpp +++ b/src/hotspot/share/gc/z/zDirector.cpp @@ -23,17 +23,22 @@ #include "cppstdlib/limits.hpp" #include "gc/shared/gc_globals.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zDirector.hpp" #include "gc/z/zDriver.hpp" #include "gc/z/zGeneration.inline.hpp" +#include "gc/z/zGlobals.hpp" #include "gc/z/zHeap.inline.hpp" #include "gc/z/zHeuristics.hpp" #include "gc/z/zLock.inline.hpp" #include "gc/z/zStat.hpp" #include "logging/log.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/init.hpp" +#include + ZDirector* ZDirector::_director; constexpr double one_in_1000 = 3.290527; @@ -46,7 +51,9 @@ struct ZWorkerResizeStats { }; struct ZDirectorHeapStats { - size_t _soft_max_heap_size; + size_t _current_max_capacity; + size_t _capacity; + size_t _heuristic_max_capacity; size_t _used; uint _total_collections; }; @@ -109,7 +116,7 @@ static uint discrete_young_gc_workers(double gc_workers) { static double select_young_gc_workers(const ZDirectorStats& stats, double serial_gc_time, double parallelizable_gc_time, double alloc_rate_sd_percent, double time_until_oom) { // Use all workers until we're warm if (!stats._old_stats._cycle._is_warm) { - const double not_warm_gc_workers = ZYoungGCThreads; + const double not_warm_gc_workers = ZAdaptiveHeapSizing ? 1 : ZYoungGCThreads; log_debug(gc, director)("Select Minor GC Workers (Not Warm), GCWorkers: %.3f", not_warm_gc_workers); return not_warm_gc_workers; } @@ -222,7 +229,19 @@ static ZDriverRequest rule_soft_minor_allocation_rate_dynamic(const ZDirectorSta serial_gc_time_passed, parallel_gc_time_passed, false /* conservative_alloc_rate */, - stats._heap._soft_max_heap_size /* capacity */); + stats._heap._heuristic_max_capacity /* capacity */); +} + +static size_t heuristic_hard_capacity(const ZDirectorStats& stats) { + if (!ZAdaptiveHeapSizing) { + return stats._heap._current_max_capacity; + } + + const size_t used_memory = stats._heap._capacity; + const size_t max_capacity = stats._heap._current_max_capacity; + const size_t available_memory = max_capacity - used_memory; + const size_t scaled_available_memory = (size_t)(available_memory * 0.2); + return align_down(used_memory + scaled_available_memory, ZGranuleSize); } static ZDriverRequest rule_semi_hard_minor_allocation_rate_dynamic(const ZDirectorStats& stats, @@ -232,7 +251,7 @@ static ZDriverRequest rule_semi_hard_minor_allocation_rate_dynamic(const ZDirect serial_gc_time_passed, parallel_gc_time_passed, false /* conservative_alloc_rate */, - ZHeap::heap()->max_capacity() /* capacity */); + heuristic_hard_capacity(stats) /* capacity */); } static ZDriverRequest rule_hard_minor_allocation_rate_dynamic(const ZDirectorStats& stats, @@ -242,7 +261,7 @@ static ZDriverRequest rule_hard_minor_allocation_rate_dynamic(const ZDirectorSta serial_gc_time_passed, parallel_gc_time_passed, true /* conservative_alloc_rate */, - ZHeap::heap()->max_capacity() /* capacity */); + stats._heap._current_max_capacity /* capacity */); } static bool rule_minor_allocation_rate_static(const ZDirectorStats& stats) { @@ -259,9 +278,9 @@ static bool rule_minor_allocation_rate_static(const ZDirectorStats& stats) { // Calculate amount of free memory available. Note that we take the // relocation headroom into account to avoid in-place relocation. - const size_t soft_max_capacity = stats._heap._soft_max_heap_size; + const size_t heuristic_max_capacity = stats._heap._heuristic_max_capacity; const size_t used = stats._heap._used; - const size_t free_including_headroom = soft_max_capacity - MIN2(soft_max_capacity, used); + const size_t free_including_headroom = heuristic_max_capacity - MIN2(heuristic_max_capacity, used); const size_t free = free_including_headroom - MIN2(free_including_headroom, ZHeuristics::relocation_headroom()); // Calculate time until OOM given the max allocation rate and the amount @@ -295,10 +314,10 @@ static bool rule_minor_allocation_rate_static(const ZDirectorStats& stats) { static bool is_young_small(const ZDirectorStats& stats) { // Calculate amount of freeable memory available. - const size_t soft_max_capacity = stats._heap._soft_max_heap_size; + const size_t heuristic_max_capacity = stats._heap._heuristic_max_capacity; const size_t young_used = stats._young_stats._general._used; - const double young_used_percent = percent_of(young_used, soft_max_capacity); + const double young_used_percent = percent_of(young_used, heuristic_max_capacity); // If the freeable memory isn't even 5% of the heap, we can't expect to free up // all that much memory, so let's not even try - it will likely be a wasted effort @@ -309,11 +328,11 @@ static bool is_young_small(const ZDirectorStats& stats) { static bool is_high_usage(const ZDirectorStats& stats, bool log = false) { // Calculate amount of free memory available. Note that we take the // relocation headroom into account to avoid in-place relocation. - const size_t soft_max_capacity = stats._heap._soft_max_heap_size; + const size_t heuristic_max_capacity = stats._heap._heuristic_max_capacity; const size_t used = stats._heap._used; - const size_t free_including_headroom = soft_max_capacity - MIN2(soft_max_capacity, used); + const size_t free_including_headroom = heuristic_max_capacity - MIN2(heuristic_max_capacity, used); const size_t free = free_including_headroom - MIN2(free_including_headroom, ZHeuristics::relocation_headroom()); - const double free_percent = percent_of(free, soft_max_capacity); + const double free_percent = percent_of(free, heuristic_max_capacity); if (log) { log_debug(gc, director)("Rule Minor: High Usage, Free: %zuMB(%.1f%%)", @@ -412,10 +431,12 @@ static bool rule_major_warmup(const ZDirectorStats& stats) { // Perform GC if heap usage passes 10/20/30% and no other GC has been // performed yet. This allows us to get some early samples of the GC // duration, which is needed by the other rules. - const size_t soft_max_capacity = stats._heap._soft_max_heap_size; + const size_t heuristic_max_capacity = stats._heap._heuristic_max_capacity; const size_t used = stats._heap._used; - const double used_threshold_percent = (stats._old_stats._cycle._nwarmup_cycles + 1) * 0.1; - const size_t used_threshold = (size_t)(soft_max_capacity * used_threshold_percent); + const double used_threshold_percent = ZAdaptiveHeapSizing + ? 0.75 + : ((stats._old_stats._cycle._nwarmup_cycles + 1) * 0.1); + const size_t used_threshold = (size_t)(heuristic_max_capacity * used_threshold_percent); log_debug(gc, director)("Rule Major: Warmup %.0f%%, Used: %zuMB, UsedThreshold: %zuMB", used_threshold_percent * 100, used / M, used_threshold / M); @@ -569,11 +590,10 @@ static bool rule_major_proactive(const ZDirectorStats& stats) { // of free space on the heap. // Only consider doing a proactive GC if the heap usage has grown by at least - // 10% of the max capacity since the previous GC, or more than 5 minutes has - // passed since the previous GC. This helps avoid superfluous GCs when running - // applications with very low allocation rate. + // 10% of the max capacity since the previous GC, and more than 5 minutes has + // passed since the previous GC. This helps avoid superfluous GCs. const size_t used_after_last_gc = stats._old_stats._stat_heap._used_at_relocate_end; - const size_t used_increase_threshold = (size_t)(stats._heap._soft_max_heap_size * 0.10); // 10% + const size_t used_increase_threshold = (size_t)(stats._heap._heuristic_max_capacity * 0.10); // 10% const size_t used_threshold = used_after_last_gc + used_increase_threshold; const size_t used = stats._heap._used; const double time_since_last_gc = stats._old_stats._cycle._time_since_last; @@ -780,14 +800,26 @@ static void adjust_gc(const ZDirectorStats& stats) { } } +static uint soft_initial_young_nworkers(uint proposed_soft_young_workers) { + if (!ZAdaptiveHeapSizing) { + return proposed_soft_young_workers; + } + + // If the proposed number of workers is obviously going to be above the GC CPU target, + // then throttle the thread count instead, and have concurrent heap expansion run while + // the GC intentionally misses the heuristic max heap size. + return MIN2(ZAdaptiveHeap::initial_young_worker_cap(), proposed_soft_young_workers); +} + static ZWorkerCounts initial_workers(const ZDirectorStats& stats, ZWorkerSelectionType type) { if (!UseDynamicNumberOfGCThreads) { return {ZYoungGCThreads, ZOldGCThreads}; } const ZDriverRequest soft_request = rule_soft_minor_allocation_rate_dynamic(stats, 0.0 /* serial_gc_time_passed */, 0.0 /* parallel_gc_time_passed */); + const uint soft_young_nworkers = soft_initial_young_nworkers(soft_request.young_nworkers()); const ZDriverRequest hard_request = rule_hard_minor_allocation_rate_dynamic(stats, 0.0 /* serial_gc_time_passed */, 0.0 /* parallel_gc_time_passed */); - const uint young_workers = MAX3(1u, soft_request.young_nworkers(), hard_request.young_nworkers()); + const uint young_workers = MAX3(1u, soft_young_nworkers, hard_request.young_nworkers()); return select_worker_threads(stats, young_workers, type); } @@ -863,9 +895,16 @@ bool ZDirector::wait_for_tick() { static ZDirectorHeapStats sample_heap_stats() { const ZHeap* const heap = ZHeap::heap(); const ZCollectedHeap* const collected_heap = ZCollectedHeap::heap(); + const size_t curr_max_capacity_sample = heap->current_max_capacity(); + const size_t used = heap->used(); + const size_t capacity = MAX2(heap->capacity(), used); + const size_t current_max_capacity = MAX2(capacity, curr_max_capacity_sample); + const size_t heuristic_max_capacity = MIN2(heap->heuristic_max_capacity(), current_max_capacity); return { - heap->soft_max_capacity(), - heap->used(), + current_max_capacity, + capacity, + heuristic_max_capacity, + used, collected_heap->total_collections() }; } @@ -913,6 +952,27 @@ static ZDirectorStats sample_stats() { }; } +static void adjust_capacity(const ZDirectorStats& stats, double sampling_interval) { + if (!ZAdaptiveHeapSizing) { + // We do not asynchronously adjust the capacity without adaptive heap sizing. + return; + } + + const size_t used = stats._heap._used; + const size_t max_capacity = stats._heap._current_max_capacity; + + const size_t used_next_byte_sample = MIN2(used + 2 * stats._mutator_alloc_rate._sampling_granule, max_capacity); + + const double avg_alloc_rate = stats._mutator_alloc_rate._avg; + const size_t used_next_time_sample = MIN2(used + 2 * size_t(sampling_interval * avg_alloc_rate), max_capacity); + + const size_t used_after_yc = stats._heap._heuristic_max_capacity; + + const size_t used_soon = align_up(MAX3(used_next_time_sample, used_next_byte_sample, used_after_yc), ZGranuleSize); + + ZHeap::heap()->adjust_capacity(used_soon); +} + void ZDirector::run_thread() { // Main loop while (wait_for_tick()) { @@ -926,6 +986,7 @@ void ZDirector::run_thread() { if (!start_gc(stats)) { adjust_gc(stats); } + adjust_capacity(stats, 1.0 / DecisionHz); } } diff --git a/src/hotspot/share/gc/z/zDriver.cpp b/src/hotspot/share/gc/z/zDriver.cpp index e8f7af179cb4..06d68aabdfcf 100644 --- a/src/hotspot/share/gc/z/zDriver.cpp +++ b/src/hotspot/share/gc/z/zDriver.cpp @@ -120,7 +120,8 @@ ZDriverMinor::ZDriverMinor() _port(), _gc_timer(), _jfr_tracer(), - _used_at_start() { + _used_at_start(), + _max_at_start() { ZDriver::set_minor(this); set_name("ZDriverMinor"); create_and_start(); @@ -164,6 +165,14 @@ size_t ZDriverMinor::used_at_start() const { return _used_at_start; } +void ZDriverMinor::set_max_at_start(size_t max) { + _max_at_start = max; +} + +size_t ZDriverMinor::max_at_start() const { + return _max_at_start; +} + class ZDriverScopeMinor : public StackObj { private: GCIdMark _gc_id; @@ -321,7 +330,8 @@ ZDriverMajor::ZDriverMajor() _port(), _gc_timer(), _jfr_tracer(), - _used_at_start() { + _used_at_start(), + _max_at_start() { ZDriver::set_major(this); set_name("ZDriverMajor"); create_and_start(); @@ -380,6 +390,14 @@ size_t ZDriverMajor::used_at_start() const { return _used_at_start; } +void ZDriverMajor::set_max_at_start(size_t max) { + _max_at_start = max; +} + +size_t ZDriverMajor::max_at_start() const { + return _max_at_start; +} + class ZDriverScopeMajor : public StackObj { private: GCIdMark _gc_id; diff --git a/src/hotspot/share/gc/z/zDriver.hpp b/src/hotspot/share/gc/z/zDriver.hpp index 8f178b886885..fb35c9428211 100644 --- a/src/hotspot/share/gc/z/zDriver.hpp +++ b/src/hotspot/share/gc/z/zDriver.hpp @@ -80,6 +80,7 @@ class ZDriverMinor : public ZDriver { ConcurrentGCTimer _gc_timer; ZMinorTracer _jfr_tracer; size_t _used_at_start; + size_t _max_at_start; void gc(const ZDriverRequest& request); void handle_alloc_stalls() const; @@ -99,6 +100,8 @@ class ZDriverMinor : public ZDriver { void set_used_at_start(size_t used); size_t used_at_start() const; + void set_max_at_start(size_t max); + size_t max_at_start() const; }; class ZDriverMajor : public ZDriver { @@ -107,6 +110,7 @@ class ZDriverMajor : public ZDriver { ConcurrentGCTimer _gc_timer; ZMajorTracer _jfr_tracer; size_t _used_at_start; + size_t _max_at_start; void collect_young(const ZDriverRequest& request); @@ -129,6 +133,8 @@ class ZDriverMajor : public ZDriver { void set_used_at_start(size_t used); size_t used_at_start() const; + void set_max_at_start(size_t max); + size_t max_at_start() const; }; class ZDriverLocker : public StackObj { diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index 0f9f4e34a5ee..7c54ff8f9d40 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -28,6 +28,7 @@ #include "gc/shared/gcVMOperations.hpp" #include "gc/shared/isGCActiveMark.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zBarrierSet.hpp" #include "gc/z/zBarrierSetAssembler.hpp" #include "gc/z/zBarrierSetNMethod.hpp" @@ -195,10 +196,20 @@ void ZGeneration::flip_age_pages(const ZRelocationSetSelector* selector) { } static double fragmentation_limit(ZGenerationId generation) { + double min_fragmentation = 0.0; + if (ZAdaptiveHeapSizing && ZHeap::heap()->is_alloc_stalling()) { + // It can be dangerous to defragment too much when the critical + // reserve of machine memory is used. When + // stalling starts, there should be very limited amounts of + // external fragmentation in the system. If we can't easily + // recover memory after this point, rather consider throwing + // OOME and calling it a day. + min_fragmentation = 25; + } if (generation == ZGenerationId::old) { - return ZFragmentationLimit; + return MAX2(ZFragmentationLimit, min_fragmentation); } else { - return ZYoungCompactionLimit; + return MAX2(ZYoungCompactionLimit, min_fragmentation); } } @@ -386,6 +397,9 @@ void ZGeneration::at_collection_start(ConcurrentGCTimer* gc_timer) { void ZGeneration::at_collection_end() { workers()->set_inactive(); stat_cycle()->at_end(stat_workers(), should_record_stats()); + if (should_record_stats() && ZAdaptiveHeapSizing) { + ZHeap::heap()->adapt_heuristic_max_capacity(_id); + } // The heap at collection end data is gathered at relocate end clear_gc_timer(); } @@ -742,7 +756,7 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st const size_t young_garbage = ZGeneration::young()->stat_heap()->garbage_at_mark_end(); const size_t young_allocated = ZGeneration::young()->stat_heap()->allocated_at_mark_end(); - const size_t soft_max_capacity = ZHeap::heap()->soft_max_capacity(); + const size_t heuristic_max_capacity = ZHeap::heap()->heuristic_max_capacity(); // The life expectancy shows by what factor on average one age changes between // two ages in the age table. Values below 1 indicate generational behaviour where @@ -764,7 +778,7 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st // resident part of the young generation is compared to the entire heap. Values // below 1 indicate it is relatively big. Conversely, values above 1 indicate // it is relatively small. - const double young_residency_reciprocal = double(soft_max_capacity) / double(young_live_total); + const double young_residency_reciprocal = double(heuristic_max_capacity) / double(young_live_total); // The old residency factor clamps the old residency reciprocal to // at least 1. That implies this factor is 1 unless the resident memory of @@ -779,14 +793,24 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st // The allocated to garbage ratio, compares the ratio of newly allocated // memory since GC started to how much garbage we are freeing up. The higher // the value, the harder it is for the YC to keep up with the allocation rate. - const double allocated_garbage_ratio = double(young_allocated) / double(young_garbage + 1); + const double allocated_garbage_ratio = MIN2(double(young_allocated) / double(young_garbage + 1), 1.0); + + // The amount of CPU spent being spent collecting the young generation vs + // the old generation. Since the CPU time of both generations contribute + // to the adaptive heap sizing, we want to reduce the amount of time spent + // in the most dominant generation. + const double young_to_old_gc_time_ratio = ZAdaptiveHeap::young_to_old_gc_time(); + + // If there is urgency for promotion we should be aggressive, and if it can + // reduce total GC time, we should also be more aggressive. + const double promotion_aggressiveness = MAX2(allocated_garbage_ratio, young_to_old_gc_time_ratio); // We slow down the young residency factor with a log. A larger log slows // it down faster. We select a log between 2 - 16 scaled by the allocated // to garbage factor. This selects a larger log when the GC has a harder // time keeping up, which causes more promotions to the old generation, // making the young collections faster so they can catch up. - const double young_log = MAX2(MIN2(allocated_garbage_ratio, 1.0) * 16, 2.0); + const double young_log = MAX2(promotion_aggressiveness * 16, 2.0); // The young log residency is essentially the young residency factor, but slowed // down by the log_{young_log}(X) function described above. @@ -802,6 +826,7 @@ uint ZGenerationYoung::compute_tenuring_threshold(ZRelocationSetSelectorStats st log_trace(gc, reloc)("Young Allocated: %zuM", young_allocated / M); log_trace(gc, reloc)("Young Garbage: %zuM", young_garbage / M); log_debug(gc, reloc)("Allocated To Garbage: %.1f", allocated_garbage_ratio); + log_debug(gc, reloc)("Young To Old GC Time: %.1f", young_to_old_gc_time_ratio); log_trace(gc, reloc)("Young Log: %.1f", young_log); log_trace(gc, reloc)("Young Residency Reciprocal: %.1f", young_residency_reciprocal); log_trace(gc, reloc)("Young Residency Factor: %.1f", young_residency_factor); diff --git a/src/hotspot/share/gc/z/zGlobals.hpp b/src/hotspot/share/gc/z/zGlobals.hpp index 278b6480d1dc..7b6488347d32 100644 --- a/src/hotspot/share/gc/z/zGlobals.hpp +++ b/src/hotspot/share/gc/z/zGlobals.hpp @@ -89,4 +89,16 @@ const size_t ZMarkProactiveFlushMax = 10; // Try complete mark timeout const uint64_t ZMarkCompleteTimeout = 200; // us +// How much CPU resources concurrent workers should use by default +const double ZConcurrentWorkersCPUShare = 0.25; + +// How much memory reserve we prefer to have on the machine +const double ZMemoryConcerningThreshold = 0.2; + +// How much memory reserve we fight to have on the machine +const double ZMemoryHighThreshold = 0.1; + +// How much memory reserve we enforce to have on the machine +const double ZMemoryCriticalThreshold = 0.05; + #endif // SHARE_GC_Z_ZGLOBALS_HPP diff --git a/src/hotspot/share/gc/z/zHeap.cpp b/src/hotspot/share/gc/z/zHeap.cpp index 44e5974993e5..bf568b07124b 100644 --- a/src/hotspot/share/gc/z/zHeap.cpp +++ b/src/hotspot/share/gc/z/zHeap.cpp @@ -26,6 +26,7 @@ #include "gc/shared/gcLogPrecious.hpp" #include "gc/shared/locationPrinter.hpp" #include "gc/shared/tlab_globals.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddress.inline.hpp" #include "gc/z/zArray.inline.hpp" #include "gc/z/zGeneration.inline.hpp" @@ -48,6 +49,8 @@ #include "memory/metaspaceUtils.hpp" #include "memory/resourceArea.hpp" #include "runtime/javaThread.hpp" +#include "runtime/os.hpp" +#include "utilities/align.hpp" #include "utilities/debug.hpp" static const ZStatCounter ZCounterUndoPageAllocation("Memory", "Undo Page Allocation", ZStatUnitOpsPerSecond); @@ -61,7 +64,7 @@ ZHeap::ZHeap() : _page_allocator(MinHeapSize, InitialHeapSize, SoftMaxHeapSize, MaxHeapSize), _page_table(), _object_allocator(), - _serviceability(InitialHeapSize, min_capacity(), max_capacity()), + _serviceability(InitialHeapSize, static_min_capacity(), static_max_capacity()), _old(&_page_table, &_page_allocator), _young(&_page_table, _old.forwarding_table(), &_page_allocator), _tlab_usage(), @@ -86,8 +89,13 @@ ZHeap::ZHeap() } // Update statistics - _young.stat_heap()->at_initialize(_page_allocator.min_capacity(), _page_allocator.max_capacity()); - _old.stat_heap()->at_initialize(_page_allocator.min_capacity(), _page_allocator.max_capacity()); + _young.stat_heap()->at_initialize(_page_allocator.static_min_capacity(), MaxHeapSize); + _old.stat_heap()->at_initialize(_page_allocator.static_min_capacity(), MaxHeapSize); + + // Initialize Adaptive heap generation data + if (ZAdaptiveHeapSizing) { + ZAdaptiveHeap::initialize_generation_data(); + } // Successfully initialized _initialized = true; @@ -97,16 +105,24 @@ bool ZHeap::is_initialized() const { return _initialized; } -size_t ZHeap::min_capacity() const { - return _page_allocator.min_capacity(); +size_t ZHeap::static_min_capacity() const { + return _page_allocator.static_min_capacity(); +} + +size_t ZHeap::static_max_capacity() const { + return _page_allocator.static_max_capacity(); +} + +size_t ZHeap::dynamic_max_capacity() const { + return _page_allocator.dynamic_max_capacity(); } -size_t ZHeap::max_capacity() const { - return _page_allocator.max_capacity(); +size_t ZHeap::current_max_capacity() const { + return _page_allocator.current_max_capacity(); } -size_t ZHeap::soft_max_capacity() const { - return _page_allocator.soft_max_capacity(); +size_t ZHeap::heuristic_max_capacity() const { + return _page_allocator.heuristic_max_capacity(); } size_t ZHeap::capacity() const { @@ -117,6 +133,16 @@ size_t ZHeap::used() const { return _page_allocator.used(); } +void ZHeap::adapt_heuristic_max_capacity(ZGenerationId generation) { + precond(ZAdaptiveHeapSizing); + _page_allocator.adapt_heuristic_max_capacity(generation); +} + +void ZHeap::adjust_capacity(size_t used_soon) { + precond(ZAdaptiveHeapSizing); + _page_allocator.adjust_capacity(used_soon); +} + size_t ZHeap::used_generation(ZGenerationId id) const { return _page_allocator.used_generation(id); } diff --git a/src/hotspot/share/gc/z/zHeap.hpp b/src/hotspot/share/gc/z/zHeap.hpp index 64ebc253b260..68076a11d5a4 100644 --- a/src/hotspot/share/gc/z/zHeap.hpp +++ b/src/hotspot/share/gc/z/zHeap.hpp @@ -73,15 +73,19 @@ class ZHeap { void out_of_memory() const; // Heap metrics - size_t min_capacity() const; - size_t max_capacity() const; - size_t soft_max_capacity() const; + size_t static_min_capacity() const; + size_t static_max_capacity() const; + size_t dynamic_max_capacity() const; + size_t current_max_capacity() const; + size_t heuristic_max_capacity() const; size_t capacity() const; size_t used() const; size_t used_generation(ZGenerationId id) const; size_t used_young() const; size_t used_old() const; size_t unused() const; + void adapt_heuristic_max_capacity(ZGenerationId generation); + void adjust_capacity(size_t used_soon); size_t tlab_capacity() const; size_t tlab_used() const; diff --git a/src/hotspot/share/gc/z/zHeuristics.cpp b/src/hotspot/share/gc/z/zHeuristics.cpp index ccd31ee1749a..7a504927a359 100644 --- a/src/hotspot/share/gc/z/zHeuristics.cpp +++ b/src/hotspot/share/gc/z/zHeuristics.cpp @@ -104,7 +104,7 @@ uint ZHeuristics::nconcurrent_workers() { // a negative impact on the application throughput, while using too few // threads will prolong the GC cycle and we then risk being out-run by the // application. - return MAX2(nworkers(25.0), 1u); + return MAX2(nworkers(ZConcurrentWorkersCPUShare * 100), 1u); } size_t ZHeuristics::significant_heap_overhead() { diff --git a/src/hotspot/share/gc/z/zLargePages.cpp b/src/hotspot/share/gc/z/zLargePages.cpp index c259448563bd..73ca7c2d505f 100644 --- a/src/hotspot/share/gc/z/zLargePages.cpp +++ b/src/hotspot/share/gc/z/zLargePages.cpp @@ -22,6 +22,7 @@ */ #include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.hpp" #include "gc/z/zLargePages.hpp" #include "runtime/os.hpp" @@ -31,7 +32,10 @@ bool ZLargePages::_os_enforced_transparent_mode; void ZLargePages::initialize() { pd_initialize(); - const size_t memory = static_cast(os::physical_memory()); + const size_t memory = os::is_containerized() + ? os::physical_memory() + : ZAdaptiveHeap::machine_physical_memory(); + log_info_p(gc, init)("Memory: " PROPERFMT, PROPERFMTARGS(memory)); log_info_p(gc, init)("Large Page Support: %s", to_string()); } @@ -48,6 +52,9 @@ const char* ZLargePages::to_string() { return "Enabled (Transparent)"; } + case Collapse: + return "Enabled (Transparent, JVM enforced)"; + default: if (_os_enforced_transparent_mode) { return "Disabled (OS enforced)"; diff --git a/src/hotspot/share/gc/z/zLargePages.hpp b/src/hotspot/share/gc/z/zLargePages.hpp index 72b9e85095c7..b94ae2b6cc8f 100644 --- a/src/hotspot/share/gc/z/zLargePages.hpp +++ b/src/hotspot/share/gc/z/zLargePages.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,13 +25,15 @@ #define SHARE_GC_Z_ZLARGEPAGES_HPP #include "memory/allStatic.hpp" +#include "utilities/globalDefinitions.hpp" class ZLargePages : public AllStatic { private: enum State { Disabled, Explicit, - Transparent + Transparent, + Collapse }; static State _state; @@ -45,6 +47,11 @@ class ZLargePages : public AllStatic { static bool is_enabled(); static bool is_explicit(); static bool is_transparent(); + static bool is_collapse(); + + static bool should_try_fallocate(); + + static bool pd_collapse(void* addr, size_t bytes); static const char* to_string(); }; diff --git a/src/hotspot/share/gc/z/zLargePages.inline.hpp b/src/hotspot/share/gc/z/zLargePages.inline.hpp index d057553249ef..acf2fd8c261f 100644 --- a/src/hotspot/share/gc/z/zLargePages.inline.hpp +++ b/src/hotspot/share/gc/z/zLargePages.inline.hpp @@ -38,4 +38,14 @@ inline bool ZLargePages::is_transparent() { return _state == Transparent; } +inline bool ZLargePages::is_collapse() { + return _state == Collapse; +} + +inline bool ZLargePages::should_try_fallocate() { + // To use madvise(MADV_HUGEPAGE) we cannot use fallocate as the pages are created before we have + // a memory range. When using explict large pages other allocation modes are more efficient. + return LINUX_ONLY(!is_transparent() && !is_explicit()) NOT_LINUX(false); +} + #endif // SHARE_GC_Z_ZLARGEPAGES_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zLock.hpp b/src/hotspot/share/gc/z/zLock.hpp index a843f2389d74..7a25b1aca69e 100644 --- a/src/hotspot/share/gc/z/zLock.hpp +++ b/src/hotspot/share/gc/z/zLock.hpp @@ -77,4 +77,14 @@ class ZLocker : public StackObj { ~ZLocker(); }; +template +class ZUnlocker : public StackObj { +private: + T* const _lock; + +public: + ZUnlocker(T* lock); + ~ZUnlocker(); +}; + #endif // SHARE_GC_Z_ZLOCK_HPP diff --git a/src/hotspot/share/gc/z/zLock.inline.hpp b/src/hotspot/share/gc/z/zLock.inline.hpp index ba5d66925217..93aa5c459590 100644 --- a/src/hotspot/share/gc/z/zLock.inline.hpp +++ b/src/hotspot/share/gc/z/zLock.inline.hpp @@ -116,4 +116,19 @@ inline ZLocker::~ZLocker() { } } +template +inline ZUnlocker::ZUnlocker(T* lock) + : _lock(lock) { + if (_lock != nullptr) { + _lock->unlock(); + } +} + +template +inline ZUnlocker::~ZUnlocker() { + if (_lock != nullptr) { + _lock->lock(); + } +} + #endif // SHARE_GC_Z_ZLOCK_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zMappedCache.hpp b/src/hotspot/share/gc/z/zMappedCache.hpp index 8688e047ae1e..0175ceaa869c 100644 --- a/src/hotspot/share/gc/z/zMappedCache.hpp +++ b/src/hotspot/share/gc/z/zMappedCache.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -126,13 +126,15 @@ class ZMappedCache { public: ZMappedCache(); + size_t size() const; + void insert(const ZVirtualMemory& vmem); ZVirtualMemory remove_contiguous(size_t size); ZVirtualMemory remove_contiguous_power_of_2(size_t min_size, size_t max_size); size_t remove_discontiguous(size_t size, ZArray* out); - // ZUncommitter support + // Uncommit support void reset_min_size_watermark(); size_t min_size_watermark(); size_t remove_for_uncommit(size_t size, ZArray* out); diff --git a/src/hotspot/share/gc/z/zRuntimeWorkers.hpp b/src/hotspot/share/gc/z/zMappedCache.inline.hpp similarity index 70% rename from src/hotspot/share/gc/z/zRuntimeWorkers.hpp rename to src/hotspot/share/gc/z/zMappedCache.inline.hpp index dede5b0b2d9b..dc27a8e8e8b0 100644 --- a/src/hotspot/share/gc/z/zRuntimeWorkers.hpp +++ b/src/hotspot/share/gc/z/zMappedCache.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,23 +21,13 @@ * questions. */ -#ifndef SHARE_GC_Z_ZRUNTIMEWORKERS_HPP -#define SHARE_GC_Z_ZRUNTIMEWORKERS_HPP +#ifndef SHARE_GC_Z_ZMAPPEDCACHE_INLINE_HPP +#define SHARE_GC_Z_ZMAPPEDCACHE_INLINE_HPP -#include "gc/shared/workerThread.hpp" +#include "gc/z/zMappedCache.hpp" -class ThreadClosure; +inline size_t ZMappedCache::size() const { + return _size; +} -class ZRuntimeWorkers { -private: - WorkerThreads _workers; - -public: - ZRuntimeWorkers(); - - WorkerThreads* workers(); - - void threads_do(ThreadClosure* tc) const; -}; - -#endif // SHARE_GC_Z_ZRUNTIMEWORKERS_HPP +#endif // SHARE_GC_Z_ZMAPPEDCACHE_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zMemoryWorker.cpp b/src/hotspot/share/gc/z/zMemoryWorker.cpp new file mode 100644 index 000000000000..2653f44c9011 --- /dev/null +++ b/src/hotspot/share/gc/z/zMemoryWorker.cpp @@ -0,0 +1,1122 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/shared/gc_globals.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" +#include "gc/z/zGlobals.hpp" +#include "gc/z/zLock.inline.hpp" +#include "gc/z/zMemoryWorker.hpp" +#include "gc/z/zNUMA.inline.hpp" +#include "gc/z/zPage.inline.hpp" +#include "gc/z/zPageAllocator.hpp" +#include "gc/z/zUtils.inline.hpp" +#include "gc/z/zVirtualMemory.inline.hpp" +#include "logging/log.hpp" +#include "logging/logHandle.hpp" +#include "runtime/init.hpp" +#include "utilities/debug.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/rbTree.inline.hpp" + +RBTreeOrdering ZMemoryWorker::ZHeatingRequestTreeComparator::cmp(zoffset first, zoffset second) { + if (first < second) { + // Start before second + return RBTreeOrdering::LT; + } + + if (first > second) { + // Start after second + return RBTreeOrdering::GT; + } + + // Same position + return RBTreeOrdering::EQ; +} + +enum class ZMemoryWorkerMode { + Uninitialized, + Commit, + Uncommit, + UncommitTargetless, + Heat, + Wait, +}; + +static const char* memory_worker_completed_mode_name(ZMemoryWorkerMode mode) { + switch (mode) { + case ZMemoryWorkerMode::Commit: + return "Committed"; + case ZMemoryWorkerMode::Uncommit: + case ZMemoryWorkerMode::UncommitTargetless: + return "Uncommitted"; + case ZMemoryWorkerMode::Heat: + return "Heated"; + case ZMemoryWorkerMode::Wait: + case ZMemoryWorkerMode::Uninitialized: + break; + } + + DEBUG_ONLY(ShouldNotReachHere();) + return "Unknown"; +} + +static const char* memory_worker_start_mode_name(ZMemoryWorkerMode mode) { + switch (mode) { + case ZMemoryWorkerMode::Commit: + return "Commit"; + case ZMemoryWorkerMode::Uncommit: + case ZMemoryWorkerMode::UncommitTargetless: + return "Uncommit"; + case ZMemoryWorkerMode::Heat: + return "Heat"; + case ZMemoryWorkerMode::Wait: + case ZMemoryWorkerMode::Uninitialized: + break; + } + + DEBUG_ONLY(ShouldNotReachHere();) + return "Unknown"; +} + +bool ZMemoryWorker::is_enabled() { + return ZAdaptiveHeapSizing || ZMemoryHeating; +} + +ZMemoryWorker::ZMemoryWorker(uint32_t id, ZPartition* partition) + : _id(id), + _partition(partition), + _lock(), + _heating_requests(), + _heating_request_bytes(0), + _target_commit_capacity(0), + _target_uncommit_capacity(0), + _targetless_uncommit(false), + _uncommit_request_time(), + _stop(false), + _currently_heating() { + if (!is_enabled()) { + // Disabled, do not start + _stop = true; + return; + } + set_name("ZMemoryWorker#%u", _id); + create_and_start(); + + if (ZNUMA::is_enabled()) { + // If NUMA is enabled we set the affinity of the thread to CPUs associated + // with the partition that the ZMemoryWorker will work on. + os::numa_set_thread_affinity(this, ZNUMA::numa_id_to_node(_id)); + } +} + +ZMemoryWorker::~ZMemoryWorker() {} + +bool ZMemoryWorker::is_stop_requested() { + return _stop; +} + +size_t ZMemoryWorker::commit_granule(size_t target_capacity) { + const size_t smallest_granule = ZGranuleSize; + const size_t largest_granule = MAX2(ZPageSizeMediumMax, smallest_granule); + + return clamp(align_up(target_capacity / 128, ZGranuleSize), smallest_granule, largest_granule); +} + +size_t ZMemoryWorker::uncommit_granule(size_t target_capacity) { + const size_t smallest_granule = ZGranuleSize; + const size_t largest_granule = MAX2(ZPageSizeMediumMax, smallest_granule); + + return clamp(align_up(target_capacity / 128, ZGranuleSize), smallest_granule, largest_granule); +} + +bool ZMemoryWorker::has_heating_request() { + assert((_heating_request_bytes == 0) == (_heating_requests.size() == 0), + "_heating_request_bytes: %zu, _heating_requests.size(): %zu", + _heating_request_bytes, _heating_requests.size()); + return _heating_request_bytes != 0; +} + +void ZMemoryWorker::verify_heating_requests() { +#ifdef ASSERT + // Don't verify the tree if it's "too" large + if (_heating_requests.size() > 10) { + return; + } + + const ZHeatingRequestNode* prev = nullptr; + size_t total_size = 0; + + _heating_requests.visit_range_in_order(zoffset(0), zoffset(ZAddressOffsetMax), [&](const ZHeatingRequestNode* node) { + // If there is a previous node, make sure that it is not adjacent to this + // node. Adjacent nodes should be merged when inserted to the tree. + if (prev != nullptr) { + assert(prev->key() + prev->val() != node->key(), "Should be merged. " + "[" PTR_FORMAT ", " PTR_FORMAT "] [" PTR_FORMAT ", " PTR_FORMAT "]", + untype(prev->key()), prev->val(), untype(node->key()), node->val()); + } + total_size += node->val(); + prev = node; + return true; + }); + + assert(total_size == _heating_request_bytes, "%zu == %zu", total_size, _heating_request_bytes); +#endif // ASSERT +} + +void ZMemoryWorker::set_targetless_uncommit(Ticks now) { + precond(ZUncommit); + + _targetless_uncommit = true; + if (_uncommit_request_time == Ticks()) { + _uncommit_request_time = now; + } +} + +void ZMemoryWorker::set_targeted_uncommit(size_t requested_capacity, Ticks now) { + precond(ZUncommit); + + _target_uncommit_capacity = requested_capacity; + if (_uncommit_request_time == Ticks()) { + _uncommit_request_time = now; + } +} + +void ZMemoryWorker::clear_targetless_uncommit() { + _targetless_uncommit = false; + + if (_target_uncommit_capacity == 0u) { + // No more uncommit request + _uncommit_request_time = Ticks(); + } +} + +void ZMemoryWorker::clear_targeted_uncommit() { + _target_uncommit_capacity = 0u; + + if (!_targetless_uncommit) { + // No more uncommit request + _uncommit_request_time = Ticks(); + } +} + +void ZMemoryWorker::stop_heap_resizing() { + precond(ZAdaptiveHeapSizing); + + // Remove requests to resize the capacity + ZLocker locker(&_lock); + _target_commit_capacity = 0u; + clear_targeted_uncommit(); +} + +void ZMemoryWorker::stop_shrink_capacity_granule() { + precond(ZAdaptiveHeapSizing); + + if (!ZUncommit) { + // Uncommit turned off. + return; + } + + // Remove requests to resize the capacity + ZLocker locker(&_lock); + clear_targetless_uncommit(); +} + +void ZMemoryWorker::request_grow_capacity(size_t requested_capacity) { + precond(ZAdaptiveHeapSizing); + + ZLocker locker(&_lock); + _target_commit_capacity = requested_capacity; + clear_targeted_uncommit(); + + _lock.notify_all(); +} + +void ZMemoryWorker::request_shrink_capacity(size_t requested_capacity) { + precond(ZAdaptiveHeapSizing); + + if (!ZUncommit) { + // Uncommit turned off. + return; + } + + const Ticks now = Ticks::now(); + + ZLocker locker(&_lock); + set_targeted_uncommit(requested_capacity, now); + _target_commit_capacity = 0u; + + _lock.notify_all(); +} + +void ZMemoryWorker::request_shrink_capacity_granule() { + precond(ZAdaptiveHeapSizing); + + if (!ZUncommit) { + // Uncommit turned off. + return; + } + + const Ticks now = Ticks::now(); + + ZLocker locker(&_lock); + set_targetless_uncommit(now); + + _lock.notify_all(); +} + +static constexpr uint64_t TargetUncommitMinDelay = 10; + +static bool has_uncommit_matured(Ticks now, Ticks since, uint64_t uncommit_delay) { + precond(now != Ticks()); + precond(since != Ticks()); + + return (now - since).milliseconds() >= uncommit_delay; +} + +static uint64_t remaining_uncommit_wait_duration(Ticks since, uint64_t uncommit_delay) { + precond(since != Ticks()); + + const uint64_t elapsed = (Ticks::now() - since).milliseconds(); + return elapsed >= uncommit_delay ? 0 : uncommit_delay - elapsed; +} + +static uint64_t targeted_uncommit_delay(uint64_t uncommit_delay) { + return MIN2(TargetUncommitMinDelay, uncommit_delay); +} + +static bool has_targetless_uncommit_matured(Ticks now, Ticks since, uint64_t uncommit_delay) { + // Short term shrinking as requested by the director should shrink according + // to the memory pressure based uncommit delay schedule. + return has_uncommit_matured(now, since, uncommit_delay); +} + +static bool has_targeted_uncommit_matured(Ticks now, Ticks since, uint64_t uncommit_delay) { + precond(ZUncommit); + + // Long term shrinking as requested by a GC should shrink fairly quickly + // regardless of memory pressure. + const uint64_t targeted_delay = targeted_uncommit_delay(uncommit_delay); + return has_uncommit_matured(now, since, targeted_delay); +} + +void ZMemoryWorker::remove_heating_request_range(const ZVirtualMemory& vmem) { + ZArray to_remove; + + const zoffset start_inclusive = vmem.start(); + const zoffset end_inclusive = vmem.start() + (vmem.size() - ZGranuleSize); + _heating_requests.visit_range_in_order(start_inclusive, end_inclusive, [&](const ZHeatingRequestNode* node) { + // Const cast the node, we only use it to modify the tree after + // visit_range_in_order is completed. + to_remove.push(const_cast(node)); + return true; + }); + + for (ZHeatingRequestNode* node: to_remove) { + const ZVirtualMemory to_remove(node->key(), node->val()); + assert(vmem.contains(to_remove), + "[" PTR_FORMAT ", " PTR_FORMAT "] [" PTR_FORMAT ", " PTR_FORMAT "]", + untype(vmem.start()), untype(vmem.end()), + untype(to_remove.start()), untype(to_remove.end())); + _heating_requests.remove(node); + _heating_request_bytes -= to_remove.size(); + } +} + +void ZMemoryWorker::register_heating_request(const ZVirtualMemory& vmem) { + precond(ZMemoryHeating); + + ZLocker locker(&_lock); + if (_stop) { + // Don't add more requests during termination + return; + } + + if (!has_heating_request() && _currently_heating.is_null()) { + // There is currently no heating going on, notify the memory worker. + _lock.notify_all(); + } + + // Verification + verify_heating_requests(); + ZOnScopeExit on_scope_exit([&]() { verify_heating_requests(); }); + + assert(_currently_heating.is_null() || !vmem.overlaps(_currently_heating), + "[" PTR_FORMAT ", " PTR_FORMAT "] [" PTR_FORMAT ", " PTR_FORMAT "]", + untype(vmem.start()), untype(vmem.end()), + untype(_currently_heating.start()), untype(_currently_heating.end())); + + const ZHeatingRequestTree::Cursor insert_cursor = _heating_requests.cursor(vmem.start()); + assert(!insert_cursor.found(), "must not be in tree"); + + // Account for the new size upfront + _heating_request_bytes += vmem.size(); + + const ZHeatingRequestTree::Cursor prev_cursor = _heating_requests.prev(insert_cursor); + const ZHeatingRequestTree::Cursor next_cursor = _heating_requests.next(insert_cursor); + + const bool extends_left = prev_cursor.valid() && prev_cursor.found() && + zoffset_end(prev_cursor.node()->key() + prev_cursor.node()->val()) == vmem.start(); + + const bool extends_right = next_cursor.valid() && next_cursor.found() && + zoffset_end(next_cursor.node()->key()) == vmem.end(); + + if (extends_left && extends_right) { + const ZVirtualMemory left_vmem(prev_cursor.node()->key(), prev_cursor.node()->val()); + const ZVirtualMemory right_vmem(next_cursor.node()->key(), next_cursor.node()->val()); + assert(left_vmem.adjacent_to(vmem), "must be"); + assert(vmem.adjacent_to(right_vmem), "must be"); + + const size_t new_size = left_vmem.size() + vmem.size() + right_vmem.size(); + // Update the left node's size + prev_cursor.node()->set_val(new_size); + + // And remove the right node + _heating_requests.remove(next_cursor.node()); + + return; + } + + if (extends_left) { + const ZVirtualMemory left_vmem(prev_cursor.node()->key(), prev_cursor.node()->val()); + assert(left_vmem.adjacent_to(vmem), "must be"); + + const size_t new_size = left_vmem.size() + vmem.size(); + prev_cursor.node()->set_val(new_size); + + return; + } + + if (extends_right) { + const ZVirtualMemory right_vmem(next_cursor.node()->key(), next_cursor.node()->val()); + assert(vmem.adjacent_to(right_vmem), "must be"); + + ZVirtualMemory new_vmem = vmem; + new_vmem.grow_from_back(right_vmem.size()); + + // Right now we don't have a way to change the key of an inserted CHeap-allocated + // node, so we have to remove the right node and insert a new node with the updated + // key and size. + _heating_requests.remove(next_cursor.node()); + + ZHeatingRequestNode* const new_node = _heating_requests.allocate_node(new_vmem.start(), new_vmem.size()); + _heating_requests.insert(new_node->key(), new_node); + + return; + } + + // The request does not extend another vmem to the left or right + ZHeatingRequestNode* const new_node = _heating_requests.allocate_node(vmem.start(), vmem.size()); + _heating_requests.insert(new_node->key(), new_node); +} + +ZVirtualMemory ZMemoryWorker::pop_heating_request() { + assert(has_heating_request(), "precondition"); + + // Verification + verify_heating_requests(); + ZOnScopeExit on_scope_exit([&]() { verify_heating_requests(); }); + + const size_t max_heating = 16 * ZGranuleSize; + + ZHeatingRequestNode* const node = _heating_requests.leftmost(); + const size_t size = node->val(); + + // If the node is small enough, we remove it entirely from the tree. Otherwise, + // we update the node's size in-place without removing and inserting it. + + if (size <= max_heating) { + const ZVirtualMemory vmem(node->key(), size); + _heating_requests.remove(node); + _heating_request_bytes -= size; + + return vmem; + } + + const size_t size_remainder = size - max_heating; + const ZVirtualMemory vmem(node->key() + size_remainder, max_heating); + + // Update the value of the node to the new size + node->set_val(size_remainder); + _heating_request_bytes -= max_heating; + + return vmem; +} + +void ZMemoryWorker::remove_heating_request(const ZVirtualMemory& vmem) { + precond(ZMemoryHeating); + + ZLocker locker(&_lock); + + // Verification + verify_heating_requests(); + ZOnScopeExit on_scope_exit([&]() { verify_heating_requests(); }); + + while (!_currently_heating.is_null() && vmem.overlaps(_currently_heating)) { + // Wait while currently heating overlaps with this vmem + _lock.wait(); + } + + const uintptr_t vmem_start = (uintptr_t)vmem.start(); + const uintptr_t vmem_end = vmem_start + vmem.size(); + + // Cut off overlap on the left + if (vmem.start() > zoffset(0)) { + ZHeatingRequestNode* const left_node = _heating_requests.closest_leq(vmem.start() - ZGranuleSize); + if (left_node != nullptr) { + const uintptr_t left_start = (uintptr_t)left_node->key(); + const size_t left_size = left_node->val(); + const uintptr_t left_end = left_start + left_size; + + if (left_end > vmem_start) { + // There is an intersection on the left side + const size_t left_leading = vmem_start - left_start; + left_node->set_val(left_leading); + + if (left_end > vmem_end) { + // Intersection continues to the right side + const size_t left_trailing = left_end - vmem_end; + ZHeatingRequestNode* const new_node = _heating_requests.allocate_node(zoffset(vmem_end), left_trailing); + _heating_requests.insert(zoffset(vmem_end), new_node); + _heating_request_bytes -= vmem.size(); + } else { + _heating_request_bytes -= left_size - left_leading; + } + } + } + } + + // Cut off overlap on the right + ZHeatingRequestNode* const right_node = _heating_requests.closest_leq(vmem.start() + vmem.size()); + if (right_node != nullptr) { + const uintptr_t right_start = (uintptr_t)right_node->key(); + const size_t right_size = right_node->val(); + const uintptr_t right_end = right_start + right_size; + + if (right_start < vmem_end && right_end > vmem_end) { + // There is an intersection on the right side + _heating_requests.remove(right_node); + const size_t right_trailing = right_end - vmem_end; + ZHeatingRequestNode* const new_node = _heating_requests.allocate_node(zoffset(vmem_end), right_trailing); + _heating_requests.insert(zoffset(vmem_end), new_node); + _heating_request_bytes -= right_size - right_trailing; + } + } + + remove_heating_request_range(vmem); +} + +size_t ZMemoryWorker::process_heating_request() { + ZVirtualMemory vmem; + { + ZLocker locker(&_lock); + if (!has_heating_request()) { + // Unmapping removed the request; bail + return 0; + } + + assert(has_heating_request(), "who else processed it?"); + + vmem = pop_heating_request(); + + assert(_currently_heating.is_null(), "must be"); + _currently_heating = vmem; + } + + _partition->heat_memory(vmem); + + { + ZLocker locker(&_lock); + _currently_heating = {}; + _lock.notify_all(); + } + + return vmem.size(); +} + +bool ZMemoryWorker::await_start() { + for (;;) { + if (_stop) { + return false; + } + + if (is_init_completed()) { + return true; + } + + // Don't start working until JVM is bootstrapped + _lock.wait(); + } +} + +class ZMemoryWorker::ZMemoryWorkerState { +private: + using Mode = ZMemoryWorkerMode; + ZMemoryWorker* const _worker; + Ticks _init_time; + Ticks _update_time; + size_t _processed; + size_t _current_target_capacity; + size_t _init_target_capacity; + size_t _target_capacity; + uint64_t _uncommit_delay; + Mode _mode; + + bool try_commit() { + precond(ZAdaptiveHeapSizing); + precond(_mode == Mode::Commit); + + const bool targetless_uncommit = _worker->_targetless_uncommit; + + if (targetless_uncommit && has_targetless_uncommit_matured(_update_time, _worker->_uncommit_request_time, _uncommit_delay)) { + const size_t min_capacity = _worker->_partition->_static_min_capacity; + if (_current_target_capacity > min_capacity) { + // This might cause _current_target_capacity to undershoot the current capacity. + // So we might not uncommit the whole uncommit granule in this target capacity truncation. + // We allow this discrepancy. + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const size_t shrink_amount = MIN2(_current_target_capacity - min_capacity, uncommit_size); + + // Shrink heuristic max + _worker->_partition->_page_allocator->shrink_heuristic_max(shrink_amount); + log_debug(gc, heap)("Memory Worker (%d) Prevented Commit: %zuM(%.0f%%)", + _worker->_id, shrink_amount / M, + percent_of(shrink_amount, _current_target_capacity)); + + assert(_worker->_target_commit_capacity == _current_target_capacity, + "Should not have released the lock since update targets"); + _current_target_capacity -= shrink_amount; + _worker->_target_commit_capacity -= shrink_amount; + _worker->_uncommit_request_time = _update_time; + } + } + + const size_t commit_size = _worker->commit_granule(_current_target_capacity); + const size_t processed = [&]() { + ZUnlocker unlocker(&_worker->_lock); + return _worker->_partition->increase_and_commit_capacity(commit_size, _current_target_capacity); + }(); + + _processed += processed; + + return processed > 0; + } + + bool try_uncommit() { + precond(ZAdaptiveHeapSizing); + precond(_mode == Mode::Uncommit); + precond(ZUncommit); + + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const size_t processed = [&]() { + ZUnlocker unlocker(&_worker->_lock); + return _worker->uncommit(uncommit_size); + }(); + + _processed += processed; + + return processed > 0; + } + + bool try_targetless_uncommit() { + precond(ZAdaptiveHeapSizing); + precond(_mode == Mode::UncommitTargetless); + precond(ZUncommit); + precond(_target_capacity == 0); + precond(_init_target_capacity == 0); + + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const size_t processed = [&]() { + ZUnlocker unlocker(&_worker->_lock); + return _worker->uncommit(uncommit_size); + }(); + + _processed += processed; + + return processed > 0; + } + + bool try_heat() { + if (!_worker->has_heating_request()) { + return false; + } + precond(ZMemoryHeating); + + const size_t processed = [&]() { + ZUnlocker unlocker(&_worker->_lock); + return _worker->process_heating_request(); + }(); + + _processed += processed; + + return processed > 0; + } + +public: + explicit ZMemoryWorkerState(ZMemoryWorker* worker) + : _worker(worker), + _init_time(), + _update_time(), + _processed(0), + _current_target_capacity(0), + _init_target_capacity(0), + _target_capacity(0), + _uncommit_delay(0), + _mode(Mode::Uninitialized) {} + + ~ZMemoryWorkerState() { + postcond(_mode != Mode::Uninitialized); + + if (_processed > 0) { + ZUnlocker unlocker(&_worker->_lock); + const char* const mode_string = memory_worker_completed_mode_name(_mode); + const auto elapsed = Ticks::now() - _init_time; + const size_t processed_mb = _processed / M; + const double process_rate_mb_per_sec = double(processed_mb) / elapsed.seconds(); + + if (_mode == Mode::UncommitTargetless) { + log_info(gc, heap)("Memory Worker (%d) %s: %zuM in %.3fms (%.2f MiB/s)", + _worker->_id, mode_string, processed_mb, + elapsed.seconds() * MILLIUNITS, process_rate_mb_per_sec); + } else { + LogTargetHandle lt = [&]() -> LogTargetHandle { + if (_mode == ZMemoryWorkerMode::Heat) { + return LogTarget(Debug, gc, heap)(); + } else { + return LogTarget(Info, gc, heap)(); + } + }(); + + const double percent_of_init_target = percent_of(_processed, _init_target_capacity); + lt.print("Memory Worker (%d) %s: %zuM(%.0f%%) in %.3fms (%.2f MiB/s)", + _worker->_id, mode_string, processed_mb, percent_of_init_target, + elapsed.seconds() * MILLIUNITS, process_rate_mb_per_sec); + } + } + } + + void select_mode() { + precond(_mode == Mode::Uninitialized); + + { + ZUnlocker unlocker(&_worker->_lock); + _init_time = Ticks::now(); + if (ZAdaptiveHeapSizing) { + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + _uncommit_delay = ZAdaptiveHeap::uncommit_delay(uncommit_size); + } + } + + const size_t target_commit_capacity = _worker->_target_commit_capacity; + const size_t target_uncommit_capacity = _worker->_target_uncommit_capacity; + const size_t capacity = _worker->_partition->capacity(); + const size_t min_capacity = _worker->_partition->_static_min_capacity; + + if (target_commit_capacity > 0 && capacity < target_commit_capacity) { + // First priority is committing memory + + postcond(ZAdaptiveHeapSizing); + postcond(target_uncommit_capacity == 0); + + _mode = Mode::Commit; + _init_target_capacity = target_commit_capacity; + } else if (target_uncommit_capacity > 0 && capacity > target_uncommit_capacity) { + // Second priority is uncommitting memory + + postcond(ZAdaptiveHeapSizing); + postcond(ZUncommit); + postcond(target_commit_capacity == 0); + postcond(target_uncommit_capacity >= min_capacity); + + _mode = Mode::Uncommit; + _init_target_capacity = target_uncommit_capacity; + } else { + // Third priority is uncommitting memory if a targetless uncommit matured + // or heating memory if available. + const bool targetless_uncommit_matured = _worker->_targetless_uncommit && ZUncommit && + has_targetless_uncommit_matured(_init_time, _worker->_uncommit_request_time, _uncommit_delay); + + if (targetless_uncommit_matured && capacity != min_capacity) { + postcond(ZAdaptiveHeapSizing); + _mode = Mode::UncommitTargetless; + } else if (_worker->has_heating_request()) { + _mode = Mode::Heat; + _init_target_capacity = _worker->_heating_request_bytes; + } else { + _mode = Mode::Wait; + } + + // Ensure that we have no other targets + _worker->_target_commit_capacity = 0u; + _worker->clear_targeted_uncommit(); + } + + LogTarget(Debug, gc, heap) lt; + + if (_init_target_capacity > 0 && lt.is_enabled()) { + ZUnlocker unlocker(&_worker->_lock); + + const size_t capacity = _worker->_partition->capacity(); + const char* const mode_string = memory_worker_start_mode_name(_mode); + + lt.print("Memory Worker (%d) Start %s, Target: %zuM(%.0f%%)", + _worker->_id, mode_string, _init_target_capacity / M, + percent_of(_init_target_capacity, capacity)); + } + + postcond(_mode != Mode::Uninitialized); + } + + bool update_targets() { + precond(_mode != Mode::Uninitialized); + + if (_mode == Mode::Wait) { + // Go directly to await + return true; + } + + { + ZUnlocker unlocker(&_worker->_lock); + _update_time = Ticks::now(); + if (ZAdaptiveHeapSizing) { + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + _uncommit_delay = ZAdaptiveHeap::uncommit_delay(uncommit_size); + } + } + + const bool targetless_uncommit = _worker->_targetless_uncommit; + const size_t capacity = _worker->_partition->capacity(); + const size_t target_commit_capacity = _worker->_target_commit_capacity; + const size_t target_uncommit_capacity = _worker->_target_uncommit_capacity; + + switch (_mode) { + case Mode::Commit: { + _current_target_capacity = target_commit_capacity; + return capacity < target_commit_capacity; + } + case Mode::Uncommit: { + _current_target_capacity = target_uncommit_capacity; + return target_uncommit_capacity != 0 && capacity > target_uncommit_capacity; + } + case Mode::UncommitTargetless: { + postcond(_init_target_capacity == 0); + postcond(_current_target_capacity == 0); + return target_commit_capacity == 0 && target_uncommit_capacity == 0 && targetless_uncommit; + } + case Mode::Heat: { + if (target_commit_capacity != 0 || target_uncommit_capacity != 0) { + // We have more important work to do + return false; + } + + return _processed < _init_target_capacity; + } + default: + return false; + } + } + + bool try_work() { + switch (_mode) { + case Mode::Commit: { + return try_commit(); + } + case Mode::Uncommit: { + if (!has_targeted_uncommit_matured(_update_time, _worker->_uncommit_request_time, _uncommit_delay)) { + // Wait for the uncommit request to mature + return true; + } + + if (try_uncommit()) { + // Set new uncommit request time + _worker->_uncommit_request_time = _update_time; + return true; + } + + // Failed to uncommit + // Reset the uncommit target + _worker->clear_targeted_uncommit(); + + return false; + } + case Mode::UncommitTargetless: { + if (!has_targetless_uncommit_matured(_update_time, _worker->_uncommit_request_time, _uncommit_delay)) { + // Wait for the uncommit request to mature + return true; + } + + if (try_targetless_uncommit()) { + // Set new uncommit request time + _worker->_uncommit_request_time = _update_time; + return true; + } + + // Failed to uncommit + // Stop targetless uncommit + _worker->clear_targetless_uncommit(); + + return false; + } + case Mode::Heat: { + return try_heat(); + } + case Mode::Wait: { + // Go directly to await + return true; + } + default: + ShouldNotReachHere(); + } + } + + bool await_work() { + switch (_mode) { + case Mode::Commit: { + // Commit immediately unless we have reached capacity + return _worker->_partition->capacity() < _current_target_capacity; + } + case Mode::Uncommit: { + precond(_current_target_capacity != 0); + + const auto get_wait_duration = [&]() -> uint64_t { + // Read the _uncommit_request_time before releasing the lock + const Ticks request_time = _worker->_uncommit_request_time; + + ZUnlocker unlocker(&_worker->_lock); + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const uint64_t targeted_delay = targeted_uncommit_delay(ZAdaptiveHeap::uncommit_delay(uncommit_size)); + return remaining_uncommit_wait_duration(request_time, targeted_delay); + }; + + const auto is_uncommit_done = [&]() { + const size_t capacity = _worker->_partition->capacity(); + const size_t target_uncommit_capacity = _worker->_target_uncommit_capacity; + return target_uncommit_capacity == 0 || capacity <= target_uncommit_capacity; + }; + + for (;;) { + // Is there more work? + if (_worker->is_stop_requested() || is_uncommit_done()) { + return false; + } + + const uint64_t wait_duration = get_wait_duration(); + + // Is there more work? + if (_worker->is_stop_requested() || is_uncommit_done()) { + return false; + } + + if (wait_duration == 0) { + // We are done waiting + return true; + } + + // Wait for uncommit + _worker->_lock.wait(wait_duration); + } + + return true; + } + case Mode::UncommitTargetless: { + // Never await targetless uncommit + return false; + } + case Mode::Heat: { + // Heat immediately if there are more requests + return _worker->has_heating_request(); + } + case Mode::Wait: { + const auto get_targetless_uncommit_wait_duration = [&]() -> uint64_t { + // Read the _uncommit_request_time before releasing the lock + const Ticks request_time = _worker->_uncommit_request_time; + + ZUnlocker unlocker(&_worker->_lock); + const size_t uncommit_size = _worker->uncommit_granule(_current_target_capacity); + const uint64_t uncommit_delay = ZAdaptiveHeap::uncommit_delay(uncommit_size); + return remaining_uncommit_wait_duration(request_time, uncommit_delay); + }; + + const auto check_for_work_non_targetless_uncommit = [&]() { + if (_worker->_target_commit_capacity > 0) { + // There is commit work to be done + return true; + } + + if (_worker->_target_uncommit_capacity > 0) { + // There is uncommit work to be done + return true; + } + + if (_worker->_heating_request_bytes > 0) { + // There is heating work to be done + return true; + } + + return false; + }; + + for (;;) { + if (check_for_work_non_targetless_uncommit() || _worker->is_stop_requested()) { + return false; + } + + // If the capacity is already minimal, wait until something changes externally. + const size_t capacity = _worker->_partition->capacity(); + const size_t min_capacity = _worker->_partition->_static_min_capacity; + + if (_worker->_targetless_uncommit && capacity != min_capacity) { + // There might be targetless uncommit work to be done + const uint64_t wait_duration = get_targetless_uncommit_wait_duration(); + + if (check_for_work_non_targetless_uncommit() || wait_duration == 0 || _worker->is_stop_requested()) { + // There is targetless work to be done or the targetless uncommit has matured + return false; + } + + // Wait for notification or targetless maturity + _worker->_lock.wait(wait_duration); + } else { + // Wait for more work + _worker->_lock.wait(); + } + } + } + default: + ShouldNotReachHere(); + } + } +}; + +class ZMemoryWorkerTimer { +private: + jlong _start; + +public: + ZMemoryWorkerTimer() + : _start(os::current_thread_cpu_time(true /* user + sys */)) {} + + ~ZMemoryWorkerTimer() { + jlong elapsed = os::current_thread_cpu_time(true /* user + sys */) - _start; + ZStatMemoryWorkers::add_accumulated_vtime(double(elapsed) / NANOSECS_PER_SEC); + } +}; + +void ZMemoryWorker::run_thread() { + // We always hold the lock, except when interacting with the OS, or awaiting + // more work. + ZLocker locker(&_lock); + + // Wait until started + if (!await_start()) { + // GC initialization failed + return; + } + + while (!_stop) { + ZMemoryWorkerState worker_state(this); + + worker_state.select_mode(); + + while (!_stop) { + ZMemoryWorkerTimer timer; + + if (!worker_state.update_targets()) { + break; + } + + if (!worker_state.try_work()) { + break; + } + + if (!worker_state.await_work()) { + break; + } + } + } +} + +size_t ZMemoryWorker::uncommit(size_t to_uncommit) { + ZArray flushed_vmems; + size_t flushed = 0; + + { + // We need to join the suspendible thread set while manipulating capacity + // and used, to make sure GC safepoints will have a consistent view. + SuspendibleThreadSetJoiner sts_joiner; + ZLocker locker(&_partition->_page_allocator->_lock); + + ZMappedCache& cache = _partition->_cache; + + // Never uncommit below min capacity + const size_t retain = MAX2(_partition->_used, _partition->_static_min_capacity); + const size_t release = _partition->_capacity - retain; + const size_t flush = MIN2(release, to_uncommit); + + // Flush memory from the mapped cache for uncommit + flushed = cache.remove_for_uncommit(flush, &flushed_vmems); + if (flushed == 0) { + // Nothing flushed + return 0; + } + + // Record flushed memory as claimed and how much we've flushed for this partition + _partition->increase_claimed(flushed); + } + + // Unmap and uncommit flushed memory + for (const ZVirtualMemory vmem : flushed_vmems) { + _partition->unmap_virtual(vmem); + _partition->uncommit_physical(vmem); + _partition->free_physical(vmem); + _partition->free_virtual(vmem); + } + + { + SuspendibleThreadSetJoiner sts_joiner; + ZLocker locker(&_partition->_page_allocator->_lock); + + // Adjust claimed and capacity to reflect the uncommit + _partition->decrease_claimed(flushed); + _partition->decrease_capacity(flushed); + } + + if (flushed > 0) { + _partition->_page_allocator->truncate_heuristic_max_after_capacity_decrease(ZPageAllocator::TruncationReason::Uncommit); + } + + return flushed; +} + +void ZMemoryWorker::terminate() { + ZLocker locker(&_lock); + _stop = true; + _lock.notify_all(); + + _heating_requests.remove_all(); + _heating_request_bytes = 0; + + while (!_currently_heating.is_null()) { + // Trying to unmap what's currently being heated; calm down! + _lock.wait(); + } +} diff --git a/src/hotspot/share/gc/z/zMemoryWorker.hpp b/src/hotspot/share/gc/z/zMemoryWorker.hpp new file mode 100644 index 000000000000..173b242434b2 --- /dev/null +++ b/src/hotspot/share/gc/z/zMemoryWorker.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SHARE_GC_Z_ZMEMORYWORKER_HPP +#define SHARE_GC_Z_ZMEMORYWORKER_HPP + +#include "gc/z/zAdaptiveHeap.hpp" +#include "gc/z/zAddress.hpp" +#include "gc/z/zArray.hpp" +#include "gc/z/zList.hpp" +#include "gc/z/zLock.hpp" +#include "gc/z/zThread.hpp" +#include "gc/z/zVirtualMemory.hpp" +#include "memory/allocation.hpp" +#include "utilities/rbTree.hpp" +#include "utilities/ticks.hpp" + +class ZPartition; + +// This worker is enabled with adaptive heap sizing. +// Its responsibilities are: +// * Change heap capacity +// - Commit memory concurrently when growing the heap +// - Uncommit memory concurrently due to long-term shrinking +// - Uncommit memory concurrently due to urgent memory pressure +// * Heat up memory before mutators start using it +// - Pre-touching memory concurrently +// - Collapse small pages to large pages +// There is one memory worker per heap partition + +class ZMemoryWorker : public ZThread { +private: + class ZMemoryWorkerState; + + class ZHeatingRequestTreeComparator : public AllStatic { + public: + static RBTreeOrdering cmp(zoffset first, zoffset second); + }; + + using ZHeatingRequestTree = RBTreeCHeap; + using ZHeatingRequestNode = RBNode; + + const uint32_t _id; + ZPartition* const _partition; + ZConditionLock _lock; + ZHeatingRequestTree _heating_requests; + size_t _heating_request_bytes; + size_t _target_commit_capacity; + size_t _target_uncommit_capacity; + bool _targetless_uncommit; + Ticks _uncommit_request_time; + bool _stop; + ZVirtualMemory _currently_heating; + + bool await_start(); + bool is_stop_requested(); + size_t commit_granule(size_t target_capacity); + size_t uncommit_granule(size_t target_capacity); + + size_t uncommit(size_t to_uncommit); + + bool has_heating_request(); + void remove_heating_request_range(const ZVirtualMemory& vmem); + ZVirtualMemory pop_heating_request(); + size_t process_heating_request(); + + void verify_heating_requests(); + + void set_targetless_uncommit(Ticks now); + void set_targeted_uncommit(size_t requested_capacity, Ticks now); + void clear_targetless_uncommit(); + void clear_targeted_uncommit(); + +protected: + virtual void run_thread(); + virtual void terminate(); + +public: + static bool is_enabled(); + + ZMemoryWorker(uint32_t id, ZPartition* partition); + ~ZMemoryWorker(); + + // Heap resizing requests + void stop_heap_resizing(); + void stop_shrink_capacity_granule(); + void request_grow_capacity(size_t requested_capacity); + void request_shrink_capacity(size_t requested_capacity); + void request_shrink_capacity_granule(); + + // Heating requests + void register_heating_request(const ZVirtualMemory& vmem); + void remove_heating_request(const ZVirtualMemory& vmem); +}; + +#endif // SHARE_GC_Z_ZMEMORYWORKER_HPP diff --git a/src/hotspot/share/gc/z/zNUMA.cpp b/src/hotspot/share/gc/z/zNUMA.cpp index cf2d88a90e5a..2d2225649669 100644 --- a/src/hotspot/share/gc/z/zNUMA.cpp +++ b/src/hotspot/share/gc/z/zNUMA.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ #include "utilities/macros.hpp" bool ZNUMA::_enabled; +uint32_t ZNUMA::_node_count; +uint32_t ZNUMA::_bound_node_count; uint32_t ZNUMA::_count; void ZNUMA::initialize() { diff --git a/src/hotspot/share/gc/z/zNUMA.hpp b/src/hotspot/share/gc/z/zNUMA.hpp index 838a114c210e..a3ff049bf492 100644 --- a/src/hotspot/share/gc/z/zNUMA.hpp +++ b/src/hotspot/share/gc/z/zNUMA.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,8 @@ class ZNUMA : public AllStatic { private: static bool _enabled; + static uint32_t _node_count; + static uint32_t _bound_node_count; static uint32_t _count; static void pd_initialize(); @@ -45,6 +47,9 @@ class ZNUMA : public AllStatic { static bool is_enabled(); static bool is_faked(); + static bool is_bound(); + + static uint32_t bound_count(); static uint32_t count(); static uint32_t id(); diff --git a/src/hotspot/share/gc/z/zNUMA.inline.hpp b/src/hotspot/share/gc/z/zNUMA.inline.hpp index d90b44e905a0..9c325f98b04d 100644 --- a/src/hotspot/share/gc/z/zNUMA.inline.hpp +++ b/src/hotspot/share/gc/z/zNUMA.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,6 +38,14 @@ inline bool ZNUMA::is_faked() { return ZFakeNUMA > 1; } +inline uint32_t ZNUMA::bound_count() { + return _bound_node_count; +} + +inline bool ZNUMA::is_bound() { + return _bound_node_count != _node_count; +} + inline uint32_t ZNUMA::count() { return _count; } diff --git a/src/hotspot/share/gc/z/zPageAllocator.cpp b/src/hotspot/share/gc/z/zPageAllocator.cpp index dbda9e9e9a21..95cab98dde5d 100644 --- a/src/hotspot/share/gc/z/zPageAllocator.cpp +++ b/src/hotspot/share/gc/z/zPageAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,8 +21,10 @@ * questions. */ +#include "gc/shared/gc_globals.hpp" #include "gc/shared/gcLogPrecious.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddress.hpp" #include "gc/z/zAllocationFlags.hpp" #include "gc/z/zArray.inline.hpp" @@ -33,7 +35,8 @@ #include "gc/z/zGlobals.hpp" #include "gc/z/zLargePages.inline.hpp" #include "gc/z/zLock.inline.hpp" -#include "gc/z/zMappedCache.hpp" +#include "gc/z/zMappedCache.inline.hpp" +#include "gc/z/zMemoryWorker.hpp" #include "gc/z/zNUMA.inline.hpp" #include "gc/z/zPage.inline.hpp" #include "gc/z/zPageAge.hpp" @@ -82,6 +85,12 @@ static void check_numa_mismatch(const ZVirtualMemory& vmem, uint32_t desired_id) } } +enum class ZPageAllocationAttempt { + initial, + retry, + stall, +}; + class ZMemoryAllocation : public CHeapObj { private: const size_t _size; @@ -92,7 +101,7 @@ class ZMemoryAllocation : public CHeapObj { size_t _harvested; size_t _increased_capacity; size_t _committed_capacity; - bool _commit_failed; + bool _has_set_committed_capacity; explicit ZMemoryAllocation(const ZMemoryAllocation& other) : ZMemoryAllocation(other._size) { @@ -124,7 +133,7 @@ class ZMemoryAllocation : public CHeapObj { void transfer_claimed_capacity(const ZMemoryAllocation& from) { assert(from._committed_capacity == 0, "Unexpected value %zu", from._committed_capacity); - assert(!from._commit_failed, "Unexpected value"); + assert(!_has_set_committed_capacity, "Should not have set committed capacity yet"); // Transfer increased capacity _increased_capacity += from._increased_capacity; @@ -153,7 +162,7 @@ class ZMemoryAllocation : public CHeapObj { _harvested(0), _increased_capacity(0), _committed_capacity(0), - _commit_failed(false) {} + _has_set_committed_capacity(false) {} void reset_for_retry() { assert(_satisfied_from_cache_vmem.is_null(), "Incompatible with reset"); @@ -164,7 +173,7 @@ class ZMemoryAllocation : public CHeapObj { _harvested = 0; _increased_capacity = 0; _committed_capacity = 0; - _commit_failed = false; + _has_set_committed_capacity = false; } size_t size() const { @@ -238,13 +247,14 @@ class ZMemoryAllocation : public CHeapObj { } void set_committed_capacity(size_t committed_capacity) { - assert(_committed_capacity == 0, "Should only commit once"); + precond(!_has_set_committed_capacity); _committed_capacity = committed_capacity; - _commit_failed = committed_capacity != _increased_capacity; + _has_set_committed_capacity = true; } bool commit_failed() const { - return _commit_failed; + precond(_has_set_committed_capacity); + return _committed_capacity != _increased_capacity; } static void destroy(ZMemoryAllocation* allocation) { @@ -586,6 +596,10 @@ ZPhysicalMemoryManager& ZPartition::physical_memory_manager() { return _page_allocator->_physical; } +ZLock* ZPartition::lock() const { + return &_page_allocator->_lock; +} + #ifdef ASSERT void ZPartition::verify_virtual_memory_multi_partition_association(const ZVirtualMemory& vmem) const { @@ -625,28 +639,125 @@ void ZPartition::verify_memory_allocation_association(const ZMemoryAllocation* a #endif // ASSERT -ZPartition::ZPartition(uint32_t numa_id, ZPageAllocator* page_allocator) +ZPartition::ZPartition(uint32_t numa_id, + ZPageAllocator* page_allocator, + size_t static_min_capacity, + size_t static_max_capacity) : _page_allocator(page_allocator), _cache(), _uncommitter(numa_id, this), - _min_capacity(ZNUMA::calculate_share(numa_id, page_allocator->min_capacity())), - _max_capacity(ZNUMA::calculate_share(numa_id, page_allocator->max_capacity())), - _current_max_capacity(_max_capacity), + _mem_worker(numa_id, this), + _static_min_capacity(ZNUMA::calculate_share(numa_id, static_min_capacity)), + _static_max_capacity(ZNUMA::calculate_share(numa_id, static_max_capacity)), _capacity(0), _claimed(0), _used(0), _numa_id(numa_id) {} +size_t ZPartition::dynamic_max_capacity() const { + return ZNUMA::calculate_share(_numa_id, _page_allocator->dynamic_max_capacity()); +} + +size_t ZPartition::current_max_capacity() const { + return ZNUMA::calculate_share(_numa_id, _page_allocator->current_max_capacity()); +} + +size_t ZPartition::static_max_capacity() const { + return _static_max_capacity; +} + +size_t ZPartition::capacity() const { + return AtomicAccess::load(&_capacity); +} + +const ZUncommitter& ZPartition::uncommitter() const { + return _uncommitter; +} + +ZUncommitter& ZPartition::uncommitter() { + return _uncommitter; +} + +const ZMemoryWorker& ZPartition::memory_worker() const { + return _mem_worker; +} + +ZMemoryWorker& ZPartition::memory_worker() { + return _mem_worker; +} + uint32_t ZPartition::numa_id() const { return _numa_id; } -size_t ZPartition::available() const { - return _current_max_capacity - _used - _claimed; +size_t ZPartition::available(ZPageAllocationAttempt attempt, size_t capacity_limit) const { + assert(_capacity == _used + _claimed + _cache.size(), "Should be consistent" + " _capacity: %zx _used: %zx _claimed: %zx _cache.size(): %zx", + _capacity, _used, _claimed, _cache.size()); + + if (attempt == ZPageAllocationAttempt::initial) { + return available_from_any_capacity(capacity_limit); + } + + if (attempt == ZPageAllocationAttempt::retry || attempt == ZPageAllocationAttempt::stall) { + return available_from_cache(capacity_limit); + } + + ShouldNotReachHere(); +} + +size_t ZPartition::available_from_any_capacity(size_t capacity_limit) const { + assert(_capacity == _used + _claimed + _cache.size(), "Should be consistent" + " _capacity: %zx _used: %zx _claimed: %zx _cache.size(): %zx", + _capacity, _used, _claimed, _cache.size()); + assert(capacity_limit <= _static_max_capacity, "Invalid capacity limit for partition: %zx > %zx", capacity_limit, _static_max_capacity); + const size_t unavailable = _used + _claimed; + + if (capacity_limit < unavailable) { + // The current max capacity may be below what is handed out + return 0; + } + + return capacity_limit - unavailable; +} + +size_t ZPartition::available_from_increase_capacity(size_t capacity_limit) const { + precond(capacity_limit >= _capacity); + const size_t available = ZPartition::available_from_any_capacity(capacity_limit); + const size_t cached = _cache.size(); + + return available - cached; +} + +size_t ZPartition::available_from_cache(size_t capacity_limit) const { + const size_t available = ZPartition::available_from_any_capacity(capacity_limit); + const size_t cached = _cache.size(); + + // The current allowed available may be below what is in the cache + return MIN2(available, cached); } -size_t ZPartition::increase_capacity(size_t size) { - const size_t increased = MIN2(size, _current_max_capacity - _capacity); +size_t ZPartition::try_increase_capacity(size_t size, ZPageAllocationAttempt attempt, size_t capacity_limit) { + if (attempt == ZPageAllocationAttempt::initial) { + // We should only increase the capacity if the capacity limit is greater + // than the already committed memory, i.e., the capacity. + return (capacity_limit > _capacity) + ? increase_capacity(size, capacity_limit) + : 0; + } + + if (attempt == ZPageAllocationAttempt::retry || attempt == ZPageAllocationAttempt::stall) { + return 0; + } + + ShouldNotReachHere(); +} + +size_t ZPartition::increase_capacity(size_t size, size_t capacity_limit) { + assert(capacity_limit > _capacity, "Cannot increase capacity. capacity limit: %zu, capacity: %zu", capacity_limit, _capacity); + + const size_t available = available_from_increase_capacity(capacity_limit); + const size_t increased = MIN2(size, available); if (increased > 0) { // Update atomically since we have concurrent readers @@ -658,21 +769,9 @@ size_t ZPartition::increase_capacity(size_t size) { return increased; } -void ZPartition::decrease_capacity(size_t size, bool set_max_capacity) { +void ZPartition::decrease_capacity(size_t size) { // Update capacity atomically since we have concurrent readers AtomicAccess::sub(&_capacity, size); - - // Adjust current max capacity to avoid further attempts to increase capacity - if (set_max_capacity) { - const size_t current_max_capacity_before = _current_max_capacity; - AtomicAccess::store(&_current_max_capacity, _capacity); - - log_debug_p(gc)("Forced to lower max partition (%u) capacity from " - "%zuM(%.0f%%) to %zuM(%.0f%%)", - _numa_id, - current_max_capacity_before / M, percent_of(current_max_capacity_before, _max_capacity), - _current_max_capacity / M, percent_of(_current_max_capacity, _max_capacity)); - } } void ZPartition::increase_used(size_t size) { @@ -689,7 +788,36 @@ void ZPartition::decrease_used(size_t size) { _used -= size; } -void ZPartition::free_memory(const ZVirtualMemory& vmem) { +void ZPartition::increase_claimed(size_t size) { + // Update claimed atomically since we have concurrent readers + AtomicAccess::add(&_claimed, size); +} + +void ZPartition::decrease_claimed(size_t size) { + // Update claimed atomically since we have concurrent readers + AtomicAccess::sub(&_claimed, size); +} + +static void pretouch_memory(zoffset start, size_t size) { + // At this point we know that we have a valid zoffset / zaddress. + const zaddress zaddr = ZOffset::address(start); + const uintptr_t addr = untype(zaddr); + const size_t page_size = ZLargePages::is_explicit() ? ZGranuleSize : os::vm_page_size(); + os::pretouch_memory((void*)addr, (void*)(addr + size), page_size); +} + +void ZPartition::heat_memory(const ZVirtualMemory& vmem) const { + verify_virtual_memory_association(vmem, true /* check_multi_partition */); + + const ZPhysicalMemoryManager& manager = physical_memory_manager(); + + pretouch_memory(vmem.start(), vmem.size()); + if (ZLargePages::is_collapse()) { + manager.collapse(vmem); + } +} + +void ZPartition::free_used_memory(const ZVirtualMemory& vmem) { const size_t size = vmem.size(); // Cache the vmem @@ -699,12 +827,22 @@ void ZPartition::free_memory(const ZVirtualMemory& vmem) { decrease_used(size); } -void ZPartition::claim_from_cache_or_increase_capacity(ZMemoryAllocation* allocation) { +void ZPartition::free_claimed_memory(const ZVirtualMemory& vmem) { + const size_t size = vmem.size(); + + // Cache the vmem + _cache.insert(vmem); + + // Update accounting + decrease_claimed(size); +} + +void ZPartition::claim_from_cache_or_increase_capacity(ZMemoryAllocation* allocation, ZPageAllocationAttempt attempt, size_t capacity_limit) { const size_t size = allocation->size(); ZArray* const out = allocation->partial_vmems(); // We are guaranteed to succeed the claiming of capacity here - assert(available() >= size, "Must be"); + assert(available(attempt, capacity_limit) >= size, "Must be"); // Associate the allocation with this partition. allocation->set_partition(this); @@ -720,7 +858,7 @@ void ZPartition::claim_from_cache_or_increase_capacity(ZMemoryAllocation* alloca } // Try increase capacity - const size_t increased_capacity = increase_capacity(size); + const size_t increased_capacity = try_increase_capacity(size, attempt, capacity_limit); allocation->set_increased_capacity(increased_capacity); @@ -739,20 +877,20 @@ void ZPartition::claim_from_cache_or_increase_capacity(ZMemoryAllocation* alloca assert(harvested + increased_capacity == size, "Mismatch harvested: %zu increased_capacity: %zu size: %zu", - harvested, increased_capacity, size); + harvested / M, increased_capacity / M, size / M); return; } -bool ZPartition::claim_capacity(ZMemoryAllocation* allocation) { +bool ZPartition::claim_capacity(ZMemoryAllocation* allocation, ZPageAllocationAttempt attempt, size_t capacity_limit) { const size_t size = allocation->size(); - if (available() < size) { + if (available(attempt, capacity_limit) < size) { // Out of memory return false; } - claim_from_cache_or_increase_capacity(allocation); + claim_from_cache_or_increase_capacity(allocation, attempt, capacity_limit); // Updated used statistics increase_used(size); @@ -761,12 +899,22 @@ bool ZPartition::claim_capacity(ZMemoryAllocation* allocation) { return true; } -bool ZPartition::claim_capacity_fast_medium(ZMemoryAllocation* allocation) { +bool ZPartition::claim_capacity_fast_medium(ZMemoryAllocation* allocation, size_t capacity_limit) { precond(ZPageSizeMediumEnabled); // Try to allocate a medium page sized contiguous vmem + const size_t available_from_cache_limit = available_from_cache(capacity_limit); + const size_t power_of_2_limit = available_from_cache_limit == 0 + ? 0 + : round_down_power_of_2(available_from_cache_limit); const size_t min_size = ZPageSizeMediumMin; - const size_t max_size = ZStressFastMediumPageAllocation ? min_size : ZPageSizeMediumMax; + + if (power_of_2_limit < min_size) { + // No medium allocation size available within the capacity limit + return false; + } + + const size_t max_size = MIN2(ZStressFastMediumPageAllocation ? min_size : ZPageSizeMediumMax, power_of_2_limit); ZVirtualMemory vmem = _cache.remove_contiguous_power_of_2(min_size, max_size); if (vmem.is_null()) { @@ -777,7 +925,7 @@ bool ZPartition::claim_capacity_fast_medium(ZMemoryAllocation* allocation) { // Found a satisfying vmem in the cache allocation->set_satisfied_from_cache_vmem_fast_medium(vmem); - // Associate the allocation with this partition. + // Associate the allocation with this partition allocation->set_partition(this); // Updated used statistics @@ -787,6 +935,87 @@ bool ZPartition::claim_capacity_fast_medium(ZMemoryAllocation* allocation) { return true; } +size_t ZPartition::increase_and_commit_capacity(size_t size, size_t capacity_limit) { + assert(Thread::current()->is_ConcurrentGC_thread(), "Should only be called by concurrent GC threads"); + + size_t commit_size; + { + ZLocker locker(lock()); + + // We are not increasing capacity, so exit early + if (capacity_limit <= _capacity) { + return 0; + } + + commit_size = increase_capacity(size, capacity_limit); + + if (commit_size == 0) { + return 0; + } + + // We account for memory that is about to be committed as claimed + increase_claimed(commit_size); + } + + ZArray vmems; + + const size_t claimed_virtual = claim_virtual(commit_size, &vmems); + + const bool depleted_virtual = claimed_virtual != commit_size; + + size_t total_committed = 0; + bool commit_failed = depleted_virtual; + ZArray to_free(vmems.length()); + for (ZVirtualMemory vmem : vmems) { + if (commit_failed) { + // If a commit has failed free any remaining vmems + free_virtual(vmem); + continue; + } + + // Claim physical + claim_physical(vmem); + + // Commit memory + const size_t committed = commit_physical(vmem); + + // Keep track of total committed + total_committed += committed; + + if (committed != vmem.size()) { + commit_failed = true; + const ZVirtualMemory not_committed_vmem = vmem.shrink_from_back(vmem.size() - committed); + free_physical(not_committed_vmem); + free_virtual(not_committed_vmem); + } + + if (vmem.size() != 0) { + // Map memory. Since this function is called from the ZMemoryWorker, + // we heat memory synchronously instead of sending a heating request. + map_virtual(vmem, false /* heat */); + + heat_memory(vmem); + + to_free.push(vmem); + } + } + + if (commit_failed) { + // A commit has failed + + ZLocker locker(lock()); + const size_t not_committed = commit_size - total_committed; + decrease_capacity(not_committed); + decrease_claimed(not_committed); + _page_allocator->truncate_heuristic_max_after_capacity_decrease(ZPageAllocator::TruncationReason::CommitFailure); + } + + // Free the memory, which puts it into the cache + _page_allocator->free_claimed_memory(&to_free); + + return total_committed; +} + void ZPartition::sort_segments_physical(const ZVirtualMemory& vmem) { verify_virtual_memory_association(vmem, true /* check_multi_partition */); @@ -820,7 +1049,9 @@ size_t ZPartition::commit_physical(const ZVirtualMemory& vmem) { ZPhysicalMemoryManager& manager = physical_memory_manager(); // Commit physical memory - return manager.commit(vmem, _numa_id); + const size_t committed = manager.commit(vmem, _numa_id); + + return committed; } size_t ZPartition::uncommit_physical(const ZVirtualMemory& vmem) { @@ -830,45 +1061,65 @@ size_t ZPartition::uncommit_physical(const ZVirtualMemory& vmem) { ZPhysicalMemoryManager& manager = physical_memory_manager(); // Uncommit physical memory - return manager.uncommit(vmem); -} + const size_t uncommitted = manager.uncommit(vmem); -void ZPartition::map_virtual(const ZVirtualMemory& vmem) { - verify_virtual_memory_association(vmem); - - ZPhysicalMemoryManager& manager = physical_memory_manager(); + return uncommitted; +} - // Map virtual memory to physical memory - manager.map(vmem, _numa_id); +void ZPartition::map_virtual(const ZVirtualMemory& vmem, bool heat_memory) { + map_virtual_inner(vmem, false /* multi_partition */, false /* sort_segments */, heat_memory); } void ZPartition::unmap_virtual(const ZVirtualMemory& vmem) { - verify_virtual_memory_association(vmem); + unmap_virtual_inner(vmem, false /* multi_partition */); +} - ZPhysicalMemoryManager& manager = physical_memory_manager(); +void ZPartition::map_virtual_from_multi_partition(const ZVirtualMemory& vmem) { + map_virtual_inner(vmem, true /* multi_partition */, true /* sort_segments */, true /* register_heating */); +} - // Unmap virtual memory from physical memory - manager.unmap(vmem); +void ZPartition::unmap_virtual_from_multi_partition(const ZVirtualMemory& vmem) { + unmap_virtual_inner(vmem, true /* multi_partition */); } -void ZPartition::map_virtual_from_multi_partition(const ZVirtualMemory& vmem) { - verify_virtual_memory_multi_partition_association(vmem); +void ZPartition::map_virtual_inner(const ZVirtualMemory& vmem, bool multi_partition, bool sort_segments, bool register_heating) { + if (multi_partition) { + verify_virtual_memory_multi_partition_association(vmem); + } else { + verify_virtual_memory_association(vmem); + } ZPhysicalMemoryManager& manager = physical_memory_manager(); - // Sort physical segments - manager.sort_segments_physical(vmem); + if (sort_segments) { + // Sort physical segments before mapping multi-partition memory. + manager.sort_segments_physical(vmem); + } - // Map virtual memory to physical memory + // Map virtual memory to physical memory. manager.map(vmem, _numa_id); + + if (register_heating && ZMemoryHeating) { + // Register a heating request for this mapping. + _mem_worker.register_heating_request(vmem); + } } -void ZPartition::unmap_virtual_from_multi_partition(const ZVirtualMemory& vmem) { - verify_virtual_memory_multi_partition_association(vmem); +void ZPartition::unmap_virtual_inner(const ZVirtualMemory& vmem, bool multi_partition) { + if (multi_partition) { + verify_virtual_memory_multi_partition_association(vmem); + } else { + verify_virtual_memory_association(vmem); + } + + if (ZMemoryHeating) { + // Remove any heating request before unmapping. + _mem_worker.remove_heating_request(vmem); + } ZPhysicalMemoryManager& manager = physical_memory_manager(); - // Unmap virtual memory from physical memory + // Unmap virtual memory from physical memory. manager.unmap(vmem); } @@ -911,22 +1162,16 @@ ZVirtualMemory ZPartition::free_and_claim_virtual_from_low_exact_or_many(size_t return manager.insert_and_remove_from_low_exact_or_many(size, _numa_id, vmems_in_out); } -static void pretouch_memory(zoffset start, size_t size) { - // At this point we know that we have a valid zoffset / zaddress. - const zaddress zaddr = ZOffset::address(start); - const uintptr_t addr = untype(zaddr); - const size_t page_size = ZLargePages::is_explicit() ? ZGranuleSize : os::vm_page_size(); - os::pretouch_memory((void*)addr, (void*)(addr + size), page_size); -} - -class ZPreTouchTask : public ZTask { +class ZPreHeatTask : public ZTask { private: + ZPartition* const _partition; volatile uintptr_t _current; const uintptr_t _end; public: - ZPreTouchTask(zoffset start, zoffset_end end) - : ZTask("ZPreTouchTask"), + ZPreHeatTask(ZPartition* partition, zoffset start, zoffset_end end) + : ZTask("ZPreHeatTask", false /* account_vtime */), + _partition(partition), _current(untype(start)), _end(untype(end)) {} @@ -944,13 +1189,13 @@ class ZPreTouchTask : public ZTask { // At this point we know that we have a valid zoffset / zaddress. const zoffset offset = to_zoffset(claimed); - // Pre-touch the granule - pretouch_memory(offset, size); + // Pre-heat the granule + _partition->heat_memory(ZVirtualMemory(offset, size)); } } }; -bool ZPartition::prime(ZWorkers* workers, size_t size) { +bool ZPartition::prime(ZWorkers* workers, size_t size, size_t capacity_limit) { if (size == 0) { return true; } @@ -964,7 +1209,7 @@ bool ZPartition::prime(ZWorkers* workers, size_t size) { assert(claimed_size == size, "must succeed %zx == %zx", claimed_size, size); // Increase capacity - increase_capacity(claimed_size); + increase_capacity(claimed_size, capacity_limit); for (ZVirtualMemory vmem : vmems) { // Claim the backing physical memory @@ -978,13 +1223,17 @@ bool ZPartition::prime(ZWorkers* workers, size_t size) { return false; } - map_virtual(vmem); + // The primed memory should be "heated" asynchronously if it shouldn't be + // pre-touched up front during startup. + const bool async_heat = !AlwaysPreTouch; + + map_virtual(vmem, async_heat); check_numa_mismatch(vmem, _numa_id); - if (AlwaysPreTouch) { - // Pre-touch memory - ZPreTouchTask task(vmem.start(), vmem.end()); + if (!async_heat) { + // Pre-heat memory synchronously + ZPreHeatTask task(this, vmem.start(), vmem.end()); workers->run_all(&task); } @@ -1055,15 +1304,21 @@ void ZPartition::copy_physical_segments_from_partition(const ZVirtualMemory& at, manager.copy_physical_segments(to, at); } -void ZPartition::commit_increased_capacity(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem) { +void ZPartition::commit_increased_capacity(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem, size_t allowed_to_commit) { assert(allocation->increased_capacity() > 0, "Nothing to commit"); + assert(allowed_to_commit > 0, "Not allowed to commit anything"); const size_t already_committed = allocation->harvested(); - const ZVirtualMemory to_be_committed_vmem = vmem.last_part(already_committed); + assert(to_be_committed_vmem.size() == allocation->increased_capacity(), "must be the same size"); + + const size_t should_commit = MIN2(to_be_committed_vmem.size(), allowed_to_commit); + assert(should_commit > 0, "There must be something to commit"); - // Try to commit the uncommitted physical memory - const size_t committed = commit_physical(to_be_committed_vmem); + const ZVirtualMemory should_commit_vmem = to_be_committed_vmem.first_part(should_commit); + + // Try to commit the not-committed physical memory + const size_t committed = commit_physical(should_commit_vmem); // Keep track of the committed amount allocation->set_committed_capacity(committed); @@ -1096,20 +1351,25 @@ void ZPartition::free_memory_alloc_failed(ZMemoryAllocation* allocation) { // Adjust capacity to reflect the failed capacity increase const size_t remaining = allocation->size() - freed; if (remaining > 0) { - const bool set_max_capacity = allocation->commit_failed(); - decrease_capacity(remaining, set_max_capacity); + decrease_capacity(remaining); } } void ZPartition::threads_do(ThreadClosure* tc) const { - tc->do_thread(const_cast(&_uncommitter)); + if (ZUncommitter::is_enabled()) { + tc->do_thread(const_cast(&_uncommitter)); + } + + if (ZMemoryWorker::is_enabled()) { + tc->do_thread(const_cast(&_mem_worker)); + } } void ZPartition::print_on(outputStream* st) const { st->print("Partition %u ", _numa_id); st->fill_to(17); st->print_cr("used %zuM, capacity %zuM, max capacity %zuM", - _used / M, _capacity / M, _max_capacity / M); + _used / M, _capacity / M, dynamic_max_capacity() / M); StreamIndentor si(st, 1); print_cache_on(st); @@ -1204,19 +1464,20 @@ class ZMultiPartitionTracker : CHeapObj { } }; -ZPageAllocator::ZPageAllocator(size_t min_capacity, +ZPageAllocator::ZPageAllocator(size_t static_min_capacity, size_t initial_capacity, size_t soft_max_capacity, - size_t max_capacity) + size_t static_max_capacity) : _lock(), - _virtual(max_capacity), - _physical(max_capacity), - _min_capacity(min_capacity), - _max_capacity(max_capacity), + _virtual(static_max_capacity), + _physical(static_min_capacity, static_max_capacity), + _static_min_capacity(static_min_capacity), + _static_max_capacity(static_max_capacity), + _heuristic_max_capacity(ZAdaptiveHeapSizing ? initial_capacity : static_max_capacity), _used(0), _used_generations{0,0}, _collection_stats{{0, 0},{0, 0}}, - _partitions(ZValueIdTagType{}, this), + _partitions(ZValueIdTagType{}, this, static_min_capacity, static_max_capacity), _stalled(), _safe_destroy(), _initialized(false) { @@ -1225,10 +1486,12 @@ ZPageAllocator::ZPageAllocator(size_t min_capacity, return; } - log_info_p(gc, init)("Min Capacity: %zuM", min_capacity / M); + log_info_p(gc, init)("Min Capacity: %zuM", static_min_capacity / M); log_info_p(gc, init)("Initial Capacity: %zuM", initial_capacity / M); - log_info_p(gc, init)("Max Capacity: %zuM", max_capacity / M); - log_info_p(gc, init)("Soft Max Capacity: %zuM", soft_max_capacity / M); + log_info_p(gc, init)("Max Capacity: %zuM", static_max_capacity / M); + if (soft_max_capacity != 0) { + log_info_p(gc, init)("Soft Max Capacity: %zuM", soft_max_capacity / M); + } if (ZPageSizeMediumEnabled) { if (ZPageSizeMediumMin == ZPageSizeMediumMax) { log_info_p(gc, init)("Page Size Medium: %zuM", ZPageSizeMediumMax / M); @@ -1239,12 +1502,15 @@ ZPageAllocator::ZPageAllocator(size_t min_capacity, log_info_p(gc, init)("Medium Page Size: N/A"); } log_info_p(gc, init)("Pre-touch: %s", AlwaysPreTouch ? "Enabled" : "Disabled"); + ZAdaptiveHeap::print(); + log_info_p(gc, init)("Memory Heating: %s", ZMemoryHeating ? "Enabled" : "Disabled"); // Warn if system limits could stop us from reaching max capacity - _physical.warn_commit_limits(max_capacity); + const size_t expected_capacity = ZAdaptiveHeap::explicit_max_capacity() + ? static_max_capacity + : initial_capacity; - // Check if uncommit should and can be enabled - _physical.try_enable_uncommit(min_capacity, max_capacity); + _physical.warn_commit_limits(expected_capacity, static_max_capacity); // Successfully initialized _initialized = true; @@ -1259,8 +1525,9 @@ bool ZPageAllocator::prime_cache(ZWorkers* workers, size_t size) { for (ZPartition* partition; iter.next(&partition);) { const uint32_t numa_id = partition->numa_id(); const size_t to_prime = ZNUMA::calculate_share(numa_id, size); + const size_t partition_limit = ZNUMA::calculate_share(numa_id, _static_max_capacity); - if (!partition->prime(workers, to_prime)) { + if (!partition->prime(workers, to_prime, partition_limit)) { return false; } } @@ -1268,29 +1535,147 @@ bool ZPageAllocator::prime_cache(ZWorkers* workers, size_t size) { return true; } -size_t ZPageAllocator::min_capacity() const { - return _min_capacity; +size_t ZPageAllocator::static_min_capacity() const { + return _static_min_capacity; } -size_t ZPageAllocator::max_capacity() const { - return _max_capacity; +size_t ZPageAllocator::static_max_capacity() const { + return _static_max_capacity; } -size_t ZPageAllocator::soft_max_capacity() const { - const size_t current_max_capacity = ZPageAllocator::current_max_capacity(); - const size_t soft_max_heapsize = AtomicAccess::load(&SoftMaxHeapSize); - return MIN2(soft_max_heapsize, current_max_capacity); +size_t ZPageAllocator::dynamic_max_capacity() const { + if (!ZAdaptiveHeapSizing) { + return _static_max_capacity; + } + + physical_memory_size_type result = ZAdaptiveHeap::machine_physical_memory(); + + if (!os::is_containerized()) { + return clamp(align_down(size_t(result), ZGranuleSize), _static_min_capacity, _static_max_capacity); + } + + physical_memory_size_type hard_container_limit; + if (os::Container::memory_limit(hard_container_limit)) { + result = MIN2(result, hard_container_limit); + } + + physical_memory_size_type throttle_container_limit; + if (os::Container::memory_throttle_limit(throttle_container_limit)) { + result = MIN2(result, throttle_container_limit); + } + + return clamp(align_down(size_t(result), ZGranuleSize), _static_min_capacity, _static_max_capacity); +} + +static size_t calculate_system_max_capacity(size_t system_used, + size_t system_max, + size_t capacity, + size_t min_capacity, + size_t max_capacity) { + // It is a bit naive to assume all available memory can be directly turned + // into our own heap memory. We need auxiliary GC data structures, and other + // processes can also take the memory as we might not be alone. By scaling + // the available memory we stay on the pessimistic size, and let the estimated + // current max capacity grow gradually as we approach the limits instead. + const double near_avoid = (1.0 - ZMemoryCriticalThreshold); + const size_t scaled_system_max = size_t(double(system_max) * near_avoid); + size_t result; + + // Calculate capacity limits based on availability + if (system_used > scaled_system_max) { + const size_t overcommitted = system_used - scaled_system_max; + + if (overcommitted > capacity) { + return min_capacity; + } + + result = capacity - overcommitted; + } else { + const double system_available = double(scaled_system_max - system_used); + const size_t scaled_system_available = size_t(system_available * near_avoid); + + result = capacity + scaled_system_available; + } + + // Clamp and align the max capacity by its legal memory bounds + return clamp(align_down(result, ZGranuleSize), min_capacity, max_capacity); } size_t ZPageAllocator::current_max_capacity() const { - size_t current_max_capacity = 0; + if (!ZAdaptiveHeapSizing) { + // When not adapting use supplied max capacity + return _static_max_capacity; + } - ZPartitionConstIterator iter = partition_iterator(); - for (const ZPartition* partition; iter.next(&partition);) { - current_max_capacity += AtomicAccess::load(&partition->_current_max_capacity); + ZMachineMemoryInfo machine_memory_info = ZAdaptiveHeap::machine_memory_info(); + if (!machine_memory_info._is_valid) { + return dynamic_max_capacity(); + } + + const size_t machine_used_memory = size_t(machine_memory_info._physical_memory - machine_memory_info._available_memory); + const size_t machine_max_memory = size_t(machine_memory_info._physical_memory); + + const size_t cap = capacity(); + + const size_t machine_max_capacity = calculate_system_max_capacity(machine_used_memory, + machine_max_memory, + cap, + _static_min_capacity, + _static_max_capacity); + + if (!os::is_containerized()) { + return machine_max_capacity; + } + + physical_memory_size_type container_max_memory; + + // Keep below the hard memory limit or the OOM killer will get us + if (!os::Container::memory_limit(container_max_memory)) { + container_max_memory = machine_max_memory; + } + + // Avoid allocating past the throttle limit; the app will become useless here + physical_memory_size_type container_high_memory; + if (os::Container::memory_throttle_limit(container_high_memory)) { + container_max_memory = MIN2(container_max_memory, container_high_memory); + } + + physical_memory_size_type container_used_memory; + + if (!os::Container::used_memory(container_used_memory)) { + // If we can't measure memory usage in the container, we also can not compare + // it to any container limits. Effectively, the JVM is not appropriately + // containerized, so use machine max capacity. + return machine_max_capacity; + } + + const size_t container_max_capacity = calculate_system_max_capacity(size_t(container_used_memory), + container_max_memory, + cap, + _static_min_capacity, + _static_max_capacity); + + return MIN2(machine_max_capacity, container_max_capacity); +} + +static size_t clamp_to_soft_max_capacity(size_t value) { + assert(is_aligned(value, ZGranuleSize), "Must be granule aligned: 0x%08zx", value); + // Note that SoftMaxHeapSize is a manageable flag + const size_t soft_max_capacity = AtomicAccess::load(&SoftMaxHeapSize); + + if (soft_max_capacity == 0) { + // SoftMaxHeapSize is disabled / unspecified + return value; } - return current_max_capacity; + // Clamp to SoftMaxHeapSize, but should not be less than MinHeapSize + return clamp(value, MinHeapSize, MAX2(MinHeapSize, align_down(soft_max_capacity, ZGranuleSize))); +} + +size_t ZPageAllocator::heuristic_max_capacity() const { + const size_t heuristic_max_capacity = _heuristic_max_capacity.load_relaxed(); + + return clamp_to_soft_max_capacity(heuristic_max_capacity); } size_t ZPageAllocator::capacity() const { @@ -1327,6 +1712,166 @@ size_t ZPageAllocator::unused() const { return unused > 0 ? (size_t)unused : 0; } +void ZPageAllocator::shrink_heuristic_max(size_t shrink_amount) { + for (;;) { + const size_t heuristic_max = _heuristic_max_capacity.load_relaxed(); + const size_t capacity = MAX2(heuristic_max - MIN2(shrink_amount, heuristic_max), ZGranuleSize); + + if (heuristic_max <= capacity) { + return; + } + + if (_heuristic_max_capacity.compare_exchange(heuristic_max, capacity) == heuristic_max) { + break; + } + } +} + +void ZPageAllocator::truncate_heuristic_max_after_capacity_decrease(TruncationReason reason) { + // Adjust heuristic max capacity to ensure GC tries to keep below current capacity + const size_t capacity = ZPageAllocator::capacity(); + + for (;;) { + const size_t heuristic_max = _heuristic_max_capacity.load_relaxed(); + if (heuristic_max <= capacity) { + return; + } + if (_heuristic_max_capacity.compare_exchange(heuristic_max, capacity) != heuristic_max) { + continue; + } + const size_t current_max = current_max_capacity(); + if (reason == TruncationReason::CommitFailure) { + log_debug(gc)("Forced to lower heap size from " + "%zuM(%.0f%%) to %zuM(%.0f%%)", + heuristic_max / M, percent_of(heuristic_max, current_max), + capacity / M, percent_of(capacity, current_max)); + } + + if (ZAdaptiveHeapSizing) { + heap_truncated(capacity, reason); + } + return; + } +} + +void ZPageAllocator::adapt_heuristic_max_capacity(ZGenerationId generation) { + const size_t heuristic_max = heuristic_max_capacity(); + const size_t static_min_capacity = _static_min_capacity; + const size_t used = ZPageAllocator::used(); + const size_t capacity = MAX2(ZPageAllocator::capacity(), used); + const size_t curr_max_capacity = MAX2(capacity, current_max_capacity()); + const size_t highest_soft_capacity = clamp_to_soft_max_capacity(curr_max_capacity); + + const double alloc_rate = ZStatMutatorAllocRate::stats()._avg; + + const ZHeapResizeMetrics metrics = { + highest_soft_capacity, + curr_max_capacity, + heuristic_max, + static_min_capacity, + capacity, + used, + alloc_rate + }; + + const size_t selected_capacity = ZAdaptiveHeap::compute_heap_size(metrics, generation); + + // Update heuristic max capacity + _heuristic_max_capacity.store_relaxed(selected_capacity); + + heap_resized(selected_capacity); +} + +void ZPageAllocator::heap_resized(size_t selected_capacity) { + precond(ZAdaptiveHeapSizing); + + // Update per partition heuristic max capacity + ZPerNUMAIterator iter = partition_iterator(); + for (ZPartition* partition; iter.next(&partition);) { + ZMemoryWorker& memory_worker = partition->memory_worker(); + const uint32_t numa_id = partition->numa_id(); + + const size_t heuristic_max_capacity = ZNUMA::calculate_share(numa_id, selected_capacity); + const size_t capacity = partition->capacity(); + + if (capacity < heuristic_max_capacity) { + // Grow the heap + memory_worker.request_grow_capacity(heuristic_max_capacity); + } else { + // Consider shrinking the heap + + // Set up direct uncommit to shrink the heap + const size_t surplus_capacity = capacity - heuristic_max_capacity; + + // Uncommit 5% of the surplus at a time for a smooth capacity decline + const size_t uncommit_fraction = 20; + const size_t uncommit_request = align_up(surplus_capacity / uncommit_fraction, ZGranuleSize); + + const size_t requested_capacity = capacity - uncommit_request; + + // If the surplus capacity isn't over 5% of the capacity, the point of + // uncommitting heuristically seems questionable and might just cause + // pointless fluctuation. + if (surplus_capacity > capacity / uncommit_fraction) { + // Update memory worker target capacity + memory_worker.request_shrink_capacity(requested_capacity); + } else { + memory_worker.stop_heap_resizing(); + } + } + } + + // Complain about misconfigurations + _physical.warn_commit_limits(selected_capacity, dynamic_max_capacity()); +} + +void ZPageAllocator::heap_truncated(size_t selected_capacity, ZPageAllocator::TruncationReason reason) { + precond(ZAdaptiveHeapSizing); + + if (reason == TruncationReason::Uncommit) { + // Uncommit heap truncation is part of heap resizing. + return; + } + assert(reason == TruncationReason::CommitFailure, "Unexpected: %d", (int)reason); + + ZPerNUMAIterator iter = partition_iterator(); + for (ZPartition* partition; iter.next(&partition);) { + // Update memory worker target capacity + ZMemoryWorker& mem_worker = partition->memory_worker(); + mem_worker.stop_heap_resizing(); + } + + // Complain about misconfigurations + _physical.warn_commit_limits(selected_capacity, dynamic_max_capacity()); +} + +void ZPageAllocator::adjust_capacity(size_t used_soon) { + const ZMemoryPressureMetrics mem_metrics = ZAdaptiveHeap::memory_pressure_metrics(); + ZStatSystemMemoryUsage::record(mem_metrics); + + ZPerNUMAIterator iter = partition_iterator(); + + for (ZPartition* partition; iter.next(&partition);) { + ZMemoryWorker& mem_worker = partition->memory_worker(); + const size_t partition_capacity = partition->capacity(); + + if (ZAdaptiveHeap::is_memory_pressure_high(mem_metrics)) { + // When memory usage is high, request uncommitting if possible + mem_worker.request_shrink_capacity_granule(); + } else { + // When memory pressure is not high, stop targetless uncommit. + mem_worker.stop_shrink_capacity_granule(); + + // When memory pressure is not high, try to commit memory ahead of mutators. + const uint32_t numa_id = partition->numa_id(); + const size_t used_soon_share = ZNUMA::calculate_share(numa_id, used_soon); + if (used_soon_share > partition_capacity) { + mem_worker.request_grow_capacity(used_soon_share); + } + } + } +} + void ZPageAllocator::update_collection_stats(ZGenerationId id) { assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint"); @@ -1346,9 +1891,7 @@ void ZPageAllocator::update_collection_stats(ZGenerationId id) { } ZPageAllocatorStats ZPageAllocator::stats_inner(ZGeneration* generation) const { - return ZPageAllocatorStats(_min_capacity, - _max_capacity, - soft_max_capacity(), + return ZPageAllocatorStats(heuristic_max_capacity(), capacity(), _used, _collection_stats[(int)generation->id()]._used_high, @@ -1404,8 +1947,9 @@ ZPage* ZPageAllocator::alloc_page(ZPageType type, size_t size, ZAllocationFlags ZPageAllocation allocation(type, size, flags, age, preferred_partition); // Allocate the page - ZPage* const page = alloc_page_inner(&allocation); + ZPage* const page = alloc_page_inner(&allocation, ZPageAllocationAttempt::initial); if (page == nullptr) { + // Out of memory return nullptr; } @@ -1464,9 +2008,7 @@ bool ZPageAllocator::alloc_page_stall(ZPageAllocation* allocation) { return result; } -ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation) { -retry: - +ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation, ZPageAllocationAttempt attempt) { // Claim the capacity needed for this allocation. // // The claimed capacity comes from memory already mapped in the cache, or @@ -1475,7 +2017,7 @@ ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation) { // // Note that this call might block in a safepoint if the non-blocking flag is // not set. - if (!claim_capacity_or_stall(allocation)) { + if (!claim_capacity_or_stall(allocation, &attempt)) { // Out of memory return nullptr; } @@ -1498,7 +2040,6 @@ ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation) { // Crash in debug builds for more information DEBUG_ONLY(fatal("Out of address space");) - return nullptr; } @@ -1509,18 +2050,19 @@ ZPage* ZPageAllocator::alloc_page_inner(ZPageAllocation* allocation) { // Commit memory for the increased capacity and map the entire vmem. if (!commit_and_map(allocation, vmem)) { free_after_alloc_page_failed(allocation); - goto retry; + assert(attempt != ZPageAllocationAttempt::retry, "Should be initial or stall"); + return alloc_page_inner(allocation, ZPageAllocationAttempt::retry); } return create_page(allocation, vmem); } -bool ZPageAllocator::claim_capacity_or_stall(ZPageAllocation* allocation) { +bool ZPageAllocator::claim_capacity_or_stall(ZPageAllocation* allocation, ZPageAllocationAttempt* attempt) { { ZLocker locker(&_lock); // Try to claim memory - if (claim_capacity(allocation)) { + if (claim_capacity(allocation, *attempt)) { // Keep track of usage increase_used(allocation->size()); @@ -1537,31 +2079,57 @@ bool ZPageAllocator::claim_capacity_or_stall(ZPageAllocation* allocation) { _stalled.insert_last(allocation); } + // We are stalling on this allocation + *attempt = ZPageAllocationAttempt::stall; + // Stall return alloc_page_stall(allocation); } -bool ZPageAllocator::claim_capacity(ZPageAllocation* allocation) { - // Fast medium allocation - if (allocation->flags().fast_medium()) { - return claim_capacity_fast_medium(allocation); - } - - // Round robin single-partition claiming - const uint32_t start_numa_id = allocation->preferred_partition(); - const uint32_t start_partition = start_numa_id; +bool ZPageAllocator::claim_capacity(ZPageAllocation* allocation, ZPageAllocationAttempt attempt) { + ZSinglePartitionAllocation* const single_partition_allocation = allocation->single_partition_allocation(); + const bool is_fast_medium = allocation->flags().fast_medium(); + const uint32_t start_partition = allocation->preferred_partition(); const uint32_t num_partitions = _partitions.count(); + // Round robin soft single-partition claiming + const size_t soft_limit = heuristic_max_capacity(); + + uint32_t highest_available_id = start_partition; + size_t highest_availiable = 0; + for (uint32_t i = 0; i < num_partitions; ++i) { const uint32_t partition_id = (start_partition + i) % num_partitions; + const size_t soft_partition_limit = ZNUMA::calculate_share(partition_id, soft_limit); - if (claim_capacity_single_partition(allocation->single_partition_allocation(), partition_id)) { + const bool claim_result = is_fast_medium + ? claim_capacity_single_partition_fast_medium(single_partition_allocation, partition_id, soft_partition_limit) + : claim_capacity_single_partition(single_partition_allocation, partition_id, attempt, soft_partition_limit); + + if (claim_result) { return true; } + + const ZPartition& partition = _partitions.get(partition_id); + const size_t partition_availiable = partition.available(attempt, partition.static_max_capacity()); + if (partition_availiable > highest_availiable) { + highest_available_id = partition_id; + highest_availiable = partition_availiable; + } + } + + // Hard single-partition claiming - only try from the highest availible partition + const size_t hard_partition_limit = _partitions.get(highest_available_id).static_max_capacity(); + const bool claim_result = is_fast_medium + ? claim_capacity_single_partition_fast_medium(single_partition_allocation, highest_available_id, hard_partition_limit) + : claim_capacity_single_partition(single_partition_allocation, highest_available_id, attempt, hard_partition_limit); + + if (claim_result) { + return true; } - if (!is_multi_partition_allowed(allocation)) { - // Multi-partition claiming is not possible + if (is_fast_medium || !is_multi_partition_allowed(allocation, attempt, _static_max_capacity)) { + // Multi-partition claiming is not allowed or not possible return false; } @@ -1572,35 +2140,24 @@ bool ZPageAllocator::claim_capacity(ZPageAllocation* allocation) { ZMultiPartitionAllocation* const multi_partition_allocation = allocation->multi_partition_allocation(); - claim_capacity_multi_partition(multi_partition_allocation, start_partition); + claim_capacity_multi_partition(multi_partition_allocation, highest_available_id, attempt); return true; } -bool ZPageAllocator::claim_capacity_fast_medium(ZPageAllocation* allocation) { - const uint32_t start_node = allocation->preferred_partition(); - const uint32_t numa_nodes = ZNUMA::count(); - - for (uint32_t i = 0; i < numa_nodes; ++i) { - const uint32_t numa_id = (start_node + i) % numa_nodes; - ZPartition& partition = _partitions.get(numa_id); - ZSinglePartitionAllocation* single_partition_allocation = allocation->single_partition_allocation(); - - if (partition.claim_capacity_fast_medium(single_partition_allocation->allocation())) { - return true; - } - } +bool ZPageAllocator::claim_capacity_single_partition_fast_medium(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id, size_t capacity_limit) { + ZPartition& partition = _partitions.get(partition_id); - return false; + return partition.claim_capacity_fast_medium(single_partition_allocation->allocation(), capacity_limit); } -bool ZPageAllocator::claim_capacity_single_partition(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id) { +bool ZPageAllocator::claim_capacity_single_partition(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id, ZPageAllocationAttempt attempt, size_t capacity_limit) { ZPartition& partition = _partitions.get(partition_id); - return partition.claim_capacity(single_partition_allocation->allocation()); + return partition.claim_capacity(single_partition_allocation->allocation(), attempt, capacity_limit); } -void ZPageAllocator::claim_capacity_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, uint32_t start_partition) { +void ZPageAllocator::claim_capacity_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, uint32_t start_partition, ZPageAllocationAttempt attempt) { const size_t size = multi_partition_allocation->size(); const uint32_t num_partitions = _partitions.count(); const size_t split_size = align_up(size / num_partitions, ZGranuleSize); @@ -1613,10 +2170,13 @@ void ZPageAllocator::claim_capacity_multi_partition(ZMultiPartitionAllocation* m return false; } + // Multi partition allocation, try to split evenly but at most static_max_capacity + const size_t capacity_limit = partition.static_max_capacity(); + const size_t max_alloc_size = claim_evenly ? MIN2(split_size, remaining) : remaining; // This guarantees that claim_physical below will succeed - const size_t alloc_size = MIN2(max_alloc_size, partition.available()); + const size_t alloc_size = MIN2(max_alloc_size, partition.available(attempt, capacity_limit)); // Skip over empty allocations if (alloc_size == 0) { @@ -1627,7 +2187,7 @@ void ZPageAllocator::claim_capacity_multi_partition(ZMultiPartitionAllocation* m ZMemoryAllocation partial_allocation(alloc_size); // Claim capacity for this allocation - this should succeed - const bool result = partition.claim_capacity(&partial_allocation); + const bool result = partition.claim_capacity(&partial_allocation, attempt, capacity_limit); assert(result, "Should have succeeded"); // Register allocation @@ -1824,12 +2384,37 @@ bool ZPageAllocator::commit_and_map_multi_partition(ZMultiPartitionAllocation* m return false; } +size_t ZPageAllocator::allowed_to_commit(size_t about_to_commit) { + // We only allow committing up to the current max capacity. Even if capacity + // may have been allowed to be increased on individual partitions, we may + // disallow that capacity to be committed if it turns out that it will put + // too much strain on the system. + const size_t current_max = current_max_capacity(); + const size_t capacity = ZPageAllocator::capacity(); + + // Capacity already accounts for the increase as part of this allocation, + // subtract it to get the already committed capacity. + const size_t committed_capacity = capacity - about_to_commit; + + if (committed_capacity >= current_max) { + return 0; + } + + return current_max - committed_capacity; +} + void ZPageAllocator::commit(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem) { ZPartition& partition = allocation->partition(); + const size_t allowed_to_commit = ZPageAllocator::allowed_to_commit(allocation->increased_capacity()); - if (allocation->increased_capacity() > 0) { + // If we both have something to commit and we're allowed to commit something, + // then try to commit. + if (allocation->increased_capacity() > 0 && allowed_to_commit > 0) { // Commit memory - partition.commit_increased_capacity(allocation, vmem); + partition.commit_increased_capacity(allocation, vmem, allowed_to_commit); + } else { + // Record nothing to commit + allocation->set_committed_capacity(0); } } @@ -1878,10 +2463,10 @@ void ZPageAllocator::map_committed_single_partition(ZSinglePartitionAllocation* ZPartition& partition = allocation->partition(); const size_t total_committed = allocation->harvested() + allocation->committed_capacity(); - const ZVirtualMemory total_committed_vmem = vmem.first_part(total_committed); - if (total_committed_vmem.size() > 0) { + if (total_committed > 0) { // Map all the committed memory + const ZVirtualMemory total_committed_vmem = vmem.first_part(total_committed); partition.map_memory(allocation, total_committed_vmem); } } @@ -2005,6 +2590,9 @@ void ZPageAllocator::free_after_alloc_page_failed(ZPageAllocation* allocation) { // Free memory free_memory_alloc_failed(allocation); + // Try not to commit too much again + truncate_heuristic_max_after_capacity_decrease(TruncationReason::CommitFailure); + // Keep track of usage decrease_used(allocation->size()); @@ -2016,23 +2604,11 @@ void ZPageAllocator::free_after_alloc_page_failed(ZPageAllocation* allocation) { } void ZPageAllocator::free_memory_alloc_failed(ZPageAllocation* allocation) { - // The current max capacity may be decreased, store the value before freeing memory - const size_t current_max_capacity_before = current_max_capacity(); - if (allocation->is_multi_partition()) { free_memory_alloc_failed_multi_partition(allocation->multi_partition_allocation()); } else { free_memory_alloc_failed_single_partition(allocation->single_partition_allocation()); } - - const size_t current_max_capacity_after = current_max_capacity(); - - if (current_max_capacity_before != current_max_capacity_after) { - log_error_p(gc)("Forced to lower max Java heap size from " - "%zuM(%.0f%%) to %zuM(%.0f%%)", - current_max_capacity_before / M, percent_of(current_max_capacity_before, _max_capacity), - current_max_capacity_after / M, percent_of(current_max_capacity_after, _max_capacity)); - } } void ZPageAllocator::free_memory_alloc_failed_single_partition(ZSinglePartitionAllocation* single_partition_allocation) { @@ -2146,15 +2722,15 @@ void ZPageAllocator::remap_and_defragment(const ZVirtualMemory& vmem, ZArray* vmems) { +void ZPageAllocator::free_used_memory(ZArray* vmems) { ZLocker locker(&_lock); // Free the vmems for (const ZVirtualMemory vmem : *vmems) { ZPartition& partition = partition_from_vmem(vmem); - // Free the vmem - partition.free_memory(vmem); + // Free the used vmem + partition.free_used_memory(vmem); // Keep track of usage decrease_used(vmem.size()); @@ -2164,6 +2740,21 @@ void ZPageAllocator::free_memory(ZArray* vmems) { satisfy_stalled(); } +void ZPageAllocator::free_claimed_memory(ZArray* vmems) { + ZLocker locker(&_lock); + + // Free the vmems + for (const ZVirtualMemory vmem : *vmems) { + ZPartition& partition = partition_from_vmem(vmem); + + // Free the claimed vmem + partition.free_claimed_memory(vmem); + } + + // Try satisfy stalled allocations + satisfy_stalled(); +} + void ZPageAllocator::satisfy_stalled() { for (;;) { ZPageAllocation* const allocation = _stalled.first(); @@ -2172,7 +2763,7 @@ void ZPageAllocator::satisfy_stalled() { return; } - if (!claim_capacity(allocation)) { + if (!claim_capacity(allocation, ZPageAllocationAttempt::stall)) { // Allocation could not be satisfied, give up return; } @@ -2192,10 +2783,10 @@ bool ZPageAllocator::is_multi_partition_enabled() const { return _virtual.is_multi_partition_enabled(); } -bool ZPageAllocator::is_multi_partition_allowed(const ZPageAllocation* allocation) const { +bool ZPageAllocator::is_multi_partition_allowed(const ZPageAllocation* allocation, ZPageAllocationAttempt attempt, size_t total_capacity_limit) const { return is_multi_partition_enabled() && allocation->type() == ZPageType::large && - allocation->size() <= sum_available(); + allocation->size() <= sum_available(attempt, total_capacity_limit); } const ZPartition& ZPageAllocator::partition_from_partition_id(uint32_t numa_id) const { @@ -2210,12 +2801,18 @@ ZPartition& ZPageAllocator::partition_from_vmem(const ZVirtualMemory& vmem) { return partition_from_partition_id(_virtual.lookup_partition_id(vmem)); } -size_t ZPageAllocator::sum_available() const { +size_t ZPageAllocator::sum_available(ZPageAllocationAttempt attempt, size_t total_capacity_limit) const { size_t total = 0; ZPartitionConstIterator iter = partition_iterator(); for (const ZPartition* partition; iter.next(&partition);) { - total += partition->available(); + if (total_capacity_limit <= total) { + // The limit smaller than the total, we will have + return total_capacity_limit; + } + + const size_t partition_capacity_limit = MIN2(partition->static_max_capacity(), total_capacity_limit - total); + total += partition->available(attempt, partition_capacity_limit); } return total; @@ -2263,7 +2860,7 @@ void ZPageAllocator::free_page(ZPage* page) { decrease_used_generation(id, size); // Free the extracted vmems - free_memory(&vmems); + free_used_memory(&vmems); } void ZPageAllocator::free_pages(ZGenerationId id, const ZArray* pages) { @@ -2281,7 +2878,7 @@ void ZPageAllocator::free_pages(ZGenerationId id, const ZArray* pages) { } // Free the extracted vmems - free_memory(&vmems); + free_used_memory(&vmems); } void ZPageAllocator::enable_safe_destroy() const { @@ -2411,7 +3008,7 @@ void ZPageAllocator::print_total_usage_on(outputStream* st) const { st->print("ZHeap "); st->fill_to(17); st->print_cr("used %zuM, capacity %zuM, max capacity %zuM", - used() / M, capacity() / M, max_capacity() / M); + used() / M, capacity() / M, dynamic_max_capacity() / M); } void ZPageAllocator::print_partition_usage_on(outputStream* st) const { diff --git a/src/hotspot/share/gc/z/zPageAllocator.hpp b/src/hotspot/share/gc/z/zPageAllocator.hpp index a2f4c94e8d40..9aba68fe0092 100644 --- a/src/hotspot/share/gc/z/zPageAllocator.hpp +++ b/src/hotspot/share/gc/z/zPageAllocator.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,6 +32,7 @@ #include "gc/z/zList.hpp" #include "gc/z/zLock.hpp" #include "gc/z/zMappedCache.hpp" +#include "gc/z/zMemoryWorker.hpp" #include "gc/z/zPage.hpp" #include "gc/z/zPageAge.hpp" #include "gc/z/zPageType.hpp" @@ -42,6 +43,48 @@ #include "gc/z/zVirtualMemoryManager.hpp" #include "utilities/ostream.hpp" +// The ZPageAllocator maintains the memory for the Java heap, represented by the +// notion of capacity. The capacity is divided among one or more partitions, which +// handles a subset of the heap (or the whole heap in case of a single partition). +// +// The most important specific terms for capacity that we have are: +// - static min Absolute lower bound configured at startup (never changes). +// The heap must not shrink below this. +// - capacity Currently committed capacity +// - soft max Target limit; heuristics try to stay below this +// - heuristic max Heuristic-derived upper bound used to guide resizing +// - current max The effective maximum currently enforced; based on system +// memory usage +// - dynamic max Same as static max for non-container. Container limit +// if in container. +// - static max Absolute upper bound configured at startup (never changes). +// This is the same value of -Xmx if set on the command-line. +// +// Notes: +// - Layouts below are illustrative. Values may move relative to each other. +// - static min and static max never change during the program's execution. +// - The heap must stay within [static min, static max]. +// +// Not container: +// LOW <-> HIGH +// +// soft max current max +// | | +// [----|------------|-----------|-|-------------|---------] +// | | | | +// static min capacity heuristic max static max +// dynamic max +// +// Container: +// LOW <-> HIGH +// +// soft max +// capacity | current max +// | | | +// [----|-|--------|-|-------|----|------------------------] +// | | | | +// static min heuristic_max dynamic max static max + class ThreadClosure; class ZGeneration; class ZMemoryAllocation; @@ -53,19 +96,21 @@ class ZSegmentStash; class ZSinglePartitionAllocation; class ZVirtualMemory; class ZWorkers; +enum class ZPageAllocationAttempt; class ZPartition { friend class VMStructs; friend class ZPageAllocator; + friend class ZMemoryWorker; friend class ZUncommitter; private: ZPageAllocator* const _page_allocator; ZMappedCache _cache; ZUncommitter _uncommitter; - const size_t _min_capacity; - const size_t _max_capacity; - volatile size_t _current_max_capacity; + ZMemoryWorker _mem_worker; + const size_t _static_min_capacity; + const size_t _static_max_capacity; volatile size_t _capacity; volatile size_t _claimed; size_t _used; @@ -77,29 +122,60 @@ class ZPartition { const ZPhysicalMemoryManager& physical_memory_manager() const; ZPhysicalMemoryManager& physical_memory_manager(); + ZLock* lock() const; + void verify_virtual_memory_multi_partition_association(const ZVirtualMemory& vmem) const NOT_DEBUG_RETURN; void verify_virtual_memory_association(const ZVirtualMemory& vmem, bool check_multi_partition = false) const NOT_DEBUG_RETURN; void verify_virtual_memory_association(const ZArray* vmems) const NOT_DEBUG_RETURN; void verify_memory_allocation_association(const ZMemoryAllocation* allocation) const NOT_DEBUG_RETURN; + size_t increase_capacity(size_t size, size_t capacity_limit); + void map_virtual_inner(const ZVirtualMemory& vmem, bool multi_partition, bool sort_segments, bool register_heating); + void unmap_virtual_inner(const ZVirtualMemory& vmem, bool multi_partition); + public: - ZPartition(uint32_t numa_id, ZPageAllocator* page_allocator); + ZPartition(uint32_t numa_id, + ZPageAllocator* page_allocator, + size_t min_capacity, + size_t static_max_capacity); + + size_t dynamic_max_capacity() const; + size_t current_max_capacity() const; + size_t static_max_capacity() const; + size_t capacity() const; + + const ZUncommitter& uncommitter() const; + ZUncommitter& uncommitter(); + + const ZMemoryWorker& memory_worker() const; + ZMemoryWorker& memory_worker(); uint32_t numa_id() const; - size_t available() const; + size_t available(ZPageAllocationAttempt attempt, size_t capacity_limit) const; + size_t available_from_any_capacity(size_t capacity_limit) const; + size_t available_from_increase_capacity(size_t capacity_limit) const; + size_t available_from_cache(size_t capacity_limit) const; - size_t increase_capacity(size_t size); - void decrease_capacity(size_t size, bool set_max_capacity); + size_t try_increase_capacity(size_t size, ZPageAllocationAttempt attempt, size_t capacity_limit); + void decrease_capacity(size_t size); void increase_used(size_t size); void decrease_used(size_t size); - void free_memory(const ZVirtualMemory& vmem); + void increase_claimed(size_t size); + void decrease_claimed(size_t size); + + void heat_memory(const ZVirtualMemory& vmem) const; + + void free_used_memory(const ZVirtualMemory& vmem); + void free_claimed_memory(const ZVirtualMemory& vmem); - void claim_from_cache_or_increase_capacity(ZMemoryAllocation* allocation); - bool claim_capacity(ZMemoryAllocation* allocation); - bool claim_capacity_fast_medium(ZMemoryAllocation* allocation); + void claim_from_cache_or_increase_capacity(ZMemoryAllocation* allocation, ZPageAllocationAttempt attempt, size_t capacity_limit); + bool claim_capacity(ZMemoryAllocation* allocation, ZPageAllocationAttempt attempt, size_t capacity_limit); + bool claim_capacity_fast_medium(ZMemoryAllocation* allocation, size_t capacity_limit); + + size_t increase_and_commit_capacity(size_t size, size_t capacity_limit); void sort_segments_physical(const ZVirtualMemory& vmem); @@ -108,7 +184,7 @@ class ZPartition { size_t commit_physical(const ZVirtualMemory& vmem); size_t uncommit_physical(const ZVirtualMemory& vmem); - void map_virtual(const ZVirtualMemory& vmem); + void map_virtual(const ZVirtualMemory& vmem, bool heat_memory = true); void unmap_virtual(const ZVirtualMemory& vmem); void map_virtual_from_multi_partition(const ZVirtualMemory& vmem); @@ -121,14 +197,14 @@ class ZPartition { void free_and_claim_virtual_from_low_many(const ZVirtualMemory& vmem, ZArray* vmems_out); ZVirtualMemory free_and_claim_virtual_from_low_exact_or_many(size_t size, ZArray* vmems_in_out); - bool prime(ZWorkers* workers, size_t size); + bool prime(ZWorkers* workers, size_t size, size_t capacity_limit); ZVirtualMemory prepare_harvested_and_claim_virtual(ZMemoryAllocation* allocation); void copy_physical_segments_to_partition(const ZVirtualMemory& at, const ZVirtualMemory& from); void copy_physical_segments_from_partition(const ZVirtualMemory& at, const ZVirtualMemory& to); - void commit_increased_capacity(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem); + void commit_increased_capacity(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem, size_t allowed_to_commit); void map_memory(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem); void free_memory_alloc_failed(ZMemoryAllocation* allocation); @@ -145,35 +221,37 @@ using ZPartitionConstIterator = ZPerNUMAConstIterator; class ZPageAllocator { friend class VMStructs; + friend class ZMemoryWorker; friend class ZMultiPartitionTracker; friend class ZPartition; friend class ZUncommitter; private: - mutable ZLock _lock; - ZVirtualMemoryManager _virtual; - ZPhysicalMemoryManager _physical; - const size_t _min_capacity; - const size_t _max_capacity; - volatile size_t _used; - volatile size_t _used_generations[2]; + mutable ZLock _lock; + ZVirtualMemoryManager _virtual; + ZPhysicalMemoryManager _physical; + const size_t _static_min_capacity; + const size_t _static_max_capacity; + Atomic _heuristic_max_capacity; + volatile size_t _used; + volatile size_t _used_generations[2]; struct { size_t _used_high; size_t _used_low; - } _collection_stats[2]; - ZPerNUMA _partitions; - ZList _stalled; - mutable ZSafeDelete _safe_destroy; - bool _initialized; + } _collection_stats[2]; + ZPerNUMA _partitions; + ZList _stalled; + mutable ZSafeDelete _safe_destroy; + bool _initialized; bool alloc_page_stall(ZPageAllocation* allocation); - ZPage* alloc_page_inner(ZPageAllocation* allocation); + ZPage* alloc_page_inner(ZPageAllocation* allocation, ZPageAllocationAttempt attempt); - bool claim_capacity_or_stall(ZPageAllocation* allocation); - bool claim_capacity(ZPageAllocation* allocation); - bool claim_capacity_fast_medium(ZPageAllocation* allocation); - bool claim_capacity_single_partition(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id); - void claim_capacity_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, uint32_t start_partition); + bool claim_capacity_or_stall(ZPageAllocation* allocation, ZPageAllocationAttempt* attempt); + bool claim_capacity(ZPageAllocation* allocation, ZPageAllocationAttempt attempt); + bool claim_capacity_single_partition_fast_medium(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id, size_t capacity_limit); + bool claim_capacity_single_partition(ZSinglePartitionAllocation* single_partition_allocation, uint32_t partition_id, ZPageAllocationAttempt attempt, size_t capacity_limit); + void claim_capacity_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, uint32_t start_partition, ZPageAllocationAttempt attempt); ZVirtualMemory satisfied_from_cache_vmem(const ZPageAllocation* allocation) const; @@ -192,6 +270,8 @@ class ZPageAllocator { bool commit_and_map_single_partition(ZSinglePartitionAllocation* single_partition_allocation, const ZVirtualMemory& vmem); bool commit_and_map_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, const ZVirtualMemory& vmem); + size_t allowed_to_commit(size_t about_to_commit); + void commit(ZMemoryAllocation* allocation, const ZVirtualMemory& vmem); bool commit_single_partition(ZSinglePartitionAllocation* single_partition_allocation, const ZVirtualMemory& vmem); bool commit_multi_partition(ZMultiPartitionAllocation* multi_partition_allocation, const ZVirtualMemory& vmem); @@ -215,18 +295,19 @@ class ZPageAllocator { void prepare_memory_for_free(ZPage* page, ZArray* vmems); void remap_and_defragment(const ZVirtualMemory& vmem, ZArray* vmems_out); - void free_memory(ZArray* vmems); + void free_used_memory(ZArray* vmems); + void free_claimed_memory(ZArray* vmems); void satisfy_stalled(); bool is_multi_partition_enabled() const; - bool is_multi_partition_allowed(const ZPageAllocation* allocation) const; + bool is_multi_partition_allowed(const ZPageAllocation* allocation, ZPageAllocationAttempt attempt, size_t total_capacity_limit) const; const ZPartition& partition_from_partition_id(uint32_t partition_id) const; ZPartition& partition_from_partition_id(uint32_t partition_id); ZPartition& partition_from_vmem(const ZVirtualMemory& vmem); - size_t sum_available() const; + size_t sum_available(ZPageAllocationAttempt attempt, size_t total_capacity_limit) const; void increase_used(size_t size); void decrease_used(size_t size); @@ -238,32 +319,46 @@ class ZPageAllocator { ZPageAllocatorStats stats_inner(ZGeneration* generation) const; public: - ZPageAllocator(size_t min_capacity, + ZPageAllocator(size_t static_min_capacity, size_t initial_capacity, size_t soft_max_capacity, - size_t max_capacity); + size_t static_max_capacity); bool is_initialized() const; bool prime_cache(ZWorkers* workers, size_t size); - size_t min_capacity() const; - size_t max_capacity() const; - size_t soft_max_capacity() const; + size_t static_min_capacity() const; + size_t static_max_capacity() const; + size_t dynamic_max_capacity() const; size_t current_max_capacity() const; + size_t heuristic_max_capacity() const; size_t capacity() const; size_t used() const; size_t used_generation(ZGenerationId id) const; size_t unused() const; - void increase_used_generation(ZGenerationId id, size_t size); - void decrease_used_generation(ZGenerationId id, size_t size); + // Adaptive heap sizing + enum class TruncationReason { + Uncommit, + CommitFailure, + }; - void promote_used(const ZPage* from, const ZPage* to); + void shrink_heuristic_max(size_t shrink_amount); + void truncate_heuristic_max_after_capacity_decrease(TruncationReason reason); + void adapt_heuristic_max_capacity(ZGenerationId generation); + void heap_resized(size_t selected_capacity); + void heap_truncated(size_t selected_capacity, TruncationReason reason); + void adjust_capacity(size_t used_soon); ZPageAllocatorStats stats(ZGeneration* generation) const; ZPageAllocatorStats update_and_stats(ZGeneration* generation); + void increase_used_generation(ZGenerationId id, size_t size); + void decrease_used_generation(ZGenerationId id, size_t size); + + void promote_used(const ZPage* from, const ZPage* to); + ZPage* alloc_page(ZPageType type, size_t size, ZAllocationFlags flags, ZPageAge age, uint32_t preferred_partition); void safe_destroy_page(ZPage* page); void free_page(ZPage* page); @@ -290,9 +385,7 @@ class ZPageAllocator { class ZPageAllocatorStats { private: - const size_t _min_capacity; - const size_t _max_capacity; - const size_t _soft_max_capacity; + const size_t _heuristic_max_capacity; const size_t _capacity; const size_t _used; const size_t _used_high; @@ -304,9 +397,7 @@ class ZPageAllocatorStats { const size_t _allocation_stalls; public: - ZPageAllocatorStats(size_t min_capacity, - size_t max_capacity, - size_t soft_max_capacity, + ZPageAllocatorStats(size_t heuristic_max_capacity, size_t capacity, size_t used, size_t used_high, @@ -317,9 +408,7 @@ class ZPageAllocatorStats { size_t compacted, size_t allocation_stalls); - size_t min_capacity() const; - size_t max_capacity() const; - size_t soft_max_capacity() const; + size_t heuristic_max_capacity() const; size_t capacity() const; size_t used() const; size_t used_high() const; diff --git a/src/hotspot/share/gc/z/zPageAllocator.inline.hpp b/src/hotspot/share/gc/z/zPageAllocator.inline.hpp index 5f357587fd79..0726e77badd5 100644 --- a/src/hotspot/share/gc/z/zPageAllocator.inline.hpp +++ b/src/hotspot/share/gc/z/zPageAllocator.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,9 +26,7 @@ #include "gc/z/zPageAllocator.hpp" -inline ZPageAllocatorStats::ZPageAllocatorStats(size_t min_capacity, - size_t max_capacity, - size_t soft_max_capacity, +inline ZPageAllocatorStats::ZPageAllocatorStats(size_t heuristic_max_capacity, size_t capacity, size_t used, size_t used_high, @@ -38,9 +36,7 @@ inline ZPageAllocatorStats::ZPageAllocatorStats(size_t min_capacity, size_t promoted, size_t compacted, size_t allocation_stalls) - : _min_capacity(min_capacity), - _max_capacity(max_capacity), - _soft_max_capacity(soft_max_capacity), + : _heuristic_max_capacity(heuristic_max_capacity), _capacity(capacity), _used(used), _used_high(used_high), @@ -51,16 +47,8 @@ inline ZPageAllocatorStats::ZPageAllocatorStats(size_t min_capacity, _compacted(compacted), _allocation_stalls(allocation_stalls) {} -inline size_t ZPageAllocatorStats::min_capacity() const { - return _min_capacity; -} - -inline size_t ZPageAllocatorStats::max_capacity() const { - return _max_capacity; -} - -inline size_t ZPageAllocatorStats::soft_max_capacity() const { - return _soft_max_capacity; +inline size_t ZPageAllocatorStats::heuristic_max_capacity() const { + return _heuristic_max_capacity; } inline size_t ZPageAllocatorStats::capacity() const { diff --git a/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp b/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp index 2e7a97028ff3..1587e1fdec33 100644 --- a/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp +++ b/src/hotspot/share/gc/z/zPhysicalMemoryManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,10 +22,12 @@ */ #include "gc/shared/gcLogPrecious.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zAddress.inline.hpp" #include "gc/z/zArray.inline.hpp" #include "gc/z/zGlobals.hpp" #include "gc/z/zGranuleMap.inline.hpp" +#include "gc/z/zInitialize.hpp" #include "gc/z/zLargePages.inline.hpp" #include "gc/z/zList.inline.hpp" #include "gc/z/zNMT.hpp" @@ -39,17 +41,22 @@ #include "runtime/globals.hpp" #include "runtime/globals_extension.hpp" #include "runtime/init.hpp" +#include "runtime/java.hpp" #include "runtime/os.hpp" #include "utilities/align.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" -ZPhysicalMemoryManager::ZPhysicalMemoryManager(size_t max_capacity) +ZPhysicalMemoryManager::ZPhysicalMemoryManager(size_t min_capacity, size_t max_capacity) : _backing(max_capacity), _physical_mappings(ZAddressOffsetMax) { assert(is_aligned(max_capacity, ZGranuleSize), "must be granule aligned"); + if (!_backing.is_initialized()) { + return; + } + // Setup backing storage limits ZBackingOffsetMax = max_capacity; ZBackingIndexMax = checked_cast(max_capacity >> ZGranuleSizeShift); @@ -77,6 +84,9 @@ ZPhysicalMemoryManager::ZPhysicalMemoryManager(size_t max_capacity) next_index += num_segments; } + // Check if uncommit should and can be enabled + try_enable_uncommit(min_capacity, max_capacity); + assert(untype(next_index) == ZBackingIndexMax, "must insert all capacity"); } @@ -84,16 +94,13 @@ bool ZPhysicalMemoryManager::is_initialized() const { return _backing.is_initialized(); } -void ZPhysicalMemoryManager::warn_commit_limits(size_t max_capacity) const { - _backing.warn_commit_limits(max_capacity); +void ZPhysicalMemoryManager::warn_commit_limits(size_t expected_capacity, size_t max_capacity) const { + _backing.warn_commit_limits(expected_capacity, max_capacity); } void ZPhysicalMemoryManager::try_enable_uncommit(size_t min_capacity, size_t max_capacity) { assert(!is_init_completed(), "Invalid state"); - // If uncommit is not explicitly disabled, max capacity is greater than - // min capacity, and uncommit is supported by the platform, then uncommit - // will be enabled. if (!ZUncommit) { log_info_p(gc, init)("Uncommit: Disabled"); return; @@ -105,13 +112,12 @@ void ZPhysicalMemoryManager::try_enable_uncommit(size_t min_capacity, size_t max return; } - // Test if uncommit is supported by the operating system by committing - // and then uncommitting a granule. + // Test if uncommit is supported by the operating system or file system by + // committing and then uncommitting a granule. const ZVirtualMemory vmem(zoffset(0), ZGranuleSize); if (!commit(vmem, 0) || !uncommit(vmem)) { - log_info_p(gc, init)("Uncommit: Implicitly Disabled (Not supported by operating system)"); - FLAG_SET_ERGO(ZUncommit, false); - return; + vm_exit_during_initialization("Uncommit not supported with the current configuration. " + "Use -XX:-ZUncommit to run without uncommit."); } const size_t max_delay_without_overflow = std::numeric_limits::max() / MILLIUNITS; @@ -391,3 +397,9 @@ void ZPhysicalMemoryManager::restore_segments(const ZArraySlice stash, const ZVirtualMemory& vmem) const; void copy_from_stash(const ZArraySlice stash, const ZVirtualMemory& vmem); + void try_enable_uncommit(size_t min_capacity, size_t max_capacity); + public: - ZPhysicalMemoryManager(size_t max_capacity); + ZPhysicalMemoryManager(size_t min_capacity, size_t max_capacity); bool is_initialized() const; - void warn_commit_limits(size_t max_capacity) const; - void try_enable_uncommit(size_t min_capacity, size_t max_capacity); + void warn_commit_limits(size_t expected_capacity, size_t max_capacity) const; void alloc(const ZVirtualMemory& vmem, uint32_t numa_id); void free(const ZVirtualMemory& vmem, uint32_t numa_id); @@ -74,6 +75,8 @@ class ZPhysicalMemoryManager { void stash_segments(const ZArraySlice& vmems, ZArray* stash_out) const; void restore_segments(const ZArraySlice& vmems, const ZArray& stash); + + void collapse(const ZVirtualMemory& vmem) const; }; #endif // SHARE_GC_Z_ZPHYSICALMEMORYMANAGER_HPP diff --git a/src/hotspot/share/gc/z/zRange.hpp b/src/hotspot/share/gc/z/zRange.hpp index 53c9980608e8..a002537e5998 100644 --- a/src/hotspot/share/gc/z/zRange.hpp +++ b/src/hotspot/share/gc/z/zRange.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -59,6 +59,7 @@ class ZRange { bool operator!=(const ZRange& other) const; bool contains(const ZRange& other) const; + bool overlaps(const ZRange& other) const; void grow_from_front(size_t size); void grow_from_back(size_t size); diff --git a/src/hotspot/share/gc/z/zRange.inline.hpp b/src/hotspot/share/gc/z/zRange.inline.hpp index d99cbefd32a8..6918ef84f9ef 100644 --- a/src/hotspot/share/gc/z/zRange.inline.hpp +++ b/src/hotspot/share/gc/z/zRange.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -87,6 +87,14 @@ inline bool ZRange::contains(const ZRange& other) const { return _start <= other._start && other.end() <= end(); } +template +inline bool ZRange::overlaps(const ZRange& other) const { + precond(!is_null()); + precond(!other.is_null()); + + return MAX2(_start, other._start) < MIN2(other.end(), end()); +} + template inline void ZRange::grow_from_front(size_t size) { precond(size_t(start()) >= size); diff --git a/src/hotspot/share/gc/z/zReferenceProcessor.cpp b/src/hotspot/share/gc/z/zReferenceProcessor.cpp index 229365a2e7e1..c398a355d610 100644 --- a/src/hotspot/share/gc/z/zReferenceProcessor.cpp +++ b/src/hotspot/share/gc/z/zReferenceProcessor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include "gc/shared/referencePolicy.hpp" #include "gc/shared/referenceProcessorStats.hpp" #include "gc/shared/suspendibleThreadSet.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zDriver.hpp" #include "gc/z/zHeap.inline.hpp" @@ -42,6 +43,14 @@ static const ZStatSubPhase ZSubPhaseConcurrentReferencesProcess("Concurrent References Process", ZGenerationId::old); static const ZStatSubPhase ZSubPhaseConcurrentReferencesEnqueue("Concurrent References Enqueue", ZGenerationId::old); +class ZLRUMaxHeapPolicy : public AbstractLRUReferencePolicy { +public: + // Capture state (of-the-VM) information needed to evaluate the policy + void setup() final { + set_max_interval((jlong)ZAdaptiveHeap::soft_ref_delay()); + } +}; + static ReferenceType reference_type(zaddress reference) { return InstanceKlass::cast(to_oop(reference)->klass())->reference_type(); } @@ -122,7 +131,7 @@ ZReferenceProcessor::ZReferenceProcessor(ZWorkers* workers) void ZReferenceProcessor::set_soft_reference_policy(bool clear_all_soft_references) { static AlwaysClearPolicy always_clear_policy; - static LRUMaxHeapPolicy lru_max_heap_policy; + static ZLRUMaxHeapPolicy lru_max_heap_policy; _uses_clear_all_soft_reference_policy = clear_all_soft_references; diff --git a/src/hotspot/share/gc/z/zServiceability.cpp b/src/hotspot/share/gc/z/zServiceability.cpp index e5ed99bfccc6..f2ef46bc9994 100644 --- a/src/hotspot/share/gc/z/zServiceability.cpp +++ b/src/hotspot/share/gc/z/zServiceability.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,18 +35,21 @@ struct ZMemoryUsageInfo { size_t _young_capacity; size_t _old_used; size_t _old_capacity; + size_t _max_capacity; }; static ZMemoryUsageInfo compute_memory_usage_info() { const size_t capacity = ZHeap::heap()->capacity(); const size_t old_used = ZHeap::heap()->used_old(); const size_t young_used = ZHeap::heap()->used_young(); + const size_t max_capacity = ZHeap::heap()->dynamic_max_capacity(); ZMemoryUsageInfo info; info._old_used = MIN2(old_used, capacity); info._old_capacity = info._old_used; info._young_capacity = capacity - info._old_capacity; info._young_used = MIN2(young_used, info._young_capacity); + info._max_capacity = MAX2(capacity, max_capacity); return info; } @@ -143,12 +146,16 @@ MemoryUsage ZServiceabilityMemoryPool::get_memory_usage() { const ZMemoryUsageInfo info = compute_memory_usage_info(); if (_generation_id == ZGenerationId::young) { - return MemoryUsage(initial_size(), info._young_used, info._young_capacity, max_size()); + return MemoryUsage(initial_size(), info._young_used, info._young_capacity, info._max_capacity); } else { - return MemoryUsage(initial_size(), info._old_used, info._old_capacity, max_size()); + return MemoryUsage(initial_size(), info._old_used, info._old_capacity, info._max_capacity); } } +size_t ZServiceabilityMemoryPool::max_size() const { + return ZHeap::heap()->dynamic_max_capacity(); +} + ZServiceabilityMemoryManager::ZServiceabilityMemoryManager(const char* name, MemoryPool* young_memory_pool, MemoryPool* old_memory_pool) diff --git a/src/hotspot/share/gc/z/zServiceability.hpp b/src/hotspot/share/gc/z/zServiceability.hpp index 2a3b56248df8..28a9c5202fb9 100644 --- a/src/hotspot/share/gc/z/zServiceability.hpp +++ b/src/hotspot/share/gc/z/zServiceability.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,6 +41,7 @@ class ZServiceabilityMemoryPool : public CollectedMemoryPool { public: ZServiceabilityMemoryPool(const char* name, ZGenerationId id, size_t min_capacity, size_t max_capacity); + virtual size_t max_size() const; virtual size_t used_in_bytes(); virtual MemoryUsage get_memory_usage(); }; diff --git a/src/hotspot/share/gc/z/zStat.cpp b/src/hotspot/share/gc/z/zStat.cpp index 5a429f02806c..09818e764975 100644 --- a/src/hotspot/share/gc/z/zStat.cpp +++ b/src/hotspot/share/gc/z/zStat.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ #include "cppstdlib/limits.hpp" #include "gc/shared/gc_globals.hpp" #include "gc/z/zAbort.inline.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zCollectedHeap.hpp" #include "gc/z/zCPU.inline.hpp" #include "gc/z/zDirector.hpp" @@ -48,12 +49,11 @@ #include "utilities/ticks.hpp" #define ZSIZE_FMT "%zuM(%.0f%%)" -#define ZSIZE_ARGS_WITH_MAX(size, max) ((size) / M), (percent_of(size, max)) -#define ZSIZE_ARGS(size) ZSIZE_ARGS_WITH_MAX(size, ZStatHeap::max_capacity()) +#define ZSIZE_ARGS(size, max) ((size) / M), (percent_of(size, max)) #define ZTABLE_ARGS_NA "%9s", "-" -#define ZTABLE_ARGS(size) "%8zuM (%.0f%%)", \ - ((size) / M), (percent_of(size, ZStatHeap::max_capacity())) +#define ZTABLE_ARGS(size, max) "%8zuM (%.0f%%)", \ + ((size) / M), (percent_of(size, max)) // // Stat sampler/counter data @@ -651,6 +651,20 @@ size_t ZStatPhaseCollection::used_at_start() const { : ZDriver::major()->used_at_start(); } +void ZStatPhaseCollection::set_max_at_start(size_t max) const { + if (_minor) { + ZDriver::minor()->set_max_at_start(max); + } else { + ZDriver::major()->set_max_at_start(max); + } +} + +size_t ZStatPhaseCollection::max_at_start() const { + return _minor + ? ZDriver::minor()->max_at_start() + : ZDriver::major()->max_at_start(); +} + void ZStatPhaseCollection::register_start(ConcurrentGCTimer* timer, const Ticks& start) const { const GCCause::Cause cause = _minor ? ZDriver::minor()->gc_cause() : ZDriver::major()->gc_cause(); @@ -660,6 +674,8 @@ void ZStatPhaseCollection::register_start(ConcurrentGCTimer* timer, const Ticks& ZCollectedHeap::heap()->trace_heap_before_gc(jfr_tracer()); set_used_at_start(ZHeap::heap()->used()); + const size_t max = ZHeap::heap()->heuristic_max_capacity(); + set_max_at_start(max); log_info(gc)("%s (%s)", name(), GCCause::to_string(cause)); } @@ -685,8 +701,8 @@ void ZStatPhaseCollection::register_end(ConcurrentGCTimer* timer, const Ticks& s log_info(gc)("%s (%s) " ZSIZE_FMT "->" ZSIZE_FMT " %.3fs", name(), GCCause::to_string(cause), - ZSIZE_ARGS(used_at_start()), - ZSIZE_ARGS(used_at_end), + ZSIZE_ARGS(used_at_start(), max_at_start()), + ZSIZE_ARGS(used_at_end, max_at_start()), duration.seconds()); } @@ -740,10 +756,12 @@ void ZStatPhaseGeneration::register_end(ConcurrentGCTimer* timer, const Ticks& s generation->stat_heap()->print(generation); + const size_t max_at_start = generation->stat_heap()->max_at_collection_start(); + log_info(gc, phases)("%s " ZSIZE_FMT "->" ZSIZE_FMT " %.3fs", name(), - ZSIZE_ARGS(generation->stat_heap()->used_at_collection_start()), - ZSIZE_ARGS(generation->stat_heap()->used_at_collection_end()), + ZSIZE_ARGS(generation->stat_heap()->used_at_collection_start(), max_at_start), + ZSIZE_ARGS(generation->stat_heap()->used_at_collection_end(), max_at_start), duration.seconds()); } @@ -950,8 +968,8 @@ void ZStatMutatorAllocRate::initialize() { void ZStatMutatorAllocRate::update_sampling_granule() { const size_t sampling_heap_granules = 128; - const size_t soft_max_capacity = ZHeap::heap()->soft_max_capacity(); - _sampling_granule = align_up(soft_max_capacity / sampling_heap_granules, ZGranuleSize); + const size_t heuristic_max_capacity = ZHeap::heap()->heuristic_max_capacity(); + _sampling_granule = align_up(heuristic_max_capacity / sampling_heap_granules, ZGranuleSize); } void ZStatMutatorAllocRate::sample_allocation(size_t allocation_bytes) { @@ -1013,7 +1031,70 @@ void ZStatMutatorAllocRate::sample_allocation(size_t allocation_bytes) { ZStatMutatorAllocRateStats ZStatMutatorAllocRate::stats() { ZLocker locker(_stat_lock); - return {_rate.avg(), _rate.predict_next(), _rate.sd()}; + return {_rate.avg(), _rate.predict_next(), _rate.sd(), _sampling_granule}; +} + +// +// Stat system memory usage stability +// +ZStatSystemMemoryUsage::ZSystemMemoryUsage::ZSystemMemoryUsage() + : _highest_usages(0.1 /* alpha */), + _highest_usage(0.0), + _memory_stability(0.0) {} + +ZStatSystemMemoryUsage::ZSystemMemoryUsage ZStatSystemMemoryUsage::_container_usage; +ZStatSystemMemoryUsage::ZSystemMemoryUsage ZStatSystemMemoryUsage::_machine_usage; + +void ZStatSystemMemoryUsage::ZSystemMemoryUsage::record_usage(double usage) { + double highest = _highest_usage.load_relaxed(); + if (highest < usage) { + // No need to do anything if the CAS fails; then someone else filled in an update value + _highest_usage.compare_exchange(highest, usage, memory_order_relaxed); + } +} + +double ZStatSystemMemoryUsage::ZSystemMemoryUsage::memory_stability() { + return _memory_stability.load_relaxed(); +} + +void ZStatSystemMemoryUsage::ZSystemMemoryUsage::sample_and_collect() { + double highest = _highest_usage.exchange(0.0, memory_order_relaxed); + if (highest != 0.0) { + _highest_usages.add(highest); + } + double sd = _highest_usages.dsd(); + _memory_stability.store_relaxed(sd); +} + +void ZStatSystemMemoryUsage::record(const ZMemoryPressureMetrics& metrics) { + const double machine_usage = double(metrics._machine._used_memory) / double(metrics._machine._max_memory); + record_machine_usage(machine_usage); + + if (metrics._is_containerized) { + const double container_usage = double(metrics._container._used_memory) / double(metrics._container._max_memory); + record_container_usage(container_usage); + } +} + +void ZStatSystemMemoryUsage::record_container_usage(double usage) { + _container_usage.record_usage(usage); +} + +void ZStatSystemMemoryUsage::record_machine_usage(double usage) { + _machine_usage.record_usage(usage); +} + +double ZStatSystemMemoryUsage::container_memory_stability() { + return _container_usage.memory_stability(); +} + +double ZStatSystemMemoryUsage::machine_memory_stability() { + return _machine_usage.memory_stability(); +} + +void ZStatSystemMemoryUsage::sample_and_collect() { + _container_usage.sample_and_collect(); + _machine_usage.sample_and_collect(); } // @@ -1037,6 +1118,9 @@ void ZStat::sample_and_collect(ZStatSamplerHistory* history) const { ZStatSamplerHistory& sampler_history = history[sampler->id()]; sampler_history.add(sampler->collect_and_reset()); } + + // Sample machine memory usage + ZStatSystemMemoryUsage::sample_and_collect(); } bool ZStat::should_print(LogTargetHandle log) const { @@ -1232,11 +1316,14 @@ ZStatCycle::ZStatCycle() _serial_time(0.7 /* alpha */), _parallelizable_time(0.7 /* alpha */), _parallelizable_duration(0.7 /* alpha */), - _last_active_workers(0.0) {} + _last_active_workers(0.0), + _start_vtime(), + _last_total_vtime() {} void ZStatCycle::at_start() { ZLocker locker(&_stat_lock); _start_of_last = Ticks::now(); + _start_vtime = os::current_thread_cpu_time(true /* user + sys */); } void ZStatCycle::at_end(ZStatWorkers* stat_workers, bool record_stats) { @@ -1252,6 +1339,7 @@ void ZStatCycle::at_end(ZStatWorkers* stat_workers, bool record_stats) { const double duration = (_end_of_last - _start_of_last).seconds(); const double workers_duration = stat_workers->get_and_reset_duration(); const double workers_time = stat_workers->get_and_reset_time(); + const double workers_vtime = stat_workers->get_and_reset_vtime(); const double serial_time = duration - workers_duration; _last_active_workers = workers_time / workers_duration; @@ -1260,6 +1348,9 @@ void ZStatCycle::at_end(ZStatWorkers* stat_workers, bool record_stats) { _serial_time.add(serial_time); _parallelizable_time.add(workers_time); _parallelizable_duration.add(workers_duration); + const jlong elapsed_vtime_nanos = os::current_thread_cpu_time(true /* user + sys */) - _start_vtime; + const double elapsed_vtime = double(elapsed_vtime_nanos) / NANOSECS_PER_SEC; + _last_total_vtime = elapsed_vtime + workers_vtime; if (end_of_last.value() != 0) { const double cycle_interval = (_end_of_last - end_of_last).seconds(); _cycle_intervals.add(cycle_interval); @@ -1320,7 +1411,8 @@ ZStatCycleStats ZStatCycle::stats() { _parallelizable_time.davg(), _parallelizable_time.dsd(), _parallelizable_duration.davg(), - _parallelizable_duration.dsd() + _parallelizable_duration.dsd(), + _last_total_vtime }; } @@ -1332,7 +1424,8 @@ ZStatWorkers::ZStatWorkers() _active_workers(0), _start_of_last(), _accumulated_duration(), - _accumulated_time() {} + _accumulated_time(), + _accumulated_vtime() {} void ZStatWorkers::at_start(uint active_workers) { ZLocker locker(&_stat_lock); @@ -1396,6 +1489,23 @@ double ZStatWorkers::get_and_reset_time() { return time; } +double ZStatWorkers::get_and_reset_vtime() { + ZLocker locker(&_stat_lock); + double result = _accumulated_vtime; + _accumulated_vtime = 0.0; + return result; +} + +void ZStatWorkers::add_accumulated_vtime(double vtime) { + for (;;) { + double prev = AtomicAccess::load(&_accumulated_vtime); + double new_val = prev + vtime; + if (AtomicAccess::cmpxchg(&_accumulated_vtime, prev, new_val) == prev) { + return; + } + } +} + ZStatWorkersStats ZStatWorkers::stats() { ZLocker locker(&_stat_lock); return { @@ -1404,6 +1514,31 @@ ZStatWorkersStats ZStatWorkers::stats() { }; } +// +// Stat memory workers +// +volatile double ZStatMemoryWorkers::_accumulated_vtime = 0.0; + +void ZStatMemoryWorkers::add_accumulated_vtime(double vtime) { + for (;;) { + double prev = AtomicAccess::load(&_accumulated_vtime); + double new_val = prev + vtime; + if (AtomicAccess::cmpxchg(&_accumulated_vtime, prev, new_val) == prev) { + return; + } + } +} + +double ZStatMemoryWorkers::get_and_reset_vtime() { + for (;;) { + double prev = AtomicAccess::load(&_accumulated_vtime); + double new_val = 0.0; + if (AtomicAccess::cmpxchg(&_accumulated_vtime, prev, new_val) == prev) { + return prev; + } + } +} + // // Stat load // @@ -1561,6 +1696,8 @@ void ZStatRelocation::print_age_table() { uint oldest_none_empty_age = 0; + const size_t max = ZGeneration::young()->stat_heap()->max_at_collection_start(); + for (ZPageAge age : ZPageAgeRangeAll) { uint i = untype(age); auto summarize_pages = [&](const ZRelocationSetSelectorGroupStats& stats) { @@ -1595,12 +1732,12 @@ void ZStatRelocation::print_age_table() { } else { return age_table() .left("%s", age_str.buffer()) - .left(ZTABLE_ARGS(live[i])); + .left(ZTABLE_ARGS(live[i], max)); } }; lt.print("%s", create_age_table() - .left(ZTABLE_ARGS(total[i] - live[i])) + .left(ZTABLE_ARGS(total[i] - live[i], max)) .left("%7zu / %zu", _selector_stats.small(age).npages_candidates(), _selector_stats.small(age).npages_selected()) @@ -1725,7 +1862,13 @@ size_t ZStatHeap::capacity_low() const { _at_relocate_end.capacity); } -size_t ZStatHeap::free(size_t used) const { +size_t ZStatHeap::free(size_t used, size_t capacity) const { + if (ZAdaptiveHeapSizing) { + // When adapting, the max capacity is likely the whole machine/container, + // so we compare against the currently committed capacity instead. + return capacity - used; + } + return _at_initialize.max_capacity - used; } @@ -1757,9 +1900,9 @@ void ZStatHeap::at_initialize(size_t min_capacity, size_t max_capacity) { void ZStatHeap::at_collection_start(const ZPageAllocatorStats& stats) { ZLocker locker(&_stat_lock); - _at_collection_start.soft_max_capacity = stats.soft_max_capacity(); + _at_collection_start.heuristic_max_capacity = stats.heuristic_max_capacity(); _at_collection_start.capacity = stats.capacity(); - _at_collection_start.free = free(stats.used()); + _at_collection_start.free = free(stats.used(), stats.capacity()); _at_collection_start.used = stats.used(); _at_collection_start.used_generation = stats.used_generation(); } @@ -1767,9 +1910,9 @@ void ZStatHeap::at_collection_start(const ZPageAllocatorStats& stats) { void ZStatHeap::at_mark_start(const ZPageAllocatorStats& stats) { ZLocker locker(&_stat_lock); - _at_mark_start.soft_max_capacity = stats.soft_max_capacity(); + _at_mark_start.heuristic_max_capacity = stats.heuristic_max_capacity(); _at_mark_start.capacity = stats.capacity(); - _at_mark_start.free = free(stats.used()); + _at_mark_start.free = free(stats.used(), stats.capacity()); _at_mark_start.used = stats.used(); _at_mark_start.used_generation = stats.used_generation(); _at_mark_start.allocation_stalls = stats.allocation_stalls(); @@ -1779,7 +1922,7 @@ void ZStatHeap::at_mark_end(const ZPageAllocatorStats& stats) { ZLocker locker(&_stat_lock); _at_mark_end.capacity = stats.capacity(); - _at_mark_end.free = free(stats.used()); + _at_mark_end.free = free(stats.used(), stats.capacity()); _at_mark_end.used = stats.used(); _at_mark_end.used_generation = stats.used_generation(); _at_mark_end.mutator_allocated = mutator_allocated(stats.used_generation(), 0 /* reclaimed */, 0 /* relocated */); @@ -1803,7 +1946,7 @@ void ZStatHeap::at_relocate_start(const ZPageAllocatorStats& stats) { assert(stats.compacted() == 0, "Nothing should have been compacted"); _at_relocate_start.capacity = stats.capacity(); - _at_relocate_start.free = free(stats.used()); + _at_relocate_start.free = free(stats.used(), stats.capacity()); _at_relocate_start.used = stats.used(); _at_relocate_start.used_generation = stats.used_generation(); _at_relocate_start.live = _at_mark_end.live - stats.promoted(); @@ -1821,14 +1964,14 @@ void ZStatHeap::at_relocate_end(const ZPageAllocatorStats& stats, bool record_st _at_relocate_end.capacity = stats.capacity(); _at_relocate_end.capacity_high = capacity_high(); _at_relocate_end.capacity_low = capacity_low(); - _at_relocate_end.free = free(stats.used()); - _at_relocate_end.free_high = free(stats.used_low()); - _at_relocate_end.free_low = free(stats.used_high()); + _at_relocate_end.free = free(stats.used(), stats.capacity()); + _at_relocate_end.free_high = free(stats.used_low(), stats.capacity()); + _at_relocate_end.free_low = free(stats.used_high(), MAX2(stats.used_high(), stats.capacity())); _at_relocate_end.used = stats.used(); _at_relocate_end.used_high = stats.used_high(); _at_relocate_end.used_low = stats.used_low(); _at_relocate_end.used_generation = stats.used_generation(); - _at_relocate_end.live = _at_mark_end.live - stats.promoted(); + _at_relocate_end.live = _at_mark_end.live - MIN2(stats.promoted(), _at_mark_end.live); _at_relocate_end.garbage = garbage(stats.freed(), stats.compacted(), stats.promoted()); _at_relocate_end.mutator_allocated = mutator_allocated(stats.used_generation(), stats.freed(), stats.compacted()); _at_relocate_end.reclaimed = reclaimed(stats.freed(), stats.compacted(), stats.promoted()); @@ -1846,14 +1989,14 @@ double ZStatHeap::reclaimed_avg() { return _reclaimed_bytes.davg() + std::numeric_limits::denorm_min(); } -size_t ZStatHeap::max_capacity() { - return _at_initialize.max_capacity; -} - size_t ZStatHeap::used_at_collection_start() const { return _at_collection_start.used; } +size_t ZStatHeap::max_at_collection_start() const { + return _at_collection_start.heuristic_max_capacity; +} + size_t ZStatHeap::used_at_mark_start() const { return _at_mark_start.used; } @@ -1862,6 +2005,10 @@ size_t ZStatHeap::used_generation_at_mark_start() const { return _at_mark_start.used_generation; } +size_t ZStatHeap::used_generation_at_relocate_end() const { + return _at_relocate_end.used_generation; +} + size_t ZStatHeap::live_at_mark_end() const { return _at_mark_end.live; } @@ -1909,12 +2056,13 @@ ZStatHeapStats ZStatHeap::stats() { } void ZStatHeap::print(const ZGeneration* generation) const { - log_info(gc, heap)("Min Capacity: " - ZSIZE_FMT, ZSIZE_ARGS(_at_initialize.min_capacity)); - log_info(gc, heap)("Max Capacity: " - ZSIZE_FMT, ZSIZE_ARGS(_at_initialize.max_capacity)); - log_info(gc, heap)("Soft Max Capacity: " - ZSIZE_FMT, ZSIZE_ARGS(_at_mark_start.soft_max_capacity)); + const size_t dynamic_max = ZHeap::heap()->dynamic_max_capacity(); + const size_t max = ZAdaptiveHeap::explicit_max_capacity() ? _at_initialize.max_capacity + : _at_collection_start.heuristic_max_capacity; + + log_info(gc, heap)("Min Capacity: " ZSIZE_FMT, ZSIZE_ARGS(_at_initialize.min_capacity, dynamic_max)); + log_info(gc, heap)("Max Capacity: " ZSIZE_FMT, ZSIZE_ARGS(dynamic_max, dynamic_max)); + log_info(gc, heap)("Target Capacity: " ZSIZE_FMT, ZSIZE_ARGS(_at_mark_start.heuristic_max_capacity, dynamic_max)); log_info(gc, heap)("Heap Statistics:"); ZStatTablePrinter heap_table(10, 18); @@ -1929,30 +2077,30 @@ void ZStatHeap::print(const ZGeneration* generation) const { .end()); log_info(gc, heap)("%s", heap_table() .right("Capacity:") - .left(ZTABLE_ARGS(_at_mark_start.capacity)) - .left(ZTABLE_ARGS(_at_mark_end.capacity)) - .left(ZTABLE_ARGS(_at_relocate_start.capacity)) - .left(ZTABLE_ARGS(_at_relocate_end.capacity)) - .left(ZTABLE_ARGS(_at_relocate_end.capacity_high)) - .left(ZTABLE_ARGS(_at_relocate_end.capacity_low)) + .left(ZTABLE_ARGS(_at_mark_start.capacity, max)) + .left(ZTABLE_ARGS(_at_mark_end.capacity, max)) + .left(ZTABLE_ARGS(_at_relocate_start.capacity, max)) + .left(ZTABLE_ARGS(_at_relocate_end.capacity, max)) + .left(ZTABLE_ARGS(_at_relocate_end.capacity_high, max)) + .left(ZTABLE_ARGS(_at_relocate_end.capacity_low, max)) .end()); log_info(gc, heap)("%s", heap_table() .right("Free:") - .left(ZTABLE_ARGS(_at_mark_start.free)) - .left(ZTABLE_ARGS(_at_mark_end.free)) - .left(ZTABLE_ARGS(_at_relocate_start.free)) - .left(ZTABLE_ARGS(_at_relocate_end.free)) - .left(ZTABLE_ARGS(_at_relocate_end.free_high)) - .left(ZTABLE_ARGS(_at_relocate_end.free_low)) + .left(ZTABLE_ARGS(_at_mark_start.free, max)) + .left(ZTABLE_ARGS(_at_mark_end.free, max)) + .left(ZTABLE_ARGS(_at_relocate_start.free, max)) + .left(ZTABLE_ARGS(_at_relocate_end.free, max)) + .left(ZTABLE_ARGS(_at_relocate_end.free_high, max)) + .left(ZTABLE_ARGS(_at_relocate_end.free_low, max)) .end()); log_info(gc, heap)("%s", heap_table() .right("Used:") - .left(ZTABLE_ARGS(_at_mark_start.used)) - .left(ZTABLE_ARGS(_at_mark_end.used)) - .left(ZTABLE_ARGS(_at_relocate_start.used)) - .left(ZTABLE_ARGS(_at_relocate_end.used)) - .left(ZTABLE_ARGS(_at_relocate_end.used_high)) - .left(ZTABLE_ARGS(_at_relocate_end.used_low)) + .left(ZTABLE_ARGS(_at_mark_start.used, max)) + .left(ZTABLE_ARGS(_at_mark_end.used, max)) + .left(ZTABLE_ARGS(_at_relocate_start.used, max)) + .left(ZTABLE_ARGS(_at_relocate_end.used, max)) + .left(ZTABLE_ARGS(_at_relocate_end.used_high, max)) + .left(ZTABLE_ARGS(_at_relocate_end.used_low, max)) .end()); log_info(gc, heap)("%s Generation Statistics:", generation->is_young() ? "Young" : "Old"); @@ -1966,46 +2114,46 @@ void ZStatHeap::print(const ZGeneration* generation) const { .end()); log_info(gc, heap)("%s", gen_table() .right("Used:") - .left(ZTABLE_ARGS(_at_mark_start.used_generation)) - .left(ZTABLE_ARGS(_at_mark_end.used_generation)) - .left(ZTABLE_ARGS(_at_relocate_start.used_generation)) - .left(ZTABLE_ARGS(_at_relocate_end.used_generation)) + .left(ZTABLE_ARGS(_at_mark_start.used_generation, max)) + .left(ZTABLE_ARGS(_at_mark_end.used_generation, max)) + .left(ZTABLE_ARGS(_at_relocate_start.used_generation, max)) + .left(ZTABLE_ARGS(_at_relocate_end.used_generation, max)) .end()); log_info(gc, heap)("%s", gen_table() .right("Live:") .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_mark_end.live)) - .left(ZTABLE_ARGS(_at_relocate_start.live)) - .left(ZTABLE_ARGS(_at_relocate_end.live)) + .left(ZTABLE_ARGS(_at_mark_end.live, max)) + .left(ZTABLE_ARGS(_at_relocate_start.live, max)) + .left(ZTABLE_ARGS(_at_relocate_end.live, max)) .end()); log_info(gc, heap)("%s", gen_table() .right("Garbage:") .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_mark_end.garbage)) - .left(ZTABLE_ARGS(_at_relocate_start.garbage)) - .left(ZTABLE_ARGS(_at_relocate_end.garbage)) + .left(ZTABLE_ARGS(_at_mark_end.garbage, max)) + .left(ZTABLE_ARGS(_at_relocate_start.garbage, max)) + .left(ZTABLE_ARGS(_at_relocate_end.garbage, max)) .end()); log_info(gc, heap)("%s", gen_table() .right("Allocated:") .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_mark_end.mutator_allocated)) - .left(ZTABLE_ARGS(_at_relocate_start.mutator_allocated)) - .left(ZTABLE_ARGS(_at_relocate_end.mutator_allocated)) + .left(ZTABLE_ARGS(_at_mark_end.mutator_allocated, max)) + .left(ZTABLE_ARGS(_at_relocate_start.mutator_allocated, max)) + .left(ZTABLE_ARGS(_at_relocate_end.mutator_allocated, max)) .end()); log_info(gc, heap)("%s", gen_table() .right("Reclaimed:") .left(ZTABLE_ARGS_NA) .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_relocate_start.reclaimed)) - .left(ZTABLE_ARGS(_at_relocate_end.reclaimed)) + .left(ZTABLE_ARGS(_at_relocate_start.reclaimed, max)) + .left(ZTABLE_ARGS(_at_relocate_end.reclaimed, max)) .end()); if (generation->is_young()) { log_info(gc, heap)("%s", gen_table() .right("Promoted:") .left(ZTABLE_ARGS_NA) .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_relocate_start.promoted)) - .left(ZTABLE_ARGS(_at_relocate_end.promoted)) + .left(ZTABLE_ARGS(_at_relocate_start.promoted, max)) + .left(ZTABLE_ARGS(_at_relocate_end.promoted, max)) .end()); } log_info(gc, heap)("%s", gen_table() @@ -2013,7 +2161,7 @@ void ZStatHeap::print(const ZGeneration* generation) const { .left(ZTABLE_ARGS_NA) .left(ZTABLE_ARGS_NA) .left(ZTABLE_ARGS_NA) - .left(ZTABLE_ARGS(_at_relocate_end.compacted)) + .left(ZTABLE_ARGS(_at_relocate_end.compacted, max)) .end()); } diff --git a/src/hotspot/share/gc/z/zStat.hpp b/src/hotspot/share/gc/z/zStat.hpp index 739c3cd5980c..42477424b64c 100644 --- a/src/hotspot/share/gc/z/zStat.hpp +++ b/src/hotspot/share/gc/z/zStat.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,6 +46,7 @@ class ZRelocationSetSelectorGroupStats; class ZStatSampler; class ZStatSamplerHistory; class ZStatWorkers; +struct ZMemoryPressureMetrics; struct ZStatCounterData; struct ZStatSamplerData; @@ -234,6 +235,9 @@ class ZStatPhaseCollection : public ZStatPhase { void set_used_at_start(size_t used) const; size_t used_at_start() const; + void set_max_at_start(size_t max) const; + size_t max_at_start() const; + public: ZStatPhaseCollection(const char* name, bool minor); @@ -353,6 +357,7 @@ struct ZStatMutatorAllocRateStats { double _avg; double _predict; double _sd; + size_t _sampling_granule; }; // @@ -379,6 +384,37 @@ class ZStatMutatorAllocRate : public AllStatic { static ZStatMutatorAllocRateStats stats(); }; +class ZStatSystemMemoryUsage : public AllStatic { +private: + class ZSystemMemoryUsage { + private: + NumberSeq _highest_usages; + Atomic _highest_usage; + Atomic _memory_stability; + + public: + ZSystemMemoryUsage(); + + void record_usage(double usage); + double memory_stability(); + void sample_and_collect(); + }; + + static ZSystemMemoryUsage _container_usage; + static ZSystemMemoryUsage _machine_usage; + +public: + static void record(const ZMemoryPressureMetrics& metrics); + static void record_container_usage(double usage); + static void record_machine_usage(double usage); + + // Returns 0.0 when memory usage is constant, and increases as memory spikes appear + static double container_memory_stability(); + static double machine_memory_stability(); + + static void sample_and_collect(); +}; + // // Stat thread // @@ -414,6 +450,7 @@ struct ZStatCycleStats { double _sd_parallelizable_time; double _avg_parallelizable_duration; double _sd_parallelizable_duration; + double _last_total_vtime; }; // @@ -430,6 +467,8 @@ class ZStatCycle { NumberSeq _parallelizable_time; NumberSeq _parallelizable_duration; double _last_active_workers; + jlong _start_vtime; + double _last_total_vtime; bool is_warm(); bool is_time_trustable(); @@ -462,6 +501,8 @@ class ZStatWorkers { Tickspan _accumulated_duration; Tickspan _accumulated_time; + volatile double _accumulated_vtime; + double accumulated_duration(); double accumulated_time(); uint active_workers(); @@ -475,9 +516,24 @@ class ZStatWorkers { double get_and_reset_duration(); double get_and_reset_time(); + void add_accumulated_vtime(double vtime); + double get_and_reset_vtime(); + ZStatWorkersStats stats(); }; +// +// Stat memory workers +// +class ZStatMemoryWorkers : public AllStatic { +private: + static volatile double _accumulated_vtime; + +public: + static void add_accumulated_vtime(double vtime); + static double get_and_reset_vtime(); +}; + // // Stat load // @@ -603,7 +659,7 @@ class ZStatHeap { } _at_initialize; struct ZAtGenerationCollectionStart { - size_t soft_max_capacity; + size_t heuristic_max_capacity; size_t capacity; size_t free; size_t used; @@ -611,7 +667,7 @@ class ZStatHeap { } _at_collection_start; struct ZAtMarkStart { - size_t soft_max_capacity; + size_t heuristic_max_capacity; size_t capacity; size_t free; size_t used; @@ -668,7 +724,7 @@ class ZStatHeap { size_t capacity_high() const; size_t capacity_low() const; - size_t free(size_t used) const; + size_t free(size_t used, size_t capacity) const; size_t mutator_allocated(size_t used, size_t freed, size_t relocated) const; size_t garbage(size_t freed, size_t relocated, size_t promoted) const; size_t reclaimed(size_t freed, size_t relocated, size_t promoted) const; @@ -684,10 +740,11 @@ class ZStatHeap { void at_relocate_start(const ZPageAllocatorStats& stats); void at_relocate_end(const ZPageAllocatorStats& stats, bool record_stats); - static size_t max_capacity(); + size_t max_at_collection_start() const; size_t used_at_collection_start() const; size_t used_at_mark_start() const; size_t used_generation_at_mark_start() const; + size_t used_generation_at_relocate_end() const; size_t live_at_mark_end() const; size_t allocated_at_mark_end() const; size_t garbage_at_mark_end() const; diff --git a/src/hotspot/share/gc/z/zTask.cpp b/src/hotspot/share/gc/z/zTask.cpp index 208a20c1914a..4a3e987439fe 100644 --- a/src/hotspot/share/gc/z/zTask.cpp +++ b/src/hotspot/share/gc/z/zTask.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,17 +22,29 @@ */ #include "gc/z/zTask.hpp" +#include "gc/z/zWorkers.hpp" +#include "utilities/globalDefinitions.hpp" ZTask::Task::Task(ZTask* task, const char* name) : WorkerTask(name), - _task(task) {} + _task(task), + _workers(nullptr) {} void ZTask::Task::work(uint worker_id) { + if (!_task->account_vtime()) { + _task->work(); + return; + } + + jlong start = os::current_thread_cpu_time(true /* user + sys */); _task->work(); + jlong elapsed = os::current_thread_cpu_time(true /* user + sys */) - start; + _workers->add_accumulated_vtime(double(elapsed) / NANOSECS_PER_SEC); } -ZTask::ZTask(const char* name) - : _worker_task(this, name) {} +ZTask::ZTask(const char* name, bool account_vtime) + : _worker_task(this, name), + _account_vtime(account_vtime) {} const char* ZTask::name() const { return _worker_task.name(); @@ -42,6 +54,14 @@ WorkerTask* ZTask::worker_task() { return &_worker_task; } +void ZTask::set_workers(ZWorkers* workers) { + _worker_task._workers = workers; +} + +bool ZTask::account_vtime() const { + return _account_vtime; +} + ZRestartableTask::ZRestartableTask(const char* name) : ZTask(name) {} diff --git a/src/hotspot/share/gc/z/zTask.hpp b/src/hotspot/share/gc/z/zTask.hpp index 37733f8873a4..7f4ecd8c074f 100644 --- a/src/hotspot/share/gc/z/zTask.hpp +++ b/src/hotspot/share/gc/z/zTask.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,11 +27,15 @@ #include "gc/shared/workerThread.hpp" #include "memory/allocation.hpp" +class ZWorkers; + class ZTask : public StackObj { private: class Task : public WorkerTask { + friend class ZTask; private: ZTask* const _task; + ZWorkers* _workers; public: Task(ZTask* task, const char* name); @@ -40,12 +44,16 @@ class ZTask : public StackObj { }; Task _worker_task; + const bool _account_vtime; public: - ZTask(const char* name); + ZTask(const char* name, bool account_vtime = true); const char* name() const; WorkerTask* worker_task(); + void set_workers(ZWorkers* workers); + + bool account_vtime() const; virtual void work() = 0; }; diff --git a/src/hotspot/share/gc/z/zUncommitter.cpp b/src/hotspot/share/gc/z/zUncommitter.cpp index 96af67a77b53..c419b29419bb 100644 --- a/src/hotspot/share/gc/z/zUncommitter.cpp +++ b/src/hotspot/share/gc/z/zUncommitter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,7 @@ */ #include "gc/shared/gc_globals.hpp" +#include "gc/z/zAdaptiveHeap.inline.hpp" #include "gc/z/zGlobals.hpp" #include "gc/z/zHeap.inline.hpp" #include "gc/z/zLock.inline.hpp" @@ -37,9 +38,14 @@ #include "utilities/ticks.hpp" #include +#include static const ZStatCounter ZCounterUncommit("Memory", "Uncommit", ZStatUnitBytesPerSecond); +bool ZUncommitter::is_enabled() { + return ZUncommit && !ZAdaptiveHeapSizing; +} + ZUncommitter::ZUncommitter(uint32_t id, ZPartition* partition) : _id(id), _partition(partition), @@ -51,16 +57,18 @@ ZUncommitter::ZUncommitter(uint32_t id, ZPartition* partition) _cycle_start(0.0), _to_uncommit(0), _uncommitted(0) { + if (!is_enabled()) { + // Disabled, do not start. + _stop = true; + return; + } + set_name("ZUncommitter#%u", id); create_and_start(); } bool ZUncommitter::wait(uint64_t timeout) const { ZLocker locker(&_lock); - while (!ZUncommit && !_stop) { - _lock.wait(); - } - if (!_stop && timeout > 0) { if (!uncommit_cycle_is_finished()) { log_trace(gc, heap)("Uncommitter (%u) Timeout: " UINT64_FORMAT "ms left to uncommit: " @@ -155,7 +163,7 @@ void ZUncommitter::run_thread() { } log_info(gc, heap)("Uncommitter (%u) Uncommitted: %zuM(%.0f%%) in %.3fms", - _id, _uncommitted / M, percent_of(_uncommitted, ZHeap::heap()->max_capacity()), + _id, _uncommitted / M, percent_of(_uncommitted, ZHeap::heap()->heuristic_max_capacity()), accumulated_time.seconds() * MILLIUNITS); } @@ -230,7 +238,7 @@ bool ZUncommitter::activate_uncommit_cycle() { const size_t to_uncommit = align_up(size_t(double(uncommit_watermark) * 0.9), ZGranuleSize); // Never uncommit below min capacity - const size_t uncommit_limit = _partition->_capacity - _partition->_min_capacity; + const size_t uncommit_limit = _partition->_capacity - _partition->_static_min_capacity; _to_uncommit = MIN2(uncommit_limit, to_uncommit); _uncommitted = 0; @@ -375,7 +383,7 @@ size_t ZUncommitter::uncommit() { // We flush out and uncommit chunks at a time (~0.8% of the max capacity, // but at least one granule and at most 256M), in case demand for memory // increases while we are uncommitting. - const size_t current_max_capacity = _partition->_current_max_capacity; + const size_t current_max_capacity = _partition->current_max_capacity(); const size_t limit_upper_bound = MAX2(ZGranuleSize, align_down(256 * M / ZNUMA::count(), ZGranuleSize)); const size_t limit = MIN2(align_up(current_max_capacity >> 7, ZGranuleSize), limit_upper_bound); @@ -387,7 +395,7 @@ size_t ZUncommitter::uncommit() { const size_t to_uncommit = MIN2(_to_uncommit, allowed_to_uncommit); // Never uncommit below min capacity. - const size_t retain = MAX2(_partition->_used, _partition->_min_capacity); + const size_t retain = MAX2(_partition->_used, _partition->_static_min_capacity); const size_t release = _partition->_capacity - retain; const size_t flush = MIN3(release, limit, to_uncommit); @@ -400,7 +408,7 @@ size_t ZUncommitter::uncommit() { } // Record flushed memory as claimed and how much we've flushed for this partition - AtomicAccess::add(&_partition->_claimed, flushed); + _partition->increase_claimed(flushed); } // Unmap and uncommit flushed memory @@ -416,8 +424,8 @@ size_t ZUncommitter::uncommit() { ZLocker locker(&_partition->_page_allocator->_lock); // Adjust claimed and capacity to reflect the uncommit - AtomicAccess::sub(&_partition->_claimed, flushed); - _partition->decrease_capacity(flushed, false /* set_max_capacity */); + _partition->decrease_claimed(flushed); + _partition->decrease_capacity(flushed); register_uncommit(flushed); } diff --git a/src/hotspot/share/gc/z/zUncommitter.hpp b/src/hotspot/share/gc/z/zUncommitter.hpp index a3cd2b17e236..d9e9d6661a6d 100644 --- a/src/hotspot/share/gc/z/zUncommitter.hpp +++ b/src/hotspot/share/gc/z/zUncommitter.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -70,6 +70,8 @@ class ZUncommitter : public ZThread { virtual void terminate(); public: + static bool is_enabled(); + ZUncommitter(uint32_t id, ZPartition* partition); void cancel_uncommit_cycle(); diff --git a/src/hotspot/share/gc/z/zUtils.hpp b/src/hotspot/share/gc/z/zUtils.hpp index cadb11594337..565827bbaf59 100644 --- a/src/hotspot/share/gc/z/zUtils.hpp +++ b/src/hotspot/share/gc/z/zUtils.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #define SHARE_GC_Z_ZUTILS_HPP #include "gc/z/zAddress.hpp" +#include "memory/allocation.hpp" #include "memory/allStatic.hpp" #include "utilities/globalDefinitions.hpp" @@ -60,4 +61,17 @@ class ZUtils : public AllStatic { static void sort(T* array, int count, Comparator comparator); }; +template +class ZOnScopeExit : StackObj { + CallableType _call_on_scope_exit; + +public: + NONCOPYABLE(ZOnScopeExit); + template + ZOnScopeExit(Callable&& call_on_scope_exit); + ~ZOnScopeExit(); +}; + +template ZOnScopeExit(CallableType) -> ZOnScopeExit; + #endif // SHARE_GC_Z_ZUTILS_HPP diff --git a/src/hotspot/share/gc/z/zUtils.inline.hpp b/src/hotspot/share/gc/z/zUtils.inline.hpp index 658e278a8dd9..3f6d2ab2b10c 100644 --- a/src/hotspot/share/gc/z/zUtils.inline.hpp +++ b/src/hotspot/share/gc/z/zUtils.inline.hpp @@ -109,4 +109,12 @@ inline void ZUtils::sort(T* array, int count, Comparator comparator) { sort(array, static_cast(count), comparator); } +template +template +ZOnScopeExit::ZOnScopeExit(Callable&& call_on_scope_exit) + : _call_on_scope_exit(static_cast(call_on_scope_exit)) {} + +template +ZOnScopeExit::~ZOnScopeExit() { _call_on_scope_exit(); } + #endif // SHARE_GC_Z_ZUTILS_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zVirtualMemoryManager.cpp b/src/hotspot/share/gc/z/zVirtualMemoryManager.cpp index aec54818a681..e618b7a17fb7 100644 --- a/src/hotspot/share/gc/z/zVirtualMemoryManager.cpp +++ b/src/hotspot/share/gc/z/zVirtualMemoryManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -253,7 +253,16 @@ ZVirtualMemoryManager::ZVirtualMemoryManager(size_t max_capacity) EXACTFMTARGS(limit), EXACTFMTARGS(desired), EXACTFMTARGS(requested)); if (reserved < max_capacity) { - ZInitialize::error_d("Failed to reserve " EXACTFMT " address space for Java heap", EXACTFMTARGS(max_capacity)); + reserver.unreserve_all(); + + const bool has_reserve_limit = os::reserve_memory_limit() != SIZE_MAX; + const char* extra_message = has_reserve_limit + ? " Use -Xmx to explicitly set the heap size to limit how much memory is reserved." + : ""; + + ZInitialize::error_d("Failed to reserve " EXACTFMT " address space for Java heap.%s", + EXACTFMTARGS(max_capacity), extra_message); + return; } diff --git a/src/hotspot/share/gc/z/zWorkers.cpp b/src/hotspot/share/gc/z/zWorkers.cpp index 1e7144a0c261..114d9ce2ea67 100644 --- a/src/hotspot/share/gc/z/zWorkers.cpp +++ b/src/hotspot/share/gc/z/zWorkers.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -97,6 +97,7 @@ void ZWorkers::run(ZTask* task) { _stats->at_start(active_workers()); } + task->set_workers(this); _workers.run_task(task->worker_task()); { @@ -130,6 +131,7 @@ void ZWorkers::run_all(ZTask* task) { // Execute task using all workers log_debug(gc, task)("Executing %s using %s with %u workers", task->name(), _workers.name(), active_workers()); + task->set_workers(this); _workers.run_task(task->worker_task()); // Restore number of active workers @@ -164,3 +166,7 @@ void ZWorkers::request_resize_workers(uint nworkers) { _requested_nworkers = nworkers; } + +void ZWorkers::add_accumulated_vtime(double vtime) { + _stats->add_accumulated_vtime(vtime); +} diff --git a/src/hotspot/share/gc/z/zWorkers.hpp b/src/hotspot/share/gc/z/zWorkers.hpp index 8d1f41525626..d65fa1728d29 100644 --- a/src/hotspot/share/gc/z/zWorkers.hpp +++ b/src/hotspot/share/gc/z/zWorkers.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -64,6 +64,8 @@ class ZWorkers { void request_resize_workers(uint nworkers); bool should_worker_resize(); + + void add_accumulated_vtime(double vtime); }; #endif // SHARE_GC_Z_ZWORKERS_HPP diff --git a/src/hotspot/share/gc/z/z_globals.hpp b/src/hotspot/share/gc/z/z_globals.hpp index 06adba80be03..d224b6ee2d06 100644 --- a/src/hotspot/share/gc/z/z_globals.hpp +++ b/src/hotspot/share/gc/z/z_globals.hpp @@ -34,10 +34,19 @@ range, \ constraint) \ \ + product(bool, ZAdaptiveHeapSizing, false, \ + "Enable Adaptive Heap Sizing") \ + \ product(double, ZAllocationSpikeTolerance, 2.0, \ "Allocation spike tolerance factor") \ range(0, INT_MAX) \ \ + product(double, ZGCIntensity, 0, MANAGEABLE, \ + "Level of GC intensity, used for adaptive heap sizing " \ + "policies to use either more CPU or memory") \ + range(0, DBL_MAX) \ + constraint(ZGCIntensityConstraintFunc, AfterErgo) \ + \ product(double, ZFragmentationLimit, 5.0, \ "Maximum allowed heap fragmentation") \ range(0, 100) \ @@ -117,6 +126,9 @@ "Young generation tenuring threshold, -1 for dynamic computation")\ range(-1, static_cast(ZPageAgeCount) - 1) \ \ + product(bool, ZMemoryHeating, true, DIAGNOSTIC, \ + "Use concurrent memory worker(s) to optimize committed memory") \ + \ develop(bool, ZVerifyOops, false, \ "Verify accessed oops") \ \ diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp index 238763863c30..803493f4a067 100644 --- a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp +++ b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.cpp @@ -66,7 +66,6 @@ void JfrFilterClassClosure::do_klass(Klass* k) { if (!_classes_to_modify->contains(klass_id)) { jclass mirror = mirror_as_local_jni_handle(ik, _thread); _classes_to_modify->put(klass_id, mirror); - _classes_to_modify->maybe_grow(); } } } diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp index e9712e616b6e..f13c74a8fa06 100644 --- a/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp +++ b/src/hotspot/share/jfr/support/methodtracer/jfrClassFilterClosure.hpp @@ -30,6 +30,7 @@ #include "jfr/utilities/jfrTypes.hpp" #include "jni.h" #include "memory/iterator.hpp" +#include "utilities/resizableHashTable.hpp" class InstanceKlass; class JavaThread; @@ -38,11 +39,6 @@ class Klass; template class GrowableArray; -template class ResizeableHashTable; - // Knuth multiplicative hashing. inline uint32_t knuth_hash(const traceid& id) { const uint32_t v = static_cast(id); diff --git a/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp b/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp index 1d704f05a154..1916481a1f9f 100644 --- a/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp +++ b/src/hotspot/share/jfr/support/methodtracer/jfrMethodTracer.cpp @@ -104,7 +104,6 @@ static void register_placeholder(const InstanceKlass* ik, const JfrMethodProcess JfrTraceTagging::tag_sticky(ik, mp); assert(!placeholder_table()->contains(JfrTraceId::load_raw(ik)), "invariant"); placeholder_table()->put(JfrTraceId::load_raw(ik), ik); - placeholder_table()->maybe_grow(); } // Quick and unlocked check to see if the Method Tracer has been activated. diff --git a/src/hotspot/share/memory/heapInspection.cpp b/src/hotspot/share/memory/heapInspection.cpp index bd0b0f4d88ea..27a807a66b9c 100644 --- a/src/hotspot/share/memory/heapInspection.cpp +++ b/src/hotspot/share/memory/heapInspection.cpp @@ -40,6 +40,7 @@ #include "runtime/atomicAccess.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/os.hpp" +#include "services/diagnosticWorkers.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" #include "utilities/stack.inline.hpp" @@ -99,24 +100,45 @@ const char* KlassInfoEntry::name() const { return name; } +const char* KlassInfoEntry::layout() const { + if (_klass->is_flatArray_klass()) { + switch (FlatArrayKlass::cast(_klass)->layout_kind()) { + case LayoutKind::NULL_FREE_NON_ATOMIC_FLAT: + return " [flat, null-restricted, non-atomic]"; + case LayoutKind::NULL_FREE_ATOMIC_FLAT: + return " [flat, null-restricted, atomic]"; + case LayoutKind::NULLABLE_NON_ATOMIC_FLAT: + return " [flat, nullable, non-atomic]"; + case LayoutKind::NULLABLE_ATOMIC_FLAT: + return " [flat, nullable, atomic]"; + default: + return " [unknown layout]"; + } + } + return ""; +} + + void KlassInfoEntry::print_on(outputStream* st) const { ResourceMark rm; // simplify the formatting (ILP32 vs LP64) - always cast the numbers to 64-bit ModuleEntry* module = _klass->module(); if (module->is_named()) { - st->print_cr(INT64_FORMAT_W(13) " " UINT64_FORMAT_W(13) " %s (%s%s%s)", + st->print_cr(INT64_FORMAT_W(13) " " UINT64_FORMAT_W(13) " %s%s (%s%s%s)", (int64_t)_instance_count, (uint64_t)_instance_words * HeapWordSize, name(), + layout(), module->name()->as_C_string(), module->version() != nullptr ? "@" : "", module->version() != nullptr ? module->version()->as_C_string() : ""); } else { - st->print_cr(INT64_FORMAT_W(13) " " UINT64_FORMAT_W(13) " %s", + st->print_cr(INT64_FORMAT_W(13) " " UINT64_FORMAT_W(13) " %s%s", (int64_t)_instance_count, (uint64_t)_instance_words * HeapWordSize, - name()); + name(), + layout()); } } @@ -713,9 +735,18 @@ uintx HeapInspection::populate_table(KlassInfoTable* cit, BoolObjectClosure *fil return ric.missed_count(); } -void HeapInspection::heap_inspection(outputStream* st, WorkerThreads* workers) { +void HeapInspection::heap_inspection(outputStream* st, uint num_workers) { ResourceMark rm; + // Use parallel workers if we have enough active workers and parallel heap iteration is supported. + WorkerThreads* workers = nullptr; + if (Universe::heap()->supports_parallel_object_iteration() && num_workers > 1) { + const uint active_workers = DiagnosticWorkers::try_and_set_active_workers(num_workers); + if (active_workers > 1) { + workers = DiagnosticWorkers::workers(); + } + } + KlassInfoTable cit(false); if (!cit.allocation_failed()) { // populate table with object allocation info diff --git a/src/hotspot/share/memory/heapInspection.hpp b/src/hotspot/share/memory/heapInspection.hpp index 646610e753d6..51f595e18249 100644 --- a/src/hotspot/share/memory/heapInspection.hpp +++ b/src/hotspot/share/memory/heapInspection.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -82,6 +82,7 @@ class KlassInfoEntry: public CHeapObj { int compare(KlassInfoEntry* e1, KlassInfoEntry* e2); void print_on(outputStream* st) const; const char* name() const; + const char* layout() const; }; class KlassInfoClosure : public StackObj { @@ -204,7 +205,7 @@ class KlassInfoClosure; class HeapInspection : public StackObj { public: - void heap_inspection(outputStream* st, WorkerThreads* workers) NOT_SERVICES_RETURN; + void heap_inspection(outputStream* st, uint num_workers) NOT_SERVICES_RETURN; uintx populate_table(KlassInfoTable* cit, BoolObjectClosure* filter, WorkerThreads* workers) NOT_SERVICES_RETURN_(0); static void find_instances_at_safepoint(Klass* k, GrowableArray* result) NOT_SERVICES_RETURN; }; diff --git a/src/hotspot/share/memory/metaspaceClosure.cpp b/src/hotspot/share/memory/metaspaceClosure.cpp index f0b1e00506fe..28466a0963f4 100644 --- a/src/hotspot/share/memory/metaspaceClosure.cpp +++ b/src/hotspot/share/memory/metaspaceClosure.cpp @@ -102,9 +102,6 @@ bool UniqueMetaspaceClosure::do_ref(MetaspaceClosure::Ref* ref, bool read_only) if (!created) { return false; // Already visited: no need to iterate embedded pointers. } else { - if (_has_been_visited.maybe_grow()) { - log_info(aot, hashtables)("Expanded _has_been_visited table to %d", _has_been_visited.table_size()); - } return do_unique_ref(ref, read_only); } } diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index b36f3d66c8ff..29ff059dd692 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -22,11 +22,11 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/javaClasses.hpp" @@ -111,7 +111,6 @@ class LatestMethodCache { }; static LatestMethodCache _finalizer_register_cache; // Finalizer.register() -static LatestMethodCache _loader_addClass_cache; // ClassLoader.addClass() static LatestMethodCache _throw_illegal_access_error_cache; // Unsafe.throwIllegalAccessError() static LatestMethodCache _throw_no_such_method_error_cache; // Unsafe.throwNoSuchMethodError() static LatestMethodCache _do_stack_walk_cache; // AbstractStackWalker.doStackWalk() @@ -217,12 +216,12 @@ class BuiltinException { #if INCLUDE_CDS_JAVA_HEAP void store_in_cds() { - _archived_root_index = HeapShared::archive_exception_instance(instance()); + _archived_root_index = AOTHeap::archive_exception_instance(instance()); } void load_from_cds() { if (_archived_root_index >= 0) { - oop obj = HeapShared::get_root(_archived_root_index); + oop obj = AOTHeap::get_root(_archived_root_index); assert(obj != nullptr, "must be"); _instance = OopHandle(Universe::vm_global(), obj); } @@ -325,11 +324,11 @@ void Universe::archive_exception_instances() { } void Universe::load_archived_object_instances() { - if (HeapShared::is_archived_heap_in_use()) { + if (AOTHeap::is_archived_heap_in_use()) { for (int i = T_BOOLEAN; i < T_VOID+1; i++) { int index = _archived_basic_type_mirror_indices[i]; if (!is_reference_type((BasicType)i) && index >= 0) { - oop mirror_oop = HeapShared::get_root(index); + oop mirror_oop = AOTHeap::get_root(index); assert(mirror_oop != nullptr, "must be"); _basic_type_mirrors[i] = OopHandle(vm_global(), mirror_oop); } @@ -351,7 +350,7 @@ void Universe::serialize(SerializeClosure* f) { #if INCLUDE_CDS_JAVA_HEAP for (int i = T_BOOLEAN; i < T_VOID+1; i++) { f->do_int(&_archived_basic_type_mirror_indices[i]); - // if f->reading(): We can't call HeapShared::get_root() yet, as the heap + // if f->reading(): We can't call AOTHeap::get_root() yet, as the heap // contents may need to be relocated. _basic_type_mirrors[i] will be // updated later in Universe::load_archived_object_instances(). } @@ -558,7 +557,7 @@ void Universe::genesis(TRAPS) { void Universe::initialize_basic_type_mirrors(TRAPS) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_using_archive() && - HeapShared::is_archived_heap_in_use() && + AOTHeap::is_archived_heap_in_use() && _basic_type_mirrors[T_INT].resolve() != nullptr) { // check that all basic type mirrors are mapped also for (int i = T_BOOLEAN; i < T_VOID+1; i++) { @@ -581,7 +580,7 @@ void Universe::initialize_basic_type_mirrors(TRAPS) { } } if (CDSConfig::is_dumping_heap()) { - HeapShared::init_scratch_objects_for_basic_type_mirrors(CHECK); + AOTHeap::init_scratch_objects_for_basic_type_mirrors(CHECK); } } @@ -920,7 +919,7 @@ jint universe_init() { Threads::add(main_thread); } - HeapShared::initialize_writing_mode(); + AOTHeap::initialize_writing_mode(); // Create the string table before the AOT object archive is loaded, // as it might need to access the string table. @@ -1082,7 +1081,6 @@ Method* LatestMethodCache::get_method() { } Method* Universe::finalizer_register_method() { return _finalizer_register_cache.get_method(); } -Method* Universe::loader_addClass_method() { return _loader_addClass_cache.get_method(); } Method* Universe::throw_illegal_access_error() { return _throw_illegal_access_error_cache.get_method(); } Method* Universe::throw_no_such_method_error() { return _throw_no_such_method_error_cache.get_method(); } Method* Universe::do_stack_walk_method() { return _do_stack_walk_cache.get_method(); } @@ -1112,12 +1110,6 @@ void Universe::initialize_known_methods(JavaThread* current) { "throwNoSuchMethodError", vmSymbols::void_method_signature(), true); - // Set up method for registering loaded classes in class loader vector - _loader_addClass_cache.init(current, - vmClasses::ClassLoader_klass(), - "addClass", - vmSymbols::class_void_signature(), false); - // Set up method for stack walking _do_stack_walk_cache.init(current, vmClasses::AbstractStackWalker_klass(), diff --git a/src/hotspot/share/memory/universe.hpp b/src/hotspot/share/memory/universe.hpp index a80271e205b2..71f483b55063 100644 --- a/src/hotspot/share/memory/universe.hpp +++ b/src/hotspot/share/memory/universe.hpp @@ -167,7 +167,7 @@ class Universe: AllStatic { #if INCLUDE_CDS_JAVA_HEAP // Each slot i stores an index that can be used to restore _basic_type_mirrors[i] - // from the archive heap using HeapShared::get_root(int) + // from the archive heap using AOTHeap::get_root(int) static int _archived_basic_type_mirror_indices[T_VOID+1]; #endif @@ -248,7 +248,6 @@ class Universe: AllStatic { static uintx the_array_interfaces_bitmap() { return _the_array_interfaces_bitmap; } static Method* finalizer_register_method(); - static Method* loader_addClass_method(); static Method* throw_illegal_access_error(); static Method* throw_no_such_method_error(); static Method* do_stack_walk_method(); diff --git a/src/hotspot/share/nmt/memMapPrinter.cpp b/src/hotspot/share/nmt/memMapPrinter.cpp index 639e06292fc7..aabc6673d083 100644 --- a/src/hotspot/share/nmt/memMapPrinter.cpp +++ b/src/hotspot/share/nmt/memMapPrinter.cpp @@ -39,6 +39,7 @@ #include "runtime/thread.hpp" #include "runtime/threadSMR.hpp" #include "runtime/vmThread.hpp" +#include "services/diagnosticWorkers.hpp" #include "utilities/bitMap.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/globalDefinitions.hpp" @@ -174,12 +175,12 @@ static bool vma_touches_thread_stack(const void* from, const void* to, const Thr return t->stack_base_or_null() != nullptr && range_intersects(from, to, (const void*)t->stack_end(), (const void*)t->stack_base()); } -struct GCThreadClosure : public ThreadClosure { +struct ThreadStackClosure : public ThreadClosure { bool _found; uintx _tid; const void* const _from; const void* const _to; - GCThreadClosure(const void* from, const void* to) : _found(false), _tid(0), _from(from), _to(to) {} + ThreadStackClosure(const void* from, const void* to) : _found(false), _tid(0), _from(from), _to(to) {} void do_thread(Thread* t) override { if (_tid == 0 && t != nullptr && vma_touches_thread_stack(_from, _to, t)) { _found = true; @@ -221,12 +222,19 @@ static void print_thread_details_for_supposed_stack_address(const void* from, co #undef HANDLE_THREAD if (Universe::heap() != nullptr) { - GCThreadClosure cl(from, to); - Universe::heap()->gc_threads_do(&cl); - if (cl._found) { - print_thread_details(cl._tid, "GC Thread", st); + ThreadStackClosure gc_cl(from, to); + Universe::heap()->gc_threads_do(&gc_cl); + if (gc_cl._found) { + print_thread_details(gc_cl._tid, "GC Thread", st); + return; } } + + ThreadStackClosure diag_cl(from, to); + DiagnosticWorkers::threads_do(&diag_cl); + if (diag_cl._found) { + print_thread_details(diag_cl._tid, "DiagWorker", st); + } } /////////////// diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index 95c4673edd2e..cc71d390d9c8 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -23,9 +23,9 @@ */ #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/javaClasses.inline.hpp" @@ -231,7 +231,7 @@ void ConstantPool::initialize_resolved_references(ClassLoaderData* loader_data, // Create a "scratch" copy of the resolved references array to archive if (CDSConfig::is_dumping_heap()) { refArrayOop scratch_references = oopFactory::new_refArray(vmClasses::Object_klass(), map_length, CHECK); - HeapShared::add_scratch_resolved_references(this, scratch_references); + AOTHeap::add_scratch_resolved_references(this, scratch_references); } } } @@ -349,7 +349,7 @@ refArrayOop ConstantPool::prepare_resolved_references_for_archiving() { keep_resolved_refs.at_put(rr_index, true); }); - refArrayOop scratch_rr = HeapShared::scratch_resolved_references(this); + refArrayOop scratch_rr = AOTHeap::scratch_resolved_references(this); Array* ref_map = reference_map(); int ref_map_len = ref_map == nullptr ? 0 : ref_map->length(); for (int i = 0; i < rr_len; i++) { @@ -360,7 +360,7 @@ refArrayOop ConstantPool::prepare_resolved_references_for_archiving() { int index = object_to_cp_index(i); if (tag_at(index).is_string()) { assert(java_lang_String::is_instance(obj), "must be"); - if (!HeapShared::is_string_too_large_to_archive(obj)) { + if (!AOTHeap::is_string_too_large_to_archive(obj)) { scratch_rr->obj_at_put(i, obj); } continue; @@ -400,7 +400,7 @@ void ConstantPool::restore_unshareable_info(TRAPS) { if (vmClasses::Object_klass_is_loaded()) { ClassLoaderData* loader_data = pool_holder()->class_loader_data(); #if INCLUDE_CDS_JAVA_HEAP - if (HeapShared::is_archived_heap_in_use() && + if (AOTHeap::is_archived_heap_in_use() && _cache->archived_references() != nullptr) { oop archived = _cache->archived_references(); // Create handle for the archived resolved reference array object @@ -425,7 +425,7 @@ void ConstantPool::restore_unshareable_info(TRAPS) { if (CDSConfig::is_dumping_final_static_archive() && CDSConfig::is_dumping_heap() && resolved_references() != nullptr) { objArrayOop scratch_references = oopFactory::new_objArray(vmClasses::Object_klass(), resolved_references()->length(), CHECK); - HeapShared::add_scratch_resolved_references(this, scratch_references); + AOTHeap::add_scratch_resolved_references(this, scratch_references); } } diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 1dc1fa57d08a..2fa899b129af 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -23,9 +23,9 @@ */ #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotHeap.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.hpp" #include "classfile/javaStackTraceClasses.hpp" #include "classfile/resolutionErrors.hpp" #include "classfile/systemDictionary.hpp" @@ -409,9 +409,9 @@ void ConstantPoolCache::remove_unshareable_info() { _archived_references_index = -1; if (CDSConfig::is_dumping_heap()) { ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(constant_pool()); - oop rr = HeapShared::scratch_resolved_references(src_cp); + oop rr = AOTHeap::scratch_resolved_references(src_cp); if (rr != nullptr) { - _archived_references_index = HeapShared::append_root(rr); + _archived_references_index = AOTHeap::append_root(rr); } } #endif @@ -719,12 +719,12 @@ oop ConstantPoolCache::archived_references() { if (_archived_references_index < 0) { return nullptr; } - return HeapShared::get_root(_archived_references_index); + return AOTHeap::get_root(_archived_references_index); } void ConstantPoolCache::clear_archived_references() { if (_archived_references_index >= 0) { - HeapShared::clear_root(_archived_references_index); + AOTHeap::clear_root(_archived_references_index); _archived_references_index = -1; } } diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 4560b2550dd4..911f249f327d 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -23,13 +23,13 @@ */ #include "cds/aotClassInitializer.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotMetaspace.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" #include "cds/cdsEnumKlass.hpp" #include "cds/classListWriter.hpp" -#include "cds/heapShared.hpp" #include "classfile/classFileParser.hpp" #include "classfile/classFileStream.hpp" #include "classfile/classLoader.hpp" @@ -792,7 +792,7 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { if (constants() != nullptr) { assert (!constants()->on_stack(), "shouldn't be called if anything is onstack"); if (!constants()->in_aot_cache()) { - HeapShared::remove_scratch_resolved_references(constants()); + AOTHeap::remove_scratch_resolved_references(constants()); MetadataFactory::free_metadata(loader_data, constants()); } // Delete any cached resolution errors for the constant pool @@ -844,7 +844,7 @@ void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - HeapShared::remove_scratch_objects(this); + AOTHeap::remove_scratch_objects(this); } #endif } @@ -3171,7 +3171,7 @@ void InstanceKlass::remove_unshareable_info() { _methods_jmethod_ids = nullptr; _jni_ids = nullptr; _oop_map_cache = nullptr; - if (CDSConfig::is_dumping_method_handles() && HeapShared::is_lambda_proxy_klass(this)) { + if (CDSConfig::is_dumping_method_handles() && AOTHeap::is_lambda_proxy_klass(this)) { // keep _nest_host } else { // clear _nest_host to ensure re-load at runtime diff --git a/src/hotspot/share/oops/klass.cpp b/src/hotspot/share/oops/klass.cpp index e0b76a9c640e..3962b5552ad1 100644 --- a/src/hotspot/share/oops/klass.cpp +++ b/src/hotspot/share/oops/klass.cpp @@ -22,9 +22,9 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/cdsConfig.hpp" #include "cds/cppVtables.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.inline.hpp" @@ -826,9 +826,9 @@ void Klass::remove_java_mirror() { assert(InstanceKlass::cast(k)->defined_by_other_loaders(), "sanity"); } } else { - oop scratch_mirror = HeapShared::scratch_java_mirror(orig_mirror); + oop scratch_mirror = AOTHeap::scratch_java_mirror(orig_mirror); if (scratch_mirror != nullptr) { - _archived_mirror_index = HeapShared::append_root(scratch_mirror); + _archived_mirror_index = AOTHeap::append_root(scratch_mirror); } } } @@ -880,7 +880,7 @@ void Klass::restore_unshareable_info(ClassLoaderData* loader_data, Handle protec if (this->has_archived_mirror_index()) { ResourceMark rm(THREAD); log_debug(aot, mirror)("%s has raw archived mirror", external_name()); - if (HeapShared::is_archived_heap_in_use()) { + if (AOTHeap::is_archived_heap_in_use()) { bool present = java_lang_Class::restore_archived_mirror(this, loader, module_handle, protection_domain, CHECK); @@ -908,12 +908,12 @@ void Klass::restore_unshareable_info(ClassLoaderData* loader_data, Handle protec #if INCLUDE_CDS_JAVA_HEAP oop Klass::archived_java_mirror() { assert(has_archived_mirror_index(), "must have archived mirror"); - return HeapShared::get_root(_archived_mirror_index); + return AOTHeap::get_root(_archived_mirror_index); } void Klass::clear_archived_mirror_index() { if (_archived_mirror_index >= 0) { - HeapShared::clear_root(_archived_mirror_index); + AOTHeap::clear_root(_archived_mirror_index); } _archived_mirror_index = -1; } diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index f3bbacda4a82..a7b2cab16f09 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -1708,7 +1708,7 @@ bool MethodData::profile_array_accesses() { } bool MethodData::profile_acmp() { - return COMPILER2_PRESENT(UseACmpProfile ||) TypeProfileLevel > 0; + return COMPILER2_PRESENT(UseACmpProfile ||) (Arguments::is_valhalla_enabled() && TypeProfileLevel > 0); } bool MethodData::profile_all_parameters() { diff --git a/src/hotspot/share/opto/addnode.cpp b/src/hotspot/share/opto/addnode.cpp index aa523fe9be80..4a6659db8794 100644 --- a/src/hotspot/share/opto/addnode.cpp +++ b/src/hotspot/share/opto/addnode.cpp @@ -1610,6 +1610,8 @@ Node* MaxLNode::Ideal(PhaseGVN* phase, bool can_reshape) { } if (can_reshape) { return fold_subI_no_underflow_pattern(this, phase); + } else { + phase->record_for_igvn(this); } return nullptr; } @@ -1642,6 +1644,8 @@ Node* MinLNode::Ideal(PhaseGVN* phase, bool can_reshape) { } if (can_reshape) { return fold_subI_no_underflow_pattern(this, phase); + } else { + phase->record_for_igvn(this); } return nullptr; } diff --git a/src/hotspot/share/opto/arraycopynode.cpp b/src/hotspot/share/opto/arraycopynode.cpp index 240750b8416b..d0fa123fda35 100644 --- a/src/hotspot/share/opto/arraycopynode.cpp +++ b/src/hotspot/share/opto/arraycopynode.cpp @@ -35,7 +35,7 @@ const TypeFunc* ArrayCopyNode::_arraycopy_type_Type = nullptr; ArrayCopyNode::ArrayCopyNode(Compile* C, bool alloc_tightly_coupled, bool has_negative_length_guard) - : CallNode(arraycopy_type(), nullptr, TypePtr::BOTTOM), + : CallNode(arraycopy_type(), nullptr, TypePtr::BOTTOM, TypePtr::BOTTOM), _kind(None), _alloc_tightly_coupled(alloc_tightly_coupled), _has_negative_length_guard(has_negative_length_guard), diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 083a3c94d480..3379da006347 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -288,6 +288,11 @@ "Maximum allowed edges in the interference graphs") \ range(0, max_juint) \ \ + product(uint, RegSplitLimit, 20*M, DIAGNOSTIC, \ + "Maximum product of spill and CFG block counts during Split " \ + "in the Register Allocator") \ + range(0, max_juint) \ + \ develop(bool, UseUniqueSubclasses, true, \ "Narrow an abstract reference to the unique concrete subclass") \ \ diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index e836629cfbb5..30089b426e44 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -146,7 +146,9 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo bool do_locks_coarsening = EliminateLocks; bool do_superword = UseSuperWord; bool do_stringopts = OptimizeStringConcat; + bool aot_compile = env->is_aot_compile(); bool for_aot_preload = (task->compile_reason() == CompileTask::Reason_AOTCompileForPreload); + assert(!for_aot_preload || aot_compile, "sanity"); assert(!for_aot_preload || (ClassInitBarrierMode > 0), "sanity"); while (!env->failing()) { @@ -160,6 +162,7 @@ void C2Compiler::compile_method(ciEnv* env, ciMethod* target, int entry_bci, boo do_locks_coarsening, do_superword, do_stringopts, + aot_compile, for_aot_preload, install_code); Compile C(env, target, entry_bci, options, directive); diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index be9bf9a20b8a..7e942e2df026 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -1373,7 +1373,7 @@ bool CallStaticJavaNode::remove_unknown_flat_array_load(PhaseIterGVN* igvn, Node } address call_addr = OptoRuntime::uncommon_trap_blob()->entry_point(); - CallNode* unc = new CallStaticJavaNode(OptoRuntime::uncommon_trap_Type(), call_addr, "uncommon_trap", nullptr); + CallNode* unc = new CallStaticJavaNode(OptoRuntime::uncommon_trap_Type(), call_addr, "uncommon_trap", nullptr, TypePtr::BOTTOM); unc->init_req(TypeFunc::Control, call->in(0)); unc->init_req(TypeFunc::I_O, call->in(TypeFunc::I_O)); unc->init_req(TypeFunc::Memory, call->in(TypeFunc::Memory)); @@ -1456,7 +1456,7 @@ Node* CallStaticJavaNode::replace_identity_hash_code(PhaseIterGVN* igvn) { } Node* new_mem = kit.reset_memory(); assert(in(TypeFunc::Memory) == new_mem, "must not modify memory"); - return TupleNode::make(tf()->range_cc(), adr_type(), igvn->C->top(), kit.i_o(), new_mem, kit.frameptr(), kit.returnadr(), replace); + return TupleNode::make(tf()->range_cc(), out_adr_type(), igvn->C->top(), kit.i_o(), new_mem, kit.frameptr(), kit.returnadr(), replace); } // Try to replace a runtime call to the substitutability test by either a simple pointer comparison @@ -1503,7 +1503,7 @@ Node* CallStaticJavaNode::replace_is_substitutable(PhaseIterGVN* igvn) { } Node* new_mem = kit.reset_memory(); assert(in(TypeFunc::Memory) == new_mem, "must not modify memory"); - return TupleNode::make(tf()->range_cc(), adr_type(), igvn->C->top(), kit.i_o(), new_mem, kit.frameptr(), kit.returnadr(), replace); + return TupleNode::make(tf()->range_cc(), out_adr_type(), igvn->C->top(), kit.i_o(), new_mem, kit.frameptr(), kit.returnadr(), replace); } #ifndef PRODUCT @@ -2090,7 +2090,7 @@ AllocateNode::AllocateNode(Compile* C, const TypeFunc *atype, Node *size, Node *klass_node, Node* initial_test, ValueTypeNode* value_type_node) - : CallNode(atype, nullptr, TypeRawPtr::BOTTOM) + : CallNode(atype, nullptr, TypeRawPtr::BOTTOM, TypePtr::BOTTOM) { init_class_id(Class_Allocate); init_flags(Flag_is_macro); diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index 349cef7fe625..c70e812fa5ed 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -70,7 +70,6 @@ class StartNode : public MultiNode { virtual int Opcode() const; virtual bool pinned() const { return true; }; virtual const Type *bottom_type() const; - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual void calling_convention( BasicType* sig_bt, VMRegPair *parm_reg, uint length ) const; @@ -81,6 +80,10 @@ class StartNode : public MultiNode { virtual void dump_spec(outputStream *st) const; virtual void dump_compact_spec(outputStream *st) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return TypePtr::BOTTOM; } + const TypePtr* in_adr_type_impl() const final { return nullptr; } }; //------------------------------StartOSRNode----------------------------------- @@ -125,6 +128,10 @@ class ReturnNode : public Node { #ifndef PRODUCT virtual void dump_req(outputStream *st = tty, DumpConfig* dc = nullptr) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return nullptr; } + const TypePtr* in_adr_type_impl() const final { return TypePtr::BOTTOM; } }; @@ -145,6 +152,10 @@ class RethrowNode : public Node { #ifndef PRODUCT virtual void dump_req(outputStream *st = tty, DumpConfig* dc = nullptr) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return nullptr; } + const TypePtr* in_adr_type_impl() const final { return TypePtr::BOTTOM; } }; @@ -340,7 +351,8 @@ class SafePointNode : public MultiNode { // Many calls take *all* of memory as input, // but some produce a limited subset of that memory as output. // The adr_type reports the call's behavior as a store, not a load. - const TypePtr* _adr_type; // What type of memory does this node produce? + const TypePtr* _out_adr_type; // What type of memory does this node produce? + const TypePtr* _in_adr_type; // What type of memory does this node consume? ReplacedNodes _replaced_nodes; // During parsing: list of pair of nodes from calls to GraphKit::replace_in_map() bool _has_ea_local_in_scope; // NoEscape or ArgEscape objects in JVM States @@ -350,11 +362,14 @@ class SafePointNode : public MultiNode { } public: SafePointNode(uint edges, JVMState* jvms, - // A plain safepoint advertises no memory effects (null): - const TypePtr* adr_type = nullptr) - : MultiNode( edges ), + // A plain safepoint advertises no memory effects (null) + const TypePtr* out_adr_type = nullptr, + // A plain safepoint reads all memory + const TypePtr* in_adr_type = TypePtr::BOTTOM) + : MultiNode(edges), _jvms(jvms), - _adr_type(adr_type), + _out_adr_type(out_adr_type), + _in_adr_type(in_adr_type), _has_ea_local_in_scope(false) { init_class_id(Class_SafePoint); @@ -568,8 +583,7 @@ class SafePointNode : public MultiNode { virtual bool pinned() const { return true; } virtual const Type* Value(PhaseGVN* phase) const; virtual const Type* bottom_type() const { return Type::CONTROL; } - virtual const TypePtr* adr_type() const { return _adr_type; } - void set_adr_type(const TypePtr* adr_type) { _adr_type = adr_type; } + void set_out_adr_type(const TypePtr* out_adr_type) { _out_adr_type = out_adr_type; } virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual Node* Identity(PhaseGVN* phase); virtual uint ideal_reg() const { return 0; } @@ -580,6 +594,10 @@ class SafePointNode : public MultiNode { #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return _out_adr_type; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //------------------------------SafePointScalarObjectNode---------------------- @@ -770,8 +788,8 @@ class CallNode : public SafePointNode { CallGenerator* _generator; // corresponding CallGenerator for some late inline calls const char* _name; // Printable name, if _method is null - CallNode(const TypeFunc* tf, address addr, const TypePtr* adr_type, JVMState* jvms = nullptr) - : SafePointNode(tf->domain_cc()->cnt(), jvms, adr_type), + CallNode(const TypeFunc* tf, address addr, const TypePtr* out_adr_type, const TypePtr* in_adr_type, JVMState* jvms = nullptr) + : SafePointNode(tf->domain_cc()->cnt(), jvms, out_adr_type, in_adr_type), _tf(tf), _entry_point(addr), _cnt(COUNT_UNKNOWN), @@ -861,8 +879,8 @@ class CallJavaNode : public CallNode { bool _override_symbolic_info; // Override symbolic call site info from bytecode bool _arg_escape; // ArgEscape in parameter list public: - CallJavaNode(const TypeFunc* tf , address addr, ciMethod* method) - : CallNode(tf, addr, TypePtr::BOTTOM), + CallJavaNode(const TypeFunc* tf , address addr, ciMethod* method, const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallNode(tf, addr, out_adr_type, in_adr_type), _method(method), _optimized_virtual(false), _override_symbolic_info(false), @@ -908,7 +926,7 @@ class CallStaticJavaNode : public CallJavaNode { public: CallStaticJavaNode(Compile* C, const TypeFunc* tf, address addr, ciMethod* method) - : CallJavaNode(tf, addr, method), _safe_for_fold_compare(true) { + : CallJavaNode(tf, addr, method, TypePtr::BOTTOM, TypePtr::BOTTOM), _safe_for_fold_compare(true) { init_class_id(Class_CallStaticJava); if (C->eliminate_boxing() && (method != nullptr) && (method->is_boxing_method() || method->is_unboxing_method())) { init_flags(Flag_is_macro); @@ -926,11 +944,9 @@ class CallStaticJavaNode : public CallJavaNode { C->add_macro_node(this); } } - CallStaticJavaNode(const TypeFunc* tf, address addr, const char* name, const TypePtr* adr_type) - : CallJavaNode(tf, addr, nullptr), _safe_for_fold_compare(true) { + CallStaticJavaNode(const TypeFunc* tf, address addr, const char* name, const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallJavaNode(tf, addr, nullptr, out_adr_type, in_adr_type), _safe_for_fold_compare(true) { init_class_id(Class_CallStaticJava); - // This node calls a runtime stub, which often has narrow memory effects. - _adr_type = adr_type; _name = name; } @@ -977,7 +993,7 @@ class CallDynamicJavaNode : public CallJavaNode { virtual uint size_of() const; // Size is bigger public: CallDynamicJavaNode(const TypeFunc* tf , address addr, ciMethod* method, int vtable_index) - : CallJavaNode(tf,addr,method), _vtable_index(vtable_index) { + : CallJavaNode(tf, addr, method, TypePtr::BOTTOM, TypePtr::BOTTOM), _vtable_index(vtable_index) { init_class_id(Class_CallDynamicJava); } @@ -1003,8 +1019,8 @@ class CallRuntimeNode : public CallNode { virtual uint size_of() const; // Size is bigger public: CallRuntimeNode(const TypeFunc* tf, address addr, const char* name, - const TypePtr* adr_type, JVMState* jvms = nullptr) - : CallNode(tf, addr, adr_type, jvms) + const TypePtr* out_adr_type, const TypePtr* in_adr_type, JVMState* jvms = nullptr) + : CallNode(tf, addr, out_adr_type, in_adr_type, jvms) { init_class_id(Class_CallRuntime); _name = name; @@ -1024,8 +1040,8 @@ class CallRuntimeNode : public CallNode { class CallLeafNode : public CallRuntimeNode { public: CallLeafNode(const TypeFunc* tf, address addr, const char* name, - const TypePtr* adr_type) - : CallRuntimeNode(tf, addr, name, adr_type) + const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallRuntimeNode(tf, addr, name, out_adr_type, in_adr_type) { init_class_id(Class_CallLeaf); } @@ -1055,7 +1071,7 @@ class CallLeafPureNode : public CallLeafNode { public: CallLeafPureNode(const TypeFunc* tf, address addr, const char* name) - : CallLeafNode(tf, addr, name, nullptr) { + : CallLeafNode(tf, addr, name, nullptr, nullptr) { init_class_id(Class_CallLeafPure); } int Opcode() const override; @@ -1070,8 +1086,8 @@ class CallLeafPureNode : public CallLeafNode { class CallLeafNoFPNode : public CallLeafNode { public: CallLeafNoFPNode(const TypeFunc* tf, address addr, const char* name, - const TypePtr* adr_type) - : CallLeafNode(tf, addr, name, adr_type) + const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallLeafNode(tf, addr, name, out_adr_type, in_adr_type) { init_class_id(Class_CallLeafNoFP); } @@ -1089,8 +1105,8 @@ class CallLeafVectorNode : public CallLeafNode { virtual uint size_of() const; // Size is bigger public: CallLeafVectorNode(const TypeFunc* tf, address addr, const char* name, - const TypePtr* adr_type, uint num_bits) - : CallLeafNode(tf, addr, name, adr_type), _num_bits(num_bits) + const TypePtr* out_adr_type, const TypePtr* in_adr_type, uint num_bits) + : CallLeafNode(tf, addr, name, out_adr_type, in_adr_type), _num_bits(num_bits) { } virtual int Opcode() const; @@ -1300,8 +1316,8 @@ class AbstractLockNode: public CallNode { void set_eliminated_lock_counter() PRODUCT_RETURN; public: - AbstractLockNode(const TypeFunc *tf) - : CallNode(tf, nullptr, TypeRawPtr::BOTTOM), + AbstractLockNode(const TypeFunc *tf, const TypePtr* out_adr_type, const TypePtr* in_adr_type) + : CallNode(tf, nullptr, out_adr_type, in_adr_type), _kind(Regular) { #ifndef PRODUCT @@ -1380,7 +1396,7 @@ class LockNode : public AbstractLockNode { virtual int Opcode() const; virtual uint size_of() const; // Size is bigger - LockNode(Compile* C, const TypeFunc *tf) : AbstractLockNode( tf ) { + LockNode(Compile* C, const TypeFunc* tf) : AbstractLockNode(tf, TypeRawPtr::BOTTOM, TypePtr::BOTTOM) { init_class_id(Class_Lock); init_flags(Flag_is_macro); C->add_macro_node(this); @@ -1405,7 +1421,7 @@ class UnlockNode : public AbstractLockNode { public: virtual int Opcode() const; virtual uint size_of() const; // Size is bigger - UnlockNode(Compile* C, const TypeFunc *tf) : AbstractLockNode( tf ) + UnlockNode(Compile* C, const TypeFunc* tf) : AbstractLockNode(tf, TypeRawPtr::BOTTOM, TypeRawPtr::BOTTOM) #ifdef ASSERT , _dbg_jvms(nullptr) #endif diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index e91c70769dfa..1e5e3ccc3434 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -1126,13 +1126,13 @@ PhiNode* PhiNode::make(Node* r, Node* x, const Type *t, const TypePtr* at) { PhiNode* PhiNode::make(Node* r, Node* x) { const Type* t = x->bottom_type(); const TypePtr* at = nullptr; - if (t == Type::MEMORY) at = flatten_phi_adr_type(x->adr_type()); + if (t == Type::MEMORY) at = flatten_phi_adr_type(x->out_adr_type()); return make(r, x, t, at); } PhiNode* PhiNode::make_blank(Node* r, Node* x) { const Type* t = x->bottom_type(); const TypePtr* at = nullptr; - if (t == Type::MEMORY) at = flatten_phi_adr_type(x->adr_type()); + if (t == Type::MEMORY) at = flatten_phi_adr_type(x->out_adr_type()); return new PhiNode(r, t, at); } @@ -1162,7 +1162,7 @@ PhiNode* PhiNode::split_out_instance(const TypePtr* at, PhaseIterGVN *igvn) cons Node* use = region->fast_out(k); if( use->is_Phi()) { PhiNode *phi2 = use->as_Phi(); - if (phi2->type() == Type::MEMORY && phi2->adr_type() == at) { + if (phi2->type() == Type::MEMORY && phi2->out_adr_type() == at) { return phi2; } } @@ -1187,7 +1187,7 @@ PhiNode* PhiNode::split_out_instance(const TypePtr* at, PhaseIterGVN *igvn) cons continue; Node *opt = MemNode::optimize_simple_memory_chain(in, t_oop, nullptr, igvn); PhiNode *optphi = opt->is_Phi() ? opt->as_Phi() : nullptr; - if (optphi != nullptr && optphi->adr_type() == TypePtr::BOTTOM) { + if (optphi != nullptr && optphi->out_adr_type() == TypePtr::BOTTOM) { opt = node_map[optphi->_idx]; if (opt == nullptr) { stack.push(ophi, i); @@ -1228,7 +1228,7 @@ void PhiNode::verify_adr_type(VectorSet& visited, const TypePtr* at) const { || (n->is_Mem() && n->in(MemNode::Address)->bottom_type() == Type::TOP)) { // ignore top inputs } else { - const TypePtr* nat = flatten_phi_adr_type(n->adr_type()); + const TypePtr* nat = flatten_phi_adr_type(n->out_adr_type()); // recheck phi/non-phi consistency at leaves: assert((nat != nullptr) == (at != nullptr), ""); assert(nat == at || nat == TypePtr::BOTTOM, @@ -1605,8 +1605,8 @@ Node* PhiNode::Identity(PhaseGVN* phase) { // Looking for phis with identical inputs. If we find one that has // type TypePtr::BOTTOM, replace the current phi with the bottom phi. - if (phase->is_IterGVN() && type() == Type::MEMORY && adr_type() != - TypePtr::BOTTOM && !adr_type()->is_known_instance()) { + if (phase->is_IterGVN() && type() == Type::MEMORY && + out_adr_type() != TypePtr::BOTTOM && !out_adr_type()->is_known_instance()) { uint phi_len = req(); Node* phi_reg = region(); for (DUIterator_Fast imax, i = phi_reg->fast_outs(imax); i < imax; i++) { @@ -2251,7 +2251,7 @@ bool PhiNode::must_wait_for_region_in_irreducible_loop(PhaseGVN* phase) const { // helps simplify the memory graph. Node* PhiNode::split_through_mergemem(PhaseIterGVN& igvn) { assert(type() == Type::MEMORY, "must be a memory Phi"); - assert(adr_type() == TypePtr::BOTTOM, "must be a bottom memory Phi"); + assert(out_adr_type() == TypePtr::BOTTOM, "must be a bottom memory Phi"); bool need_splitting = false; bool need_process = false; @@ -2702,7 +2702,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { PhaseIterGVN* igvn = phase->is_IterGVN(); assert(igvn != nullptr, "must be during IGVN"); - if (const TypePtr* at = adr_type(); at == TypePtr::BOTTOM) { + if (const TypePtr* at = out_adr_type(); at == TypePtr::BOTTOM) { progress = split_through_mergemem(*igvn); if (progress != nullptr && progress != this) { return progress; @@ -2728,7 +2728,7 @@ Node *PhiNode::Ideal(PhaseGVN *phase, bool can_reshape) { // // Other optimizations on the memory chain // - const TypePtr* at = adr_type(); + const TypePtr* at = out_adr_type(); for( uint i=1; iis_IterGVN(); uint phi_len = req(); Node* phi_reg = region(); @@ -2941,10 +2941,17 @@ class PushValueTypeDown { _nodes_from_phi.push(in); } } else if (n->is_ValueType()) { - Node* buf = n->as_ValueType()->get_oop(); - if (buf != nullptr) { - _nodes_from_phi.push(buf); - _subgraph_to_clone.push(n); + ValueTypeNode* value_type = n->as_ValueType(); + Node* buf = value_type->get_oop(); + _nodes_from_phi.push(buf); + _subgraph_to_clone.push(n); + // We may have a cycle through one the ValueType's field so follow those inputs + for (uint j = 0; j < value_type->field_count(); ++j) { + Node* field = value_type->field_value(j); + const Type* field_type = _phase->type(field); + if (field_type->isa_instptr() && !field_type->is_valueklassptr() && field_type->is_instptr()->can_be_value_type()) { + _nodes_from_phi.push(field); + } } } } @@ -3012,11 +3019,21 @@ class PushValueTypeDown { assert(in_clone != nullptr, "must be cloned"); n_clone->set_req(1, in_clone); } else if (n->is_ValueType()) { - Node* in = n->as_ValueType()->get_oop(); + ValueTypeNode* value_type = n->as_ValueType(); + Node* in = value_type->get_oop(); Node* in_clone = get_clone(in); if (in_clone != nullptr) { _phase->is_IterGVN()->rehash_node_delayed(n); - n->as_ValueType()->set_oop(*_phase, in_clone); + value_type->set_oop(*_phase, in_clone); + } + // If the ValueType is reached from one of its field's input, make sure we update that input to not have a cycle + for (uint j = 0; j < value_type->field_count(); ++j) { + Node* field = value_type->field_value(j); + Node* field_clone = get_clone(field); + if (field_clone != nullptr) { + _phase->is_IterGVN()->rehash_node_delayed(n); + value_type->set_field_value(j, field_clone); + } } } } @@ -3043,7 +3060,7 @@ class PushValueTypeDown { before_encode_decode++; } else if (n->is_ValueType()) { Node* buf = n->as_ValueType()->get_oop(); - if (buf != nullptr && i >= _init_nodes) { + if (i >= _init_nodes) { vts_to_skip++; } } @@ -3101,9 +3118,15 @@ class PushValueTypeDown { } } else if (n->is_ValueType()) { assert(i < init_nodes, ""); - Node* buf = n->as_ValueType()->get_oop(); - if (buf != nullptr) { - after.push(buf); + ValueTypeNode* value_type = n->as_ValueType(); + Node* buf = value_type->get_oop(); + after.push(buf); + for (uint j = 0; j < value_type->field_count(); ++j) { + Node* field = value_type->field_value(j); + const Type* field_type = _phase->type(field); + if (field_type->isa_instptr() && !field_type->is_valueklassptr() && field_type->is_instptr()->can_be_value_type()) { + after.push(field); + } } } } @@ -3337,9 +3360,9 @@ const TypeTuple* PhiNode::collect_types(PhaseGVN* phase) const { } bool PhiNode::can_be_replaced_by(PhaseGVN* phase, const PhiNode* other) const { - const TypePtr* at = adr_type(); + const TypePtr* at = out_adr_type(); return type() == Type::MEMORY && other->type() == Type::MEMORY && at != TypePtr::BOTTOM && - other->adr_type() == TypePtr::BOTTOM && has_same_inputs_as(other) && + other->out_adr_type() == TypePtr::BOTTOM && has_same_inputs_as(other) && // Normally, the alias class corresponding to 'at' not being in '_excluded_idx' does not // imply that it is contained in the bottom memory Phi 'other'. However, since 'this' and // 'other' have the same inputs, either both represent the memory state of 'at', or @@ -3487,7 +3510,7 @@ void PhiNode::dump_spec(outputStream *st) const { if (is_tripcount(T_INT) || is_tripcount(T_LONG)) { st->print(" #tripcount"); } - if (type() == Type::MEMORY && adr_type() == TypePtr::BOTTOM && _excluded_idx != nullptr) { + if (type() == Type::MEMORY && out_adr_type() == TypePtr::BOTTOM && _excluded_idx != nullptr) { st->print(" #excluded_alias_idx:{"); bool first = true; for (int alias_idx = Compile::AliasIdxRaw; alias_idx < Compile::current()->num_alias_types(); alias_idx++) { diff --git a/src/hotspot/share/opto/cfgnode.hpp b/src/hotspot/share/opto/cfgnode.hpp index 5dfdbcf38d94..9ee37d7466d9 100644 --- a/src/hotspot/share/opto/cfgnode.hpp +++ b/src/hotspot/share/opto/cfgnode.hpp @@ -391,7 +391,6 @@ class PhiNode : public TypeNode { bool try_clean_memory_phi(PhaseIterGVN* igvn); virtual int Opcode() const; virtual bool pinned() const { return in(0) != nullptr; } - virtual const TypePtr *adr_type() const { verify_adr_type(true); return _adr_type; } void set_inst_mem_id(int inst_mem_id) { _inst_mem_id = inst_mem_id; } int inst_mem_id() const { return _inst_mem_id; } @@ -432,6 +431,12 @@ class PhiNode : public TypeNode { const TypeTuple* collect_types(PhaseGVN* phase) const; bool can_be_replaced_by(PhaseGVN* phase, const PhiNode* other) const; + +private: + const TypePtr* out_adr_type_impl() const final { + verify_adr_type(true); + return _adr_type; + } }; //------------------------------GotoNode--------------------------------------- diff --git a/src/hotspot/share/opto/chaitin.cpp b/src/hotspot/share/opto/chaitin.cpp index 82c2e374d672..c0cb85010f85 100644 --- a/src/hotspot/share/opto/chaitin.cpp +++ b/src/hotspot/share/opto/chaitin.cpp @@ -206,7 +206,6 @@ PhaseChaitin::PhaseChaitin(uint unique, PhaseCFG &cfg, Matcher &matcher, bool sc ) , _live(nullptr) , _lo_degree(0), _lo_stk_degree(0), _hi_degree(0), _simplified(0) - , _oldphi(unique) #ifndef PRODUCT , _trace_spilling(C->directive()->TraceSpillingOption) #endif @@ -2699,20 +2698,8 @@ void PhaseChaitin::verify(ResourceArea* a, bool verify_ifg) const { } #endif // ASSERT -int PhaseChaitin::_final_loads = 0; -int PhaseChaitin::_final_stores = 0; -int PhaseChaitin::_final_memoves= 0; -int PhaseChaitin::_final_copies = 0; -double PhaseChaitin::_final_load_cost = 0; -double PhaseChaitin::_final_store_cost = 0; -double PhaseChaitin::_final_memove_cost= 0; -double PhaseChaitin::_final_copy_cost = 0; int PhaseChaitin::_conserv_coalesce = 0; -int PhaseChaitin::_conserv_coalesce_pair = 0; -int PhaseChaitin::_conserv_coalesce_trie = 0; -int PhaseChaitin::_conserv_coalesce_quad = 0; int PhaseChaitin::_post_alloc = 0; -int PhaseChaitin::_lost_opp_pp_coalesce = 0; int PhaseChaitin::_lost_opp_cflow_coalesce = 0; int PhaseChaitin::_used_cisc_instructions = 0; int PhaseChaitin::_unused_cisc_instructions = 0; @@ -2724,19 +2711,11 @@ uint PhaseChaitin::_high_pressure = 0; uint PhaseChaitin::_low_pressure = 0; void PhaseChaitin::print_chaitin_statistics() { - tty->print_cr("Inserted %d spill loads, %d spill stores, %d mem-mem moves and %d copies.", _final_loads, _final_stores, _final_memoves, _final_copies); - tty->print_cr("Total load cost= %6.0f, store cost = %6.0f, mem-mem cost = %5.2f, copy cost = %5.0f.", _final_load_cost, _final_store_cost, _final_memove_cost, _final_copy_cost); - tty->print_cr("Adjusted spill cost = %7.0f.", - _final_load_cost*4.0 + _final_store_cost * 2.0 + - _final_copy_cost*1.0 + _final_memove_cost*12.0); - tty->print("Conservatively coalesced %d copies, %d pairs", - _conserv_coalesce, _conserv_coalesce_pair); - if( _conserv_coalesce_trie || _conserv_coalesce_quad ) - tty->print(", %d tries, %d quads", _conserv_coalesce_trie, _conserv_coalesce_quad); + tty->print("Conservatively coalesced %d copies", + _conserv_coalesce); tty->print_cr(", %d post alloc.", _post_alloc); - if( _lost_opp_pp_coalesce || _lost_opp_cflow_coalesce ) - tty->print_cr("Lost coalesce opportunity, %d private-private, and %d cflow interfered.", - _lost_opp_pp_coalesce, _lost_opp_cflow_coalesce ); + if (_lost_opp_cflow_coalesce) + tty->print_cr("Lost coalesce opportunity, %d cflow interfered.", _lost_opp_cflow_coalesce); if( _used_cisc_instructions || _unused_cisc_instructions ) tty->print_cr("Used cisc instruction %d, remained in register %d", _used_cisc_instructions, _unused_cisc_instructions); diff --git a/src/hotspot/share/opto/chaitin.hpp b/src/hotspot/share/opto/chaitin.hpp index fd834c1002bb..296b4699a8fb 100644 --- a/src/hotspot/share/opto/chaitin.hpp +++ b/src/hotspot/share/opto/chaitin.hpp @@ -479,8 +479,6 @@ class PhaseChaitin : public PhaseRegAlloc { bool is_high_pressure( Block *b, LRG *lrg, uint insidx ); - uint _oldphi; // Node index which separates pre-allocation nodes - Block **_blks; // Array of blocks sorted by frequency for coalescing double _high_frequency_lrg; // Frequency at which LRG will be spilled for debug info @@ -777,12 +775,9 @@ class PhaseChaitin : public PhaseRegAlloc { private: - static int _final_loads, _final_stores, _final_copies, _final_memoves; - static double _final_load_cost, _final_store_cost, _final_copy_cost, _final_memove_cost; - static int _conserv_coalesce, _conserv_coalesce_pair; - static int _conserv_coalesce_trie, _conserv_coalesce_quad; + static int _conserv_coalesce; static int _post_alloc; - static int _lost_opp_pp_coalesce, _lost_opp_cflow_coalesce; + static int _lost_opp_cflow_coalesce; static int _used_cisc_instructions, _unused_cisc_instructions; static int _allocator_attempts, _allocator_successes; diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 54fe6c1baf78..63edde39f72b 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -606,7 +606,7 @@ void Compile::print_compile_messages() { if( PrintOpto ) { if (is_osr_compilation()) { tty->print("[OSR]"); - } else if (env()->task()->is_aot_compile()) { + } else if (is_aot_compile()) { if (for_aot_preload()) { tty->print("[PRE]"); } else { @@ -649,9 +649,8 @@ void Compile::print_ideal_ir(const char* compile_phase_name) const { NoSafepointVerifier nsv; ttyLocker ttyl; if (xtty != nullptr) { - CompileTask* task = env()->task(); - bool aot_comp = task->is_aot_load_or_compile(); - bool aot_preload_comp = task->is_aot_preload_or_compile(); + bool aot_comp = is_aot_compile(); + bool aot_preload_comp = for_aot_preload(); xtty->head("ideal compile_id='%d'%s compile_phase='%s'", compile_id(), is_osr_compilation() ? " compile_kind='osr'" : @@ -786,7 +785,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, set_print_intrinsics(directive->PrintIntrinsicsOption); set_has_irreducible_loop(true); // conservative until build_loop_tree() reset it - if ((ProfileTraps && !ci_env->is_aot_compile()) || + if ((ProfileTraps && !is_aot_compile()) || AOTCodeCache::is_using_code()) { // Make sure the method being compiled gets its own MDO, // so we can at least track the decompile_count(). @@ -1295,7 +1294,7 @@ uint Compile::count_live_nodes_by_graph_walk() { void Compile::print_missing_nodes() { // Return if CompileLog is null and PrintIdealNodeCount is false. - if ((_log == nullptr) && (! PrintIdealNodeCount)) { + if ((_log == nullptr) && !directive()->PrintIdealNodeCountOption) { return; } @@ -1326,7 +1325,7 @@ void Compile::print_missing_nodes() { if (_log != nullptr) { _log->elem("mismatched_node_info node_idx='%d' type='both live and dead'", i); } - if (PrintIdealNodeCount) { + if (directive()->PrintIdealNodeCountOption) { // Print the log message to tty tty->print_cr("mismatched_node idx='%d' both live and dead'", i); useful.at(i)->dump(); @@ -1337,7 +1336,7 @@ void Compile::print_missing_nodes() { if (_log != nullptr) { _log->elem("mismatched_node_info node_idx='%d' type='neither live nor dead'", i); } - if (PrintIdealNodeCount) { + if (directive()->PrintIdealNodeCountOption) { // Print the log message to tty tty->print_cr("mismatched_node idx='%d' type='neither live nor dead'", i); } @@ -2336,7 +2335,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { Node* n = wq.at(i); if (n->is_Mem()) { const TypePtr* adr_type = nullptr; - adr_type = get_adr_type(get_alias_index(n->adr_type())); + adr_type = get_adr_type(get_alias_index(n->in_adr_type())); if (adr_type == TypeAryPtr::INLINES) { memnodes.push(n); } @@ -2371,7 +2370,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { int start_alias = num_alias_types(); // Start of new aliases for (uint i = 0; i < memnodes.size(); i++) { Node* m = memnodes.at(i); - const TypePtr* adr_type = m->adr_type(); + const TypePtr* adr_type = m->in_adr_type(); #ifdef ASSERT m->as_Mem()->set_adr_type(adr_type); #endif // ASSERT @@ -2401,8 +2400,8 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { // bottom memory, we pop element off the stack one at a // time, in reverse order, and move them to the right slice // by changing their memory edges. - if ((n->is_Phi() && n->adr_type() != TypePtr::BOTTOM) || n->is_Mem() || - (n->adr_type() == TypeAryPtr::INLINES && !n->is_NarrowMemProj())) { + if ((n->is_Phi() && n->out_adr_type() != TypePtr::BOTTOM) || n->is_Mem() || + (n->out_adr_type() == TypeAryPtr::INLINES && !n->is_NarrowMemProj())) { assert(!seen.test_set(n->_idx), ""); // Uses (a load for instance) will need to be moved to the // right slice as well and will get a new memory state @@ -2452,7 +2451,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { n = n->in(0)->in(TypeFunc::Memory); } } else { - assert(n->adr_type() == TypePtr::BOTTOM || (n->Opcode() == Op_Node && n->_idx >= last) || n->is_NarrowMemProj(), ""); + assert(n->out_adr_type() == TypePtr::BOTTOM || (n->Opcode() == Op_Node && n->_idx >= last) || n->is_NarrowMemProj(), ""); // Build a new MergeMem node to carry the new memory state // as we build it. IGVN should fold extraneous MergeMem // nodes. @@ -2472,7 +2471,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { Node* base = alloc->in(TypeFunc::Memory); assert(base->bottom_type() == Type::MEMORY, "the memory input of AllocateNode must be a memory"); - assert(base->adr_type() == TypePtr::BOTTOM, "the memory input of AllocateNode must be a bottom memory"); + assert(base->out_adr_type() == TypePtr::BOTTOM, "the memory input of AllocateNode must be a bottom memory"); // Must create a MergeMem with base as the base memory, do not clone if base is a // MergeMem because it may not be processed yet mm = MergeMemNode::make(nullptr); @@ -2513,7 +2512,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { uint idx = stack.index(); if (m->is_Mem()) { // Move memory node to its new slice - const TypePtr* adr_type = m->adr_type(); + const TypePtr* adr_type = m->in_adr_type(); int alias = get_alias_index(adr_type); Node* prev = mm->memory_at(alias); igvn.replace_input_of(m, MemNode::Memory, prev); @@ -2560,7 +2559,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { if (!adr_type->isa_aryptr() || !adr_type->is_flat()) { continue; } - MemBarNode* mb = new MemBarCPUOrderNode(this, j, nullptr); + MemBarNode* mb = new MemBarCPUOrderNode(this, adr_type, adr_type, nullptr); igvn.register_new_node_with_optimizer(mb); Node* mem = mm->memory_at(j); mb->init_req(TypeFunc::Control, ctrl); @@ -2617,7 +2616,7 @@ void Compile::adjust_flat_array_access_aliases(PhaseIterGVN& igvn) { wq.push(root()); for (uint i = 0; i < wq.size(); i++) { Node* n = wq.at(i); - assert(n->adr_type() != TypeAryPtr::INLINES, "should have been removed from the graph"); + assert(n->out_adr_type() != TypeAryPtr::INLINES && n->in_adr_type() != TypeAryPtr::INLINES, "should have been removed from the graph"); for (uint j = 0; j < n->req(); j++) { Node* m = n->in(j); if (m != nullptr) { @@ -4073,7 +4072,7 @@ void Compile::final_graph_reshaping_impl(Node *n, Final_Reshape_Counts& frc, Uni #ifdef ASSERT if( n->is_Mem() ) { - int alias_idx = get_alias_index(n->as_Mem()->adr_type()); + int alias_idx = get_alias_index(n->as_Mem()->in_adr_type()); assert( n->in(0) != nullptr || alias_idx != Compile::AliasIdxRaw || // oop will be recorded in oop map if load crosses safepoint (n->is_Load() && (n->as_Load()->bottom_type()->isa_oopptr() || @@ -4165,7 +4164,7 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f if (!Matcher::match_rule_supported(Op_CallLeafPure)) { CallNode* call = n->as_Call(); CallNode* new_call = new CallLeafNode(call->tf(), call->entry_point(), - call->_name, TypeRawPtr::BOTTOM); + call->_name, nullptr, nullptr); new_call->init_req(TypeFunc::Control, call->in(TypeFunc::Control)); new_call->init_req(TypeFunc::I_O, C->top()); new_call->init_req(TypeFunc::Memory, C->top()); @@ -5359,7 +5358,7 @@ Compile::TracePhase::~TracePhase() { return; // timing code, not stressing bailouts. } #ifdef ASSERT - if (PrintIdealNodeCount) { + if (_compile->directive()->PrintIdealNodeCountOption) { tty->print_cr("phase name='%s' nodes='%d' live='%d' live_graph_walk='%d'", phase_name(), _compile->unique(), _compile->live_nodes(), _compile->count_live_nodes_by_graph_walk()); } @@ -6293,7 +6292,7 @@ Node* Compile::make_debug_print_call(const char* str, address call_addr, PhaseGV Node* parm6) const { Node* str_node = gvn->transform(new ConPNode(TypeRawPtr::make(((address) str)))); const TypeFunc* type = OptoRuntime::debug_print_Type(parm0, parm1, parm2, parm3, parm4, parm5, parm6); - Node* call = new CallLeafNode(type, call_addr, "debug_print", TypeRawPtr::BOTTOM); + Node* call = new CallLeafNode(type, call_addr, "debug_print", nullptr, nullptr); // find the most suitable control input Unique_Node_List worklist, candidates; diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 008c6bd9a84b..d2306909d360 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -189,6 +189,7 @@ class Options { const bool _do_locks_coarsening; // Do locks coarsening const bool _do_superword; // Do SuperWord const bool _do_stringopts; // Do StringOpts + const bool _aot_compile; // AOT code compilation const bool _for_aot_preload; // Generate AOT code for preload (before Java method execution), // include class init barriers const bool _install_code; // Install the code that was compiled @@ -201,6 +202,7 @@ class Options { bool do_locks_coarsening, bool do_superword, bool do_stringopts, + bool aot_compile, bool for_aot_preload, bool install_code) : _subsume_loads(subsume_loads), @@ -211,6 +213,7 @@ class Options { _do_locks_coarsening(do_locks_coarsening), _do_superword(do_superword), _do_stringopts(do_stringopts), + _aot_compile(aot_compile), _for_aot_preload(for_aot_preload), _install_code(install_code) { } @@ -225,6 +228,7 @@ class Options { /* do_lock_coarsening = */ false, /* do_superword = */ true, /* do_stringopts = */ false, + /* aot_compile = */ false, /* for_aot_preload = */ false, /* install_code = */ true ); @@ -612,6 +616,7 @@ class Compile : public Phase { bool do_superword() const { return _options._do_superword; } bool do_stringopts() const { return _options._do_stringopts; } + bool is_aot_compile() const { return _options._aot_compile; } bool do_clinit_barriers() const { return _options._for_aot_preload; } bool for_aot_preload() const { return _options._for_aot_preload; } diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 28a727202d27..6f3c78a126f3 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -542,7 +542,7 @@ bool Parse::can_not_compile_call_site(ciMethod *dest_method, ciInstanceKlass* kl if (!holder_klass->is_being_initialized() && !holder_klass->is_initialized() && !holder_klass->is_interface()) { - if (C->env()->task()->is_aot_compile()) { + if (C->is_aot_compile()) { ResourceMark rm; log_debug(aot, compilation)("Emitting uncommon trap (cannot compile call site) in AOT code for %s", holder_klass->name()->as_klass_external_name()); } diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 731580155b78..89729574b0e9 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -4437,14 +4437,14 @@ PhiNode* ConnectionGraph::create_split_phi(PhiNode* orig_phi, int alias_idx, Uni Compile *C = _compile; PhaseGVN* igvn = _igvn; new_created = false; - int phi_alias_idx = C->get_alias_index(orig_phi->adr_type()); + int phi_alias_idx = C->get_alias_index(orig_phi->out_adr_type()); // nothing to do if orig_phi is bottom memory or matches alias_idx if (phi_alias_idx == alias_idx) { return orig_phi; } // Have we recently created a Phi for this alias index? PhiNode *result = get_map_phi(orig_phi->_idx); - if (result != nullptr && C->get_alias_index(result->adr_type()) == alias_idx) { + if (result != nullptr && C->get_alias_index(result->out_adr_type()) == alias_idx) { return result; } // Previous check may fail when the same wide memory Phi was split into Phis @@ -4454,7 +4454,7 @@ PhiNode* ConnectionGraph::create_split_phi(PhiNode* orig_phi, int alias_idx, Uni for (DUIterator_Fast imax, i = region->fast_outs(imax); i < imax; i++) { Node* phi = region->fast_out(i); if (phi->is_Phi() && - C->get_alias_index(phi->as_Phi()->adr_type()) == alias_idx) { + C->get_alias_index(phi->as_Phi()->out_adr_type()) == alias_idx) { assert(phi->_idx >= nodes_size(), "only new Phi per instance memory slice"); return phi->as_Phi(); } @@ -4596,7 +4596,7 @@ void ConnectionGraph::move_inst_mem(Node* n, Unique_Node_List& orig_phis) { record_for_optimizer(use); continue; } - tp = use->as_MemBar()->adr_type()->isa_ptr(); + tp = use->as_MemBar()->in_adr_type()->isa_ptr(); if ((tp != nullptr && C->get_alias_index(tp) == alias_idx) || alias_idx == general_idx) { continue; // Nothing to do @@ -4631,7 +4631,7 @@ void ConnectionGraph::move_inst_mem(Node* n, Unique_Node_List& orig_phis) { "Following memory nodes should have new memory input or be on the same memory slice"); } else if (use->is_Phi()) { // Phi nodes should be split and moved already. - tp = use->as_Phi()->adr_type()->isa_ptr(); + tp = use->as_Phi()->in_adr_type()->isa_ptr(); assert(tp != nullptr, "ptr type"); int idx = C->get_alias_index(tp); assert(idx == alias_idx, "Following Phi nodes should be on the same memory slice"); @@ -4742,8 +4742,8 @@ Node* ConnectionGraph::find_inst_mem(Node* orig_mem, int alias_idx, Unique_Node_ // which contains this memory slice, otherwise skip over it. if (alloc == nullptr || alloc->_idx != (uint)toop->instance_id()) { result = proj_in->in(TypeFunc::Memory); - } else if (C->get_alias_index(result->adr_type()) != alias_idx) { - assert(C->get_general_index(alias_idx) == C->get_alias_index(result->adr_type()), "should be projection for the same field/array element"); + } else if (C->get_alias_index(result->out_adr_type()) != alias_idx) { + assert(C->get_general_index(alias_idx) == C->get_alias_index(result->out_adr_type()), "should be projection for the same field/array element"); result = get_map(result->_idx); assert(result != nullptr, "new projection should have been allocated"); break; @@ -4794,7 +4794,7 @@ Node* ConnectionGraph::find_inst_mem(Node* orig_mem, int alias_idx, Unique_Node_ mmem->set_memory_at(alias_idx, result); } } else if (result->is_Phi() && - C->get_alias_index(result->as_Phi()->adr_type()) != alias_idx) { + C->get_alias_index(result->as_Phi()->out_adr_type()) != alias_idx) { Node *un = result->as_Phi()->unique_input(igvn); if (un != nullptr) { orig_phis.push(result); @@ -4848,7 +4848,7 @@ Node* ConnectionGraph::find_inst_mem(Node* orig_mem, int alias_idx, Unique_Node_ if (result->is_Phi()) { PhiNode *mphi = result->as_Phi(); assert(mphi->bottom_type() == Type::MEMORY, "memory phi required"); - const TypePtr *t = mphi->adr_type(); + const TypePtr* t = mphi->out_adr_type(); if (!is_instance) { // Push all non-instance Phis on the orig_phis worklist to update inputs // during Phase 4 if needed. @@ -5139,7 +5139,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, InitializeNode* init = alloc->as_Allocate()->initialization(); assert(init != nullptr, "can't find Initialization node for this Allocate node"); auto process_narrow_proj = [&](NarrowMemProjNode* proj) { - const TypePtr* adr_type = proj->adr_type(); + const TypePtr* adr_type = proj->out_adr_type(); const TypePtr* new_adr_type = tinst->with_offset(adr_type->offset()); if (adr_type->isa_aryptr()) { // In the case of a flat value type array, each field has its own slice so we need a @@ -5611,7 +5611,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, // also be processed here. for (uint j = 0; j < orig_phis.size(); j++) { PhiNode* phi = orig_phis.at(j)->as_Phi(); - int alias_idx = _compile->get_alias_index(phi->adr_type()); + int alias_idx = _compile->get_alias_index(phi->out_adr_type()); igvn->hash_delete(phi); for (uint i = 1; i < phi->req(); i++) { Node *mem = phi->in(i); diff --git a/src/hotspot/share/opto/gcm.cpp b/src/hotspot/share/opto/gcm.cpp index 0c661e02cd29..5d14b608dd7a 100644 --- a/src/hotspot/share/opto/gcm.cpp +++ b/src/hotspot/share/opto/gcm.cpp @@ -736,7 +736,7 @@ Block* PhaseCFG::raise_above_anti_dependences(Block* LCA, Node* load, const bool // Compute the alias index. Loads and stores with different alias indices // do not need anti-dependence edges. - int load_alias_idx = C->get_alias_index(load->adr_type()); + int load_alias_idx = C->get_alias_index(load->in_adr_type()); #ifdef ASSERT assert(Compile::AliasIdxTop <= load_alias_idx && load_alias_idx < C->num_alias_types(), "Invalid alias index"); if (load_alias_idx == Compile::AliasIdxBot && C->do_aliasing() && @@ -904,7 +904,7 @@ Block* PhaseCFG::raise_above_anti_dependences(Block* LCA, Node* load, const bool // Compute the alias index. If the use_mem_state has an alias index // different from the load's, it is not anti-dependent. Wide MemBar's // are anti-dependent with everything (except immutable memories). - const TypePtr* adr_type = use_mem_state->adr_type(); + const TypePtr* adr_type = use_mem_state->out_adr_type(); if (!C->can_alias(adr_type, load_alias_idx)) continue; // Most slow-path runtime calls do NOT modify Java memory, but diff --git a/src/hotspot/share/opto/generateOptoStub.cpp b/src/hotspot/share/opto/generateOptoStub.cpp index a18ef3b1db64..8a420bc1d855 100644 --- a/src/hotspot/share/opto/generateOptoStub.cpp +++ b/src/hotspot/share/opto/generateOptoStub.cpp @@ -163,7 +163,7 @@ void GraphKit::gen_stub(address C_function, //----------------------------- // Make the call node. - CallRuntimeNode* call = new CallRuntimeNode(c_sig, C_function, name, TypePtr::BOTTOM, new (C) JVMState(0)); + CallRuntimeNode* call = new CallRuntimeNode(c_sig, C_function, name, TypePtr::BOTTOM, TypePtr::BOTTOM, new (C) JVMState(0)); //----------------------------- // Fix-up the debug info for the call. diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index b24801b42e28..73f69aeafbab 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -81,8 +81,6 @@ GraphKit::GraphKit(JVMState* jvms, PhaseGVN* gvn) #ifdef ASSERT if (_gvn.is_IterGVN() != nullptr) { assert(_gvn.is_IterGVN()->delay_transform(), "Transformation must be delayed if IterGVN is used"); - // Save the initial size of _for_igvn worklist for verification (see ~GraphKit) - _worklist_size = _gvn.C->igvn_worklist()->size(); } #endif } @@ -2267,7 +2265,7 @@ void GraphKit::set_predefined_output_for_runtime_call(Node* call, // Make sure the call advertises its memory effects precisely. // This lets us build accurate anti-dependences in gcm.cpp. - assert(C->alias_type(call->adr_type()) == C->alias_type(hook_mem), + assert(C->alias_type(call->out_adr_type()) == C->alias_type(hook_mem), "call node must be constructed correctly"); } else { assert(hook_mem == nullptr, ""); @@ -2873,7 +2871,7 @@ Node* GraphKit::opt_iff(Node* region, Node* iff) { Node* GraphKit::make_runtime_call(int flags, const TypeFunc* call_type, address call_addr, const char* call_name, - const TypePtr* adr_type, + const TypePtr* out_adr_type, // The following parms are all optional. // The first null ends the list. Node* parm0, Node* parm1, @@ -2889,34 +2887,35 @@ Node* GraphKit::make_runtime_call(int flags, assert(!is_leaf, "must supply name for leaf"); call_name = OptoRuntime::stub_name(call_addr); } + + // Slow path call has no side-effects, uses few values + bool wide_in = !(flags & RC_NARROW_MEM); + bool wide_out = (C->get_alias_index(out_adr_type) == Compile::AliasIdxBot); + + const TypePtr* in_adr_type = wide_in ? TypePtr::BOTTOM : out_adr_type; CallNode* call; if (!is_leaf) { - call = new CallStaticJavaNode(call_type, call_addr, call_name, adr_type); + call = new CallStaticJavaNode(call_type, call_addr, call_name, out_adr_type, in_adr_type); } else if (flags & RC_NO_FP) { - call = new CallLeafNoFPNode(call_type, call_addr, call_name, adr_type); + call = new CallLeafNoFPNode(call_type, call_addr, call_name, out_adr_type, in_adr_type); } else if (flags & RC_VECTOR){ uint num_bits = call_type->range_sig()->field_at(TypeFunc::Parms)->is_vect()->length_in_bytes() * BitsPerByte; - call = new CallLeafVectorNode(call_type, call_addr, call_name, adr_type, num_bits); + call = new CallLeafVectorNode(call_type, call_addr, call_name, out_adr_type, in_adr_type, num_bits); } else if (flags & RC_PURE) { - assert(adr_type == nullptr, "pure call does not touch memory"); + assert(out_adr_type == nullptr, "pure call does not touch memory"); call = new CallLeafPureNode(call_type, call_addr, call_name); } else { - call = new CallLeafNode(call_type, call_addr, call_name, adr_type); + call = new CallLeafNode(call_type, call_addr, call_name, out_adr_type, in_adr_type); } // The following is similar to set_edges_for_java_call, // except that the memory effects of the call are restricted to AliasIdxRaw. - - // Slow path call has no side-effects, uses few values - bool wide_in = !(flags & RC_NARROW_MEM); - bool wide_out = (C->get_alias_index(adr_type) == Compile::AliasIdxBot); - Node* prev_mem = nullptr; if (wide_in) { prev_mem = set_predefined_input_for_runtime_call(call); } else { assert(!wide_out, "narrow in => narrow out"); - Node* narrow_mem = memory(adr_type); + Node* narrow_mem = memory(in_adr_type); prev_mem = set_predefined_input_for_runtime_call(call, narrow_mem); } @@ -2956,7 +2955,7 @@ Node* GraphKit::make_runtime_call(int flags, set_predefined_output_for_runtime_call(call); } else { // Slow path call has few side-effects, and/or sets few values. - set_predefined_output_for_runtime_call(call, prev_mem, adr_type); + set_predefined_output_for_runtime_call(call, prev_mem, out_adr_type); } if (has_io) { @@ -3509,7 +3508,7 @@ void GraphKit::clinit_barrier(ciInstanceKlass* ik, ciMethod* context) { } else if (ik->is_initialized()) { return; // no barrier needed } else { - if (C->env()->task()->is_aot_compile()) { + if (C->is_aot_compile()) { ResourceMark rm; log_debug(aot, compilation)("Emitting uncommon trap (clinit barrier) in AOT code for %s", ik->name()->as_klass_external_name()); } @@ -4948,7 +4947,7 @@ void GraphKit::memory_effect(Node* res_mem, const TypePtr* src_type, const TypeP if (src_type != dst_type) { Node* all_mem = reset_memory(); set_all_memory(all_mem); - Node* membar = new MemBarCPUOrderNode(C, C->get_alias_index(src_type), nullptr); + Node* membar = new MemBarCPUOrderNode(C, src_type, TypePtr::BOTTOM, nullptr); membar->init_req(TypeFunc::Control, control()); membar->init_req(TypeFunc::Memory, all_mem); membar = _gvn.transform(membar); @@ -4975,7 +4974,7 @@ void GraphKit::inflate_string(Node* src, Node* dst, const TypeAryPtr* dst_type, const TypePtr* src_type = TypeAryPtr::BYTES; const TypePtr* adr_type; Node* mem = capture_memory(adr_type, src_type, dst_type); - StrInflatedCopyNode* str = new StrInflatedCopyNode(control(), mem, adr_type, src, dst, count); + StrInflatedCopyNode* str = new StrInflatedCopyNode(control(), mem, dst_type, adr_type, src, dst, count); Node* res_mem = _gvn.transform(str); memory_effect(res_mem, src_type, dst_type); } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index a1796ea81259..28932b145ebf 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -65,9 +65,6 @@ class GraphKit : public Phase { int _bci; // JVM Bytecode Pointer ciMethod* _method; // JVM Current Method BarrierSetC2* _barrier_set; -#ifdef ASSERT - uint _worklist_size; -#endif private: int _sp; // JVM Expression Stack Pointer; don't modify directly! diff --git a/src/hotspot/share/opto/idealGraphPrinter.cpp b/src/hotspot/share/opto/idealGraphPrinter.cpp index 0409224121ea..827c710669b5 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.cpp +++ b/src/hotspot/share/opto/idealGraphPrinter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,7 +54,7 @@ class PrintProperties }; void PrintProperties::print_alias_properties(Node* node) { - const TypePtr* adr_type = node->adr_type(); + const TypePtr* adr_type = node->in_adr_type(); Compile* C = _printer->C; if (adr_type != nullptr && C->have_alias_type(adr_type)) { Compile::AliasType* at = C->alias_type(adr_type); @@ -611,10 +611,15 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) { } } } - if (n->adr_type() != nullptr) { + if (const TypePtr* out = n->out_adr_type(); out != nullptr) { stringStream adr_type_stream; - n->adr_type()->dump_on(&adr_type_stream); - print_prop("adr_type", adr_type_stream.freeze()); + out->dump_on(&adr_type_stream); + print_prop("out_adr_type", adr_type_stream.freeze()); + } + if (const TypePtr* in = n->in_adr_type(); in != nullptr) { + stringStream adr_type_stream; + in->dump_on(&adr_type_stream); + print_prop("in_adr_type", adr_type_stream.freeze()); } if (C->cfg() != nullptr) { @@ -705,8 +710,7 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) { } t->dump_on(&s2); } else if( t == Type::MEMORY ) { - s2.print(" Memory:"); - MemNode::dump_adr_type(node->adr_type(), &s2); + s2.print(" Memory"); } assert(s2.size() < sizeof(buffer), "size in range"); @@ -906,7 +910,7 @@ void IdealGraphPrinter::print_field(const Node* node) { } ciField* IdealGraphPrinter::get_field(const Node* node) { - const TypePtr* adr_type = node->adr_type(); + const TypePtr* adr_type = node->in_adr_type(); Compile::AliasType* atp = nullptr; if (C->have_alias_type(adr_type)) { atp = C->alias_type(adr_type); @@ -929,7 +933,7 @@ ciField* IdealGraphPrinter::find_source_field_of_array_access(const Node* node, } do { - if (node->adr_type() != nullptr && node->adr_type()->isa_aryptr()) { + if (const TypePtr* in = node->in_adr_type(); in != nullptr && in->isa_aryptr()) { // Only process array accesses. Pattern match to find actual field source access. node = get_load_node(node); if (node != nullptr) { diff --git a/src/hotspot/share/opto/idealKit.cpp b/src/hotspot/share/opto/idealKit.cpp index 7d45fb770ef2..02d3aa0fda7e 100644 --- a/src/hotspot/share/opto/idealKit.cpp +++ b/src/hotspot/share/opto/idealKit.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -485,7 +485,7 @@ Node* IdealKit::make_leaf_call(const TypeFunc *slow_call_type, uint adr_idx = C->get_alias_index(adr_type); // Slow-path leaf call - CallNode *call = (CallNode*)new CallLeafNode( slow_call_type, slow_call, leaf_name, adr_type); + CallNode* call = new CallLeafNode(slow_call_type, slow_call, leaf_name, adr_type, adr_type); // Set fixed predefined input arguments call->init_req( TypeFunc::Control, ctrl() ); @@ -514,7 +514,8 @@ Node* IdealKit::make_leaf_call(const TypeFunc *slow_call_type, // Set the RawPtr memory state only. set_memory(mem, adr_idx); - assert(C->alias_type(call->adr_type()) == C->alias_type(adr_type), + assert(C->alias_type(call->out_adr_type()) == C->alias_type(adr_type) && + C->alias_type(call->in_adr_type()) == C->alias_type(adr_type), "call node must be constructed correctly"); Node* res = nullptr; if (slow_call_type->range_sig()->cnt() > TypeFunc::Parms) { @@ -537,7 +538,7 @@ void IdealKit::make_leaf_call_no_fp(const TypeFunc *slow_call_type, uint adr_idx = C->get_alias_index(adr_type); // Slow-path leaf call - CallNode *call = (CallNode*)new CallLeafNoFPNode( slow_call_type, slow_call, leaf_name, adr_type); + CallNode* call = new CallLeafNoFPNode(slow_call_type, slow_call, leaf_name, adr_type, adr_type); // Set fixed predefined input arguments call->init_req( TypeFunc::Control, ctrl() ); @@ -566,6 +567,7 @@ void IdealKit::make_leaf_call_no_fp(const TypeFunc *slow_call_type, // Set the RawPtr memory state only. set_memory(mem, adr_idx); - assert(C->alias_type(call->adr_type()) == C->alias_type(adr_type), + assert(C->alias_type(call->out_adr_type()) == C->alias_type(adr_type) && + C->alias_type(call->in_adr_type()) == C->alias_type(adr_type), "call node must be constructed correctly"); } diff --git a/src/hotspot/share/opto/intrinsicnode.cpp b/src/hotspot/share/opto/intrinsicnode.cpp index 0b7f5ed21eae..c0d8b9b0a536 100644 --- a/src/hotspot/share/opto/intrinsicnode.cpp +++ b/src/hotspot/share/opto/intrinsicnode.cpp @@ -47,7 +47,7 @@ static Node* ideal_mem_intrinsic_node(PhaseGVN* phase, bool can_reshape, Node* n } // If mem input is a MergeMem, get the desired slice - if (const TypePtr* adr_type = n->adr_type(); can_reshape && adr_type != TypePtr::BOTTOM) { + if (const TypePtr* adr_type = n->in_adr_type(); can_reshape && adr_type != TypePtr::BOTTOM) { Node* mem = n->in(MemNode::Memory); uint alias_idx = phase->C->get_alias_index(adr_type); Node* new_mem = mem->is_MergeMem() ? mem->as_MergeMem()->memory_at(alias_idx) : mem; diff --git a/src/hotspot/share/opto/intrinsicnode.hpp b/src/hotspot/share/opto/intrinsicnode.hpp index 230ad8d81fe4..711921c98aa6 100644 --- a/src/hotspot/share/opto/intrinsicnode.hpp +++ b/src/hotspot/share/opto/intrinsicnode.hpp @@ -28,6 +28,7 @@ #include "opto/connode.hpp" #include "opto/node.hpp" #include "opto/opcodes.hpp" +#include "opto/type.hpp" //----------------------PartialSubtypeCheckNode-------------------------------- @@ -81,7 +82,6 @@ class StrIntrinsicNode : public Node { Node(control, char_array_mem, s1, s2), _encoding(encoding) { } - virtual const TypePtr* adr_type() const override = 0; virtual uint match_edge(uint idx) const override; virtual uint ideal_reg() const override { return Op_RegI; } virtual Node* Ideal(PhaseGVN* phase, bool can_reshape) override; @@ -100,7 +100,9 @@ class StrCompNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, c1, s2, c2, encoding) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::INT; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //------------------------------StrEquals------------------------------------- @@ -111,7 +113,9 @@ class StrEqualsNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, s2, c, encoding) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::BOOL; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //------------------------------StrIndexOf------------------------------------- @@ -122,7 +126,9 @@ class StrIndexOfNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, c1, s2, c2, encoding) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::INT; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //------------------------------StrIndexOfChar------------------------------------- @@ -133,49 +139,55 @@ class StrIndexOfCharNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, c1, c, encoding) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::INT; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //--------------------------StrCompressedCopy------------------------------- class StrCompressedCopyNode final : public StrIntrinsicNode { private: - const TypePtr* const _adr_type; + const TypePtr* const _in_adr_type; public: - StrCompressedCopyNode(Node* control, Node* arymem, const TypePtr* adr_type, + StrCompressedCopyNode(Node* control, Node* arymem, const TypePtr* in_adr_type, Node* s1, Node* s2, Node* c): - StrIntrinsicNode(control, arymem, s1, s2, c, none), _adr_type(adr_type) {}; + StrIntrinsicNode(control, arymem, s1, s2, c, none), _in_adr_type(in_adr_type) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::INT; } private: virtual uint size_of() const override { return sizeof(StrCompressedCopyNode); } - virtual uint hash() const override { return StrIntrinsicNode::hash() + (uint)(uintptr_t) _adr_type; } + virtual uint hash() const override { return StrIntrinsicNode::hash() + (uint)(uintptr_t) _in_adr_type; } virtual bool cmp(const Node& n) const override { - return StrIntrinsicNode::cmp(n) && _adr_type == static_cast(n)._adr_type; + return StrIntrinsicNode::cmp(n) && _in_adr_type == static_cast(n)._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _adr_type; } + const TypePtr* out_adr_type_impl() const final { return TypeAryPtr::BYTES; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //--------------------------StrInflatedCopy--------------------------------- class StrInflatedCopyNode final : public StrIntrinsicNode { private: - const TypePtr* const _adr_type; + const TypePtr* const _out_adr_type; + const TypePtr* const _in_adr_type; public: - StrInflatedCopyNode(Node* control, Node* arymem, const TypePtr* adr_type, + StrInflatedCopyNode(Node* control, Node* arymem, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* s1, Node* s2, Node* c): - StrIntrinsicNode(control, arymem, s1, s2, c, none), _adr_type(adr_type) {}; + StrIntrinsicNode(control, arymem, s1, s2, c, none), _out_adr_type(out_adr_type), _in_adr_type(in_adr_type) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return Type::MEMORY; } private: virtual uint size_of() const override { return sizeof(StrInflatedCopyNode); } - virtual uint hash() const override { return StrIntrinsicNode::hash() + (uint)(uintptr_t) _adr_type; } + virtual uint hash() const override { return StrIntrinsicNode::hash() + (uint)(uintptr_t) _out_adr_type + (uint)(uintptr_t) _in_adr_type; } virtual bool cmp(const Node& n) const override { - return StrIntrinsicNode::cmp(n) && _adr_type == static_cast(n)._adr_type; + auto& other = static_cast(n); + return StrIntrinsicNode::cmp(n) && _out_adr_type == other._out_adr_type && _in_adr_type == other._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _adr_type; } + const TypePtr* out_adr_type_impl() const final { return _out_adr_type; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //------------------------------AryEq--------------------------------------- @@ -196,7 +208,7 @@ class AryEqNode final : public StrIntrinsicNode { virtual bool cmp(const Node& n) const override { return StrIntrinsicNode::cmp(n) && _in_adr_type == static_cast(n)._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _in_adr_type; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //------------------------------CountPositives------------------------------ @@ -206,7 +218,9 @@ class CountPositivesNode final : public StrIntrinsicNode { StrIntrinsicNode(control, char_array_mem, s1, c1, none) {}; virtual int Opcode() const override; virtual const Type* bottom_type() const override { return TypeInt::POS; } - virtual const TypePtr* adr_type() const override { return TypeAryPtr::BYTES; } + +private: + const TypePtr* in_adr_type_impl() const final { return TypeAryPtr::BYTES; } }; //------------------------------VectorizedHashCodeNode---------------------- @@ -230,20 +244,20 @@ class VectorizedHashCodeNode final : public Node { virtual bool cmp(const Node& n) const override { return Node::cmp(n) && _in_adr_type == static_cast(n)._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _in_adr_type; } virtual bool depends_only_on_test_impl() const override { return false; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; //------------------------------EncodeISOArray-------------------------------- // encode char[] to byte[] in ISO_8859_1 or ASCII class EncodeISOArrayNode final : public Node { private: - const TypePtr* const _adr_type; + const TypePtr* const _in_adr_type; bool _ascii; public: - EncodeISOArrayNode(Node* control, Node* arymem, const TypePtr* adr_type, Node* s1, Node* s2, Node* c, bool ascii) - : Node(control, arymem, s1, s2, c), _adr_type(adr_type), _ascii(ascii) {} + EncodeISOArrayNode(Node* control, Node* arymem, const TypePtr* in_adr_type, Node* s1, Node* s2, Node* c, bool ascii) + : Node(control, arymem, s1, s2, c), _in_adr_type(in_adr_type), _ascii(ascii) {} bool is_ascii() { return _ascii; } virtual int Opcode() const override; @@ -255,13 +269,14 @@ class EncodeISOArrayNode final : public Node { private: virtual uint size_of() const override { return sizeof(EncodeISOArrayNode); } - virtual uint hash() const override { return Node::hash() + (uint)(uintptr_t) _adr_type + _ascii; } + virtual uint hash() const override { return Node::hash() + (uint)(uintptr_t) _in_adr_type + _ascii; } virtual bool cmp(const Node& n) const override { const EncodeISOArrayNode& e = static_cast(n); - return Node::cmp(n) && _ascii == e._ascii && _adr_type == e._adr_type; + return Node::cmp(n) && _ascii == e._ascii && _in_adr_type == e._in_adr_type; } - virtual const TypePtr* adr_type() const override { return _adr_type; } virtual bool depends_only_on_test_impl() const override { return false; } + const TypePtr* out_adr_type_impl() const override { return TypeAryPtr::BYTES; } + const TypePtr* in_adr_type_impl() const override { return _in_adr_type; } }; //-------------------------------DigitNode---------------------------------------- diff --git a/src/hotspot/share/opto/lcm.cpp b/src/hotspot/share/opto/lcm.cpp index 833aa348b470..0c67d3965c97 100644 --- a/src/hotspot/share/opto/lcm.cpp +++ b/src/hotspot/share/opto/lcm.cpp @@ -325,21 +325,30 @@ void PhaseCFG::implicit_null_check(Block* block, Node *proj, Node *val, int allo } } - // Check ctrl input to see if the null-check dominates the memory op Block *cb = get_block_for_node(mach); - cb = cb->_idom; // Always hoist at least 1 block - if( !was_store ) { // Stores can be hoisted only one block - while( cb->_dom_depth > (block->_dom_depth + 1)) - cb = cb->_idom; // Hoist loads as far as we want - // The non-null-block should dominate the memory op, too. Live - // range spilling will insert a spill in the non-null-block if it is - // needs to spill the memory op for an implicit null check. - if (cb->_dom_depth == (block->_dom_depth + 1)) { - if (cb != not_null_block) continue; - cb = cb->_idom; + + if (was_store) { + // A store can only be moved from the immediate non-null successor. + if (cb != not_null_block) { + continue; } + } else { + // A load can be used here only if its block is dominated by the + // non-null successor. Check this explicitly because a candidate load + // may have been scheduled on the null path instead. + // For example, in JDK-8389578, loop opts did not recognize equivalent + // oop and value-type null_marker checks, leaving a redundant check. + // The load was protected by earlier null checks, but its only use + // was in the scalarized debug info of the redundant check's uncommon + // trap. This allowed the load to be scheduled on that check's null path. + if (!not_null_block->dominates(cb)) { + continue; + } + } + + if (not_null_block->_idom != block) { + continue; } - if( cb != block ) continue; // Found a memory user; see if it can be hoisted to check-block uint vidx = 0; // Capture index of value into memop diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 6b3f61342a9b..1cc670a135f7 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -1202,21 +1202,26 @@ bool LibraryCallKit::inline_countPositives() { return true; } +// +// int checkIndex(int index, int length, +// BiFunction, X> oobef) +// +// long checkIndex(long index, long length, +// BiFunction, X> oobef) bool LibraryCallKit::inline_preconditions_checkIndex(BasicType bt) { Node* index = argument(0); Node* length = bt == T_INT ? argument(1) : argument(2); - if (too_many_traps(Deoptimization::Reason_intrinsic) || too_many_traps(Deoptimization::Reason_range_check)) { - return false; - } // check that length is positive Node* len_pos_cmp = _gvn.transform(CmpNode::make(length, integercon(0, bt), bt)); Node* len_pos_bol = _gvn.transform(new BoolNode(len_pos_cmp, BoolTest::ge)); { + // Let the intepreter throw the exception since the actual exception that needs throwing is the + // result of oobef, which is not trivial BuildCutout unless(this, len_pos_bol, PROB_MAX); uncommon_trap(Deoptimization::Reason_intrinsic, - Deoptimization::Action_make_not_entrant); + Deoptimization::Action_none); } if (stopped()) { @@ -1244,10 +1249,12 @@ bool LibraryCallKit::inline_preconditions_checkIndex(BasicType bt) { } set_control(_gvn.transform(new IfTrueNode(rc))); { + // Let the intepreter throw the exception since the actual exception that needs throwing is the + // result of oobef, which is not trivial PreserveJVMState pjvms(this); set_control(_gvn.transform(new IfFalseNode(rc))); uncommon_trap(Deoptimization::Reason_range_check, - Deoptimization::Action_make_not_entrant); + Deoptimization::Action_none); } if (stopped()) { @@ -2816,6 +2823,7 @@ bool LibraryCallKit::inline_unsafe_flat_access(bool is_store, AccessKind kind) { if (new_base->is_top()) { uncommon_trap(Deoptimization::Reason_intrinsic, Deoptimization::Action_none); + return true; } replace_in_map(base, new_base); base = new_base; @@ -4999,17 +5007,34 @@ bool LibraryCallKit::inline_newArray(bool null_free, bool atomic) { // public static native boolean ValueClass::isNullRestrictedArray(Object array); // public static native boolean ValueClass::isAtomicArray(Object array); bool LibraryCallKit::inline_getArrayProperties(ArrayPropertiesCheck check) { + if (!Arguments::is_valhalla_enabled()) { + Node* res = gvn().transform(check == ArrayPropertiesCheck::IsAtomic ? intcon(1) : intcon(0)); + set_result(res); + return true; + } Node* array = argument(0); Node* bol; - switch(check) { + switch (check) { case IsFlat: - bol = flat_array_test(array); + if (UseArrayFlattening) { + bol = flat_array_test(array); + } else { + Node* res = gvn().transform(intcon(0)); + set_result(res); + return true; + } break; case IsNullRestricted: bol = null_free_array_test(array); break; case IsAtomic: { + if (!UseArrayFlattening) { + // Non-flat arrays are always atomic + Node* res = gvn().transform(intcon(1)); + set_result(res); + return true; + } // See conditions in JVM_IsAtomicArray // 1. If not flat, then atomic, or else... RegionNode* atomic_region = new RegionNode(1); @@ -5330,8 +5355,10 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { if (Arguments::is_valhalla_enabled()) { // Handle value type arrays // TODO 8251971 This is too strong - generate_fair_guard(flat_array_test(original), bailout); - generate_fair_guard(flat_array_test(refined_klass_node), bailout); + if (UseArrayFlattening) { + generate_fair_guard(flat_array_test(original), bailout); + generate_fair_guard(flat_array_test(refined_klass_node), bailout); + } generate_fair_guard(null_free_array_test(original), bailout); } @@ -6710,7 +6737,7 @@ void LibraryCallKit::arraycopy_move_allocation_here(AllocateArrayNode* alloc, No int klass_idx = C->get_alias_index(ary_type->add_offset(oopDesc::klass_offset_in_bytes())); #endif auto move_proj = [&](ProjNode* proj) { - int alias_idx = C->get_alias_index(proj->adr_type()); + int alias_idx = C->get_alias_index(proj->out_adr_type()); assert(alias_idx == Compile::AliasIdxRaw || alias_idx == elemidx || alias_idx == mark_idx || @@ -7166,8 +7193,10 @@ bool LibraryCallKit::inline_arraycopy() { generate_fair_guard(tst, slow_region); // TODO 8251971 This is too strong - generate_fair_guard(flat_array_test(src), slow_region); - generate_fair_guard(flat_array_test(dest), slow_region); + if (UseArrayFlattening) { + generate_fair_guard(flat_array_test(src), slow_region); + generate_fair_guard(flat_array_test(dest), slow_region); + } } { diff --git a/src/hotspot/share/opto/loopPredicate.cpp b/src/hotspot/share/opto/loopPredicate.cpp index be099747fc01..f3c45cd497fd 100644 --- a/src/hotspot/share/opto/loopPredicate.cpp +++ b/src/hotspot/share/opto/loopPredicate.cpp @@ -457,10 +457,10 @@ class Invariance : public StackObj { } }; -//------------------------------is_range_check_if ----------------------------------- -// Returns true if the predicate of iff is in "scale*iv + offset u< load_range(ptr)" format -// Note: this function is particularly designed for loop predication. We require load_range -// and offset to be loop invariant computed on the fly by "invar" +// Returns true if the predicate has the form +// scale*iv + offset = 0. +// Note: This is used by Loop Predication and PhaseIdealLoop::extract_long_range_check(). bool IdealLoopTree::is_range_check_if(IfProjNode* if_success_proj, PhaseIdealLoop *phase, BasicType bt, Node *iv, Node *&range, Node *&offset, jlong &scale) const { IfNode* iff = if_success_proj->in(0)->as_If(); @@ -518,20 +518,23 @@ bool IdealLoopTree::is_range_check_if(IfProjNode* if_success_proj, PhaseIdealLoo return false; } range = cmp->in(2); + + // Now we check the condition R >= 0 from the method comment. if (range->Opcode() != Op_LoadRange) { const TypeInteger* tinteger = phase->_igvn.type(range)->isa_integer(bt); if (tinteger == nullptr || tinteger->empty() || tinteger->lo_as_long() < 0) { - // Allow predication on positive values that aren't LoadRanges. - // This allows optimization of loops where the length of the - // array is a known value and doesn't need to be loaded back - // from the array. + // R is not proven to be non-negative. Bail out. return false; } } else { + // R = LoadRange are always non-negative. assert(bt == T_INT, "no LoadRange for longs"); } scale = 0; offset = nullptr; + // Now check the form + // scale*iv + offset is_scaled_iv_plus_offset(cmp->in(1), iv, bt, &scale, &offset)) { return false; } diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 209e4bd6bea4..371c1d851625 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -2612,8 +2612,15 @@ void PhaseIdealLoop::add_constraint(jlong stride_con, jlong scale_con, Node* off //----------------------------------is_iv------------------------------------ // Return true if exp is the value (of type bt) of the given induction var. -// This grammar of cases is recognized, where X is I|L according to bt: -// VIV[iv] = iv | (CastXX VIV[iv]) | (ConvI2X VIV[iv]) +// On success, we guarantee this form: +// if bt = T_INT (implies iv is int): +// exp = iv +// if bt = T_LONG and iv is long: +// exp = iv +// if bt = T_LONG and iv is int: +// exp = ConvI2L(iv) +// +// See grammar specification in PhaseIdealLoop::is_scaled_iv_plus_offset(). bool PhaseIdealLoop::is_iv(Node* exp, Node* iv, BasicType bt) { exp = exp->uncast(); if (exp == iv && iv->bottom_type()->isa_integer(bt)) { @@ -2628,88 +2635,129 @@ bool PhaseIdealLoop::is_iv(Node* exp, Node* iv, BasicType bt) { //------------------------------is_scaled_iv--------------------------------- // Return true if exp is a constant times the given induction var (of type bt). -// The multiplication is either done in full precision (exactly of type bt), -// or else bt is T_LONG but iv is scaled using 32-bit arithmetic followed by a ConvI2L. -// This grammar of cases is recognized, where X is I|L according to bt: -// SIV[iv] = VIV[iv] | (CastXX SIV[iv]) -// | (MulX VIV[iv] ConX) | (MulX ConX VIV[iv]) -// | (LShiftX VIV[iv] ConI) -// | (ConvI2L SIV[iv]) -- a "short-scale" can occur here; note recursion -// | (SubX 0 SIV[iv]) -- same as MulX(iv, -scale); note recursion -// | (AddX SIV[iv] SIV[iv]) -- sum of two scaled iv; note recursion -// | (SubX SIV[iv] SIV[iv]) -- difference of two scaled iv; note recursion -// VIV[iv] = [either iv or its value converted; see is_iv() above] -// On success, the constant scale value is stored back to *p_scale. -// The value (*p_short_scale) reports if such a ConvI2L conversion was present. -bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, bool* p_short_scale, int depth) { - BasicType exp_bt = bt; - exp = exp->uncast(); //strip casts - assert(exp_bt == T_INT || exp_bt == T_LONG, "unexpected int type"); - if (is_iv(exp, iv, exp_bt)) { +// On success, we guarantee this form: +// if bt = T_INT (implies iv is int): +// exp = MulI( iv, p_scale) where p_scale is int +// if bt = T_LONG and iv is long: +// exp = MulL( iv, p_scale) +// if bt = T_LONG and iv is int: +// exp = MulL(ConvI2L(iv), p_scale) +// where the constant scale value is stored back to *p_scale unless null. +// The scaling is always done in full precision (exactly of type bt). +// See grammar specification in PhaseIdealLoop::is_scaled_iv_plus_offset(). +bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, int depth) { + exp = exp->uncast(); // Strip casts. + assert(bt == T_INT || bt == T_LONG, "unexpected int type"); + if (is_iv(exp, iv, bt)) { + // if bt = T_INT (implies iv is int): + // exp = iv = MulI( iv, p_scale=1) where p_scale=1 is trivially an int + // if bt = T_LONG and iv is long: + // exp = iv = MulL( iv, p_scale=1) + // if bt = T_LONG and iv is int: + // exp = ConvI2L(iv) = MulL(ConvI2L(iv), p_scale=1) if (p_scale != nullptr) { *p_scale = 1; } - if (p_short_scale != nullptr) { - *p_short_scale = false; - } return true; } - if (exp_bt == T_LONG && iv->bottom_type()->isa_int() && exp->Opcode() == Op_ConvI2L) { - exp = exp->in(1); - exp_bt = T_INT; + if (bt == T_LONG && iv->bottom_type()->isa_int() && exp->Opcode() == Op_ConvI2L) { + // Risk for short scaling: avoid long range-check transformations, because + // the current implementation (transform_long_range_checks) does not protect + // against overflow. + return false; } int opc = exp->Opcode(); int which = 0; // this is which subexpression we find the iv in // Can't use is_Mul() here as it's true for AndI and AndL - if (opc == Op_Mul(exp_bt)) { - if ((is_iv(exp->in(which = 1), iv, exp_bt) && exp->in(2)->is_Con()) || - (is_iv(exp->in(which = 2), iv, exp_bt) && exp->in(1)->is_Con())) { + if (opc == Op_Mul(bt)) { + // We need to test both ways because the multiplication node may not have + // been canonicalized yet, for example after successful loop predication. + if ((is_iv(exp->in(which = 1), iv, bt) && exp->in(2)->is_Con()) || + (is_iv(exp->in(which = 2), iv, bt) && exp->in(1)->is_Con())) { Node* factor = exp->in(which == 1 ? 2 : 1); // the other argument - jlong scale = factor->find_integer_as_long(exp_bt, 0); + jlong scale = factor->find_integer_as_long(bt, 0); if (scale == 0) { - return false; // might be top + return false; // find_integer_as_long may have failed, value might be top. } + // if bt = T_INT (implies iv is int): + // exp->in(which) = iv + // Note: find_integer_as_long ensures that scale is int + // -> which = 1: exp = MulI( iv, scale) where scale is int + // -> which = 2: exp = MulI(scale, iv) = MulI( iv, scale) where scale is int + // if bt = T_LONG and iv is long: + // exp->in(which) = iv + // -> which = 1: exp = MulL( iv, scale) + // -> which = 2: exp = MulL(scale, iv) = MulL( iv, scale) + // if bt = T_LONG and iv is int: + // exp->in(which) = ConvI2L(iv) + // -> which = 1: exp = MulL(ConvI2L(iv), scale) + // -> which = 2: exp = MulL(scale, ConvI2L(iv)) = MulL(ConvI2L(iv), scale) if (p_scale != nullptr) { *p_scale = scale; } - if (p_short_scale != nullptr) { - // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough... - *p_short_scale = (exp_bt != bt && scale != 1); - } return true; } - } else if (opc == Op_LShift(exp_bt)) { - if (is_iv(exp->in(1), iv, exp_bt) && exp->in(2)->is_Con()) { + } else if (opc == Op_LShift(bt)) { + if (is_iv(exp->in(1), iv, bt) && exp->in(2)->is_Con()) { jint shift_amount = exp->in(2)->find_int_con(min_jint); if (shift_amount == min_jint) { - return false; // might be top + return false; // find_int_con may have failed, value might be top. } jlong scale; - if (exp_bt == T_INT) { + if (bt == T_INT) { + // LShiftI(x, shift_amount) = MulI(x, LShiftI(1, shift_amount)) = MulI(x, scale) + // Note: scale is guaranteed to be an int scale = java_shift_left((jint)1, (juint)shift_amount); - } else if (exp_bt == T_LONG) { + } else if (bt == T_LONG) { + // LShiftL(x, shift_amount) = MulL(x, LShiftL(1L, shift_amount)) = MulL(x, scale) scale = java_shift_left((jlong)1, (julong)shift_amount); } + // if bt = T_INT (implies iv is int): + // exp->in(1) = iv + // exp = LShiftI( iv, shift_amount) = MulI( iv, scale) where scale is int + // if bt = T_LONG and iv is long: + // exp->in(1) = iv + // exp = LShiftL( iv, shift_amount) = MulL( iv, scale) + // if bt = T_LONG and iv is int: + // exp->in(1) = ConvI2L(iv) + // exp = LShiftL(ConvI2L(iv), shift_amount) = MulL(ConvI2L(iv), scale) if (p_scale != nullptr) { *p_scale = scale; } - if (p_short_scale != nullptr) { - // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough... - *p_short_scale = (exp_bt != bt && scale != 1); - } return true; } - } else if (opc == Op_Add(exp_bt)) { + } else if (opc == Op_Add(bt)) { jlong scale_l = 0; jlong scale_r = 0; - bool short_scale_l = false; - bool short_scale_r = false; if (depth == 0 && - is_scaled_iv(exp->in(1), iv, exp_bt, &scale_l, &short_scale_l, depth + 1) && - is_scaled_iv(exp->in(2), iv, exp_bt, &scale_r, &short_scale_r, depth + 1)) { - // AddX(iv*K1, iv*K2) => iv*(K1+K2) + is_scaled_iv(exp->in(1), iv, bt, &scale_l, depth + 1) && + is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) { + // We know that: + // AddI(MulI(x, scale_l), MulI(x, scale_r)) = MulI(x, scale=AddI(scale_l, scale_r)) where scale is int + // AddL(MulL(x, scale_l), MulL(x, scale_r)) = MulL(x, scale=AddL(scale_l, scale_r)) + // + // if bt = T_INT (implies iv is int): + // e1 = exp->in(1) = MulI(iv, scale_l) where scale_l is int + // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int + // exp = AddI(e1, e2) = MulI(iv, scale=AddI(scale_l, scale_r)) where scale is int + // if bt = T_LONG and iv is long: + // e1 = exp->in(1) = MulL(iv, scale_l) + // e2 = exp->in(2) = MulL(iv, scale_r) + // exp = AddL(e1, e2) = MulL(iv, scale=AddL(scale_l, scale_r)) + // if bt = T_LONG and iv is int: + // e1 = exp->in(1) = MulL(ConvI2L(iv), scale_l) + // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r) + // exp = AddL(e1, e2) = MulL(ConvI2L(iv), scale=AddL(scale_l, scale_r)) + // + // For the scale computation, we could have used the same type case + // distinction as in LShift above. But instead, we compute in long for + // all cases. We need to show that this is OK if bt=T_INT. + // We would have computed + // scale = AddI(scale_l, scale_r) + // which is equivalent to the long addition if the long addition stays + // within (min_jint, max_jint]. jlong scale_sum = java_add(scale_l, scale_r); - if (scale_sum > max_signed_integer(exp_bt) || scale_sum <= min_signed_integer(exp_bt)) { + if (scale_sum > max_signed_integer(bt) || scale_sum <= min_signed_integer(bt)) { // This logic is shared by int and long. For int, the result may overflow // as we use jlong to compute so do the check here. Long result may also // overflow but that's fine because result wraps. @@ -2718,41 +2766,76 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc if (p_scale != nullptr) { *p_scale = scale_sum; } - if (p_short_scale != nullptr) { - *p_short_scale = short_scale_l && short_scale_r; - } return true; } - } else if (opc == Op_Sub(exp_bt)) { - if (exp->in(1)->find_integer_as_long(exp_bt, -1) == 0) { - jlong scale = 0; - if (depth == 0 && is_scaled_iv(exp->in(2), iv, exp_bt, &scale, p_short_scale, depth + 1)) { - // SubX(0, iv*K) => iv*(-K) - if (scale == min_signed_integer(exp_bt)) { + } else if (opc == Op_Sub(bt)) { + if (exp->in(1)->find_integer_as_long(bt, -1) == 0) { + jlong scale_r = 0; + if (depth == 0 && is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) { + // We know that: + // SubI(0, MulI(x, scale_r)) = MulI(x, scale=SubI(0, scale_r)) where scale is int + // SubL(0, MulL(x, scale_r)) = MulL(x, scale=SubL(0, scale_r)) + // + // if bt = T_INT (implies iv is int): + // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int + // exp = SubI(0, e2) = MulI(iv, scale=SubI(0, scale_r)) where scale is int + // if bt = T_LONG and iv is long: + // e2 = exp->in(2) = MulL(iv, scale_r) + // exp = SubL(0, e2) = MulL(iv, scale=SubL(0, scale_r)) + // if bt = T_LONG and iv is int: + // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r) + // exp = SubL(0, e2) = MulL(ConvI2L(iv), scale=SubL(0, scale_r)) + // + // Again, we could have used bt-sensitive computations as for LShift. + // Instead, we compute in all cases + // scale = SubL(0, scale_r) + // How is this correct for bt=T_INT? + // We know that the input scale is in the int range. + // And so as long as the input scale != min_jint, the + // computation in long is equivalent, since neither the int nor + // the long computation would overflow. + if (scale_r == min_signed_integer(bt)) { // This should work even if -K overflows, but let's not. return false; } - scale = java_multiply(scale, (jlong)-1); + jlong scale_neg = java_multiply(scale_r, (jlong)-1); if (p_scale != nullptr) { - *p_scale = scale; - } - if (p_short_scale != nullptr) { - // (ConvI2L (MulI iv K)) can be 64-bit linear if iv is kept small enough... - *p_short_scale = *p_short_scale || (exp_bt != bt && scale != 1); + *p_scale = scale_neg; } return true; } } else { jlong scale_l = 0; jlong scale_r = 0; - bool short_scale_l = false; - bool short_scale_r = false; if (depth == 0 && - is_scaled_iv(exp->in(1), iv, exp_bt, &scale_l, &short_scale_l, depth + 1) && - is_scaled_iv(exp->in(2), iv, exp_bt, &scale_r, &short_scale_r, depth + 1)) { - // SubX(iv*K1, iv*K2) => iv*(K1-K2) + is_scaled_iv(exp->in(1), iv, bt, &scale_l, depth + 1) && + is_scaled_iv(exp->in(2), iv, bt, &scale_r, depth + 1)) { + // We know that: + // SubI(MulI(x, scale_l), MulI(x, scale_r)) = MulI(x, scale=SubI(scale_l, scale_r)) where scale is int + // SubL(MulL(x, scale_l), MulL(x, scale_r)) = MulL(x, scale=SubL(scale_l, scale_r)) + // + // if bt = T_INT (implies iv is int): + // e1 = exp->in(1) = MulI(iv, scale_l) where scale_l is int + // e2 = exp->in(2) = MulI(iv, scale_r) where scale_r is int + // exp = SubI(e1, e2) = MulI(iv, scale=SubI(scale_l, scale_r)) where scale is int + // if bt = T_LONG and iv is long: + // e1 = exp->in(1) = MulL(iv, scale_l) + // e2 = exp->in(2) = MulL(iv, scale_r) + // exp = SubL(e1, e2) = MulL(iv, scale=SubL(scale_l, scale_r)) + // if bt = T_LONG and iv is int: + // e1 = exp->in(1) = MulL(ConvI2L(iv), scale_l) + // e2 = exp->in(2) = MulL(ConvI2L(iv), scale_r) + // exp = SubL(e1, e2) = MulL(ConvI2L(iv), scale=SubL(scale_l, scale_r)) + // + // For the scale computation, we could have used the same type case + // distinction as in LShift above. But instead, we compute in long for + // all cases. We need to show that this is OK if bt=T_INT. + // We would have computed + // scale = SubI(scale_l, scale_r) + // which is equivalent to the long subtraction if the long subtraction + // stays within (min_jint, max_jint]. jlong scale_diff = java_subtract(scale_l, scale_r); - if (scale_diff > max_signed_integer(exp_bt) || scale_diff <= min_signed_integer(exp_bt)) { + if (scale_diff > max_signed_integer(bt) || scale_diff <= min_signed_integer(bt)) { // This logic is shared by int and long. For int, the result may // overflow as we use jlong to compute so do the check here. Long // result may also overflow but that's fine because result wraps. @@ -2761,9 +2844,6 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc if (p_scale != nullptr) { *p_scale = scale_diff; } - if (p_short_scale != nullptr) { - *p_short_scale = short_scale_l && short_scale_r; - } return true; } } @@ -2773,27 +2853,75 @@ bool PhaseIdealLoop::is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_sc } //-------------------------is_scaled_iv_plus_offset-------------------------- -// Return true if exp is a simple linear transform of the given induction var. +// Return true if exp is a simple affine transform of the given induction var. // The scale must be constant and the addition tree (if any) must be simple. -// This grammar of cases is recognized, where X is I|L according to bt: +// On success, we guarantee this form: +// if bt = T_INT (implies iv is int): +// exp = AddI(MulI( iv, p_scale), p_offset) where p_scale is int +// if bt = T_LONG and iv is long: +// exp = AddL(MulL( iv, p_scale), p_offset) +// if bt = T_LONG and iv is int: +// exp = AddL(MulL(ConvI2L(iv), p_scale), p_offset) +// where: +// - the constant scale value is stored back to *p_scale unless null, and +// - the offset (perhaps a synthetic AddX or SubX node) is stored to *p_offset unless null. +// To avoid looping, the match is depth-limited. +// +// This grammar of cases is recognized, where X is I (int) or L (long) according +// to bt. CastXX operations are also accepted, but omitted for conciseness: +// +// is_iv(): +// +// IV[iv,I] = iv, if iv is int +// IV[iv,L] = iv, if iv is long +// | ConvI2L(iv), if iv is int +// +// is_scaled_iv(): +// +// SIV0[iv,X] = IV[iv,X] +// | MulX(IV[iv,X], ConX), if get_con(ConX) != 0 (ConX is top or MulX is non canonical) +// | MulX(ConX, IV[iv,X]), if get_con(ConX) != 0 (ConX is top or MulX is non canonical) +// | LShiftX(IV[iv,X], ConI), if get_con(ConI) != min_jint (ConI is top or LShiftX is non canonical) // -// OIV[iv] = SIV[iv] | (CastXX OIV[iv]) -// | (AddX SIV[iv] E) | (AddX E SIV[iv]) -// | (SubX SIV[iv] E) | (SubX E SIV[iv]) -// SSIV[iv] = (ConvI2X SIV[iv]) -- a "short scale" might occur here -// SIV[iv] = [a possibly scaled value of iv; see is_scaled_iv() above] +// SIV[iv,X] = SIV0[iv,X] +// | e:AddX(SIV0[iv,X], SIV0[iv,X]), if min_jX < scale(e) <= max_jX +// | SubX(0, e:SIV0[iv,X]), if scale(e) != min_jX +// | e:SubX(SIV0[iv,X], SIV0[iv,X]), if min_jX < scale(e) <= max_jX // -// On success, the constant scale value is stored back to *p_scale unless null. -// Likewise, the addend (perhaps a synthetic AddX node) is stored to *p_offset. -// Also, (*p_short_scale) reports if a ConvI2L conversion was seen after a MulI, -// meaning bt is T_LONG but iv was scaled using 32-bit arithmetic. -// To avoid looping, the match is depth-limited, and so may fail to match the grammar to complex expressions. -bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, bool* p_short_scale, int depth) { +// is_scaled_iv_plus_offset(): +// +// OSIV0[iv,X] = SIV[iv,X] +// | AddX(SIV[iv,X], e) +// | AddX(e, SIV[iv,X]) +// | SubX(SIV[iv,X], e) +// | SubX(e1, e2:SIV[iv,X]), if scale(e2) != min_jX +// +// OSIV1[iv,X] = OSIV0[iv,X] +// | AddX(OSIV0[iv,X], ConX) +// | AddX(ConX, OSIV0[iv,X]) +// +// OSIV[iv,X] = OSIV1[iv,X] +// | AddX(OSIV1[iv,X], ConX) +// | AddX(ConX, OSIV1[iv,X]) +// +// where scale(IV[iv,X]) = 1L +// scale(MulX(_, ConX)) = get_con(ConX) +// scale(MulX(ConX, _)) = get_con(ConX) +// scale(LShiftX(_, ConI)) = java_shift_left((jX)1, get_con(ConI)) +// scale(AddX(e1:SIV0, e2:SIV0)) = java_add(scale(e1), scale(e2)) +// scale(SubX(0, e:SIV0)) = java_multiply(scale(e), -1L) +// scale(SubX(e1:SIV0, e2:SIV0)) = java_subtract(scale(e1), scale(e2)) +bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, int depth) { assert(bt == T_INT || bt == T_LONG, "unexpected int type"); jlong scale = 0; // to catch result from is_scaled_iv() - BasicType exp_bt = bt; exp = exp->uncast(); - if (is_scaled_iv(exp, iv, exp_bt, &scale, p_short_scale)) { + if (is_scaled_iv(exp, iv, bt, &scale)) { + // if bt = T_INT (implies iv is int): + // exp = MulI( iv, scale) = AddI(MulI( iv, scale), offset=0) where scale is int + // if bt = T_LONG and iv is long: + // exp = MulL( iv, scale) = AddL(MulL( iv, scale), offset=0) + // if bt = T_LONG and iv is int: + // exp = MulL(ConvI2L(iv), scale) = AddL(MulL(ConvI2L(iv), scale), offset=0) if (p_scale != nullptr) { *p_scale = scale; } @@ -2803,58 +2931,109 @@ bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, } return true; } - if (exp_bt != bt) { - // We would now be matching inputs like (ConvI2L exp:(AddI (MulI iv S) E)). - // It's hard to make 32-bit arithmetic linear if it overflows. Although we do - // cope with overflowing multiplication by S, it would be even more work to - // handle overflowing addition of E. So we bail out here on ConvI2L input. - return false; - } int opc = exp->Opcode(); - int which = 0; // this is which subexpression we find the iv in - Node* offset = nullptr; - if (opc == Op_Add(exp_bt)) { - // Check for a scaled IV in (AddX (MulX iv S) E) or (AddX E (MulX iv S)). - if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale, p_short_scale) || - is_scaled_iv(exp->in(which = 2), iv, bt, &scale, p_short_scale)) { - offset = exp->in(which == 1 ? 2 : 1); // the other argument + int which = 0; // Input index of the subexpression we find the IV in. + Node* offset_other = nullptr; // Offset subexpression (the other input). + if (opc == Op_Add(bt)) { + jlong scale_which = 0; // Scale of the IV subexpression. + // Check for a scaled IV in AddX(MulX(iv, S), E) or AddX(E, MulX(iv, S)). + if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale_which) || + is_scaled_iv(exp->in(which = 2), iv, bt, &scale_which)) { + offset_other = exp->in(which == 1 ? 2 : 1); // The other argument. + // if bt = T_INT (implies iv is int): + // exp->in(which) = MulI( iv, scale_which) where scale_which is int + // -> which = 1: exp = AddI(MulI( iv, scale_which), offset_other) where scale_which is int + // -> which = 2: exp = AddI(offset_other, MulI( iv, scale_which)) = AddI(MulI( iv, scale_which), offset_other) where scale_which is int + // if bt = T_LONG and iv is long: + // exp->in(which) = MulL( iv, scale_which) + // -> which = 1: exp = AddL(MulL( iv, scale_which), offset_other) + // -> which = 2: exp = AddL(offset_other, MulL( iv, scale_which)) = AddL(MulL( iv, scale_which), offset_other) + // if bt = T_LONG and iv is int: + // exp->in(which) = MulL(ConvI2L(iv), scale_which) + // -> which = 1: exp = AddL(MulL(ConvI2L(iv), scale_which), offset_other) + // -> which = 2: exp = AddL(offset_other, MulL(ConvI2L(iv), scale_which)) = AddL(MulL(ConvI2L(iv), scale_which), offset_other) if (p_scale != nullptr) { - *p_scale = scale; + *p_scale = scale_which; } if (p_offset != nullptr) { - *p_offset = offset; + *p_offset = offset_other; } return true; } - // Check for more addends, like (AddX (AddX (MulX iv S) E1) E2), etc. - if (is_scaled_iv_plus_extra_offset(exp->in(1), exp->in(2), iv, bt, p_scale, p_offset, p_short_scale, depth) || - is_scaled_iv_plus_extra_offset(exp->in(2), exp->in(1), iv, bt, p_scale, p_offset, p_short_scale, depth)) { + // Check for more addends, like AddX(AddX(MulX(iv, S), E1), E2), etc. + if (is_scaled_iv_plus_extra_offset(exp->in(1), exp->in(2), iv, bt, p_scale, p_offset, depth) || + is_scaled_iv_plus_extra_offset(exp->in(2), exp->in(1), iv, bt, p_scale, p_offset, depth)) { + // See proof in is_scaled_iv_plus_extra_offset. return true; } - } else if (opc == Op_Sub(exp_bt)) { - if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale, p_short_scale) || - is_scaled_iv(exp->in(which = 2), iv, bt, &scale, p_short_scale)) { - // Match (SubX SIV[iv] E) as if (AddX SIV[iv] (SubX 0 E)), and - // match (SubX E SIV[iv]) as if (AddX E (SubX 0 SIV[iv])). - offset = exp->in(which == 1 ? 2 : 1); // the other argument + } else if (opc == Op_Sub(bt)) { + jlong scale_which = 0; // Scale of the IV subexpression. + if (is_scaled_iv(exp->in(which = 1), iv, bt, &scale_which) || + is_scaled_iv(exp->in(which = 2), iv, bt, &scale_which)) { + offset_other = exp->in(which == 1 ? 2 : 1); // The other argument. + // if bt = T_INT (implies iv is int): + // exp->in(which) = MulI(iv, scale_which) where scale_which is int + // if which=1: + // exp = SubI(MulI( iv, scale_which), offset_other ) + // = AddI(MulI( iv, scale_which), offset=SubI(0, offset_other)) + // if which=2: + // exp = SubI(offset_other, MulI( iv, scale_which )) + // = AddI(offset_other, MulI( iv, scale=SubI(0, scale_which))) + // = AddI(MulI( iv, scale=SubI(0, scale_which)), offset_other) + // + // if bt = T_LONG and iv is long: + // exp->in(which) = MulL(iv, scale_which) + // if which=1: + // exp = SubL(MulL( iv, scale_which), offset_other ) + // = AddL(MulL( iv, scale_which), offset=SubL(0, offset_other)) + // if which=2: + // exp = SubL(offset_other, MulL( iv, scale_which )) + // = AddL(offset_other, MulL( iv, scale=SubL(0, scale_which))) + // = AddL(MulL( iv, scale=SubL(0, scale_which)), offset_other) + // + // if bt = T_LONG and iv is int: + // exp->in(which) = MulL(ConvI2L(iv), scale_which) + // if which=1: + // exp = SubL(MulL(ConvI2L(iv), scale_which), offset_other ) + // = AddL(MulL(ConvI2L(iv), scale_which), offset=SubL(0, offset_other)) + // if which=2: + // exp = SubL(offset_other, MulL(ConvI2L(iv), scale_which )) + // = AddL(offset_other, MulL(ConvI2L(iv), scale=SubL(0, scale_which))) + // = AddL(MulL(ConvI2L(iv), scale=SubL(0, scale_which)), offset_other) + // + // Match SubX(SIV[iv], E) as if AddX(SIV[iv], SubX(0, E)), and + // match SubX(E, SIV[iv]) as if AddX(E, SubX(0, SIV[iv])). + // + // Let's compute the scale: + // which=1: scale= scale_which + // which=2: scale=SubX(0, scale_which) + // We could have used the bt case distinction for SubX(0, scale_which), + // but instead we compute it in long for both cases. For int, this + // can only lead to a wrong result (i.e. go out of int range) for + // min_jint, so we exclude that case. + jlong scale_maybe_neg = scale_which; if (which == 2) { // We can't handle a scale of min_jint (or min_jlong) here as -1 * min_jint = min_jint - if (scale == min_signed_integer(bt)) { + if (scale_which == min_signed_integer(bt)) { return false; // cannot negate the scale of the iv } - scale = java_multiply(scale, (jlong)-1); + // scale = SubL(0, scale_which) = MulL(scale_which, -1) + scale_maybe_neg = java_multiply(scale_which, (jlong)-1); } if (p_scale != nullptr) { - *p_scale = scale; + *p_scale = scale_maybe_neg; } + // Now let's compute the offset: + // which=1: offset=SubX(0, offset_other) + // which=2: offset= offset_other if (p_offset != nullptr) { + Node* offset_maybe_neg = offset_other; if (which == 1) { // must negate the extracted offset - Node* zero = integercon(0, exp_bt); - Node *ctrl_off = get_ctrl(offset); - offset = SubNode::make(zero, offset, exp_bt); - register_new_node(offset, ctrl_off); + Node* zero = integercon(0, bt); + offset_maybe_neg = SubNode::make(zero, offset_other, bt); + register_new_node(offset_maybe_neg, get_ctrl(offset_other)); } - *p_offset = offset; + *p_offset = offset_maybe_neg; } return true; } @@ -2863,26 +3042,54 @@ bool PhaseIdealLoop::is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, } // Helper for is_scaled_iv_plus_offset(), not called separately. -// The caller encountered (AddX exp1 offset3) or (AddX offset3 exp1). -// Here, exp1 is inspected to see if it is a simple linear transform of iv. -// If so, the offset3 is combined with any other offset2 from inside exp1. +// The caller encountered AddX(exp1, offset3) or AddX(offset3, exp1). +// Here, exp1 is inspected to see if it is a simple affine transform of iv. +// If so, the (constant) offset3 is combined with any other offset2 from inside exp1. +// +// We assume the input form is equivalent to: +// if bt = T_INT: +// exp = AddI(exp1, offset3) +// if bt = T_LONG: +// exp = AddL(exp1, offset3) +// +// On success, we guarantee this form: +// if bt = T_INT (implies iv is int): +// exp = AddI(MulI( iv, p_scale), p_offset) where p_scale is int +// if bt = T_LONG and iv is long: +// exp = AddL(MulL( iv, p_scale), p_offset) +// if bt = T_LONG and iv is int: +// exp = AddL(MulL(ConvI2L(iv), p_scale), p_offset) +// bool PhaseIdealLoop::is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset3, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, - bool* p_short_scale, int depth) { + int depth) { // By the time we reach here, it is unlikely that exp1 is a simple iv*K. - // If is a linear iv transform, it is probably an add or subtract. + // If it is an affine iv transform, it is probably an add or subtract. // Let's collect the internal offset2 from it. Node* offset2 = nullptr; if (offset3->is_Con() && depth < 2 && is_scaled_iv_plus_offset(exp1, iv, bt, p_scale, - &offset2, p_short_scale, depth+1)) { + &offset2, depth+1)) { + // We know that: + // AddI(AddI(x, offset2), offset3) = AddI(x, offset=AddI(offset2, offset3)) + // AddL(AddL(x, offset2), offset3) = AddL(x, offset=AddL(offset2, offset3)) + // + // if bt = T_INT (implies iv is int): + // exp1 = AddI(MulI( iv, p_scale), offset2) where p_scale is int + // -> exp = AddI(AddI(MulI( iv, p_scale), offset2), offset3) = AddI(MulI( iv, p_scale), offset=AddI(offset2, offset3)) + // if bt = T_LONG and iv is long: + // exp1 = AddL(MulL( iv, p_scale), offset2) + // -> exp = AddL(AddL(MulL( iv, p_scale), offset2), offset3) = AddL(MulL( iv, p_scale), offset=AddL(offset2, offset3)) + // if bt = T_LONG and iv is int: + // exp1 = AddL(MulL(ConvI2L(iv), p_scale), offset2) + // -> exp = AddL(AddL(MulL(ConvI2L(iv), p_scale), offset2), offset3) = AddL(MulL(ConvI2L(iv), p_scale), offset=AddL(offset2, offset3)) + // if (p_offset != nullptr) { - Node* ctrl_off2 = get_ctrl(offset2); - Node* offset = AddNode::make(offset2, offset3, bt); - register_new_node(offset, ctrl_off2); - *p_offset = offset; + Node* offset_sum = AddNode::make(offset2, offset3, bt); + register_new_node(offset_sum, get_ctrl(offset2)); + *p_offset = offset_sum; } return true; } @@ -4030,7 +4237,7 @@ bool PhaseIdealLoop::match_fill_loop(IdealLoopTree* lpt, Node*& store, Node*& st // Make sure there is an appropriate fill routine BasicType t = msg == nullptr ? - store->adr_type()->isa_aryptr()->elem()->array_element_basic_type() : T_VOID; + store->out_adr_type()->isa_aryptr()->elem()->array_element_basic_type() : T_VOID; const char* fill_name; if (msg == nullptr && StubRoutines::select_fill_function(t, false, fill_name) == nullptr) { @@ -4277,7 +4484,7 @@ bool PhaseIdealLoop::intrinsify_fill(IdealLoopTree* lpt) { #endif } - BasicType t = store->adr_type()->isa_aryptr()->elem()->array_element_basic_type(); + BasicType t = store->out_adr_type()->isa_aryptr()->elem()->array_element_basic_type(); bool aligned = false; if (offset != nullptr && head->init_trip()->is_Con()) { int element_size = type2aelembytes(t); @@ -4302,8 +4509,9 @@ bool PhaseIdealLoop::intrinsify_fill(IdealLoopTree* lpt) { Node* result_ctrl; Node* result_mem; const TypeFunc* call_type = OptoRuntime::array_fill_Type(); - CallLeafNode *call = new CallLeafNoFPNode(call_type, fill, - fill_name, TypeAryPtr::get_array_body_type(t)); + const TypePtr* adr_type = TypeAryPtr::get_array_body_type(t); + assert(adr_type == mem_phi->out_adr_type(), "incorrect adr_type"); + CallLeafNode* call = new CallLeafNoFPNode(call_type, fill, fill_name, adr_type, adr_type); uint cnt = 0; call->init_req(TypeFunc::Parms + cnt++, from); call->init_req(TypeFunc::Parms + cnt++, store_value); diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 6fc07d83a923..b524b76a21d8 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -548,7 +548,7 @@ void PhaseIdealLoop::add_parse_predicate(Deoptimization::DeoptReason reason, Nod address call_addr = OptoRuntime::uncommon_trap_blob()->entry_point(); const TypePtr* no_memory_effects = nullptr; CallNode* unc = new CallStaticJavaNode(OptoRuntime::uncommon_trap_Type(), call_addr, "uncommon_trap", - no_memory_effects); + no_memory_effects, TypePtr::BOTTOM); Node* mem = nullptr; Node* i_o = nullptr; @@ -599,7 +599,7 @@ static bool no_side_effect_since_safepoint(Compile* C, const Node* head, const N Node* u = head->fast_out(i); if (u->is_memory_phi()) { Node* m = u->in(LoopNode::LoopBackControl); - if (u->adr_type() == TypePtr::BOTTOM) { + if (u->out_adr_type() == TypePtr::BOTTOM) { if (m->is_MergeMem() && mem->is_MergeMem()) { if (m != mem DEBUG_ONLY(|| true)) { // MergeMemStream can modify m, for example to adjust the length to mem. @@ -629,11 +629,11 @@ static bool no_side_effect_since_safepoint(Compile* C, const Node* head, const N } } else { if (mem->is_MergeMem()) { - if (m != mem->as_MergeMem()->memory_at(C->get_alias_index(u->adr_type()))) { + if (m != mem->as_MergeMem()->memory_at(C->get_alias_index(u->out_adr_type()))) { return false; } #ifdef ASSERT - mm->set_memory_at(C->get_alias_index(u->adr_type()), mem->as_MergeMem()->base_memory()); + mm->set_memory_at(C->get_alias_index(u->out_adr_type()), mem->as_MergeMem()->base_memory()); #endif } else { if (m != mem) { @@ -1356,11 +1356,16 @@ bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint strid return true; } +// This method extracts long range checks and computes the reduced iteration count (reduced_iters_limit) such that all +// extracted long range checks can be transformed. In particular, we check that +// - reduced_iters_limit * ABS(scale) <= original_iters_limit < max_jint +// - we have the proper form "scale*iv + offset _body.size(); i++) { Node* c = loop->_body.at(i); if (c->is_IfProj() && c->in(0)->is_RangeCheck()) { @@ -1375,6 +1380,7 @@ int PhaseIdealLoop::extract_long_range_checks(const IdealLoopTree* loop, jint st scale != min_jlong && original_iters_limit / ABS(scale) >= min_iters * ABS(stride_con)) { assert(scale == (jint)scale, "scale should be an int"); + // Ensure: reduced_iters_limit * ABS(scale) <= original_iters_limit < max_jint reduced_iters_limit = MIN2(reduced_iters_limit, original_iters_limit/ABS(scale)); range_checks.push(c); } @@ -1385,138 +1391,1056 @@ int PhaseIdealLoop::extract_long_range_checks(const IdealLoopTree* loop, jint st return checked_cast(reduced_iters_limit); } -// One execution of the inner loop covers a sub-range of the entire iteration range of the loop: [A,Z), aka [A=init, -// Z=limit). If the loop has at least one trip (which is the case here), the iteration variable i always takes A as its -// first value, followed by A+S (S is the stride), next A+2S, etc. The limit is exclusive, so that the final value B of -// i is never Z. It will be B=Z-1 if S=1, or B=Z+1 if S=-1. - -// If |S|>1 the formula for the last value B would require a floor operation, specifically B=floor((Z-sgn(S)-A)/S)*S+A, -// which is B=Z-sgn(S)U for some U in [1,|S|]. So when S>0, i ranges as i:[A,Z) or i:[A,B=Z-U], or else (in reverse) -// as i:(Z,A] or i:[B=Z+U,A]. It will become important to reason about this inclusive range [A,B] or [B,A]. - -// Within the loop there may be many range checks. Each such range check (R.C.) is of the form 0 <= i*K+L < R, where K -// is a scale factor applied to the loop iteration variable i, and L is some offset; K, L, and R are loop-invariant. -// Because R is never negative (see below), this check can always be simplified to an unsigned check i*K+L 0), where the limit is -// chosen to prevent various cases of 32-bit overflow (including multiplications j*K below). In the sub-loop the -// logical value i is offset from j by a 64-bit constant C, so i ranges in i:C+[0,Z_2). - -// For S<0, j ranges (in reverse!) through j:[-|B_2|,0] or (-|Z_2|,0]. For either sign of S, we can say i=j+C and j -// ranges through 32-bit ranges [A_2,B_2] or [B_2,A_2] (A_2=0 of course). - -// The disjoint union of all the C+[A_2,B_2] ranges from the sub-loops must be identical to the whole range [A,B]. -// Assuming S>0, the first C must be A itself, and the next C value is the previous C+B_2, plus S. If |S|=1, the next -// C value is also the previous C+Z_2. In each sub-loop, j counts from j=A_2=0 and i counts from C+0 and exits at -// j=B_2 (i=C+B_2), just before it gets to i=C+Z_2. Both i and j count up (from C and 0) if S>0; otherwise they count -// down (from C and 0 again). - -// Returning to range checks, we see that each i*K+L 0 and K>0), Q_min=A_2*K+Q and Q_max=Q (if S<0 and K>0), -// Q_min=B_2*K+Q and Q_max=Q if (S>0 and K<0), Q_min=Q and Q_max=A_2*K+Q (if S<0 and K<0) - -// Note that the first R.C. value is always Q=(S*K>0 ? Q_min : Q_max). Also Q_{min,max} = Q + {min,max}(A_2*K,B_2*K). -// If S*K>0 then, as the loop iterations progress, each R.C. value i*K+L = j*K+Q goes up from Q=Q_min towards Q_max. -// If S*K<0 then j*K+Q starts at Q=Q_max and goes down towards Q_min. - -// Case A: Some Negatives (but no overflow). -// Number line: -// |s64_min . . . 0 . . . s64_max| -// | . Q_min..Q_max . 0 . . . . | s64 negative -// | . . . . R=0 R< R< R< R< | (against R values) -// | . . . Q_min..0..Q_max . . . | small mixed -// | . . . . R R R< R< R< | (against R values) -// -// R values which are out of range (>Q_max+1) are reduced to max(0,Q_max+1). They are marked on the number line as R<. -// -// So, if Q_min 0 (R.C.E. steps upward) -// j*K + s32_trunc(Q_max) R R R< R< | (against R values) -// | . . . . 0 . Q_min..Q_max . | s64 positive -// | . . . . R> R> R R R< | (against R values) -// -// R values which are out of range (Q_max+1) are reduced as marked: R> up to Q_min, R< down to Q_max+1. -// Then the whole comparison is shifted left by Q_min, so it can take place at zero, which is a nice 32-bit value. -// -// So, if both Q_min, Q_max+1 >=s64 0, then use this test: -// j*K + 0 0 -// More generally: -// j*K + Q - Q_min R> R> R> R | (against R values) -// -// In this case, Q_min >s64 Q_max+1, even though the mathematical values of Q_min and Q_max+1 are correctly ordered. -// The formulas from the previous case can be used, except that the bad upper bound Q_max is replaced by max_jlong. -// (In fact, we could use any replacement bound from R to max_jlong inclusive, as the input to the clamp function.) -// -// So if Q_min >=s64 0 but Q_max+1 0 -// More generally: -// j*K + Q - Q_min H, it returns L not H. -// -// All of the formulas above can be merged into a single one: -// L_clamp = Q_min < 0 ? 0 : Q_min --whether and how far to left-shift -// H_clamp = Q_max+1 < Q_min ? max_jlong : Q_max+1 -// = Q_max+1 < 0 && Q_min >= 0 ? max_jlong : Q_max+1 -// Q_first = Q = (S*K>0 ? Q_min : Q_max) = (C*K+L) -// R_clamp = clamp(R, L_clamp, H_clamp) --reduced dynamic range -// replacement R.C.: -// j*K + Q_first - L_clamp = min_iters * ABS(stride_con) (KI-1) +// where +// K = scale +// min_iters = 2 +// 0 < original_iters_limit < max_jint +// ABS(stride_con) > 0 +// From (KI-1) +// min_iters * ABS(stride_con) = 2 * ABS(stride_con) <= original_iters_limit / ABS(K) +// then +// 2 * ABS(stride_con) * ABS(K) <= original_iters_limit +// and +// 0 <= ABS(K) <= original_iters_limit < max_jint +// +// It follows that K is indeed an int32_t (KI). +// +// Condition (R0) +// -------------- +// In PhaseIdealLoop::extract_long_range_check(), we check IdealLoopTree::is_range_check_if() which guarantees that +// R >= 0 +// because R is either a LoadRange or an integer that is non-negative (R0). +// +// Conditions (P-lo), (P-hi), (Q-lo), (Q-hi), and (Q-lo-hi) +// -------------------------------------------------------- +// j is to be the induction variable of an int counted loop (i.e. the inner iv). +// From PhaseIdealLoop::extract_long_range_check() and K = scale, we reduce the iterations so that: +// reduced_iters_limit * ABS(K) <= original_iters_limit < max_jint (Q1) +// +// By the fundamentals of an int counted loop, we have: +// j_min, j_max being int32_t values +// such that +// SI(j_min) <= SI(j) <= SI(j_max) (Q2) +// +// Furthermore, due to how we construct the int counted loop, we also have +// 0 <= SI(j_max) - SI(j_min) <= reduced_iters_limit (Q3) +// +// This means that: +// +// For K > 0, we multiply K to (Q2) +// SI(j_min) * K <= SI(j) * K <= SI(j_max) * K +// and to (Q3) +// 0 <= SI(j_max) * K - SI(j_min) * K <= reduced_iters_limit * K +// < max_jint (apply (Q1)) +// and we get +// 0 < SI(j_max) * K - SI(j_min) * K + 1 <= max_jint (Q4) +// +// For K < 0, we multiply K to (Q2) +// SI(j_min) * K >= SI(j) * K >= SI(j_max) * K +// and to (Q3) +// 0 <= SI(j_min) * K - SI(j_max) * K <= reduced_iters_limit * -K +// < max_jint (apply (Q1)) +// and we get +// 0 < SI(j_min) * K - SI(j_max) * K + 1 <= max_jint (Q5) +// +// Since the multiplication of int32_t values must be an int64_t value, we can choose: +// (P-lo): P_lo such that SL(P_lo) is the smallest value of "SI(j) * K" +// (P-hi): P_hi such that SL(P_hi) is the largest value of "SI(j) * K" plus 1. +// +// This means that: +// +// For K > 0, we have: +// SL(P_lo) = SI(j_min) * K +// SL(P_hi) = SI(j_max) * K + 1 +// which implies +// SL(P_lo) = SI(j_min) * K <= SI(j) * K <= SI(j_max) * K + 1 = SL(P_hi) +// Starting from (Q4) +// 0 < SI(j_max) * K - SI(j_min) * K + 1 <= max_jint +// we get +// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q6) +// +// For K < 0, we have: +// SL(P_lo) = SI(j_max) * K +// SL(P_hi) = SI(j_min) * K + 1 +// which implies +// SL(P_lo) = SI(j_max) * K <= SI(j) * K <= SI(j_min) * K + 1 = SL(P_hi) +// Starting from (Q5) +// 0 < SI(j_min) * K - SI(j_max) * K + 1 <= max_jint +// we get +// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q7) +// +// As a result, from (Q6) and (Q7), we see that +// 0 < SL(P_hi) - SL(P_lo) <= max_jint (Q8) +// +// From (P-lo) +// SL(P_lo) <= SI(j) * K +// and (P-hi) +// SL(P_hi) >= SI(j) * K + 1 +// and (Q8) follows +// 0 <= SI(j) * K - SL(P_lo) +// < SL(P_hi) - SL(P_lo) +// <= max_jint (Q9) +// +// Since SI(j) and K are int32_t values, their multiplication +// SI(j) * K +// cannot overflow the int64_t range and therefore +// SI(j) * K = SL(MulL(ConvI2L(j), K)) (Q10) +// +// Starting from (Q9) +// 0 <= SI(j) * K - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint +// it follows with (Q10) +// 0 <= SL(MulL(ConvI2L(j), K)) - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint (Q11) +// +// From (Q9) +// 0 <= SL(P_hi) - SL(P_lo) <= max_jint +// it follows that SubL(P_hi, P_lo) does not overflow the signed long domain: +// SL(SubL(P_hi, P_lo)) = SL(P_hi) - SL(P_lo) (Q12) +// and from (Q11) +// SubL(MulL(ConvI2L(j), K), P_lo) +// does not overflow the signed long domain: +// SL(SubL(MulL(ConvI2L(j), K), P_lo) = SL(MulL(ConvI2L(j), K)) - SL(P_lo) (Q13) +// +// From (Q11) +// 0 <= SL(MulL(ConvI2L(j), K)) - SL(P_lo) < SL(P_hi) - SL(P_lo) <= max_jint +// we apply (Q13) +// 0 <= SL(SubL(MulL(ConvI2L(j), K), P_lo)) < SL(P_hi) - SL(P_lo) <= max_jint +// and apply (Q12) +// 0 <= SubL(MulL(ConvI2L(j), K), P_lo) < SubL(P_hi, P_lo) <= max_jint (Q14) +// +// Note that we can do a simple reassociation of SubL if x, y, and z are int64_t: +// SubL(x, y) = SubL(AddL(x, z), AddL(y, z)) (Q15) +// We apply (Q15) to both sides of (Q14) +// 0 <= SubL( MulL(ConvI2L(j), K), P_lo)) < SubL( P_hi, P_lo) <= max_jint +// and get +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), AddL(P_lo, Q)) < SubL(AddL(P_hi, Q), AddL(P_lo, Q)) <= max_jint (Q16) +// +// We can insert +// Q_lo = AddL(P_lo, Q) (Q-lo) +// and +// Q_hi = AddL(P_hi, Q) (Q-hi) +// in (Q16) and get (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo ) < SubL(Q_hi, Q_lo) <= max_jint +// +// Condition (LHS) +// --------------- +// We ensure with +// PhaseIdealLoop::extract_long_range_check() -> PhaseIdealLoop::is_range_check_if() +// that we indeed have the form +// AddL(MulL(ConvI2L(j), K), Q) (done with PhaseIdealLoop::is_scaled_iv_plus_offset()) +// for the long range check with R (R0): +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// +// +// We have our conditions +// (KI): K is an int32_t constant +// (P-lo): P_lo: The smallest value of "j * K" (i.e. the smallest value of the inner scaled iv), an int64_t value +// (P-hi): P_hi: The largest value of "j * K" (i.e. the largest value of the inner scaled iv) plus 1, an int64_t value +// (Q-lo): Q_lo = AddL(P_lo, Q), an int64_t value +// (Q-hi): Q_hi = AddL(P_hi, Q), an int64_t value +// (R0): R is an int64_t value such that R >= 0 +// (Q-lo-hi): 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// (LHS): LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// and define the additional variables +// (JI): j is the inner iv, an int32_t value (actually further clamped by the possible values of the inner iv) +// (QL): Q is an int64_t value +// (L-clamp): L_clamp = MaxL(Q_lo, 0) +// (H-clamp): H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// (R-clamp): R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// and our target int range check as +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// +// +// Now that we established our conditions and definitions, we prove that +// (LHS < 0) == (RHS < 0) +// which means that we can safely replace the long range check (LHS) with the int range check (RHS). +// +// +// Proof +// ===== +// Before starting with the proof, we start with some definitions and lemmas relevant for the proof. +// +// Definitions +// =========== +// Given x, y being int64_t values, we have: +// (CmpL-SL): CmpL(x, y) = Cmp(SL(x), SL(y)) +// (CmpUL-UL): CmpUL(x, y) = Cmp(UL(x), UL(y)) +// +// Given x, y being int64_t values with x < 0 and y >= 0: +// (CmpUL-gt): CmpUL(x, y) > 0 +// +// Given x being an int32_t value: +// (CmpU-gte-zero): CmpU(x, 0) >= 0 +// +// When the following AddL/SubL do not overflow, then: +// (AddL-SL-no-overflow): SL(AddL(x, y)) = SL(x) + SL(y) +// (SubL-UL-no-overflow): UL(SubL(x, y)) = UL(x) - UL(y) +// +// When x, y < 0, then +// (SubL-no-overflow): SubL(x, y) does not overflow the long signed domain +// +// No-Overflow-Lemmas +// ================== +// SubL-pos-no-overflow +// -------------------- +// When x, y >= 0, both being int64_t values, then +// SubL(x, y) +// does not overflow the long signed domain. +// +// Proof: +// max_jlong >= SL(x) - 0 +// > SL(x) - 1 +// > ... +// > SL(x) - max_jlong +// > min_jlong +// +// We refer to this lemma with (SubL-pos-no-overflow). +// +// AddL-no-overflow +// ---------------- +// When x <= 0 and y >= 0, both being int64_t values, then +// AddL(x, y) +// does not overflow the long signed domain. +// +// Proof: +// min_jlong <= SL(x) + 0 +// < SL(x) + 1 +// < ... +// < SL(x) + max_jlong +// <= max_jlong +// +// We refer to this lemma with (AddL-no-overflow). +// +// Lemma 1 +// ======= +// For x being an int64_t value such that min_jint <= x <= max_jint +// x = ConvI2L(ConvL2I(x)) +// +// Proof: +// If 0 <= x <= max_jint, the upper 33 bits of x are 0s. +// ConvL2I(x) truncates 32 bits, leaving a value with the highest bit being 0. +// ConvI2L(ConvL2I(x)) signed extends this value, filling the 32 upper bits of the result with 0. +// As a result, x and ConvI2L(ConvL2I(x)) have the same 32 lower bits, and their 32 upper bits are all 0, +// which means x == ConvI2L(ConvL2I(x)). +// +// Otherwise, min_jint <= x < 0, the upper 33 bits of x are 1s. +// ConvL2I(x) truncates 32 bits, leaving a value with the highest bit being 1. +// ConvI2L(ConvL2I(x)) signed extends this value, filling the 32 upper bits of the result with 1. +// As a result, x and ConvI2L(ConvL2I(x)) have the same 32 lower bits, and their 32 upper bits are all 1, which means x == ConvI2L(ConvL2I(x)) +// +// Lemma 2 +// ======= +// Fox x being an int32_t value +// x = ConvL2I(ConvI2L(x)) +// +// Proof: +// ConvL2I(ConvI2L(x)) extends x with 32 bits, then truncates those without touching the 32 lower bits. As a result, x == ConvL2I(ConvI2L(x)) +// +// Lemma 3 +// ======= +// For x, y being int32_t values +// CmpU(x, y) = CmpUL(ConvI2L(x), ConvI2L(y)) +// +// Proof: +// This is because +// UL(ConvI2L(0)) < UL(ConvI2L(1)) < UL(ConvI2L(2)) < ... < UL(ConvI2L(max_jint)) +// < UL(ConvI2L(min_jint)) < ... < UL(ConvI2L(-1)) +// is the same ordering as +// UI(0) < UI(1) < UI(2) < ... < UI(max_jint) < UI(min_jint) < ... < UI(-1) +// +// Lemma 4 +// ======= +// For x, y being int64_t values +// ConvL2I(AddL/SubL/MulL(x, y)) == AddI/SubI/MulI(ConvL2I(x), ConvL2I(y)) +// +// Proof: +// UI(ConvL2I(AddL(x, y))) == UL(AddL(x, y)) mod 2^32 (because UI(ConvL2I(v)) == UL(v) mod 2^32) +// == ((UL(x) + UL(y)) mod 2^64) mod 2^32 (because UL(AddL(u, v)) == (UL(u) + UL(v)) mod 2^64) +// == (UL(x) + UL(y)) mod 2^32 (because (v mod 2^64) mod 2^32 == v mod 2^32) +// == ((UL(x) mod 2^32) + (UL(y) mod 2^32)) mod 2^32 (because (u + v) mod 2^32 == ((u mod 2^32) + (v mod 2^32)) mod 2^32) +// == (UI(ConvL2I(x)) + UI(ConvL2I(y))) mod 2^32 (because UI(ConvL2I(v)) == UL(v) mod 2^32) +// == UI(AddI(ConvL2I(x), ConvL2I(y))) (because UI(AddI(u, v)) == (UI(u) + UI(v)) mod 2^32) +// +// Which means ConvL2I(AddL(x, y)) == AddI(ConvL2I(x), ConvL2I(y)) (because UI(u) == UI(v) necessarily implies u == v) +// +// Lemma 5 +// ======= +// For x, y, z being int64_t values such that AddL(x, z) and AddL(y, z) do not under-/overflow the signed long domain. +// CmpL(x, y) == CmpL(AddL(x, z), AddL(y, z)) +// +// Proof: +// This means that: +// CmpL(AddL(x, z), AddL(y, z)) = Cmp(SL(AddL(x, z)), SL(AddL(y, z)) (apply (CmpL-SL)) +// = Cmp(SL(x) + SL(z), SL(y) + SL(z)) (apply (AddL-SL-no-overflow))) +// = Cmp(S(x), S(y)) (because Cmp(u + t, v + t) == Cmp(u, v)) +// = CmpL(x, z) (apply (CmpL-SL)) +// +// Lemma 6 +// ======= +// For x, y, z being int64_t values such that SubL(x, z) and SubL(y, z) do not under-/overflow the unsigned long domain +// CmpUL(x, y) == CmpUL(SubL(x, z), SubL(y, z)) +// +// Proof: +// This means that: +// CmpUL(SubL(x, z), SubL(y, z)) = Cmp(UL(SubL(x, z)), UL(SubL(y, z))) (apply (CmpL-SL)) +// = Cmp(U(x) - U(z), U(y) - U(z)) (apply (SubL-UL-no-overflow)) +// = Cmp(U(x), U(y)) (because Cmp(u - t, v - t) == Cmp(u, v)) +// = CmpUL(x, y) (apply (CmpL-SL)) +// +// Lemma 7 +// ======= +// For x, y, z being int64_t values such that x < y, y >= 0, z >= 0 +// CmpUL(x, z) = CmpUL(x, MinL(y, z)) +// +// Proof: +// If y > z (trivial): +// CmpUL(x, z) = CmpUL(x, MinL(y, z)) +// = CmpUL(x, z) +// +// If y <= z: +// CmpUL(x, z) = CmpUL(x, MinL(y, z)) +// = CmpUL(x, y) +// +// We now show that for all x, the above equation is true. +// +// Case x >= 0: +// From all case assumptions, we have: +// 0 <= x < y <= z +// We can directly convert to unsigned +// 0 <=u x = 0 +// - 4. Q_hi < 0 < Q_lo +// for which we all need to show that +// (LHS < 0) == (RHS < 0) +// +// Case 1 and 3 can actually be improved: +// Case 1: Q_lo, Q_hi <= 0 -> Q_lo < Q_hi <= 0 +// Case 3: Q_lo, Q_hi >= 0 -> 0 <= Q_lo < Q_hi +// +// Proof by Contradiction: +// If Q_lo = Q_hi: +// SubL(Q_hi, Q_lo) = SubL(Q_hi, Q_hi) = 0 +// which violates (Q-lo-hi) +// 0 < SubL(Q_hi, Q_lo) <= max_jint +// +// If Q_lo > Q_hi: +// Case 1: Q_hi < Q_lo <= 0 +// Case 3: 0 <= Q_hi < Q_lo +// then +// min_jlong <= SubL(Q_hi, Q_lo) < 0 +// in both cases which violates (Q-lo-hi) +// +// +// We use the improved cases 1 and 3 in the following when proving +// (LHS < 0) == (RHS < 0) +// for all 4 cases for Q_hi and Q_lo: +// +// +// Case 1: Q_lo < Q_hi <= 0 +// ======================== +// We can simplify the given clamp values: +// L_clamp = MaxL(Q_lo, 0) +// = 0 +// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// = Q_hi +// R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// = MaxL(MinL(R, Q_hi ), 0) +// = MaxL(Q_hi, 0) (because R >= 0, Q_hi < 0) +// = 0 (because Q_hi < 0) +// +// +// From (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) +// together with Q_lo < 0 (Case 1), we see that +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo) +// ---------------- >= 0 ------------------ --- < 0 --- +// and +// AddL(SubL(Q_hi, Q_lo), Q_lo) +// ----- > 0 ----- --- < 0 --- +// both cannot not overflow the signed long domain (1-no-overflow) due to (AddL-no-overflow). +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0 +// with (1-no-overflow), we can apply Lemma 5 and add Q_lo to both sides +// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0 +// cancel out the Q_lo terms +// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0 +// and with (Case 1) see that +// AddL(MulL(ConvI2L(j), K), Q) < Q_hi <= 0 (1a) +// +// From (1a), we know that +// a = AddL(MulL(ConvI2L(j), K), Q) < 0 +// and from (R0) that +// b = R >= 0 +// which satisfies (CmpUL-gt) and we can conclude that +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) > 0 +// ----------- = a ----------- --- = b --- +// +// Inserting the simplified L_clamp and R_clamp from above, we get for RHS +// RHS == CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// == CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, 0))), ConvL2I(SubL(0, 0))) +// == CmpU(AddI(MulI(j, K), ConvL2I( Q )), 0) +// == CmpU(c , 0) +// >= 0 (apply (CmpU-gte-zero)) +// +// +// We showed that +// LHS > 0 +// and +// RHS >= 0 +// and therefore proved that +// (LHS < 0) == (RHS < 0) == false +// +// +// Case 2: Q_lo < 0 < Q_hi +// ======================= +// We can simplify the given clamp values: +// L_clamp = MaxL(Q_lo, 0) +// = 0 +// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// = Q_hi +// R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// = MaxL(MinL(R, Q_hi ), 0) +// = MinL(R, Q_hi) (because R, Q_hi >= 0) +// +// +// Looking at (Case 2): +// Q_hi > 0 > Q_lo +// we have a constrained positive difference +// 0 < SL(Q_hi) - SL(Q_lo) +// <= max_jlong + (-min_jlong) +// = (2 * max_jlong) + 1 +// = 2^64 - 1 +// +// If +// 0 < SL(Q_hi) - SL(Q_lo) <= max_jlong +// then we don't have an overflow of SubL(Q_hi, Q_lo). +// +// If +// max_jlong < SL(Q_hi) - SL(Q_lo) <= 2^64 - 1 +// then SubL(Q_hi, Q_lo) leads to exactly one overflow. The result is always negative: +// SubL(Q_hi, Q_lo) = TruncL(SL(Q_hi) - SL(Q_lo)) = SL(Q_hi) - SL(Q_lo) - 2^64 < 0 +// This case is impossible as it violates (Q-lo-hi) +// 0 < SubL(Q_hi, Q_lo) +// We conclude that SubL(Q_hi, Q_lo) does not overflow the signed long domain (2a-no-overflow). +// +// +// From (Q-lo-hi) and (2a-no-overflow), we get +// max_jint >= SubL(Q_hi, Q_lo) +// = SL(SubL(Q_hi, Q_lo)) +// = SL(Q_hi) - SL(Q_lo) (2a) +// +// Moreover, from (Case 2), we get +// Q_hi > 0 +// SL(Q_hi) > 0 (2b) +// and +// Q_lo < 0 +// SL(Q_lo) < 0 (2c) +// +// It follows for Q_lo +// SL(Q_lo) = SL(Q_hi) - (SL(Q_hi) - SL(Q_lo)) +// > 0 - (SL(Q_hi) - SL(Q_lo)) (apply (2b)) +// >= 0 - max_jint (apply (2a)) +// = -max_jint +// and as a result +// Q_lo > -max_jint (2e) +// +// It follows for Q_hi +// SL(Q_hi) = (SL(Q_hi) - SL(Q_lo)) + SL(Q_lo) +// <= max_jint + SL(Q_lo) (apply (2a)) +// < max_jint (apply (2c)) +// and as a result +// Q_hi < max_jint (2f) +// +// With (2e) (2f), and (Case 2), we see that Q_lo and Q_hi fit into the 32-bit integer range: +// -max_jint < Q_lo < 0 < Q_hi < max_jint (2g) +// +// +// From (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// together with Q_lo < 0 (Case 2), we see that neither adding Q_lo to the left side +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo) +// ---------------- >= 0 ------------------ --- < 0 --- +// nor to the right side +// AddL(SubL(Q_hi, Q_lo), Q_lo) +// ----- > 0 ----- --- < 0 --- +// can overflow the signed long domain (2b-no-overflow). +// +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0 +// with (2b-no-overflow), we can apply Lemma 5 to add Q_lo +// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0 +// cancel out the Q_lo terms +// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0 +// and with (Case 2) and (2f) see that +// AddL(MulL(ConvI2L(j), K), Q) < Q_hi < max_jint (2h) +// +// +// From (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) +// we see that neither adding Q_lo to the left side +// AddL(0, Q_lo) = Q_lo +// nor to the right side (already shown with (2b-no-overflow)) +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo) +// can overflow the signed long domain (2c-no-overflow). +// +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( 0, SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)) <= 0 +// with (2c-no-overflow), we can apply Lemma 5 to add Q_lo +// CmpL(AddL(0, Q_lo), AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)) <= 0 +// cancel out the Q_lo terms and simplify +// CmpL( Q_lo , AddL( MulL(ConvI2L(j), K), Q)) <= 0 +// and with (Case 2) and (2e) see that +// AddL(MulL(ConvI2L(j), K), Q) >= Q_lo > -max_jint (2i) +// +// +// Putting (2h) and (2i) together, we see that the AddL actually fits into the 32-bit integer range +// -max_jint < Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi < max_jint (2j) +// +// With (2j) and (R0), we also conclude +// -max_jint < MinL(Q_hi, R) < max_jint (2k) +// +// We notice with +// (2h): x = AddL(MulL(ConvI2L(j), K), Q) < Q_hi +// (Case 2): y = Q_hi > 0 +// (R0): z = R >= 0 +// that Lemma 7 can be applied +// LHS = CmpUL( AddL(MulL(ConvI2L(j), K), Q), R) +// ------------------ x ----------------- -- z -- +// = CmpUL( AddL(MulL(ConvI2L(j), K), Q), MinL(Q_hi, R)) +// ------------------ x ----------------- -- y -- -- z -- +// apply Lemma 1 to add ConvI2L-ConvL2I to both sides of CmpUL because they both fit into an int32_t ((2j) and (2k)) +// LHS = CmpUL(ConvI2L(ConvL2I(AddL(MulL(ConvI2L(j), K), Q))), ConvI2L(ConvL2I(MinL(Q_hi, R)))) +// apply Lemma 3 to transform to CmpU because both sides of CmpUL are int32_t values +// = CmpU ( ConvL2I(AddL(MulL(ConvI2L(j), K), Q)) , ConvL2I(MinL(Q_hi, R)) +// apply Lemma 4 twice to move the ConvL2I inwards +// = CmpU ( AddI(ConvL2I( MulL(ConvI2L(j), K)), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R))) +// = CmpU ( AddI(MulI( ConvL2I(ConvI2L(j)), ConvL2I(K)), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R))) +// apply Lemma 2 to remove the ConvL2I-ConvI2L due to (JI) and simplify K due to (KI) +// = CmpU ( AddI(MulI( j, K), ConvL2I(Q)), ConvL2I(MinL(Q_hi, R))) +// we now add a minus 0 on Q and MinL() +// = CmpU( AddI(MulI( j, K), ConvL2I(SubL(Q, 0))), ConvL2I(SubL(MinL(Q_hi, R), 0))) +// and eventually insert the simplified L_clamp and R_clamp values +// = CmpU( AddI(MulI( j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// which is equivalent to the definition of RHS +// +// +// We proved that +// LHS = RHS +// and thus +// (LHS < 0) == (RHS < 0) +// +// +// Case 3: 0 <= Q_lo < Q_hi +// ======================== +// We can simplify the given clamp values: +// L_clamp = MaxL(Q_lo, 0) +// = Q_lo +// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// = Q_hi +// R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// = MaxL(MinL(R, Q_hi), Q_lo) +// +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo))) +// +// Let's first look at both SubL of (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// +// We see with (SubL-pos-no-overflow) and (Case 3) that +// SubL(Q_hi, Q_lo) +// does not overflow the signed long domain. Therefore, adding +// Q_lo >= 0 (from Case 3) +// to the right side +// AddL(SubL(Q_hi, Q_lo), Q_lo) = Q_hi +// does not overflow the signed long domain and therefore adding +// Q_lo >= 0 (from Case 3) +// to the left side SubL +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) Q_lo) < AddL(SubL(Q_hi, Q_lo), Q_lo) +// also does not overflow the signed long domain (3a-no-overflow). +// +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) < 0 +// with (3a-no-overflow), we can apply Lemma 5 and add Q_lo to both sides without overflow +// CmpL(AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo), AddL(SubL(Q_hi, Q_lo), Q_lo)) < 0 +// cancel out the Q_lo terms +// CmpL( AddL(MulL(ConvI2L(j), K), Q), Q_hi) < 0 +// and see that +// AddL(MulL(ConvI2L(j), K), Q) < Q_hi (3a) +// +// +// From (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) +// we see that neither adding Q_lo to the left side +// AddL(0, Q_lo) = Q_lo +// nor to the right side (3a-no-overflow) +// AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo) +// can overflow the signed long domain (3b-no-overflow). +// +// +// The form (Q-lo-hi) +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < SubL(Q_hi, Q_lo) <= max_jint +// can also be written as +// CmpL( 0, SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)) <= 0 +// with (3b-no-overflow), we can apply Lemma 5 and add Q_lo to both sides without overflow +// CmpL(AddL(0, Q_lo), AddL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), Q_lo)) <= 0 +// cancel out the Q_lo terms and simplify +// CmpL( Q_lo , AddL( MulL(ConvI2L(j), K), Q)) <= 0 +// and seee that +// AddL(MulL(ConvI2L(j), K), Q) >= Q_lo (3b) +// +// +// Putting (3a) and (3b) together, we see that +// 0 <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi (3c) +// +// Looking at the definition of LHS +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// we have 3 cases for R with respect to Q_lo and Q_hi: +// 3-R1: 0 <= R <= Q_lo +// 3-R2: 0 <= Q_lo < R < Q_hi +// 3-R3: 0 <= Q_lo < Q_hi <= R +// +// We need to prove for all three cases that (LHS < 0) == (RHS < 0) +// +// 3-R1: 0 <= R <= Q_lo +// -------------------- +// Combining (3-R1) and (3c), we have +// 0 <= R <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi +// and conclude +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// >= 0 +// +// From the definition of RHS +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo))) +// we know from (3-R1) that R < Q_hi: +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( R , Q_lo), Q_lo))) +// and R <= Q_lo: +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( Q_lo , Q_lo))) +// = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), 0) +// = CmpU(c , 0) +// >= 0 (apply (CmpU-gte-zero)) +// +// +// We showed that +// LHS >= 0 +// and +// RHS >= 0 +// and therefore proved that +// (LHS < 0) == (RHS < 0) == false +// +// +// 3-R2: 0 <= Q_lo < R < Q_hi +// -------------------------- +// From (Q-lo-hi) +// 0 <= SubL(Q_hi, Q_lo) <= max_jint +// and (3-R2), we see that subtracting Q_lo from R +// 0 < SubL(R, Q_lo) < SubL(Q_hi, Q_lo) <= max_jint (3d) +// does not overflow the unsigned long domain (3-R2a-no-overflow). +// +// Further, we know from (3c) +// 0 <= Q_lo <= AddL(MulL(ConvI2L(j), K), Q) +// that subtracting Q_lo +// 0 <= SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) <= AddL(MulL(ConvI2L(j), K), Q) +// does also not overflow the unsigned long domain (3-R2b-no-overflow). +// +// We can now apply several lemmas and transformations to show the equivalence of LHS and RHS +// LHS = CmpUL( AddL(MulL(ConvI2L(j), K), Q), R) +// apply Lemma 6 to add SubL to both sides because both SubL do not overflow ((3-R2a-no-overflow) and (3-R2b-no-overflow)) +// LHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(R, Q_lo)) +// apply Lemma 1 to add ConvI2L-ConvL2I because both sides of CmpUL are int32_t values ((Q-lo-hi) + (3d)) +// LHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvI2L( ConvL2I(SubL(R, Q_lo))))) +// apply Lemma 3 to transform to CmpU because both sides of CmpUL are int32_t values +// LHS = CmpU (ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvL2I(SubL(R, Q_lo))) +// apply Lemma 4 twice to move the ConvL2I inwards +// LHS = CmpU (AddI(ConvL2I( MulL(ConvI2L(j), K )), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo))) +// = CmpU (AddI(MulI(ConvL2I( ConvI2L(j)), ConvL2I(K)), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo))) +// apply Lemma 2 to remove the ConvL2I-ConvI2L due to (JI) and simplify K due to (KI) +// LHS = CmpU (AddI(MulI( j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(R, Q_lo))) +// and space it differently +// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( R , Q_lo))) +// apply R > Q_lo (3-R2) to add an equivalent MaxL for R +// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( R , Q_lo), Q_lo))) +// apply R < Q_hi (3-R2) to add an equivlanet MinL for R +// LHS = CmpU (AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo))) +// which is equivalent to the definition of RHS +// +// +// We proved that +// LHS = RHS +// and thus +// (LHS < 0) == (RHS < 0) +// +// +// 3-R3: 0 <= Q_lo < Q_hi <= R +// -------------------------- +// From (3-R3) and (3c), we know that +// 0 <= AddL(MulL(ConvI2L(j), K), Q) < Q_hi <= R +// 0 <= AddL(MulL(ConvI2L(j), K), Q) < R +// and therefore +// LHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// < 0 +// +// +// From the definition of +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(MinL(R, Q_hi), Q_lo), Q_lo))) +// we know from (3-R3) that R >= Q_hi: +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(MaxL( Q_hi, Q_lo), Q_lo))) +// and Q_lo < Q_hi: +// RHS = CmpU(AddI(MulI(j, K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL( Q_hi, Q_lo))) +// apply Lemma 2 to j due to (JI) +// RHS = CmpU(AddI(MulI(ConvL2I(ConvI2L(j)), K), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo))) +// apply Lemma 4 twice to move the ConvL2I outwards +// RHS = CmpU(AddI(ConvL2I(MulL(ConvI2L(j), K)), ConvL2I(SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo))) +// CmpU(ConvL2I(AddL(MulL(ConvI2L(j), K), SubL(Q, Q_lo))), ConvL2I(SubL(Q_hi, Q_lo))) +// ------- x -------- -- y -- -- z -- +// reassociated AddL/SubL +// AddL(x, SubL(y, z)) = SubL(AddL(x, y), z) +// and space it differently as +// RHS = CmpU (ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), ConvL2I( SubL(Q_hi, Q_lo))) +// ------- x -------- -- y -- -- z -- +// apply Lemma 3 to transform to CmpUL form because both sides are int32_t values due to (Q-lo-hi) +// RHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo))), ConvI2L(ConvL2I(SubL(Q_hi, Q_lo)))) +// apply Lemma 1 to remove both ConvI2L-ConvL2I because the inner values are int32_t due to (Q-lo-hi) +// RHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(Q_hi, Q_lo)) +// and see with (Q-lo-hi) that +// RHS < 0 +// +// We showed that +// LHS < 0 +// and +// RHS < 0 +// and therefore proved that +// (LHS < 0) == (RHS < 0) == true +// +// +// Case 4: Q_hi < 0 < Q_lo +// ======================= +// We can simplify the given clamp values: +// L_clamp = MaxL(Q_lo, 0) +// = Q_lo +// H_clamp = Q_hi < Q_lo ? max_jlong : Q_hi +// = max_jlong +// R_clamp = MaxL(MinL(R, H_clamp), L_clamp) +// = MaxL(MinL(R, max_jlong), Q_lo) +// = MaxL(R, Q_lo) (because R <= max_jlong (R0)) +// +// And insert them into the definition of +// RHS = CmpU(AddI(MulI( j, K), ConvL2I(SubL(Q, L_clamp))), ConvL2I(SubL(R_clamp, L_clamp))) +// = CmpU(AddI(MulI( j, K), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))) +// apply Lemma 2 to add ConvL2I-ConvI2L due to (JI) +// RHS = CmpU(AddI(MulI(ConvL2I(ConvI2L(j)), K), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))) +// apply Lemma 4 twice to move the ConvL2I outwards: +// RHS = CmpU(AddI(ConvL2I(MulL(ConvI2L(j), K)), ConvL2I(SubL(Q, Q_lo)), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))) +// = CmpU(ConvL2I(AddL(MulL(ConvI2L(j), K), SubL(Q, Q_lo))), ConvL2I(SubL(MaxL(R, Q_lo), Q_lo))) +// ------- x -------- -- y -- -- z -- +// reassociate AddL/SubL +// AddL(x, SubL(y, z)) = SubL(AddL(x, y), z) +// and space it differently as +// RHS = CmpU(ConvL2I( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo)), C onvL2I(SubL(MaxL(R, Q_lo), Q_lo))) +// ------- x -------- -- y -- -- z -- +// apply Lemma 3 to transform to CmpUL form because both sides are int32_t values with ConvL2I +// RHS = CmpUL(ConvI2L(ConvL2I(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo))), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))) +// apply Lemma 1 to remove the left ConvI2L-ConvL2I because the inner value is int32_t due to (Q-lo-hi) +// RHS = CmpUL( SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))) +// +// +// To remove the right-side ConvI2L-ConvL2I, we first need to show that +// SubL(MaxL(R, Q_lo), Q_lo) +// is an int32_t value +// +// SubL is an int32_t +// ------------------ +// (Case 4) +// Q_hi < 0 < Q_lo +// is equivalent to +// 0 u 0 +// does not overflow the unsigned long domain and thus +// UL(Q_hi) - UL(Q_lo) = UL(SubL(Q_hi, Q_lo)) (4b) +// +// It also follows that +// Q_hi u> max_jlong +// UL(Q_hi) > max_jlong (4c) +// +// From (R0) +// 0 <= R <= max_jlong +// and (Case 4) +// 0 <= Q_lo <= max_jlong +// we see that +// 0 <= MaxL(R, Q_lo) <= max_jlong (4d) +// +// We notice that +// 0 < Q_lo <= MaxL(R, Q_lo) +// which means that +// 0 <= SubL(MaxL(R, Q_lo), Q_lo) +// does not overflow the unsigned long domain (4b-no-overflow) +// +// +// With (4b-no-overflow), we get +// 0 <= SubL(MaxL(R, Q_lo), Q_lo) +// = UL(SubL(MaxL(R, Q_lo), Q_lo)) +// = UL(MaxL(R, Q_lo)) - UL(Q_lo) +// <= max_jlong - UL(Q_lo) (apply (4d)) +// < UL(Q_hi) - UL(Q_lo) (apply (4c)) +// = UL(SubL(Q_hi, Q_lo)) (apply (4b)) +// <= UL(max_jint) (apply (Q-lo-hi)) +// = max_jint +// and thus showed that +// 0 <= SubL(MaxL(R, Q_lo), Q_lo) < max_jint (4e) +// indeed fits into an int32_t. +// +// +// Thus, we can apply Lemma 1 to remove the second ConvI2L-ConvL2I as well +// RHS = CmpUL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), ConvI2L(ConvL2I(SubL(MaxL(R, Q_lo), Q_lo)))) +// RHS = CmpUL(SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo), SubL(MaxL(R, Q_lo), Q_lo)) +// +// We now want to remove the SubL on both sides with Lemma 6. We already showed with (4e) that the right SubL does +// not overflow. Which leaves us by showing that the left SubL does not overflow. +// +// +// No Overflow of Left SubL +// ------------------------ +// Consider the subtraction +// UL(x - Q_lo) +// which only overflows if +// x = Q_lo +// 4-R2: R < Q_lo +// +// We need to prove for both cases that (LHS < 0) == (RHS < 0) +// +// 4-R1: R >= Q_lo +// --------------- +// Starting from (4g) +// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), MaxL(R, Q_lo)) +// = CmpUL(AddL(MulL(ConvI2L(j), K), Q), R) +// = LHS +// +// which implies (LHS < 0) == (RHS < 0) +// +// 4-R2: R < Q_lo +// --------------- +// Starting from (4g) +// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), MaxL(R, Q_lo)) +// = CmpUL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) +// +// From (4c-no-overflow), we know, that +// SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) +// does not overflow and with (Q-lo-hi) +// 0 < SubL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) < max_jint +// it follows that +// 0 =u Q_lo (4h) +// must hold and therefore +// RHS = CmpUL(AddL(MulL(ConvI2L(j), K), Q), Q_lo) >= 0 +// +// From (4-R2) and (R0) we know +// 0 <= R < Q_lo +// which is the same as +// R 0 +// +// We showed that +// LHS > 0 +// and +// RHS >= 0 +// and therefore proved that +// (LHS < 0) == (RHS < 0) == false +// +// We proved that the transformation is valid under the stated pre-conditions. void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List &range_checks, Node* outer_phi, Node* inner_iters_actual_int, Node* inner_phi, Node* iv_add, LoopNode* inner_head) { @@ -1536,150 +2460,195 @@ void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List // could be shared and have already been taken care of continue; } - bool short_scale = false; - bool ok = is_scaled_iv_plus_offset(rc_cmp->in(1), iv_add, T_LONG, &scale, &offset, &short_scale); + bool ok = is_scaled_iv_plus_offset(rc_cmp->in(1), iv_add, T_LONG, &scale, &offset); assert(ok, "inconsistent: was tested before"); + // Because is_scaled_iv_plus_offset is successful, we know that + // rc_cmp->in(1) is semantically equivalent to either: + // AddL(MulL(iv_add, scale), offset), if iv_add is long; or + // AddL(MulL(ConvI2L(iv_add), scale), offset), if iv_add is int. + // See the equivalence proof in PhaseIdealLoop::is_scaled_iv_plus_offset and + // functions called therein. Node* range = rc_cmp->in(2); Node* c = rc->in(0); Node* entry_control = inner_head->in(LoopNode::EntryControl); Node* R = range; Node* K = longcon(scale); - Node* L = offset; - - if (short_scale) { - // This converts: - // (int)i*K + L u64 (long)max_jint and still is C, int_zero, inner_iters_actual_int, int_stride); - register_new_node(B_2, entry_control); - B_2 = new SubINode(B_2, int_stride); - register_new_node(B_2, entry_control); - B_2 = new ConvI2LNode(B_2); - register_new_node(B_2, entry_control); - - Node* Q_max = new MulLNode(B_2, K); - register_new_node(Q_max, entry_control); - Q_max = new AddLNode(Q_max, Q_first); - register_new_node(Q_max, entry_control); - + // Last iteration, j = last, where + // last = ((iter_count - 1) * stride + init) + // = ( iter_count * stride + init) - stride + // = LoopLimitNode(init, limit, stride ) - stride + // = LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride + // which is used to get + // Q_last = (j * K) + Q + // = (last * K) + Q + // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + Q (def. of last) + // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + (C * K) + L (def. of Q) + // = (LoopLimitNode(0, inner_iters_actual_int, int_stride) - stride * K) + Q_first (def. of Q_first) + Node* last = new LoopLimitNode(this->C, int_zero, inner_iters_actual_int, int_stride); + register_new_node(last, entry_control); + last = new SubINode(last, int_stride); + register_new_node(last, entry_control); + last = new ConvI2LNode(last); + register_new_node(last, entry_control); + + Node* Q_last = new MulLNode(last, K); + register_new_node(Q_last, entry_control); + Q_last = new AddLNode(Q_last, Q_first); + register_new_node(Q_last, entry_control); + + // We define Q_lo and Q_hi now from Q_first, Q_last, P_lo, and P_hi: + // + // If Q_first < Q_last (which is the case if scale * stride_con > 0): + // Q_lo = P_lo + Q (def. Q_lo) + // = 0 * K + Q (def. P_lo) + // = Q + // = Q_first + // and + // Q_hi = P_hi + Q (def. Q_hi) + // = last * K + 1 + Q (def. P_hi) + // = last * K + Q + 1 + // = Q_last + 1 + // If Q_first > Q_last (which is the case if scale * stride_con < 0): + // Q_lo = P_lo + Q (def. Q_lo) + // = last * K + Q (def. P_lo) + // = Q_last + // and + // Q_hi = P_hi + Q (def. Q_hi) + // = 0 * K + 1 + Q (def. P_hi) + // = 0 * K + Q + 1 + // = Q_first + 1 + Node* Q_lo = Q_first; + Node* Q_hi = Q_last; if (scale * stride_con < 0) { - swap(Q_min, Q_max); + swap(Q_lo, Q_hi); } - // Now, mathematically, Q_max > Q_min, and they are close enough so that (Q_max-Q_min) fits in 32 bits. - // L_clamp = Q_min < 0 ? 0 : Q_min - Node* Q_min_cmp = new CmpLNode(Q_min, long_zero); - register_new_node(Q_min_cmp, entry_control); - Node* Q_min_bool = new BoolNode(Q_min_cmp, BoolTest::lt); - register_new_node(Q_min_bool, entry_control); - Node* L_clamp = new CMoveLNode(Q_min_bool, Q_min, long_zero, TypeLong::LONG); - register_new_node(L_clamp, entry_control); - // (This could also be coded bitwise as L_clamp = Q_min & ~(Q_min>>63).) + // Q_lo is now correct. Add plus one to get the correct Q_hi value as defined above as well: + Q_hi = new AddLNode(Q_hi, long_one); + register_new_node(Q_hi, entry_control); - Node* Q_max_plus_one = new AddLNode(Q_max, long_one); - register_new_node(Q_max_plus_one, entry_control); + // Let's next define all clamp values: + // + // First, we compute + // L_clamp = Q_lo < 0 ? 0 : Q_lo + Node* Q_lo_cmp = new CmpLNode(Q_lo, long_zero); + register_new_node(Q_lo_cmp, entry_control); + Node* Q_lo_bool = new BoolNode(Q_lo_cmp, BoolTest::lt); + register_new_node(Q_lo_bool, entry_control); + Node* L_clamp = new CMoveLNode(Q_lo_bool, Q_lo, long_zero, TypeLong::LONG); + register_new_node(L_clamp, entry_control); + // (This could also be coded bitwise as L_clamp = Q_lo & ~(Q_lo>>63).) - // H_clamp = Q_max+1 < Q_min ? max_jlong : Q_max+1 - // (Because Q_min and Q_max are close, the overflow check could also be encoded as Q_max+1 < 0 & Q_min >= 0.) + // Next, we compute + // H_clamp = Q_hi< Q_lo ? max_jlong : Q_hi Node* max_jlong_long = longcon(max_jlong); - Node* Q_max_cmp = new CmpLNode(Q_max_plus_one, Q_min); - register_new_node(Q_max_cmp, entry_control); - Node* Q_max_bool = new BoolNode(Q_max_cmp, BoolTest::lt); - register_new_node(Q_max_bool, entry_control); - Node* H_clamp = new CMoveLNode(Q_max_bool, Q_max_plus_one, max_jlong_long, TypeLong::LONG); + Node* Q_hi_cmp = new CmpLNode(Q_hi, Q_lo); + register_new_node(Q_hi_cmp, entry_control); + Node* Q_hi_bool = new BoolNode(Q_hi_cmp, BoolTest::lt); + register_new_node(Q_hi_bool, entry_control); + Node* H_clamp = new CMoveLNode(Q_hi_bool, Q_hi, max_jlong_long, TypeLong::LONG); register_new_node(H_clamp, entry_control); - // (This could also be coded bitwise as H_clamp = ((Q_max+1)<<1 | M)>>>1 where M = (Q_max+1)>>63 & ~Q_min>>63.) - - // R_2 = clamp(R, L_clamp, H_clamp) - L_clamp - // that is: R_2 = clamp(R, L_clamp=0, H_clamp=Q_max) if Q_min < 0 - // or else: R_2 = clamp(R, L_clamp, H_clamp) - Q_min if Q_min >= 0 - // and also: R_2 = clamp(R, L_clamp, Q_max+1) - L_clamp if Q_min < Q_max+1 (no overflow) - // or else: R_2 = clamp(R, L_clamp, *no limit*)- L_clamp if Q_max+1 < Q_min (overflow) - Node* R_2 = clamp(R, L_clamp, H_clamp); - R_2 = new SubLNode(R_2, L_clamp); - register_new_node(R_2, entry_control); - R_2 = new ConvL2INode(R_2, TypeInt::POS); - register_new_node(R_2, entry_control); - - // L_2 = Q_first - L_clamp - // We are subtracting L_clamp from both sides of the 0, then Q_first == 0 and the R.C. expression at -L_clamp and steps upward to Q_max-L_clamp. - // If S*K<0, then Q_first != 0 and the R.C. expression starts high and steps downward to Q_min-L_clamp. - Node* L_2 = new SubLNode(Q_first, L_clamp); - register_new_node(L_2, entry_control); - L_2 = new ConvL2INode(L_2, TypeInt::INT); - register_new_node(L_2, entry_control); - - // Transform the range check using the computed values L_2/R_2 - // from: i*K + L >>1 where M = (Q_hi)>>63 & ~Q_lo>>63.) + + // Finally, we compute + // R_clamp = MaxL(MinL(R, H_clamp), L_clamp) + Node* R_clamp = clamp(R, L_clamp, H_clamp); + + // Now we have everything together to transform the long range check: + // (i * K) + L (scale)); Node* scaled_iv = new MulINode(inner_phi, K); register_new_node(scaled_iv, c); - Node* scaled_iv_plus_offset = new AddINode(scaled_iv, L_2); + Node* scaled_iv_plus_offset = new AddINode(scaled_iv, L2); register_new_node(scaled_iv_plus_offset, c); - Node* new_rc_cmp = new CmpUNode(scaled_iv_plus_offset, R_2); + Node* new_rc_cmp = new CmpUNode(scaled_iv_plus_offset, R2); register_new_node(new_rc_cmp, c); _igvn.replace_input_of(rc_bol, 1, new_rc_cmp); } } -Node* PhaseIdealLoop::clamp(Node* R, Node* L, Node* H) { - Node* min = MinMaxNode::signed_min(R, H, TypeLong::LONG, _igvn); +Node* PhaseIdealLoop::clamp(Node* R, Node* L_clamp, Node* H_clamp) { + Node* min = MinMaxNode::signed_min(R, H_clamp, TypeLong::LONG, _igvn); set_subtree_ctrl(min, true); - Node* max = MinMaxNode::signed_max(L, min, TypeLong::LONG, _igvn); + Node* max = MinMaxNode::signed_max(L_clamp, min, TypeLong::LONG, _igvn); set_subtree_ctrl(max, true); return max; } @@ -3360,14 +4329,14 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte for (DUIterator_Fast imax, i = cle_out->fast_outs(imax); i < imax; i++) { Node* u = cle_out->fast_out(i); if (u->is_Store()) { - int alias_idx = igvn->C->get_alias_index(u->adr_type()); + int alias_idx = igvn->C->get_alias_index(u->out_adr_type()); Node* first = u; for (;;) { Node* next = first->in(MemNode::Memory); if (!next->is_Store() || next->in(0) != cle_out) { break; } - assert(igvn->C->get_alias_index(next->adr_type()) == alias_idx, ""); + assert(igvn->C->get_alias_index(next->out_adr_type()) == alias_idx, ""); first = next; } Node* last = u; @@ -3378,7 +4347,7 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte if (uu->is_Store() && uu->in(0) == cle_out) { assert(next == nullptr, "only one in the outer loop"); next = uu; - assert(igvn->C->get_alias_index(next->adr_type()) == alias_idx, ""); + assert(igvn->C->get_alias_index(next->out_adr_type()) == alias_idx, ""); } } if (next == nullptr) { @@ -3392,10 +4361,10 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte if (uu->is_Phi()) { Node* be = uu->in(LoopNode::LoopBackControl); if (be->is_Store() && be->in(0) == inner_cl->in(LoopNode::LoopBackControl)) { - assert(igvn->C->get_alias_index(uu->adr_type()) != alias_idx && igvn->C->get_alias_index(uu->adr_type()) != Compile::AliasIdxBot, "unexpected store"); + assert(igvn->C->get_alias_index(uu->out_adr_type()) != alias_idx && igvn->C->get_alias_index(uu->out_adr_type()) != Compile::AliasIdxBot, "unexpected store"); } if (be == last || be == first->in(MemNode::Memory)) { - assert(igvn->C->get_alias_index(uu->adr_type()) == alias_idx || igvn->C->get_alias_index(uu->adr_type()) == Compile::AliasIdxBot, "unexpected alias"); + assert(igvn->C->get_alias_index(uu->out_adr_type()) == alias_idx || igvn->C->get_alias_index(uu->out_adr_type()) == Compile::AliasIdxBot, "unexpected alias"); assert(phi == nullptr, "only one phi"); phi = uu; } @@ -3405,9 +4374,9 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte for (DUIterator_Fast jmax, j = inner_cl->fast_outs(jmax); j < jmax; j++) { Node* uu = inner_cl->fast_out(j); if (uu->is_memory_phi()) { - if (uu->adr_type() == igvn->C->get_adr_type(igvn->C->get_alias_index(u->adr_type()))) { + if (uu->out_adr_type() == igvn->C->get_adr_type(igvn->C->get_alias_index(u->out_adr_type()))) { assert(phi == uu, "what's that phi?"); - } else if (uu->adr_type() == TypePtr::BOTTOM) { + } else if (uu->out_adr_type() == TypePtr::BOTTOM) { Node* n = uu->in(LoopNode::LoopBackControl); uint limit = igvn->C->live_nodes(); uint i = 0; @@ -3421,7 +4390,7 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte } else if (n->is_Phi()) { n = n->in(1); } else if (n->is_MergeMem()) { - n = n->as_MergeMem()->memory_at(igvn->C->get_alias_index(u->adr_type())); + n = n->as_MergeMem()->memory_at(igvn->C->get_alias_index(u->out_adr_type())); } else if (n->is_Store() || n->is_LoadStore() || n->is_ClearArray()) { n = n->in(MemNode::Memory); } else { @@ -3438,7 +4407,7 @@ void OuterStripMinedLoopNode::fix_sunk_stores_when_back_to_counted_loop(PhaseIte // inner loop has no phi for that memory // slice, create one for the outer loop phi = PhiNode::make(inner_cl, first->in(MemNode::Memory), Type::MEMORY, - igvn->C->get_adr_type(igvn->C->get_alias_index(u->adr_type()))); + igvn->C->get_adr_type(igvn->C->get_alias_index(u->out_adr_type()))); phi->set_req(LoopNode::LoopBackControl, last); phi = register_new_node(phi, inner_cl, igvn, iloop); igvn->replace_input_of(first, MemNode::Memory, phi); @@ -4233,8 +5202,8 @@ void IdealLoopTree::merge_many_backedges( PhaseIdealLoop *phase ) { if( out->is_Phi() ) { PhiNode* n = out->as_Phi(); igvn.hash_delete(n); // Delete from hash before hacking edges - Node *hot_phi = nullptr; - Node *phi = new PhiNode(r, n->type(), n->adr_type()); + Node* hot_phi = nullptr; + Node* phi = new PhiNode(r, n->type(), n->out_adr_type()); // Check all inputs for the ones to peel out uint j = 1; for( uint i = 2; i < n->req(); i++ ) { @@ -6772,7 +7741,7 @@ Node *PhaseIdealLoop::get_late_ctrl( Node *n, Node *early ) { // dominated by early is considered a potentially interfering store. // This can produce false positives. Node* PhaseIdealLoop::get_late_ctrl_with_anti_dep(LoadNode* n, Node* early, Node* LCA) { - int load_alias_idx = C->get_alias_index(n->adr_type()); + int load_alias_idx = C->get_alias_index(n->in_adr_type()); if (C->alias_type(load_alias_idx)->is_rewritable()) { Unique_Node_List worklist; @@ -6796,7 +7765,7 @@ Node* PhaseIdealLoop::get_late_ctrl_with_anti_dep(LoadNode* n, Node* early, Node Node* sctrl = has_ctrl(s) ? get_ctrl(s) : s->in(0); assert(sctrl != nullptr || !s->is_reachable_from_root(), "must have control"); if (sctrl != nullptr && !sctrl->is_top() && is_dominator(early, sctrl)) { - const TypePtr* adr_type = s->adr_type(); + const TypePtr* adr_type = s->out_adr_type(); if (s->is_ArrayCopy()) { // Copy to known instance needs destination type to test for aliasing const TypePtr* dest_type = s->as_ArrayCopy()->_dest_type; @@ -6826,7 +7795,7 @@ Node* PhaseIdealLoop::get_late_ctrl_with_anti_dep(LoadNode* n, Node* early, Node if (LCA != early) { for (uint i = 0; i < worklist.size(); i++) { Node* s = worklist.at(i); - if (s->is_Phi() && C->can_alias(s->adr_type(), load_alias_idx)) { + if (s->is_Phi() && C->can_alias(s->out_adr_type(), load_alias_idx)) { Node* r = s->in(0); for (uint j = 1; j < s->req(); j++) { Node* in = s->in(j); diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index a6f0ddd8b6b8..70ee7fc45f35 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1569,12 +1569,12 @@ class PhaseIdealLoop : public PhaseTransform { void do_unroll( IdealLoopTree *loop, Node_List &old_new, bool adjust_min_trip ); // Return true if exp is a constant times an induction var - bool is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, bool* p_short_scale, int depth = 0); + bool is_scaled_iv(Node* exp, Node* iv, BasicType bt, jlong* p_scale, int depth = 0); bool is_iv(Node* exp, Node* iv, BasicType bt); - // Return true if exp is a scaled induction var plus (or minus) constant - bool is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, bool* p_short_scale = nullptr, int depth = 0); + // Return true if exp is a scaled induction var plus (or minus) an offset + bool is_scaled_iv_plus_offset(Node* exp, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, int depth = 0); bool is_scaled_iv_plus_offset(Node* exp, Node* iv, int* p_scale, Node** p_offset) { jlong long_scale; if (is_scaled_iv_plus_offset(exp, iv, T_INT, &long_scale, p_offset)) { @@ -1587,10 +1587,10 @@ class PhaseIdealLoop : public PhaseTransform { return false; } // Helper for finding more complex matches to is_scaled_iv_plus_offset. - bool is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset2, Node* iv, + bool is_scaled_iv_plus_extra_offset(Node* exp1, Node* offset3, Node* iv, BasicType bt, jlong* p_scale, Node** p_offset, - bool* p_short_scale, int depth); + int depth); // Create a new if above the uncommon_trap_if_pattern for the predicate to be promoted IfTrueNode* create_new_if_for_predicate(const ParsePredicateSuccessProj* parse_predicate_proj, Node* new_entry, @@ -2040,7 +2040,7 @@ class PhaseIdealLoop : public PhaseTransform { void try_sink_out_of_loop(Node* n); - Node* clamp(Node* R, Node* L, Node* H); + Node* clamp(Node* R, Node* L_clamp, Node* H_clamp); bool safe_for_if_replacement(const Node* dom) const; diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 77ccf3b31bdd..c2699cd4afb9 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -612,6 +612,8 @@ Node* PhaseIdealLoop::remix_address_expressions(Node* n) { n_op == Op_MulD) { if (n2_loop == n_loop) { assert(n1_loop != n_loop, ""); + // IGVN may want to swap the edges back later, to commute (canonicalize) the operation. + _igvn.rehash_node_delayed(n); n->swap_edges(1, 2); } } @@ -1166,8 +1168,10 @@ void PhaseIdealLoop::move_flat_array_check_out_of_loop(Node* n) { // Replace memory input and re-compute ctrl to move the check out of the loop _igvn.replace_input_of(n, 1, mem); set_ctrl_and_loop(n, get_early_ctrl(n)); - Node* bol = n->unique_out(); - set_ctrl_and_loop(bol, get_early_ctrl(bol)); + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { + Node* bol = n->fast_out(i); + set_ctrl_and_loop(bol, get_early_ctrl(bol)); + } } } diff --git a/src/hotspot/share/opto/machnode.cpp b/src/hotspot/share/opto/machnode.cpp index b7b50ceaf5a3..7fb49c506221 100644 --- a/src/hotspot/share/opto/machnode.cpp +++ b/src/hotspot/share/opto/machnode.cpp @@ -27,7 +27,10 @@ #include "gc/shared/collectedHeap.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.hpp" +#include "opto/ad.hpp" #include "opto/machnode.hpp" +#include "opto/node.hpp" +#include "opto/opcodes.hpp" #include "opto/output.hpp" #include "opto/regalloc.hpp" #include "utilities/vmError.hpp" @@ -343,9 +346,73 @@ const Node* MachNode::get_base_and_disp(intptr_t &offset, const TypePtr* &adr_ty return base; } +const TypePtr* MachNode::out_adr_type_impl() const { + // A node produces memory if it is a memory node, or if it has a memory Proj + bool produce_memory = bottom_type() == Type::MEMORY; + if (!produce_memory) { + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + const Node* out = fast_out(i); + if (out->is_Proj() && out->bottom_type() == Type::MEMORY) { + produce_memory = true; + break; + } + } + } + + if (const TypePtr* res = _out_adr_type; res != TYPE_PTR_SENTINAL) { +#ifdef ASSERT + if (produce_memory) { + assert(res != nullptr, "must declare an out_adr_type"); + verify_adr_type(res, compute_adr_type_from_inputs()); + } else { + assert(res == nullptr, "unexpected memory producer"); + } +#endif // ASSERT + return res; + } else if (produce_memory) { + return compute_adr_type_from_inputs(); + } else { + return nullptr; + } +} + +const TypePtr* MachNode::in_adr_type_impl() const { + if (const TypePtr* res = _in_adr_type; res != TYPE_PTR_SENTINAL) { + DEBUG_ONLY(verify_adr_type(res, compute_adr_type_from_inputs())); + return res; + } else { + return compute_adr_type_from_inputs(); + } +} -//---------------------------------adr_type--------------------------------- -const class TypePtr *MachNode::adr_type() const { +void MachNode::verify_adr_type(const TypePtr* declared, const TypePtr* verify) const { +#ifdef ASSERT + if (Node::in_dump() || VMError::is_error_reported()) { + return; + } + + if (verify == nullptr) { + return; + } + + if (declared == nullptr) { + assert(ideal_Opcode() == Op_PrefetchAllocation, "must either not declare an adr_type, or declare a non-null adr_type"); + return; + } + + if (declared != verify && declared->cast_to_ptr_type(TypePtr::BotPTR) != verify) { + stringStream ss; + verify->dump_on(&ss); ss.cr(); + declared->dump_on(&ss); ss.cr(); + tty->print_cr("%s", ss.as_string()); + assert(false, "adr_type may be incorrect"); + } +#endif // ASSERT +} + +// Fall-back computation for nodes that miss _adr_type assignments due to post-match expansion, +// seen on PPC +const TypePtr* MachNode::compute_adr_type_from_inputs() const { intptr_t offset = 0; const TypePtr *adr_type = TYPE_PTR_SENTINAL; // attempt computing adr_type const Node *base = get_base_and_disp(offset, adr_type); @@ -573,7 +640,7 @@ void MachNode::dump_spec(outputStream *st) const { st->print(" _"); } } - const TypePtr *t = adr_type(); + const TypePtr* t = in_adr_type(); if( t ) { Compile* C = Compile::current(); if( C->alias_type(t)->is_volatile() ) @@ -660,22 +727,6 @@ const Type *MachProjNode::bottom_type() const { return Type::mreg2type[_ideal_reg]; } -const TypePtr *MachProjNode::adr_type() const { - if (bottom_type() == Type::MEMORY) { - // in(0) might be a narrow MemBar; otherwise we will report TypePtr::BOTTOM - Node* ctrl = in(0); - if (ctrl == nullptr) return nullptr; // node is dead - const TypePtr* adr_type = ctrl->adr_type(); - #ifdef ASSERT - if (!VMError::is_error_reported() && !Node::in_dump()) - assert(adr_type != nullptr, "source must have adr_type"); - #endif - return adr_type; - } - assert(bottom_type()->base() != Type::Memory, "no other memories?"); - return nullptr; -} - #ifndef PRODUCT void MachProjNode::dump_spec(outputStream *st) const { ProjNode::dump_spec(st); @@ -701,15 +752,6 @@ const RegMask &MachReturnNode::in_RegMask( uint idx ) const { return _in_rms[idx]; } -const TypePtr *MachReturnNode::adr_type() const { - // most returns and calls are assumed to consume & modify all of memory - // the matcher will copy non-wide adr_types from ideal originals - return _adr_type; -} - -//============================================================================= -const Type *MachSafePointNode::bottom_type() const { return TypeTuple::MEMBAR; } - //------------------------------Registers-------------------------------------- const RegMask &MachSafePointNode::in_RegMask( uint idx ) const { // Values in the domain use the users calling convention, embodied in the @@ -893,13 +935,6 @@ JVMState *MachHaltNode::jvms() const { return &jvms_for_throw; } -uint MachMemBarNode::size_of() const { return sizeof(*this); } - -const TypePtr *MachMemBarNode::adr_type() const { - return _adr_type; -} - - //============================================================================= #ifndef PRODUCT void labelOper::int_format(PhaseRegAlloc *ra, const MachNode *node, outputStream *st) const { diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp index fda3f41db704..ee94e84cfeb4 100644 --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -57,6 +57,8 @@ class PhaseRegAlloc; class RegMask; class State; +#define TYPE_PTR_SENTINAL ((const TypePtr*)-1) + //---------------------------MachOper------------------------------------------ class MachOper : public ResourceObj { public: @@ -220,8 +222,12 @@ class MachOper : public ResourceObj { // Base type for all machine specific nodes. All node classes generated by the // ADLC inherit from this class. class MachNode : public Node { +private: + friend class Matcher; + public: - MachNode() : Node((uint)0), _bottom_type(nullptr), _barrier(0), _num_opnds(0), _opnds(nullptr) { + MachNode() : Node((uint)0), _bottom_type(nullptr), _out_adr_type(TYPE_PTR_SENTINAL), _in_adr_type(TYPE_PTR_SENTINAL), + _barrier(0), _num_opnds(0), _opnds(nullptr) { init_class_id(Class_Mach); } // Required boilerplate @@ -286,6 +292,13 @@ class MachNode : public Node { // Capture the type of the matched ideal node const Type* _bottom_type; +private: + // Capture the adr_type of the matched ideal node, it is the union of the adr_type of all nodes + // that are matched into it + const TypePtr* _out_adr_type; + const TypePtr* _in_adr_type; + +public: // The GC might require some barrier metadata for machine code emission. uint8_t _barrier; @@ -362,7 +375,7 @@ class MachNode : public Node { // which serves as node bottom. // If the offset is not statically determined, set it to Type::OffsetBot. // This method is free to ignore stack slots if that helps. - #define TYPE_PTR_SENTINAL ((const TypePtr*)-1) + // Passing TYPE_PTR_SENTINAL as adr_type asks for computation of the adr_type if possible const Node* get_base_and_disp(intptr_t &offset, const TypePtr* &adr_type) const; @@ -378,9 +391,6 @@ class MachNode : public Node { // If it returns (MachOper*)-1, this means there are multiple memories. virtual const MachOper* memory_operand() const { return nullptr; } - // Call "get_base_and_disp" to decide which category of memory is used here. - virtual const class TypePtr *adr_type() const; - // Apply peephole rule(s) to this instruction virtual int peephole(Block *block, int block_index, PhaseCFG* cfg_, PhaseRegAlloc *ra_); @@ -417,6 +427,14 @@ class MachNode : public Node { virtual void dump_spec(outputStream *st) const; // Print per-node info void dump_format(PhaseRegAlloc *ra, outputStream *st) const; // access to virtual #endif + +private: + const TypePtr* out_adr_type_impl() const final; + const TypePtr* in_adr_type_impl() const final; + + // Call "get_base_and_disp" to decide which category of memory is used here. + void verify_adr_type(const TypePtr* declared, const TypePtr* verify) const; + const TypePtr* compute_adr_type_from_inputs() const; }; //------------------------------MachIdealNode---------------------------- @@ -805,7 +823,6 @@ class MachProjNode : public ProjNode { }; virtual int Opcode() const; virtual const Type *bottom_type() const; - virtual const TypePtr *adr_type() const; virtual const RegMask& in_RegMask(uint) const { return RegMask::EMPTY; } virtual const RegMask &out_RegMask() const { return _rout; } virtual uint ideal_reg() const { return _ideal_reg; } @@ -860,17 +877,12 @@ class MachReturnNode : public MachNode { public: RegMask *_in_rms; // Input register masks, set during allocation ReallocMark _nesting; // assertion check for reallocations - const TypePtr* _adr_type; // memory effects of call or return MachReturnNode() : MachNode() { init_class_id(Class_MachReturn); - _adr_type = TypePtr::BOTTOM; // the default: all of memory } - void set_adr_type(const TypePtr* atp) { _adr_type = atp; } - virtual const RegMask &in_RegMask(uint) const; virtual bool pinned() const { return true; }; - virtual const TypePtr *adr_type() const; }; //------------------------------MachSafePointNode----------------------------- @@ -892,7 +904,7 @@ class MachSafePointNode : public MachReturnNode { void set_jvms(JVMState* s) { _jvms = s; } - virtual const Type *bottom_type() const; + virtual const Type* bottom_type() const { return Type::CONTROL; } virtual const RegMask &in_RegMask(uint) const; @@ -1057,7 +1069,6 @@ class MachCallRuntimeNode : public MachCallNode { virtual uint size_of() const; // Size is bigger public: const char *_name; // Printable name, if _method is null - bool _leaf_no_fp; // Is this CallLeafNoFP? MachCallRuntimeNode() : MachCallNode() { init_class_id(Class_MachCallRuntime); } @@ -1088,19 +1099,12 @@ class MachHaltNode : public MachReturnNode { }; class MachMemBarNode : public MachNode { - virtual uint size_of() const; // Size is bigger public: - const TypePtr* _adr_type; // memory effects MachMemBarNode() : MachNode() { init_class_id(Class_MachMemBar); - _adr_type = TypePtr::BOTTOM; // the default: all of memory } - - void set_adr_type(const TypePtr* atp) { _adr_type = atp; } - virtual const TypePtr *adr_type() const; }; - //------------------------------MachTempNode----------------------------------- // Node used by the adlc to construct inputs to represent temporary registers class MachTempNode : public MachNode { diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 6285f4810a4e..745b7a63c9d4 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -121,9 +121,9 @@ CallNode* PhaseMacroExpand::make_slow_call(CallNode *oldcall, const TypeFunc* sl Node* parm0, Node* parm1, Node* parm2) { // Slow-path call - CallNode *call = leaf_name - ? (CallNode*)new CallLeafNode ( slow_call_type, slow_call, leaf_name, TypeRawPtr::BOTTOM ) - : (CallNode*)new CallStaticJavaNode( slow_call_type, slow_call, OptoRuntime::stub_name(slow_call), TypeRawPtr::BOTTOM ); + CallNode* call = leaf_name + ? (CallNode*)new CallLeafNode (slow_call_type, slow_call, leaf_name, TypeRawPtr::BOTTOM, TypePtr::BOTTOM) + : (CallNode*)new CallStaticJavaNode(slow_call_type, slow_call, OptoRuntime::stub_name(slow_call), TypeRawPtr::BOTTOM, TypePtr::BOTTOM); // Slow path call has no side-effects, uses few values copy_predefined_input_for_runtime_call(slow_path, oldcall, call ); @@ -193,7 +193,7 @@ static Node *scan_mem_chain(Node *mem, int alias_idx, int offset, Node *start_me #endif } } else if (mem->is_Store()) { - const TypePtr* atype = mem->as_Store()->adr_type(); + const TypePtr* atype = mem->as_Store()->out_adr_type(); int adr_idx = phase->C->get_alias_index(atype); if (adr_idx == alias_idx) { assert(atype->isa_oopptr(), "address type must be oopptr"); @@ -598,12 +598,12 @@ Node* PhaseMacroExpand::value_from_mem(Node* origin, Node* ctl, BasicType ft, co if (mem == nullptr) { done = true; // Something went wrong. } else if (mem->is_Store()) { - const TypePtr* atype = mem->as_Store()->adr_type(); + const TypePtr* atype = mem->as_Store()->out_adr_type(); assert(C->get_alias_index(atype) == Compile::AliasIdxRaw, "store is correct memory slice"); done = true; } } else if (mem->is_Store()) { - const TypeOopPtr* atype = mem->as_Store()->adr_type()->isa_oopptr(); + const TypeOopPtr* atype = mem->as_Store()->out_adr_type()->isa_oopptr(); assert(atype != nullptr, "address type must be oopptr"); assert(C->get_alias_index(atype) == alias_idx && atype->is_known_instance_field() && atype->flat_offset() == offset && @@ -1889,9 +1889,9 @@ void PhaseMacroExpand::expand_allocate_common( } // Generate slow-path call - CallNode *call = new CallStaticJavaNode(slow_call_type, slow_call_address, + CallNode* call = new CallStaticJavaNode(slow_call_type, slow_call_address, OptoRuntime::stub_name(slow_call_address), - TypePtr::BOTTOM); + TypePtr::BOTTOM, TypePtr::BOTTOM); call->init_req(TypeFunc::Control, slow_region); call->init_req(TypeFunc::I_O, top()); // does no i/o call->init_req(TypeFunc::Memory, slow_mem); // may gc ptrs @@ -2135,7 +2135,7 @@ void PhaseMacroExpand::expand_initialize_membar(AllocateNode* alloc, InitializeN transform_later(ctrl); Node* old_raw_mem_proj = nullptr; auto find_raw_mem = [&](ProjNode* proj) { - if (C->get_alias_index(proj->adr_type()) == Compile::AliasIdxRaw) { + if (C->get_alias_index(proj->out_adr_type()) == Compile::AliasIdxRaw) { assert(old_raw_mem_proj == nullptr, "only one expected"); old_raw_mem_proj = proj; } @@ -2175,7 +2175,7 @@ void PhaseMacroExpand::expand_dtrace_alloc_probe(AllocateNode* alloc, Node* oop, CAST_FROM_FN_PTR(address, static_cast(SharedRuntime::dtrace_object_alloc)), "dtrace_object_alloc", - TypeRawPtr::BOTTOM); + TypeRawPtr::BOTTOM, TypeRawPtr::BOTTOM); // Get base of thread-local storage area Node* thread = new ThreadLocalNode(); @@ -2896,7 +2896,7 @@ void PhaseMacroExpand::expand_mh_intrinsic_return(CallStaticJavaNode* call) { handler_call = new CallLeafNoFPNode(OptoRuntime::pack_value_type_Type(), nullptr, "pack handler", - TypeRawPtr::BOTTOM); + TypeRawPtr::BOTTOM, TypeRawPtr::BOTTOM); handler_call->init_req(TypeFunc::Control, fast_oop_ctrl); handler_call->init_req(TypeFunc::Memory, fast_oop_rawmem); handler_call->init_req(TypeFunc::I_O, top()); @@ -2912,7 +2912,7 @@ void PhaseMacroExpand::expand_mh_intrinsic_return(CallStaticJavaNode* call) { CallStaticJavaNode* slow_call = new CallStaticJavaNode(OptoRuntime::store_value_type_fields_Type(), SharedRuntime::store_value_type_fields_to_buf_entry(), "store_value_type_fields", - TypePtr::BOTTOM); + TypePtr::BOTTOM, TypePtr::BOTTOM); slow_call->init_req(TypeFunc::Control, needgc_ctrl); slow_call->init_req(TypeFunc::Memory, mem); slow_call->init_req(TypeFunc::I_O, io); @@ -3088,13 +3088,20 @@ void PhaseMacroExpand::expand_subtypecheck_node(SubTypeCheckNode *check) { // ... // } void PhaseMacroExpand::expand_flatarraycheck_node(FlatArrayCheckNode* check) { + assert(UseArrayFlattening, "Should not be called if flat arrays are not enabled"); bool use_mark_word = _igvn.type(check->in(FlatArrayCheckNode::ArrayOrKlass))->isa_oopptr() != nullptr; - Node* bol = check->unique_out(); - for (DUIterator_Fast imax, i = bol->fast_outs(imax); i < imax; i++) { - if (!bol->fast_out(i)->is_If()) { - // No control input, fall back to layout helper check - use_mark_word = false; - break; + Unique_Node_List outputs; + + for (DUIterator_Fast imax, i = check->fast_outs(imax); i < imax; i++) { + Node* bol = check->fast_out(i); + assert(bol->is_Bool(), "expected Bool node, but got: %s", bol->Name()); + outputs.push(bol); + for (DUIterator_Fast jmax, j = bol->fast_outs(jmax); j < jmax; j++) { + if (!bol->fast_out(j)->is_If()) { + // No control input, fall back to layout helper check + use_mark_word = false; + break; + } } } @@ -3112,16 +3119,26 @@ void PhaseMacroExpand::expand_flatarraycheck_node(FlatArrayCheckNode* check) { } assert(!mark->is_Con(), "Should have been optimized out"); - // Replace the bool node - assert(bol->is_Bool() && bol->as_Bool()->_test._test == BoolTest::ne, "unexpected condition"); - // Check if flat array bits are set Node* mask = MakeConX(markWord::flat_array_bit_in_place); Node* masked = _igvn.transform(new AndXNode(mark, mask)); Node* cmp = _igvn.transform(new CmpXNode(masked, MakeConX(0))); - Node* is_not_flat = _igvn.transform(new BoolNode(cmp, BoolTest::eq)); - _igvn.replace_node(bol, is_not_flat); + // Replace the bool nodes + for (uint i = 0; i < outputs.size(); ++i) { + Node* bol = outputs.at(i); + assert(bol->as_Bool()->_test._test == BoolTest::ne || bol->as_Bool()->_test._test == BoolTest::eq, "unexpected condition"); + + // FlatArrayCheckNode returns true if one of the inputs is flat. + // - Bool(FlatArrayCheckNode, !=) is testing that the arrays are not flat, + // which means that none of the mark words has the flatness bit on, + // that is `mark & mask == 0`, so we need to build Bool(Cmp(mark & mask, 0), ==). + // - Similarly, Bool(FlatArrayCheckNode, ==) is testing that at least one + // array is flat, so we need to build Bool(Cmp(mark & mask, 0), !=). + // Hence, the .negate() thereunder. + Node* flatness_test = _igvn.transform(new BoolNode(cmp, bol->as_Bool()->_test.negate())); + _igvn.replace_node(bol, flatness_test); + } _igvn.replace_node(check, C->top()); } else { // Fall back to layout helper check @@ -3144,16 +3161,18 @@ void PhaseMacroExpand::expand_flatarraycheck_node(FlatArrayCheckNode* check) { } Node* masked = transform_later(new AndINode(lhs, intcon(Klass::_lh_array_tag_flat_value_bit_inplace))); Node* cmp = transform_later(new CmpINode(masked, intcon(0))); - Node* new_bol = transform_later(new BoolNode(cmp, BoolTest::eq)); - Node* m2b = transform_later(new Conv2BNode(masked)); - // The matcher expects the input to If/CMove nodes to be produced by a Bool(CmpI..) - // pattern, but the input to other potential users (e.g. Phi) to be some - // other pattern (e.g. a Conv2B node, possibly idealized as a CMoveI). - for (DUIterator_Last imin, i = bol->last_outs(imin); i >= imin; --i) { - Node* user = bol->last_out(i); - for (uint j = 0; j < user->req(); j++) { - if (user->in(j) == bol) { - _igvn.replace_input_of(user, j, (user->is_If() || user->is_CMove()) ? new_bol : m2b); + + for (uint i = 0; i < outputs.size(); ++i) { + Node* bol = outputs.at(i); + for (DUIterator_Last jmin, j = bol->last_outs(jmin); j >= jmin; --j) { + Node* user = bol->last_out(j); + // See comment above for the logic of the .negate(). + Node* new_bol = transform_later(new BoolNode(cmp, bol->as_Bool()->_test.negate())); + assert(user->is_If() || user->is_CMove(), "must be a legit user of Bool node"); + for (uint k = 0; k < user->req(); k++) { + if (user->in(k) == bol) { + _igvn.replace_input_of(user, k, new_bol); + } } } } diff --git a/src/hotspot/share/opto/macroArrayCopy.cpp b/src/hotspot/share/opto/macroArrayCopy.cpp index f37f66a474eb..72cc6bccc804 100644 --- a/src/hotspot/share/opto/macroArrayCopy.cpp +++ b/src/hotspot/share/opto/macroArrayCopy.cpp @@ -89,7 +89,7 @@ Node* PhaseMacroExpand::make_leaf_call(Node* ctrl, Node* mem, Node* parm2, Node* parm3, Node* parm4, Node* parm5, Node* parm6, Node* parm7) { - Node* call = new CallLeafNoFPNode(call_type, call_addr, call_name, adr_type); + Node* call = new CallLeafNoFPNode(call_type, call_addr, call_name, adr_type, TypePtr::BOTTOM); call->init_req(TypeFunc::Control, ctrl); call->init_req(TypeFunc::I_O , top()); call->init_req(TypeFunc::Memory , mem); @@ -306,10 +306,12 @@ Node* PhaseMacroExpand::mark_word_test(Node** ctrl, Node* obj, MergeMemNode* mem } Node* PhaseMacroExpand::generate_flat_array_guard(Node** ctrl, Node* array, MergeMemNode* mem, RegionNode* region) { + assert(UseArrayFlattening, "Should not be called if flat arrays are disabled"); return mark_word_test(ctrl, array, mem, markWord::flat_array_bit_in_place, region); } Node* PhaseMacroExpand::generate_null_free_array_guard(Node** ctrl, Node* array, MergeMemNode* mem, RegionNode* region) { + assert(Arguments::is_valhalla_enabled(), "Should not be called if value types are disabled"); return mark_word_test(ctrl, array, mem, markWord::null_free_array_bit_in_place, region); } @@ -1123,7 +1125,7 @@ MergeMemNode* PhaseMacroExpand::generate_slow_arraycopy(ArrayCopyNode *ac, const TypeFunc* call_type = OptoRuntime::slow_arraycopy_Type(); CallNode* call = new CallStaticJavaNode(call_type, OptoRuntime::slow_arraycopy_Java(), - "slow_arraycopy", TypePtr::BOTTOM); + "slow_arraycopy", TypePtr::BOTTOM, TypePtr::BOTTOM); call->init_req(TypeFunc::Control, *ctrl); call->init_req(TypeFunc::I_O , *io); @@ -1298,6 +1300,7 @@ void PhaseMacroExpand::generate_unchecked_arraycopy(Node** ctrl, MergeMemNode** const TypePtr* PhaseMacroExpand::adjust_for_flat_array(const TypeAryPtr* top_dest, Node*& src_offset, Node*& dest_offset, Node*& length, BasicType& dest_elem, Node*& dest_length) { + assert(UseArrayFlattening, "Should not be called if flat arrays are not enabled"); #ifdef ASSERT assert(top_dest->elem()->make_ptr()->is_instptr()->is_valueklassptr(), "must be concrete value klass"); BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); @@ -1590,8 +1593,10 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { // We need to be careful here because 'adjust_for_flat_array' will adjust offsets/length etc. which then does not work anymore for the slow call to SharedRuntime::slow_arraycopy_C. assert(top_src->is_flat() == top_dest->is_flat(), "must have bailed out before"); if (Arguments::is_valhalla_enabled() && !flat_and_same_nullness) { - generate_flat_array_guard(&ctrl, src, merge_mem, slow_region); - generate_flat_array_guard(&ctrl, dest, merge_mem, slow_region); + if (UseArrayFlattening) { + generate_flat_array_guard(&ctrl, src, merge_mem, slow_region); + generate_flat_array_guard(&ctrl, dest, merge_mem, slow_region); + } generate_null_free_array_guard(&ctrl, dest, merge_mem, slow_region); } } diff --git a/src/hotspot/share/opto/matcher.cpp b/src/hotspot/share/opto/matcher.cpp index 46640b2d8618..c780710796dc 100644 --- a/src/hotspot/share/opto/matcher.cpp +++ b/src/hotspot/share/opto/matcher.cpp @@ -31,6 +31,7 @@ #include "opto/addnode.hpp" #include "opto/callnode.hpp" #include "opto/idealGraphPrinter.hpp" +#include "opto/machnode.hpp" #include "opto/matcher.hpp" #include "opto/memnode.hpp" #include "opto/movenode.hpp" @@ -65,8 +66,6 @@ Matcher::Matcher() _dontcare(&_states_arena), _reduceOp(reduceOp), _leftOp(leftOp), _rightOp(rightOp), _swallowed(swallowed), - _begin_inst_chain_rule(_BEGIN_INST_CHAIN_RULE), - _end_inst_chain_rule(_END_INST_CHAIN_RULE), _must_clone(must_clone), _shared_nodes(C->comp_arena()), #ifndef PRODUCT @@ -967,15 +966,15 @@ void Matcher::init_spill_mask( Node *ret ) { #ifdef ASSERT static void match_alias_type(Compile* C, Node* n, Node* m) { if (!VerifyAliases) return; // do not go looking for trouble by default - const TypePtr* nat = n->adr_type(); - const TypePtr* mat = m->adr_type(); + const TypePtr* nat = n->out_adr_type(); + const TypePtr* mat = m->out_adr_type(); int nidx = C->get_alias_index(nat); int midx = C->get_alias_index(mat); // Detune the assert for cases like (AndI 0xFF (LoadB p)). if (nidx == Compile::AliasIdxTop && midx >= Compile::AliasIdxRaw) { for (uint i = 1; i < n->req(); i++) { Node* n1 = n->in(i); - const TypePtr* n1at = n1->adr_type(); + const TypePtr* n1at = n1->out_adr_type(); if (n1at != nullptr) { nat = n1at; nidx = C->get_alias_index(n1at); @@ -1073,9 +1072,6 @@ Node *Matcher::xform( Node *n, int max_stack ) { m = n->is_SafePoint() ? match_sfpt(n->as_SafePoint()):match_tree(n); if (C->failing()) return nullptr; if (m == nullptr) { Matcher::soft_match_failure(); return nullptr; } - if (n->is_MemBar()) { - m->as_MachMemBar()->set_adr_type(n->adr_type()); - } } else { // Nothing the matcher cares about if (n->is_Proj() && n->in(0) != nullptr && n->in(0)->is_Multi()) { // Projections? if (n->in(0)->is_Initialize() && n->as_Proj()->_con == TypeFunc::Memory) { @@ -1261,7 +1257,6 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { else if( mcall->is_MachCallRuntime() ) { MachCallRuntimeNode* mach_call_rt = mcall->as_MachCallRuntime(); mach_call_rt->_name = call->as_CallRuntime()->_name; - mach_call_rt->_leaf_no_fp = call->is_CallLeafNoFP(); } msfpt = mcall; } @@ -1276,9 +1271,6 @@ MachNode *Matcher::match_sfpt( SafePointNode *sfpt ) { } msfpt->_has_ea_local_in_scope = sfpt->has_ea_local_in_scope(); - // Advertise the correct memory effects (for anti-dependence computation). - msfpt->set_adr_type(sfpt->adr_type()); - // Allocate a private array of RegMasks. These RegMasks are not shared. msfpt->_in_rms = NEW_RESOURCE_ARRAY( RegMask, cnt ); // Empty them all. @@ -1782,24 +1774,24 @@ MachNode *Matcher::ReduceInst( State *s, int rule, Node *&mem ) { m = _mem_node; assert(m != nullptr && m->is_Mem(), "expecting memory node"); } - const Type* mach_at = mach->adr_type(); + const TypePtr* mach_at = mach->in_adr_type(); // DecodeN node consumed by an address may have different type // than its input. Don't compare types for such case. - if (m->adr_type() != mach_at && + if (m->in_adr_type() != mach_at && (m->in(MemNode::Address)->is_DecodeNarrowPtr() || (m->in(MemNode::Address)->is_AddP() && m->in(MemNode::Address)->in(AddPNode::Address)->is_DecodeNarrowPtr()) || (m->in(MemNode::Address)->is_AddP() && m->in(MemNode::Address)->in(AddPNode::Address)->is_AddP() && m->in(MemNode::Address)->in(AddPNode::Address)->in(AddPNode::Address)->is_DecodeNarrowPtr()))) { - mach_at = m->adr_type(); + mach_at = m->in_adr_type(); } - if (m->adr_type() != mach_at) { + if (m->in_adr_type() != mach_at) { m->dump(); tty->print_cr("mach:"); mach->dump(1); } - assert(m->adr_type() == mach_at, "matcher should not change adr type"); + assert(m->in_adr_type() == mach_at, "matcher should not change adr type"); } #endif } @@ -1854,6 +1846,27 @@ void Matcher::handle_precedence_edges(Node* n, MachNode *mach) { } } +void Matcher::combine_adr_type(const Node* n, MachNode* mach) const { + if (mach->_out_adr_type == TYPE_PTR_SENTINAL) { + assert(mach->_in_adr_type == TYPE_PTR_SENTINAL, "must initialize at the same time"); + mach->_out_adr_type = nullptr; + mach->_in_adr_type = nullptr; + } + + // Merge the adr_type of all nodes in the subtree into the adr_type of 'mach' + if (const TypePtr* leaf_out_adr_type = n->out_adr_type(); leaf_out_adr_type != nullptr) { + // Only support a single out_adr_type, there must not be multiple memory-producing nodes + assert(mach->_out_adr_type == nullptr, "must only have a single memory-producing node"); + mach->_out_adr_type = leaf_out_adr_type; + } + if (const TypePtr* leaf_in_adr_type = n->in_adr_type(); leaf_in_adr_type != nullptr) { + // Only support a single in_adr_type, even if there are multiple nodes that consume memory, + // they must consume the same memory + assert(mach->_in_adr_type == nullptr || mach->_in_adr_type == leaf_in_adr_type, "inconsistent in_adr_type in a subtree"); + mach->_in_adr_type = leaf_in_adr_type; + } +} + void Matcher::ReduceInst_Chain_Rule(State* s, int rule, Node* &mem, MachNode* mach) { // 'op' is what I am expecting to receive int op = _leftOp[rule]; @@ -1888,6 +1901,7 @@ void Matcher::ReduceInst_Chain_Rule(State* s, int rule, Node* &mem, MachNode* ma uint Matcher::ReduceInst_Interior( State *s, int rule, Node *&mem, MachNode *mach, uint num_opnds ) { handle_precedence_edges(s->_leaf, mach); + combine_adr_type(s->_leaf, mach); if( s->_leaf->is_Load() ) { Node *mem2 = s->_leaf->in(MemNode::Memory); @@ -1973,6 +1987,7 @@ void Matcher::ReduceOper( State *s, int rule, Node *&mem, MachNode *mach ) { } handle_precedence_edges(s->_leaf, mach); + combine_adr_type(s->_leaf, mach); if( s->_leaf->in(0) && s->_leaf->req() > 1) { if( !mach->in(0) ) diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index 07b8d1c6cd9d..c5f656b738f6 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -108,10 +108,6 @@ class Matcher : public PhaseTransform { // Map dense opcode number to info on when rule is swallowed constant. const bool *_swallowed; - // Map dense rule number to determine if this is an instruction chain rule - const uint _begin_inst_chain_rule; - const uint _end_inst_chain_rule; - // We want to clone constants and possible CmpI-variants. // If we do not clone CmpI, then we can have many instances of // condition codes alive at once. This is OK on some chips and @@ -169,6 +165,7 @@ class Matcher : public PhaseTransform { MachNode* _mach_null; void handle_precedence_edges(Node* n, MachNode *mach); + void combine_adr_type(const Node* n, MachNode* mach) const; public: int LabelRootDepth; @@ -178,7 +175,6 @@ class Matcher : public PhaseTransform { RegMask *idealreg2debugmask [_last_machine_leaf]; void init_spill_mask( Node *ret ); // Convert machine register number to register mask - static uint mreg2regmask_max; static RegMask mreg2regmask[]; static RegMask STACK_ONLY_mask; static RegMask caller_save_regmask; diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index a522ea77064e..2659eff3fa92 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -58,6 +58,7 @@ #include "opto/regmask.hpp" #include "opto/rootnode.hpp" #include "opto/traceMergeStoresTag.hpp" +#include "opto/type.hpp" #include "opto/valuetypenode.hpp" #include "opto/vectornode.hpp" #include "runtime/arguments.hpp" @@ -77,7 +78,7 @@ static Node *step_through_mergemem(PhaseGVN *phase, MergeMemNode *mmem, const T //============================================================================= uint MemNode::size_of() const { return sizeof(*this); } -const TypePtr *MemNode::adr_type() const { +const TypePtr* MemNode::in_adr_type_impl() const { Node* adr = in(Address); if (adr == nullptr) return nullptr; // node is dead const TypePtr* cross_check = nullptr; @@ -363,7 +364,7 @@ Node *MemNode::optimize_memory_chain(Node *mchain, const TypePtr *t_adr, Node *l if (is_instance && igvn != nullptr && result->is_Phi()) { PhiNode* phi = result->as_Phi(); assert(phi->bottom_type() == Type::MEMORY, "memory phi required"); - const TypePtr* phi_at = phi->adr_type(); + const TypePtr* phi_at = phi->out_adr_type(); assert(t_oop->higher_equal(phi_at), "a memory Phi must have the a wider adr_type than an access"); const TypePtr* t_oop_widened = phase->C->get_adr_type(phase->C->get_alias_index(t_oop)); if (t_oop_widened != phi_at) { @@ -501,7 +502,7 @@ Node *MemNode::Ideal_common(PhaseGVN *phase, bool can_reshape) { if (can_reshape && igvn != nullptr && (igvn->_worklist.member(address) || - (igvn->_worklist.size() > 0 && t_adr != adr_type())) ) { + (igvn->_worklist.size() > 0 && t_adr != in_adr_type())) ) { // The address's base and type may change when the address is processed. // Delay this mem node transformation until the address is processed. igvn->_worklist.push(this); @@ -537,7 +538,7 @@ Node *MemNode::Ideal_common(PhaseGVN *phase, bool can_reshape) { MergeMemNode* mmem = mem->as_MergeMem(); const TypePtr *tp = t_adr->is_ptr(); - mem = step_through_mergemem(phase, mmem, tp, adr_type(), tty); + mem = step_through_mergemem(phase, mmem, tp, in_adr_type(), tty); } if (mem != old_mem) { @@ -899,7 +900,7 @@ AccessAnalyzer::AccessAnalyzer(PhaseGVN* phase, MemNode* n) _base = AddPNode::Ideal_base_and_offset(adr, _phase, _offset); _maybe_raw = MemNode::check_if_adr_maybe_raw(adr); _alloc = AllocateNode::Ideal_allocation(_base); - _adr_type = _n->adr_type(); + _adr_type = _n->in_adr_type(); if (_adr_type != nullptr && _adr_type->base() != TypePtr::AnyPtr) { // Avoid the cases that will upset Compile::get_alias_index @@ -1090,7 +1091,7 @@ void LoadNode::dump_spec(outputStream *st) const { st->print("unknown control"); } else if (control_dependency() == Pinned) { st->print("pinned"); - } else if (adr_type() == TypeRawPtr::BOTTOM) { + } else if (in_adr_type() == TypeRawPtr::BOTTOM) { st->print("raw access"); } else { st->print("unknown reason"); @@ -2210,7 +2211,7 @@ Node* LoadNode::Ideal_load_common(PhaseGVN* phase, bool can_reshape) { // Is there a dominating load that loads the same value? Leave // anything that is not a load of a field/array element (like // barriers etc.) alone - if (in(0) != nullptr && !adr_type()->isa_rawptr() && can_reshape) { + if (in(0) != nullptr && !in_adr_type()->isa_rawptr() && can_reshape) { for (DUIterator_Fast imax, i = mem->fast_outs(imax); i < imax; i++) { Node* use = mem->fast_out(i); if (use != this && @@ -2974,7 +2975,7 @@ LoadNode* LoadNode::clone_pinned() const { // those case, since there is not a dependency between the node and its control input, we do not // need to pin it. LoadNode* LoadNode::pin_node_under_control_impl() const { - const TypePtr* adr_type = this->adr_type(); + const TypePtr* adr_type = this->in_adr_type(); if (adr_type != nullptr && adr_type->isa_aryptr()) { // Only array accesses have dependencies on their control input return clone_pinned(); @@ -3754,7 +3755,7 @@ StoreNode* MergePrimitiveStores::make_merged_store(const Node_List& merge_list, Node* first_mem = first_store->in(MemNode::Memory); Node* first_adr = first_store->in(MemNode::Address); - const TypePtr* new_adr_type = _store->adr_type(); + const TypePtr* new_adr_type = _store->out_adr_type(); int new_memory_size = _store->memory_size() * merge_list.size(); BasicType bt = T_ILLEGAL; @@ -3805,7 +3806,7 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { // Back-to-back stores to same address? Fold em up. Generally // unsafe if I have intervening uses... if ((!this->is_StoreVector() || this->Opcode() == Op_StoreVector) && - phase->C->get_adr_type(phase->C->get_alias_index(adr_type())) != TypeAryPtr::INLINES) { + phase->C->get_adr_type(phase->C->get_alias_index(out_adr_type())) != TypeAryPtr::INLINES) { Node* st = mem; // If Store 'st' has more than one use, we cannot fold 'st' away. // For example, 'st' might be the final state at a conditional @@ -3821,7 +3822,7 @@ Node *StoreNode::Ideal(PhaseGVN *phase, bool can_reshape) { assert(Opcode() == st->Opcode() || st->Opcode() == Op_StoreVector || Opcode() == Op_StoreVector || - phase->C->get_alias_index(adr_type()) == Compile::AliasIdxRaw || + phase->C->get_alias_index(out_adr_type()) == Compile::AliasIdxRaw || (Opcode() == Op_StoreL && st->Opcode() == Op_StoreI) || // expanded ClearArrayNode (Opcode() == Op_StoreI && st->Opcode() == Op_StoreL) || // initialization by arraycopy (Opcode() == Op_StoreL && st->Opcode() == Op_StoreN) || @@ -4354,7 +4355,7 @@ LoadStoreNode::LoadStoreNode( Node *c, Node *mem, Node *adr, Node *val, const Ty init_req(MemNode::Address, adr); init_req(MemNode::ValueIn, val); init_class_id(Class_LoadStore); - DEBUG_ONLY(_adr_type = at; adr_type();) + DEBUG_ONLY(_adr_type = at; out_adr_type();) } //------------------------------Value----------------------------------------- @@ -4378,7 +4379,7 @@ const Type* LoadStoreNode::Value(PhaseGVN* phase) const { return bottom_type(); } -const TypePtr* LoadStoreNode::adr_type() const { +const TypePtr* LoadStoreNode::out_adr_type_impl() const { const TypePtr* cross_check = DEBUG_ONLY(_adr_type) NOT_DEBUG(nullptr); return MemNode::calculate_adr_type(in(MemNode::Address)->bottom_type(), cross_check); } @@ -4471,7 +4472,7 @@ const Type* LoadStoreConditionalNode::Value(PhaseGVN* phase) const { //============================================================================= //-------------------------------adr_type-------------------------------------- -const TypePtr* ClearArrayNode::adr_type() const { +const TypePtr* ClearArrayNode::out_adr_type_impl() const { Node *adr = in(3); if (adr == nullptr) return nullptr; // node is dead return MemNode::calculate_adr_type(adr->bottom_type()); @@ -4677,9 +4678,9 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, } //============================================================================= -MemBarNode::MemBarNode(Compile* C, int alias_idx, Node* precedent) +MemBarNode::MemBarNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) : MultiNode(TypeFunc::Parms + (precedent == nullptr? 0: 1)), - _adr_type(C->get_adr_type(alias_idx)), _kind(Standalone) + _out_adr_type(out_adr_type), _in_adr_type(in_adr_type), _kind(Standalone) #ifdef ASSERT , _pair_idx(0) #endif @@ -4701,22 +4702,23 @@ bool MemBarNode::cmp( const Node &n ) const { //------------------------------make------------------------------------------- MemBarNode* MemBarNode::make(Compile* C, int opcode, int atp, Node* pn) { + const TypePtr* adr_type = C->get_adr_type(atp); switch (opcode) { - case Op_MemBarAcquire: return new MemBarAcquireNode(C, atp, pn); - case Op_LoadFence: return new LoadFenceNode(C, atp, pn); - case Op_MemBarRelease: return new MemBarReleaseNode(C, atp, pn); - case Op_StoreFence: return new StoreFenceNode(C, atp, pn); - case Op_MemBarStoreStore: return new MemBarStoreStoreNode(C, atp, pn); - case Op_StoreStoreFence: return new StoreStoreFenceNode(C, atp, pn); - case Op_MemBarAcquireLock: return new MemBarAcquireLockNode(C, atp, pn); - case Op_MemBarReleaseLock: return new MemBarReleaseLockNode(C, atp, pn); - case Op_MemBarStoreLoad: return new MemBarStoreLoadNode(C, atp, pn); - case Op_MemBarVolatile: return new MemBarVolatileNode(C, atp, pn); - case Op_MemBarFull: return new MemBarFullNode(C, atp, pn); - case Op_MemBarCPUOrder: return new MemBarCPUOrderNode(C, atp, pn); - case Op_OnSpinWait: return new OnSpinWaitNode(C, atp, pn); - case Op_Initialize: return new InitializeNode(C, atp, pn); - default: ShouldNotReachHere(); return nullptr; + case Op_MemBarAcquire: return new MemBarAcquireNode(C, adr_type, adr_type, pn); + case Op_LoadFence: return new LoadFenceNode(C, adr_type, adr_type, pn); + case Op_MemBarRelease: return new MemBarReleaseNode(C, adr_type, adr_type, pn); + case Op_StoreFence: return new StoreFenceNode(C, adr_type, adr_type, pn); + case Op_MemBarStoreStore: return new MemBarStoreStoreNode(C, adr_type, adr_type, pn); + case Op_StoreStoreFence: return new StoreStoreFenceNode(C, adr_type, adr_type, pn); + case Op_MemBarAcquireLock: return new MemBarAcquireLockNode(C, adr_type, adr_type, pn); + case Op_MemBarReleaseLock: return new MemBarReleaseLockNode(C, adr_type, adr_type, pn); + case Op_MemBarStoreLoad: return new MemBarStoreLoadNode(C, adr_type, adr_type, pn); + case Op_MemBarVolatile: return new MemBarVolatileNode(C, adr_type, adr_type, pn); + case Op_MemBarFull: return new MemBarFullNode(C, adr_type, adr_type, pn); + case Op_MemBarCPUOrder: return new MemBarCPUOrderNode(C, adr_type, adr_type, pn); + case Op_OnSpinWait: return new OnSpinWaitNode(C, adr_type, adr_type, pn); + case Op_Initialize: return new InitializeNode(C, pn); + default: ShouldNotReachHere(); return nullptr; } } @@ -5080,13 +5082,11 @@ MemBarNode* MemBarNode::leading_membar() const { // reasonable limit on the complexity of optimized initializations. //---------------------------InitializeNode------------------------------------ -InitializeNode::InitializeNode(Compile* C, int adr_type, Node* rawoop) - : MemBarNode(C, adr_type, rawoop), +InitializeNode::InitializeNode(Compile* C, Node* rawoop) + : MemBarNode(C, TypeRawPtr::BOTTOM, TypePtr::BOTTOM, rawoop), _is_complete(Incomplete), _does_not_escape(false) { init_class_id(Class_Initialize); - - assert(adr_type == Compile::AliasIdxRaw, "only valid atp"); assert(in(RawAddress) == rawoop, "proper init"); // Note: allocation() can be null, for secondary initialization barriers } @@ -6003,7 +6003,7 @@ void InitializeNode::replace_mem_projs_by(Node* mem, PhaseIterGVN* igvn) { bool InitializeNode::already_has_narrow_mem_proj_with_adr_type(const TypePtr* adr_type) const { auto find_proj = [&](ProjNode* proj) { - if (proj->adr_type() == adr_type) { + if (proj->out_adr_type() == adr_type) { return BREAK_AND_RETURN_CURRENT_PROJ; } return CONTINUE; @@ -6408,7 +6408,7 @@ static void verify_memory_slice(const MergeMemNode* m, int alias_idx, Node* n) { n = n->as_MergeMem()->memory_at(alias_idx); } Compile* C = Compile::current(); - const TypePtr* n_adr_type = n->adr_type(); + const TypePtr* n_adr_type = n->out_adr_type(); if (n == m->empty_memory()) { // Implicit copy of base_memory() } else if (n_adr_type != TypePtr::BOTTOM) { @@ -6449,9 +6449,9 @@ Node* MergeMemNode::memory_at(uint alias_idx) const { n = base_memory(); assert(Node::in_dump() || n == nullptr || n->bottom_type() == Type::TOP - || n->adr_type() == nullptr // address is TOP - || n->adr_type() == TypePtr::BOTTOM - || n->adr_type() == TypeRawPtr::BOTTOM + || n->out_adr_type() == nullptr // address is TOP + || n->out_adr_type() == TypePtr::BOTTOM + || n->out_adr_type() == TypeRawPtr::BOTTOM || n->is_NarrowMemProj() || !Compile::current()->do_aliasing(), "must be a wide memory"); diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index 95d36b86a535..033f5d438f2b 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -74,7 +74,7 @@ class MemNode : public Node { _unsafe_access(false), _barrier_data(0) { init_class_id(Class_Mem); - DEBUG_ONLY(_adr_type=at; adr_type();) + DEBUG_ONLY(_adr_type=at; in_adr_type();) } MemNode( Node *c0, Node *c1, Node *c2, const TypePtr* at, Node *c3 ) : Node(c0,c1,c2,c3), @@ -83,7 +83,7 @@ class MemNode : public Node { _unsafe_access(false), _barrier_data(0) { init_class_id(Class_Mem); - DEBUG_ONLY(_adr_type=at; adr_type();) + DEBUG_ONLY(_adr_type=at; in_adr_type();) } MemNode( Node *c0, Node *c1, Node *c2, const TypePtr* at, Node *c3, Node *c4) : Node(c0,c1,c2,c3,c4), @@ -92,7 +92,7 @@ class MemNode : public Node { _unsafe_access(false), _barrier_data(0) { init_class_id(Class_Mem); - DEBUG_ONLY(_adr_type=at; adr_type();) + DEBUG_ONLY(_adr_type=at; in_adr_type();) } virtual Node* find_previous_arraycopy(PhaseValues* phase, Node* ld_alloc, Node*& mem, bool can_see_stored_value) const { return nullptr; } @@ -115,8 +115,6 @@ class MemNode : public Node { return dom_result == DomResult::Dominate; } - virtual const class TypePtr *adr_type() const; // returns bottom_type of address - // Shared code for Ideal methods: Node *Ideal_common(PhaseGVN *phase, bool can_reshape); // Return -1 for short-circuit null. @@ -189,6 +187,10 @@ class MemNode : public Node { // Support for reinterpret variants used by StoreNode and LoadNode static BasicType get_reinterpret_variant(BasicType bt); bool has_reinterpret_variant(const Type* vt) const; + +private: + const TypePtr* out_adr_type_impl() const final { return is_Load() ? nullptr : in_adr_type_impl(); } + const TypePtr* in_adr_type_impl() const final; }; // Analyze a MemNode to try to prove that it is independent from other memory accesses @@ -383,7 +385,7 @@ class LoadNode : public MemNode { // manner of other optimizations). Basically, it's ugly but so is the alternative. // See comment in macro.cpp, around line 125 expand_allocate_common(). virtual bool depends_only_on_test_impl() const { - return adr_type() != TypeRawPtr::BOTTOM && _control_dependency == DependsOnlyOnTest; + return in_adr_type() != TypeRawPtr::BOTTOM && _control_dependency == DependsOnlyOnTest; } LoadNode* clone_pinned() const; @@ -899,7 +901,6 @@ class LoadStoreNode : public Node { virtual const Type *bottom_type() const { return _type; } virtual uint ideal_reg() const; - virtual const TypePtr* adr_type() const; virtual const Type* Value(PhaseGVN* phase) const; bool result_not_used() const; @@ -914,6 +915,7 @@ class LoadStoreNode : public Node { private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final; }; class LoadStoreConditionalNode : public LoadStoreNode { @@ -1172,7 +1174,6 @@ class ClearArrayNode: public Node { virtual const Type *bottom_type() const { return Type::MEMORY; } // ClearArray modifies array elements, and so affects only the // array memory addressed by the bottom_type of its base address. - virtual const class TypePtr *adr_type() const; virtual Node* Identity(PhaseGVN* phase); virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual uint match_edge(uint idx) const; @@ -1215,6 +1216,7 @@ class ClearArrayNode: public Node { private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final; }; //------------------------------MemBar----------------------------------------- @@ -1232,7 +1234,8 @@ class MemBarNode: public MultiNode { virtual uint size_of() const { return sizeof(*this); } // Memory type this node is serializing. Usually either rawptr or bottom. - const TypePtr* _adr_type; + const TypePtr* _out_adr_type; + const TypePtr* _in_adr_type; // How is this membar related to a nearby memory access? enum { @@ -1253,9 +1256,8 @@ class MemBarNode: public MultiNode { enum { Precedent = TypeFunc::Parms // optional edge to force precedence }; - MemBarNode(Compile* C, int alias_idx, Node* precedent); + MemBarNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent); virtual int Opcode() const = 0; - virtual const class TypePtr *adr_type() const { return _adr_type; } virtual const Type* Value(PhaseGVN* phase) const; virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual uint match_edge(uint idx) const { return 0; } @@ -1290,6 +1292,10 @@ class MemBarNode: public MultiNode { #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return _out_adr_type; } + const TypePtr* in_adr_type_impl() const final { return _in_adr_type; } }; // "Acquire" - no following ref can move before (but earlier refs can @@ -1297,8 +1303,8 @@ class MemBarNode: public MultiNode { // visibility. Inserted after a volatile load. class MemBarAcquireNode: public MemBarNode { public: - MemBarAcquireNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarAcquireNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1308,8 +1314,8 @@ class MemBarAcquireNode: public MemBarNode { // for intrinsic Unsafe.loadFence(). class LoadFenceNode: public MemBarNode { public: - LoadFenceNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + LoadFenceNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1318,8 +1324,8 @@ class LoadFenceNode: public MemBarNode { // multi-cpu visibility. Inserted before a volatile store. class MemBarReleaseNode: public MemBarNode { public: - MemBarReleaseNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarReleaseNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1329,8 +1335,8 @@ class MemBarReleaseNode: public MemBarNode { // for intrinsic Unsafe.storeFence(). class StoreFenceNode: public MemBarNode { public: - StoreFenceNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + StoreFenceNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1339,8 +1345,8 @@ class StoreFenceNode: public MemBarNode { // visibility. Inserted after a Lock. class MemBarAcquireLockNode: public MemBarNode { public: - MemBarAcquireLockNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarAcquireLockNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1349,15 +1355,15 @@ class MemBarAcquireLockNode: public MemBarNode { // multi-cpu visibility. Inserted before a FastUnLock. class MemBarReleaseLockNode: public MemBarNode { public: - MemBarReleaseLockNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarReleaseLockNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; class MemBarStoreStoreNode: public MemBarNode { public: - MemBarStoreStoreNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) { + MemBarStoreStoreNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) { init_class_id(Class_MemBarStoreStore); } virtual int Opcode() const; @@ -1365,15 +1371,15 @@ class MemBarStoreStoreNode: public MemBarNode { class StoreStoreFenceNode: public MemBarNode { public: - StoreStoreFenceNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + StoreStoreFenceNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; class MemBarStoreLoadNode : public MemBarNode { public: - MemBarStoreLoadNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarStoreLoadNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1381,16 +1387,16 @@ class MemBarStoreLoadNode : public MemBarNode { // Requires multi-CPU visibility? class MemBarVolatileNode: public MemBarNode { public: - MemBarVolatileNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarVolatileNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; // A full barrier blocks all loads and stores from moving across it class MemBarFullNode : public MemBarNode { public: - MemBarFullNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarFullNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1399,16 +1405,16 @@ class MemBarFullNode : public MemBarNode { // compiler because the CPU does all the ordering for us. class MemBarCPUOrderNode: public MemBarNode { public: - MemBarCPUOrderNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + MemBarCPUOrderNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; virtual uint ideal_reg() const { return 0; } // not matched in the AD file }; class OnSpinWaitNode: public MemBarNode { public: - OnSpinWaitNode(Compile* C, int alias_idx, Node* precedent) - : MemBarNode(C, alias_idx, precedent) {} + OnSpinWaitNode(Compile* C, const TypePtr* out_adr_type, const TypePtr* in_adr_type, Node* precedent) + : MemBarNode(C, out_adr_type, in_adr_type, precedent) {} virtual int Opcode() const; }; @@ -1434,7 +1440,7 @@ class InitializeNode: public MemBarNode { RawStores = TypeFunc::Parms+1 // zero or more stores (or TOP) }; - InitializeNode(Compile* C, int adr_type, Node* rawoop); + InitializeNode(Compile* C, Node* rawoop); virtual int Opcode() const; virtual uint size_of() const { return sizeof(*this); } virtual uint ideal_reg() const { return 0; } // not matched in the AD file @@ -1571,7 +1577,6 @@ class MergeMemNode: public Node { virtual uint match_edge(uint idx) const { return 0; } virtual const RegMask &out_RegMask() const; virtual const Type *bottom_type() const { return Type::MEMORY; } - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } // sparse accessors // Fetch the previously stored "set_memory_at", or else the base memory. // (Caller should clone it if it is a phi-nest.) @@ -1594,6 +1599,9 @@ class MergeMemNode: public Node { #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; #endif + +private: + const TypePtr* out_adr_type_impl() const final { return TypePtr::BOTTOM; } }; class MergeMemStream : public StackObj { @@ -1794,11 +1802,11 @@ class CacheWBNode : public Node { virtual int Opcode() const; virtual uint ideal_reg() const { return NotAMachineReg; } virtual uint match_edge(uint idx) const { return (idx == 2); } - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } virtual const Type *bottom_type() const { return Type::MEMORY; } private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final { return TypeRawPtr::BOTTOM; } }; // cachewb pre sync node for ensuring that writebacks are serialised @@ -1809,11 +1817,11 @@ class CacheWBPreSyncNode : public Node { virtual int Opcode() const; virtual uint ideal_reg() const { return NotAMachineReg; } virtual uint match_edge(uint idx) const { return false; } - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } virtual const Type *bottom_type() const { return Type::MEMORY; } private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final { return TypeRawPtr::BOTTOM; } }; // cachewb pre sync node for ensuring that writebacks are serialised @@ -1824,11 +1832,11 @@ class CacheWBPostSyncNode : public Node { virtual int Opcode() const; virtual uint ideal_reg() const { return NotAMachineReg; } virtual uint match_edge(uint idx) const { return false; } - virtual const TypePtr *adr_type() const { return TypePtr::BOTTOM; } virtual const Type *bottom_type() const { return Type::MEMORY; } private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final { return TypeRawPtr::BOTTOM; } }; //------------------------------Prefetch--------------------------------------- @@ -1844,6 +1852,7 @@ class PrefetchAllocationNode : public Node { private: virtual bool depends_only_on_test_impl() const { return false; } + const TypePtr* out_adr_type_impl() const final { return bottom_type() == Type::MEMORY ? TypeRawPtr::BOTTOM : nullptr; } }; #endif // SHARE_OPTO_MEMNODE_HPP diff --git a/src/hotspot/share/opto/mempointer.cpp b/src/hotspot/share/opto/mempointer.cpp index 68abaffe6429..2a6aa0afd991 100644 --- a/src/hotspot/share/opto/mempointer.cpp +++ b/src/hotspot/share/opto/mempointer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -344,7 +344,7 @@ bool MemPointerParser::is_native_memory_base_candidate(Node* n) { if (n->Opcode() != Op_LoadL) { return false; } LoadNode* load = n->as_Load(); - const TypeInstPtr* inst_ptr = load->adr_type()->isa_instptr(); + const TypeInstPtr* inst_ptr = load->in_adr_type()->isa_instptr(); if (inst_ptr == nullptr) { return false; } ciInstanceKlass* klass = inst_ptr->instance_klass(); @@ -396,7 +396,7 @@ bool MemPointerParser::is_safe_to_decompose_op(const int opc, const NoOverflowIn return false; } - const TypeAryPtr* ary_ptr_t = _mem->adr_type()->isa_aryptr(); + const TypeAryPtr* ary_ptr_t = _mem->in_adr_type()->isa_aryptr(); if (ary_ptr_t != nullptr) { // Array accesses that are not Unsafe always have a RangeCheck which ensures // that there is no int overflow. And without overflows, all decompositions diff --git a/src/hotspot/share/opto/multnode.cpp b/src/hotspot/share/opto/multnode.cpp index 6f763563f690..29bb6cd8b793 100644 --- a/src/hotspot/share/opto/multnode.cpp +++ b/src/hotspot/share/opto/multnode.cpp @@ -151,27 +151,6 @@ const Type *ProjNode::bottom_type() const { return proj_type(in(0)->bottom_type()); } -const TypePtr* ProjNode::adr_type() const { - if (bottom_type() != Type::MEMORY) { - assert(bottom_type()->base() != Type::Memory, "no other memories?"); - return nullptr; - } - - Node* ctrl = in(0); - // node is dead or we are in the process of removing a dead subgraph - if (ctrl == nullptr || ctrl->is_top()) { - return nullptr; - } - - const TypePtr* adr_type = ctrl->adr_type(); -#ifdef ASSERT - if (!VMError::is_error_reported() && !Node::in_dump()) { - assert(adr_type != nullptr, "source must have adr_type"); - } -#endif // ASSERT - return adr_type; -} - bool ProjNode::pinned() const { return in(0)->pinned(); } #ifndef PRODUCT void ProjNode::dump_spec(outputStream *st) const { st->print("#%d",_con); if(_is_io_use) st->print(" (i_o_use)");} @@ -301,6 +280,27 @@ CallStaticJavaNode* ProjNode::is_uncommon_trap_if_pattern(Deoptimization::DeoptR return as_IfProj()->other_if_proj()->is_uncommon_trap_proj(reason); } +const TypePtr* ProjNode::out_adr_type_impl() const { + if (bottom_type() != Type::MEMORY) { + assert(bottom_type()->base() != Type::Memory, "no other memories?"); + return nullptr; + } + + Node* ctrl = in(0); + // node is dead or we are in the process of removing a dead subgraph + if (ctrl == nullptr || ctrl->is_top()) { + return nullptr; + } + + const TypePtr* adr_type = ctrl->out_adr_type(); +#ifdef ASSERT + if (!VMError::is_error_reported() && !Node::in_dump()) { + assert(adr_type != nullptr, "source must have adr_type"); + } +#endif // ASSERT + return adr_type; +} + NarrowMemProjNode::NarrowMemProjNode(InitializeNode* src, const TypePtr* adr_type) : ProjNode(src, TypeFunc::Memory), _adr_type(adr_type) { assert(Compile::current()->have_alias_type(adr_type), "alias index should have been allocated already"); diff --git a/src/hotspot/share/opto/multnode.hpp b/src/hotspot/share/opto/multnode.hpp index 8cc6cc1e0dfa..5af3d65df54d 100644 --- a/src/hotspot/share/opto/multnode.hpp +++ b/src/hotspot/share/opto/multnode.hpp @@ -205,7 +205,6 @@ class ProjNode : public Node { virtual int Opcode() const; virtual bool is_CFG() const; virtual const Type *bottom_type() const; - virtual const TypePtr *adr_type() const; virtual bool pinned() const; virtual Node* Identity(PhaseGVN* phase); virtual const Type* Value(PhaseGVN* phase) const; @@ -226,6 +225,9 @@ class ProjNode : public Node { // other_proj->[region->..]call_uct" // null otherwise CallStaticJavaNode* is_uncommon_trap_if_pattern(Deoptimization::DeoptReason reason = Deoptimization::Reason_none) const; + +private: + virtual const TypePtr* out_adr_type_impl() const; }; // A ProjNode variant that captures an adr_type(). Used as a projection of InitializeNode to have the right adr_type() @@ -246,11 +248,12 @@ class NarrowMemProjNode : public ProjNode { public: NarrowMemProjNode(InitializeNode* src, const TypePtr* adr_type); - virtual const TypePtr* adr_type() const { + virtual int Opcode() const; + +private: + const TypePtr* out_adr_type_impl() const final { return _adr_type; } - - virtual int Opcode() const; }; template ProjNode* MultiNode::apply_to_projs(DUIterator_Fast& imax, DUIterator_Fast& i, Callback callback, uint which_proj) const { @@ -284,7 +287,7 @@ class TupleNode : public MultiNode { const TypeTuple* _tf; const TypePtr* _adr_type; - TupleNode(const TypeTuple* tf, const TypePtr* adr_type) : MultiNode(tf->cnt()), _tf(tf), _adr_type(adr_type) {} + TupleNode(const TypeTuple* tf, const TypePtr* out_adr_type) : MultiNode(tf->cnt()), _tf(tf), _adr_type(out_adr_type) {} template static void make_helper(TupleNode* tn, uint i, Node* node, NN... nn) { @@ -300,16 +303,18 @@ class TupleNode : public MultiNode { * TupleNode::make(tf, adr_type, input1, input2, input3, input4) */ template - static TupleNode* make(const TypeTuple* tf, const TypePtr* adr_type, NN... nn) { - TupleNode* tn = new TupleNode(tf, adr_type); + static TupleNode* make(const TypeTuple* tf, const TypePtr* out_adr_type, NN... nn) { + TupleNode* tn = new TupleNode(tf, out_adr_type); make_helper(tn, 0, nn...); return tn; } int Opcode() const override; const Type* bottom_type() const override { return _tf; } - const TypePtr* adr_type() const override { return _adr_type; } uint size_of() const override { return sizeof(TupleNode); } + +private: + const TypePtr* out_adr_type_impl() const final { return _adr_type; } }; #endif // SHARE_OPTO_MULTNODE_HPP diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index d16edc38e239..0619fadd23d1 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -45,8 +45,10 @@ #include "opto/valuetypenode.hpp" #include "utilities/copy.hpp" #include "utilities/macros.hpp" +#include "utilities/ostream.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/stringUtils.hpp" +#include "utilities/vmError.hpp" class RegMask; // #include "phase.hpp" @@ -1146,6 +1148,80 @@ void Node::raise_bottom_type(const Type* new_type) { } } +const TypePtr* Node::out_adr_type() const { + const TypePtr* res = out_adr_type_impl(); + +#ifdef ASSERT + if (Node::in_dump() || VMError::is_error_reported()) { + return res; + } + + // Verify that in_adr_type contains out_adr_type, except for Start, obviously it consumes nothing + if (is_Start()) { + return res; + } + + // Load must have no out_adr_type + if (is_Load() && res != nullptr) { + stringStream ss; + res->dump_on(&ss); + assert(false, "Node %s must have no out_adr_type, out: %s", Name(), ss.as_string()); + } + + const TypePtr* in_type = in_adr_type_impl(); + // Store, LoadStore, and Phi must have the same out_adr_type and in_adr_type + if ((is_Store() || is_LoadStore() || is_Phi()) && res != in_type) { + stringStream ss; + ss.print(", out: "); + if (res == nullptr) { + ss.print("nullptr"); + } else { + res->dump_on(&ss); + } + ss.print(", in: "); + if (in_type == nullptr) { + ss.print("nullptr"); + } else { + in_type->dump_on(&ss); + } + assert(false, "Node %s must have the same out_adr_type and in_adr_type%s", Name(), ss.as_string()); + } + + // For some reasons, Raw can be used as Bot (see GraphKit::set_output_for_allocation for + // example), so be lenient here + if (res != nullptr && in_type != TypePtr::BOTTOM && in_type != TypeRawPtr::BOTTOM && res != in_type) { + stringStream ss; + ss.print(", out: "); + res->dump_on(&ss); + ss.print(", in: "); + if (in_type == nullptr) { + ss.print("nullptr"); + } else { + in_type->dump_on(&ss); + } + assert(false, "Node %s: in_adr_type must contain out_adr_type%s", Name(), ss.as_string()); + } + + // Unless this node is pinned, we must either have no out_adr_type or have the same out_adr_type + // and in_adr_type. This is because we DO NOT know how to compute the anti-dependence of any + // other node. The exceptions are the intrinsic nodes which are constructed specially to + // materialize anti-dependencies as def-use dependencies. + bool has_special_anti_dependence_construction = Opcode() == Op_StrCompressedCopy || + Opcode() == Op_StrInflatedCopy || + Opcode() == Op_EncodeISOArray; + if (!is_Mach() && !is_CFG() && !pinned() && res != nullptr && res != in_type && !has_special_anti_dependence_construction) { + stringStream ss; + ss.print(", out: "); + res->dump_on(&ss); + ss.print(", in: "); + in_type->dump_on(&ss); + assert(false, "Node %s: cannot compute anti-dependencies%s", Name(), ss.as_string()); + } +#endif // ASSERT + + return res; +} + //------------------------------Identity--------------------------------------- // Return a node that the given node is equivalent to. Node* Node::Identity(PhaseGVN* phase) { @@ -1238,6 +1314,10 @@ bool Node::has_special_unique_user() const { } else if (op == Op_LoadUS && n->Opcode() == Op_LShiftI) { // Condition for RShiftI(LShiftI(LoadUS(...), 16), 16) => LoadS(...), see RShiftINode::Ideal return true; + } else if ((op == Op_LoadB || op == Op_LoadS) && n->Opcode() == Op_AndI) { + // AndINode::Ideal turns AndI(LoadB/S) into AndI(LoadUB/US), if the LoadB + // only has a single use. + return true; } else if (op == Op_AddL) { // Condition for convL2I(addL(x,y)) ==> addI(convL2I(x),convL2I(y)) return n->Opcode() == Op_ConvL2I && n->in(1) == this; @@ -2630,7 +2710,7 @@ void Node::dump(const char* suffix, bool mark, outputStream* st, DumpConfig* dc) t->dump_on(st); } else if (t == Type::MEMORY) { st->print(" Memory:"); - MemNode::dump_adr_type(adr_type(), st); + MemNode::dump_adr_type(out_adr_type(), st); } else if (Verbose || WizardMode) { st->print(" Type:"); if (t) { diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 18efd08c82bc..cbf817aac4fb 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -30,6 +30,7 @@ #include "opto/compile.hpp" #include "opto/type.hpp" #include "utilities/copy.hpp" +#include "utilities/debug.hpp" // Portions of code courtesy of Clifford Click @@ -1268,12 +1269,28 @@ class Node { // Be sure to do the hash_delete game in the "rehash" variant. void raise_bottom_type(const Type* new_type); - // Get the address type with which this node uses and/or defs memory, - // or null if none. The address type is conservatively wide. - // Returns non-null for calls, membars, loads, stores, etc. - // Returns TypePtr::BOTTOM if the node touches memory "broadly". - virtual const class TypePtr *adr_type() const { return nullptr; } + // Get the memory which this node produces. + // Generally, if you are unsure whether you need out_adr_type or in_adr_type: + // - A Store, a LoadStore, or a Phi must have the same out_adr_type and in_adr_type, this is + // essential in its semantics, so sometimes it is unclear whether out_adr_type or in_adr_type + // should be invoked. For those nodes, calling either is fine correctness-wise. + // - A Load has no out_adr_type, combined with the fact that a Store or a LoadStore has the same + // out_adr_type and in_adr_type, it is always correct to use in_adr_type for a Mem or a + // LoadStore (these nodes answer true for is_Mem()). + // - For other nodes, it is almost always correct to use out_adr_type, except when we are + // computing a node's anti-dependence, or when we are trying to modify the memory input of a + // node. + const TypePtr* out_adr_type() const; + + // Get the memory which this node consumes, it must include the out_adr_type, since if the node + // produces some memory, it also kills that memory, which is a form of consuming + const TypePtr* in_adr_type() const { return in_adr_type_impl(); } +private: + virtual const TypePtr* out_adr_type_impl() const { return nullptr; } + virtual const TypePtr* in_adr_type_impl() const { return out_adr_type_impl(); } + +public: // Return an existing node which computes the same function as this node. // The optimistic combined algorithm requires this to return a Node which // is a small number of steps away (e.g., one of my inputs). diff --git a/src/hotspot/share/opto/output.cpp b/src/hotspot/share/opto/output.cpp index 6f480f754545..e1b995c4bc87 100644 --- a/src/hotspot/share/opto/output.cpp +++ b/src/hotspot/share/opto/output.cpp @@ -1919,15 +1919,14 @@ void PhaseOutput::fill_buffer(C2_MacroAssembler* masm, uint* blk_starts) { // This output goes directly to the tty, not the compiler log. // To enable tools to match it up with the compilation activity, // be sure to tag this tty output with the compile ID. + bool aot_comp = C->is_aot_compile(); + bool aot_preload_comp = C->for_aot_preload(); if (xtty != nullptr) { - CompileTask* task = C->env()->task(); - bool aot_comp = task->is_aot_load_or_compile(); - bool aot_preload_comp = task->is_aot_preload_or_compile(); xtty->head("opto_assembly compile_id='%d'%s", C->compile_id(), C->is_osr_compilation() ? " compile_kind='osr'" : (aot_preload_comp ? " compile_kind='AP'" : (aot_comp ? " compile_kind='A'" : ""))); } - const char* is_aot = C->env()->is_aot_compile() ? (C->for_aot_preload() ? "(AP) " : "(A) -") : "-----"; + const char* is_aot = aot_comp ? (aot_preload_comp ? "(AP) " : "(A) -") : "-----"; if (C->method() != nullptr) { tty->print_cr("----------------------- MetaData before Compile_id = %d %s-------------------", C->compile_id(), is_aot); tty->print_raw(method_metadata_str.freeze()); diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 984ad391dc08..c7710947a307 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -1351,8 +1351,7 @@ static int scale_limit(int64_t limit) { } void Parse::count_aot_code_calls() { - bool is_aot_compilation = C->env()->is_aot_compile(); - if (UseAOTCodeCounters && (depth() == 1) && is_aot_compilation) { + if (UseAOTCodeCounters && (depth() == 1) && C->is_aot_compile()) { // Clear out dead values from the debug info in following runtime call kill_dead_locals(); diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index ffd9ccf19161..51f83f816ffb 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -440,22 +440,34 @@ PhaseRenumberLive::PhaseRenumberLive(PhaseGVN* gvn, assert(worklist.is_subset_of(_useful), "only useful nodes should still be in the worklist"); + // While we don't need to rehash, some optimizations are sensitive to the + // relative ordering of node IDs, for example AddNode::commute wants to keep + // in1 < in2. Therefore, we keep the relative ordering stable. + uint next_idx = 0; + for (int old_idx = 0; old_idx < _old2new_map.length(); old_idx++) { + if (_useful.member_set().test(old_idx)) { + _old2new_map.at_put(old_idx, next_idx++); + } + } + assert(next_idx == _useful.size(), "must map all useful nodes"); + // Iterate over the set of live nodes. for (uint current_idx = 0; current_idx < _useful.size(); current_idx++) { Node* n = _useful.at(current_idx); - const Type* type = gvn->type_or_null(n); - _new_type_array.map(current_idx, type); + uint old_idx = n->_idx; + int new_idx = _old2new_map.at(old_idx); + assert(new_idx >= 0, "every useful node must have new idx"); - assert(_old2new_map.at(n->_idx) == -1, "already seen"); - _old2new_map.at_put(n->_idx, current_idx); + const Type* type = gvn->type_or_null(n); + _new_type_array.map(new_idx, type); if (old_node_note_array != nullptr) { - Node_Notes* nn = C->locate_node_notes(old_node_note_array, n->_idx); - C->set_node_notes_at(current_idx, nn); + Node_Notes* nn = C->locate_node_notes(old_node_note_array, old_idx); + C->set_node_notes_at(new_idx, nn); } - n->set_idx(current_idx); // Update node ID. + n->set_idx(new_idx); // Update node ID. if (update_embedded_ids(n) < 0) { _delayed.push(n); // has embedded IDs; handle later @@ -1391,6 +1403,51 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit // 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()) { + // BoolNode::Ideal canonicalizes integer comparisons, but only if "!is_counted_loop_exit_test". + // We only optimize if: + // Cmp -> Bool -> If + // And not if: + // Cmp -> Bool -> CountedLoopEnd + // In loop unswitching, we turn a CountedLoopEnd into an If. + // We would now have to notify the input of the modified node, + // which we don't do so far. + // One simple hack would be to just push the Bool node on + // the worklist in that optimization, in an ad-hoc fashon. + // But more principled would probably be to do this as part + // of replace_input_of, which calls set_req_X, which already + // has some notification logic, for example using + // Node::has_special_unique_user. There are also other places + // the graph could be updated and lose the CountedLoopEnd node, + // and in those places we also use Node::has_special_unique_user, + // and other related code. + // I would suggest we refactor and extend that notification + // code in a future RFE. + // + // Found with: + // -XX:VerifyIterativeGVN=1110 + // compiler/loopopts/TestBaseCountedEndLoopUnswitchCandidate.java + case Op_Bool: + return; + + // I have encountered some issues with AddHF. + // It could be due to issues arising from JDK-8388873, we should + // check again after that is fixed. + // + // Found on aarch64 with: + // -XX:-TieredCompilation -XX:VerifyIterativeGVN=1110 + // compiler/c2/irTests/TestFloat16ScalarOperations.java + case Op_AddHF: + return; + + // In XorI/LNode::Ideal, we optimize Xor(x, -1), but + // only if is_used_in_only_arithmetic: so we would + // need notification if the Xor only has arithmetic + // nodes. I think this also calls for a generalization + // of Node::has_special_unique_user. + case Op_XorI: + case Op_XorL: + return; + // RegionNode::Ideal does "Skip around the useless IF diamond". // 245 IfTrue === 244 // 258 If === 245 257 @@ -1410,182 +1467,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit case Op_Region: return; - // 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 - - // AddFPNode::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; - - // 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; - - // 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; - - // 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; - - // 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; - // SubTypeCheckNode::Ideal calls SubTypeCheckNode::verify_helper, which does // Node* cmp = phase->transform(new CmpPNode(subklass, in(SuperKlass))); // record_for_cleanup(cmp, phase); @@ -1683,20 +1564,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit case Op_ConvI2L: return; - // 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; - // ArrayCopyNode::Ideal // calls ArrayCopyNode::prepare_array_copy // calls Compile::conv_I2X_index -> is called with sizetype = intcon(0), I think that @@ -1743,67 +1610,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit case Op_CastII: return; - // 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; - - // 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; - - // Bool -> constant folded to 1. - // Issue with notification? - // - // Found with: - // compiler/c2/irTests/TestVectorizationMismatchedAccess.java - // -XX:VerifyIterativeGVN=1110 - case Op_Bool: - return; - - // 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; - - // 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; - // AddPNode::Ideal seems to do set_req without removing lock first. // Found with various vector tests tier1-tier3. case Op_AddP: @@ -1853,13 +1659,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit // java -XX:VerifyIterativeGVN=1110 -Xcomp --version case Op_CmpP: return; - - // 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; } if (n->is_Store()) { @@ -2065,24 +1864,6 @@ void PhaseIterGVN::verify_Identity_for(Node* n) { case Op_CheckCastPP: return; - // 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; - // PhiNode::Identity checks for patterns like: // r = (x != con) ? x : con; // that can be constant folded to "x". @@ -2096,22 +1877,6 @@ void PhaseIterGVN::verify_Identity_for(Node* n) { // -XX:VerifyIterativeGVN=1110 case Op_Phi: return; - - // 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; - - // AbsINode::Identity - // Not investigated yet. - case Op_AbsI: - return; } if (n->is_Load()) { @@ -2545,8 +2310,18 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } - int use_op = use->Opcode(); - if(use->is_Cmp()) { // Enable CMP/BOOL optimization + // AndLNode::Ideal folds GraphKit::mark_word_test patterns. Give it a chance to run. + if (n->is_Load() && use->is_Phi()) { + for (DUIterator_Fast imax, i = use->fast_outs(imax); i < imax; i++) { + Node* u = use->fast_out(i); + if (u->Opcode() == Op_AndL) { + worklist.push(u); + } + } + } + + const int use_op = use->Opcode(); + if (use->is_Cmp()) { // Enable CMP/BOOL optimization add_users_to_worklist0(use, worklist); // Put Bool on worklist for (DUIterator_Fast jmax, j = use->fast_outs(jmax); j < jmax; j++) { Node* bol = use->fast_out(j); @@ -2692,6 +2467,18 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ return u->Opcode() == Op_AndI || u->Opcode() == Op_AndL; }); } + // OrI/LNode::Ideal converts shift/or patterns into rotates, which + // needs to trigger when x and y common. + // e.g. (x << 14) | (y >>> 18) + if (use_op == Op_LShiftI || use_op == Op_URShiftI) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->Opcode() == Op_OrI; + }); + } else if (use_op == Op_LShiftL || use_op == Op_URShiftL) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->Opcode() == Op_OrL; + }); + } // If changed LShift inputs, check CompressBits and ExpandBits users for // compress(x, 1 << n), compress(x, -1 << n), // expand(x, 1 << n), expand(x, -1 << n) optimizations. @@ -2701,6 +2488,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ u->in(2) == use; }); } + // AddNode::Ideal_collapse_variable_times_con optimizes 3-hop pattern: + // e.g. ((x << c1) + (x << c2)) + x -> c * x + // pattern: n -> use=shift -> add1 -> add2 + if (use_op == Op_LShiftI || use_op == Op_LShiftL) { + const int add_op = (use_op == Op_LShiftI) ? Op_AddI : Op_AddL; + for (DUIterator_Fast imax, i = use->fast_outs(imax); i < imax; i++) { + Node* add1 = use->fast_out(i); + if (add1->Opcode() == add_op) { + add_users_to_worklist_if(worklist, add1, [&](Node* add2) { + return add2->Opcode() == add_op; + }); + } + } + } // If changed ExpandBits inputs, check CompressBits users for // compress(expand(x, m), m) optimization. if (use_op == Op_ExpandBits) { @@ -2721,6 +2522,33 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ return u->Opcode() == Op_CmpU; }); } + // LShiftNode::IdealIL: + // (x + c1) << c2 -> (x << c2) + c3 + // (c1 - x) << c2 -> c3 - (x << c2) + if (use_op == Op_AddI || use_op == Op_AddL || + use_op == Op_SubI || use_op == Op_SubL) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->Opcode() == Op_LShiftI || u->Opcode() == Op_LShiftL; + }); + } + // AddNode::Ideal reassociates constants to the outside, e.g.: + // (x + C) + y -> (x + y) + C + // (x | C) | y -> (x | y) | C + if (use->is_Add()) { + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == use_op; + }); + } + // If changed AddI/AddL inputs, check for add users: + // e.g. x - (y + n) -> x - (y + C) -> (x - y) + (-C) + // before mutation after mutation optimized by + // AddNode::Ideal + if (use_op == Op_AddI || use_op == Op_AddL) { + int sub_op = (use_op == Op_AddI) ? Op_SubI : Op_SubL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == sub_op; + }); + } // If changed AddI/AddL inputs, check URShift users for // "((X << z) + Y) >>> z" optimization in URShift{I,L}Node::Ideal. if (use_op == Op_AddI || use_op == Op_AddL) { @@ -2749,10 +2577,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } // If changed AndI/AndL inputs, check RShift/URShift users for "(x & mask) >> shift" optimization opportunity + // LShiftNode::IdealIL optimizes patterns like: LShift(And(RShift(x, c), Y), c) if (use_op == Op_AndI || use_op == Op_AndL) { add_users_to_worklist_if(worklist, use, [](Node* u) { return u->Opcode() == Op_RShiftI || u->Opcode() == Op_RShiftL || - u->Opcode() == Op_URShiftI || u->Opcode() == Op_URShiftL; + u->Opcode() == Op_URShiftI || u->Opcode() == Op_URShiftL || + u->Opcode() == Op_LShiftI || u->Opcode() == Op_LShiftL; + }); + } + // MulNode::AndIL_sum_and_mask can optimize: + // AndX(AddX(x, y), mask) -> AndX(x, mask) + if (use_op == Op_AddI || use_op == Op_AddL) { + const int and_op = (use_op == Op_AddI) ? Op_AndI : Op_AndL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == and_op; }); } // If changed AndL inputs, check ConvL2I users for @@ -2769,15 +2607,20 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ // ConvI2F->ConvF2I->ConvI2F // Note: there may be other 3-nodes conversion chains that would require to be added here, but these // are the only ones that are known to trigger missed optimizations otherwise + // + // ConvI2LNode::Identity optimizes the following 2-hop optimization, once x is proven to be in int range: + // ConvI2L(ConvL2I(x)) -> x if (use_op == Op_ConvL2D || use_op == Op_ConvI2F || use_op == Op_ConvL2F || - use_op == Op_ConvF2I) { - add_users_to_worklist_if(worklist, use, [=](Node* u) { + use_op == Op_ConvF2I || + use_op == Op_ConvL2I) { + add_users_to_worklist_if(worklist, use, [&](Node* u) { return (use_op == Op_ConvL2D && u->Opcode() == Op_ConvD2L) || (use_op == Op_ConvI2F && u->Opcode() == Op_ConvF2I) || (use_op == Op_ConvL2F && u->Opcode() == Op_ConvF2L) || - (use_op == Op_ConvF2I && u->Opcode() == Op_ConvI2F); + (use_op == Op_ConvF2I && u->Opcode() == Op_ConvI2F) || + (use_op == Op_ConvL2I && u->Opcode() == Op_ConvI2L); }); } // ConvD2F::Ideal matches ConvD2F(SqrtD(ConvF2D(x))) => SqrtF(x). @@ -2796,7 +2639,7 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ // address expression flattening. if (use_op == Op_AddP) { bool offset_changed = n == use->in(AddPNode::Offset); - add_users_to_worklist_if(worklist, use, [=](Node* u) { + add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->is_Mem() || (offset_changed && u->is_AddP() && u->in(AddPNode::Offset)->is_Con()); }); @@ -2809,13 +2652,22 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ }); } // Check for Max/Min(A, Max/Min(B, C)) where A == B or A == C - if (use->is_MinMax()) { - add_users_to_worklist_if(worklist, use, [](Node* u) { return u->is_MinMax(); }); + // MinMaxNode::IdealI also optimizes these cases (if no overflow): + // MinI(AddI(x, -1), x) -> AddI(x, -1) + if (use->is_MinMax() || use->Opcode() == Op_AddI) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->is_MinMax(); + }); } // Check for A | (B | C) and (B | C) | A where A == B or A == C. if (use_op == Op_OrI || use_op == Op_OrL) { add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == use->Opcode(); }); } + // MulNode::Ideal idealizes (x | c1) & c2 -> (x & c2) | (c1 & c2) + if (use_op == Op_OrI || use_op == Op_OrL) { + const int and_op = use_op == Op_OrI ? Op_AndI : Op_AndL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == and_op; }); + } auto enqueue_init_mem_projs = [&](ProjNode* proj) { add_users_to_worklist0(proj, worklist); }; @@ -2976,9 +2828,92 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ // If changed Sub inputs, check Add for identity. // e.g., (x - y) + y -> x; x + (y - x) -> y. + // SubI/LNode::Ideal also optimize this: + // (x - y) - x -> -y if (use_op == Op_SubI || use_op == Op_SubL) { const int add_op = (use_op == Op_SubI) ? Op_AddI : Op_AddL; - add_users_to_worklist_if(worklist, use, [=](Node* u) { return u->Opcode() == add_op; }); + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == add_op || u->Opcode() == use_op; + }); + } + + // If changed Mul inputs, check Add/Sub for common-factor reassociation. + // e.g., (a * b) + (a * c) -> a * (b + c). + // Additionally, check for nested Mul reassociation. + // e.g. (x * c1) * c2 -> x * (c1 * c2) + if (use_op == Op_MulI || use_op == Op_MulL) { + const int add_op = (use_op == Op_MulI) ? Op_AddI : Op_AddL; + const int sub_op = (use_op == Op_MulI) ? Op_SubI : Op_SubL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == add_op || u->Opcode() == sub_op || u->Opcode() == use_op; + }); + } + + // MulNode::Ideal distributes constant multiplication: + // e.g., (x + c1) * c2 -> x * c2 + (c1 * c2) + if (use_op == Op_AddI || use_op == Op_AddL) { + const int mul_op = (use_op == Op_AddI) ? Op_MulI : Op_MulL; + add_users_to_worklist_if(worklist, use, [&](Node* u) { return u->Opcode() == mul_op; }); + } + + // We may have a loop-phi that is about to close the AddI recurrence, + // and we would have to ensure AddNode::commute is called for the AddI. + // e.g. use = Phi(x, n) -> use = Phi(x, u) -> use = Phi(x, u) + // u = AddI(y, use) u = AddI(y, use) -> u = AddI(use, y) + // current state, After mutation, After commute, + // before mutation before commute canonical loop incr + // + // Alternatively, we may have a region-phi, that is about to turn + // into a loop-phi, and where the AddI recurrence is already closed, + // and the AddI has its inputs sorted by idx. In this case, we also + // need AddNode::commute to canonicalize the AddI, so that the phi + // input is on the left now. + // e.g. use = Phi(region, u) -> use = Phi(loop, u) -> use = Phi(loop, u) + // u = AddI(y, use) u = AddI(y, use) -> u = AddI(use, y) + // current state, loop-phi, After commute, + // region-phi before commute canonical loop incr + if (use->is_Phi() && + use->req() == 3 && + (n == use->in(0) || // possibly: region-phi -> loop-phi + n == use->in(LoopNode::LoopBackControl))) { // possibly: closing AddI recurrence + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->is_Add(); + }); + } + + // AndLNode::Ideal optimizes GraphKit::mark_word_test patterns + // Pattern: AndL(LoadL(x=mark word), y) + // Pattern: AndL(Phi(LoadL(x=mark word), z), y) + if (use->is_Load()) { + for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { + Node* u = use->fast_out(i2); + if (u->Opcode() == Op_AndL) { + // n -> Load -> AndL + worklist.push(u); + } else if (u->is_Phi()) { + // n -> Load -> Phi -> AndL + add_users_to_worklist_if(worklist, u, [](Node* u2) { + return u2->Opcode() == Op_AndL; + }); + } + } + } + + // PhiNode::Ideal can optimize: + // Pattern: Phi(condition, x + y, x) -> x + Phi(condition, y, 0) + if (use_op == Op_AddI) { + add_users_to_worklist_if(worklist, use, [](Node* u) { + return u->is_Phi(); + }); + } + + // AndVNode::Ideal and OrVNode::Ideal have 2-hop operations for + // repeated and/or with the same value: + // Pattern: AndV(AndV(a, b), a) -> AndV(a, b) + if (use_op == Op_AndV || use_op == Op_OrV) { + add_users_to_worklist_if(worklist, use, [&](Node* u) { + return u->Opcode() == use_op; + }); } // (Phi (ValueType ValueType)) is transformed into (ValueType (Phi ) (Phi )...) // and is applied if there are EncodeP/DecodeN or casts between a Phi and InlineType nodes diff --git a/src/hotspot/share/opto/reg_split.cpp b/src/hotspot/share/opto/reg_split.cpp index 96f87fe6947d..7b2597d74146 100644 --- a/src/hotspot/share/opto/reg_split.cpp +++ b/src/hotspot/share/opto/reg_split.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -534,6 +534,11 @@ uint PhaseChaitin::Split(uint maxlrg, ResourceArea* split_arena) { } } + if (static_cast(spill_cnt) * _cfg.number_of_blocks() > RegSplitLimit) { + C->record_method_not_compilable("PhaseChaitin::Split requires too much memory"); + return 0; + } + // Create side arrays for propagating reaching defs info. // Each block needs a node pointer for each spilling live range for the // Def which is live into the block. Phi nodes handle multiple input diff --git a/src/hotspot/share/opto/stringopts.cpp b/src/hotspot/share/opto/stringopts.cpp index 16b34e149cf9..ceea0136b52d 100644 --- a/src/hotspot/share/opto/stringopts.cpp +++ b/src/hotspot/share/opto/stringopts.cpp @@ -216,7 +216,7 @@ class StringConcat : public ResourceObj { const TypePtr* no_memory_effects = nullptr; Compile* C = _stringopts->C; CallStaticJavaNode* call = new CallStaticJavaNode(call_type, call_addr, "uncommon_trap", - no_memory_effects); + no_memory_effects, TypePtr::BOTTOM); for (int e = 0; e < TypeFunc::Parms; e++) { call->init_req(e, uct->in(e)); } diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp index 69be1c2b8947..18e82113f1b1 100644 --- a/src/hotspot/share/opto/subnode.cpp +++ b/src/hotspot/share/opto/subnode.cpp @@ -2222,16 +2222,32 @@ const Type* ReverseLNode::Value(PhaseGVN* phase) const { return bottom_type(); } -Node* ReverseINode::Identity(PhaseGVN* phase) { - if (in(1)->Opcode() == Op_ReverseI) { - return in(1)->in(1); +static Node* simplify_involution(PhaseGVN* phase, Node* involution) { + if (involution->in(1)->Opcode() == involution->Opcode()) { + Node* original = involution->in(1)->in(1); + const TypeInt* type = phase->type(original)->isa_int(); + // Operations on sub-int types might not be "real" involutions for values outside their type range, + // for example, a ReverseBytesS node with an input larger than short. + // Make sure not to drop potential truncations. + if (type == nullptr || involution->bottom_type()->is_int()->contains(type)) { + return original; + } } - return this; + return involution; +} + +Node* NegNode::Identity(PhaseGVN* phase) { + return simplify_involution(phase, this); +} + +Node* ReverseBytesNode::Identity(PhaseGVN* phase) { + return simplify_involution(phase, this); +} + +Node* ReverseINode::Identity(PhaseGVN* phase) { + return simplify_involution(phase, this); } Node* ReverseLNode::Identity(PhaseGVN* phase) { - if (in(1)->Opcode() == Op_ReverseL) { - return in(1)->in(1); - } - return this; + return simplify_involution(phase, this); } diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp index 3b3877f35daf..d4706affb796 100644 --- a/src/hotspot/share/opto/subnode.hpp +++ b/src/hotspot/share/opto/subnode.hpp @@ -321,6 +321,7 @@ class FlatArrayCheckNode : public CmpNode { ArrayOrKlass }; FlatArrayCheckNode(Compile* C, Node* mem, Node* array_or_klass) : CmpNode(mem, array_or_klass) { + assert(UseArrayFlattening, "Should not be used without flat arrays enabled"); init_class_id(Class_FlatArrayCheck); init_flags(Flag_is_macro); C->add_macro_node(this); @@ -329,6 +330,9 @@ class FlatArrayCheckNode : public CmpNode { virtual const Type* sub(const Type*, const Type*) const { ShouldNotReachHere(); return nullptr; } const Type* Value(PhaseGVN* phase) const; virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + +private: + const TypePtr* in_adr_type_impl() const final { return TypeRawPtr::BOTTOM; } }; //------------------------------BoolTest--------------------------------------- @@ -473,6 +477,7 @@ class NegNode : public Node { NegNode(Node* in1) : Node(nullptr, in1) { init_class_id(Class_Neg); } + virtual Node* Identity(PhaseGVN* phase); }; //------------------------------NegINode--------------------------------------- @@ -603,6 +608,7 @@ class ReverseBytesNode : public Node { public: ReverseBytesNode(Node* in) : Node(nullptr, in) {} virtual const Type* Value(PhaseGVN* phase) const; + virtual Node* Identity(PhaseGVN* phase); }; //-------------------------------ReverseBytesINode-------------------------------- // reverse bytes of an integer diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 8acdf2b80002..9ab788a344eb 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -1081,10 +1081,12 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod if (is_marked_reduction(use1) && is_marked_reduction(use2)) { Node* use1_in2 = use1->in(2); if (use1_in2->is_Phi() || is_marked_reduction(use1_in2)) { + igvn().rehash_node_delayed(use1); use1->swap_edges(1, 2); } Node* use2_in2 = use2->in(2); if (use2_in2->is_Phi() || is_marked_reduction(use2_in2)) { + igvn().rehash_node_delayed(use2); use2->swap_edges(1, 2); } return PairOrderStatus::Ordered; @@ -1100,14 +1102,17 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod if (i1 != i2) { if ((i1 == (3-i2)) && (use2->is_Add() || use2->is_Mul())) { // 2. Commutative: swap edges, and hope the other position matches too. + igvn().rehash_node_delayed(use2); use2->swap_edges(i1, i2); } else if (VectorNode::is_muladds2i(use2) && use1 != use2) { // 3.a/b: MulAddS2I. if (i1 == 5 - i2) { // ((i1 == 3 && i2 == 2) || (i1 == 2 && i2 == 3) || (i1 == 1 && i2 == 4) || (i1 == 4 && i2 == 1)) + igvn().rehash_node_delayed(use2); use2->swap_edges(1, 2); use2->swap_edges(3, 4); } if (i1 == 3 - i2 || i1 == 7 - i2) { // ((i1 == 1 && i2 == 2) || (i1 == 2 && i2 == 1) || (i1 == 3 && i2 == 4) || (i1 == 4 && i2 == 3)) + igvn().rehash_node_delayed(use2); use2->swap_edges(2, 3); use2->swap_edges(1, 4); } @@ -1118,6 +1123,7 @@ SuperWord::PairOrderStatus SuperWord::order_inputs_of_uses_to_match_def_pair(Nod } } else if (i1 == i2 && VectorNode::is_muladds2i(use2) && use1 != use2) { // 3.c: MulAddS2I. + igvn().rehash_node_delayed(use2); use2->swap_edges(1, 3); use2->swap_edges(2, 4); return PairOrderStatus::Unknown; @@ -2510,12 +2516,15 @@ static bool can_subword_truncate(Node* in, const Type* type) { case Op_RotateRight: case Op_RotateLeft: case Op_PopCountI: + case Op_PopCountL: case Op_ReverseBytesS: case Op_ReverseBytesUS: case Op_ReverseBytesI: case Op_ReverseI: case Op_CountLeadingZerosI: + case Op_CountLeadingZerosL: case Op_CountTrailingZerosI: + case Op_CountTrailingZerosL: case Op_IsFiniteF: case Op_IsFiniteD: case Op_IsInfiniteF: @@ -2678,8 +2687,8 @@ const Type* VLoopTypes::container_type(Node* n) const { } bool VLoopMemorySlices::same_memory_slice(MemNode* m1, MemNode* m2) const { - return _vloop.phase()->C->get_alias_index(m1->adr_type()) == - _vloop.phase()->C->get_alias_index(m2->adr_type()); + return _vloop.phase()->C->get_alias_index(m1->in_adr_type()) == + _vloop.phase()->C->get_alias_index(m2->in_adr_type()); } LoadNode::ControlDependency SuperWordVTransformBuilder::load_control_dependency(const Node_List* pack) const { diff --git a/src/hotspot/share/opto/superwordVTransformBuilder.cpp b/src/hotspot/share/opto/superwordVTransformBuilder.cpp index f8b8bfe2ed04..b42397177c34 100644 --- a/src/hotspot/share/opto/superwordVTransformBuilder.cpp +++ b/src/hotspot/share/opto/superwordVTransformBuilder.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -189,11 +189,11 @@ VTransformVectorNode* SuperWordVTransformBuilder::make_vector_vtnode_for_pack(co const VPointer& scalar_p = _vloop_analyzer.vpointers().vpointer(p0->as_Load()); const VPointer vector_p(scalar_p.make_with_size(scalar_p.size() * vlen)); const LoadNode::ControlDependency control_dependency = load_control_dependency(pack); - vtn = new (_vtransform.arena()) VTransformLoadVectorNode(_vtransform, properties, vector_p, p0->adr_type(), control_dependency); + vtn = new (_vtransform.arena()) VTransformLoadVectorNode(_vtransform, properties, vector_p, p0->in_adr_type(), control_dependency); } else if (p0->is_Store()) { const VPointer& scalar_p = _vloop_analyzer.vpointers().vpointer(p0->as_Store()); const VPointer vector_p(scalar_p.make_with_size(scalar_p.size() * vlen)); - vtn = new (_vtransform.arena()) VTransformStoreVectorNode(_vtransform, properties, vector_p, p0->adr_type()); + vtn = new (_vtransform.arena()) VTransformStoreVectorNode(_vtransform, properties, vector_p, p0->in_adr_type()); } else if (p0->is_Cmp()) { vtn = new (_vtransform.arena()) VTransformCmpVectorNode(_vtransform, properties); } else if (p0->is_Bool()) { diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index 1ec37b9683d1..384e126bf67e 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -557,22 +557,16 @@ void Type::Initialize_shared(Compile* current) { BOTTOM = make(Bottom); // Everything HALF = make(Half); // Placeholder half of doublewide type - TypeF::MAX = TypeF::make(max_jfloat); // Float MAX - TypeF::MIN = TypeF::make(min_jfloat); // Float MIN TypeF::ZERO = TypeF::make(0.0); // Float 0 (positive zero) TypeF::ONE = TypeF::make(1.0); // Float 1 TypeF::POS_INF = TypeF::make(jfloat_cast(POSITIVE_INFINITE_F)); TypeF::NEG_INF = TypeF::make(-jfloat_cast(POSITIVE_INFINITE_F)); - TypeH::MAX = TypeH::make(max_jfloat16); // HalfFloat MAX - TypeH::MIN = TypeH::make(min_jfloat16); // HalfFloat MIN TypeH::ZERO = TypeH::make((jshort)0); // HalfFloat 0 (positive zero) TypeH::ONE = TypeH::make(one_jfloat16); // HalfFloat 1 TypeH::POS_INF = TypeH::make(pos_inf_jfloat16); TypeH::NEG_INF = TypeH::make(neg_inf_jfloat16); - TypeD::MAX = TypeD::make(max_jdouble); // Double MAX - TypeD::MIN = TypeD::make(min_jdouble); // Double MIN TypeD::ZERO = TypeD::make(0.0); // Double 0 (positive zero) TypeD::ONE = TypeD::make(1.0); // Double 1 TypeD::POS_INF = TypeD::make(jdouble_cast(POSITIVE_INFINITE_D)); @@ -616,10 +610,8 @@ void Type::Initialize_shared(Compile* current) { TypeLong::ONE = TypeLong::make( 1); // 1 TypeLong::NON_ZERO = TypeLong::make_or_top(TypeIntPrototype{{min_jlong, max_jlong}, {1, max_julong}, {0, 0}}, WidenMin)->is_long(); TypeLong::POS = TypeLong::make(0, max_jlong, WidenMin); // Non-neg values - TypeLong::NEG = TypeLong::make(min_jlong, -1, WidenMin); TypeLong::LONG = TypeLong::make(min_jlong, max_jlong, WidenMax); // 64-bit integers TypeLong::INT = TypeLong::make((jlong)min_jint, (jlong)max_jint,WidenMin); - TypeLong::UINT = TypeLong::make(0, (jlong)max_juint, WidenMin); TypeLong::TYPE_DOMAIN = TypeLong::LONG; const Type **fboth =(const Type**)shared_type_arena->AmallocWords(2*sizeof(Type*)); @@ -1615,12 +1607,10 @@ void Type::verify_meet_join() { //============================================================================= // Convenience common pre-built types. -const TypeF *TypeF::MAX; // Floating point max -const TypeF *TypeF::MIN; // Floating point min -const TypeF *TypeF::ZERO; // Floating point zero -const TypeF *TypeF::ONE; // Floating point one -const TypeF *TypeF::POS_INF; // Floating point positive infinity -const TypeF *TypeF::NEG_INF; // Floating point negative infinity +const TypeF* TypeF::ZERO; // Floating point zero +const TypeF* TypeF::ONE; // Floating point one +const TypeF* TypeF::POS_INF; // Floating point positive infinity +const TypeF* TypeF::NEG_INF; // Floating point negative infinity //------------------------------make------------------------------------------- // Create a float constant @@ -1707,8 +1697,6 @@ bool TypeF::empty(void) const { //============================================================================= // Convenience common pre-built types. -const TypeH* TypeH::MAX; // Half float max -const TypeH* TypeH::MIN; // Half float min const TypeH* TypeH::ZERO; // Half float zero const TypeH* TypeH::ONE; // Half float one const TypeH* TypeH::POS_INF; // Half float positive infinity @@ -1814,12 +1802,10 @@ bool TypeH::empty(void) const { //============================================================================= // Convenience common pre-built types. -const TypeD *TypeD::MAX; // Floating point max -const TypeD *TypeD::MIN; // Floating point min -const TypeD *TypeD::ZERO; // Floating point zero -const TypeD *TypeD::ONE; // Floating point one -const TypeD *TypeD::POS_INF; // Floating point positive infinity -const TypeD *TypeD::NEG_INF; // Floating point negative infinity +const TypeD* TypeD::ZERO; // Floating point zero +const TypeD* TypeD::ONE; // Floating point one +const TypeD* TypeD::POS_INF; // Floating point positive infinity +const TypeD* TypeD::NEG_INF; // Floating point negative infinity //------------------------------make------------------------------------------- const TypeD *TypeD::make(double d) { @@ -2110,10 +2096,8 @@ const TypeLong* TypeLong::ZERO; // 0 const TypeLong* TypeLong::ONE; // 1 const TypeLong* TypeLong::NON_ZERO; const TypeLong* TypeLong::POS; // >=0 -const TypeLong* TypeLong::NEG; const TypeLong* TypeLong::LONG; // 64-bit integers const TypeLong* TypeLong::INT; // 32-bit subrange -const TypeLong* TypeLong::UINT; // 32-bit unsigned subrange const TypeLong* TypeLong::TYPE_DOMAIN; // alias for TypeLong::LONG TypeLong::TypeLong(const TypeIntPrototype& t, int widen) @@ -4367,7 +4351,7 @@ TypeInstPtr::TypeInstPtr(PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, assert(k != nullptr && (k->is_loaded() || o == nullptr), "cannot have constants with non-loaded klass"); - assert(!xk || (k->is_loaded() && !k->is_abstract()), "pointer to an oop of an exact type must be concrete"); + assert(!xk || k->is_loaded(), "pointer to an oop of an exact type must be loaded"); assert(!xk || interfaces->eq(k->as_instance_klass()), "inconsistency between k and interfaces"); }; diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 8807bbd973d5..9c347492e192 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -461,17 +461,16 @@ class Type { } // Convenience common pre-built types. - static const Type *ABIO; - static const Type *BOTTOM; - static const Type *CONTROL; - static const Type *DOUBLE; - static const Type *FLOAT; - static const Type *HALF_FLOAT; - static const Type *HALF; - static const Type *MEMORY; - static const Type *MULTI; - static const Type *RETURN_ADDRESS; - static const Type *TOP; + static const Type* ABIO; + static const Type* BOTTOM; + static const Type* CONTROL; + static const Type* DOUBLE; + static const Type* FLOAT; + static const Type* HALF_FLOAT; + static const Type* HALF; + static const Type* MEMORY; + static const Type* RETURN_ADDRESS; + static const Type* TOP; // Mapping from compiler type to VM BasicType BasicType basic_type() const { return _type_info[_base].basic_type; } @@ -549,12 +548,10 @@ class TypeF : public Type { virtual const Type* xmeet(const Type* t) const; virtual const Type* xjoin(const Type* t) const; // Convenience common pre-built types. - static const TypeF *MAX; - static const TypeF *MIN; - static const TypeF *ZERO; // positive zero only - static const TypeF *ONE; - static const TypeF *POS_INF; - static const TypeF *NEG_INF; + static const TypeF* ZERO; // positive zero only + static const TypeF* ONE; + static const TypeF* POS_INF; + static const TypeF* NEG_INF; #ifndef PRODUCT virtual void dump2( Dict &d, uint depth, outputStream *st ) const; #endif @@ -581,8 +578,6 @@ class TypeH : public Type { virtual const Type* xmeet(const Type* t) const; virtual const Type* xjoin(const Type* t) const; // Convenience common pre-built types. - static const TypeH* MAX; - static const TypeH* MIN; static const TypeH* ZERO; // positive zero only static const TypeH* ONE; static const TypeH* POS_INF; @@ -612,12 +607,10 @@ class TypeD : public Type { virtual const Type* xmeet(const Type* t) const; virtual const Type* xjoin(const Type* t) const; // Convenience common pre-built types. - static const TypeD *MAX; - static const TypeD *MIN; - static const TypeD *ZERO; // positive zero only - static const TypeD *ONE; - static const TypeD *POS_INF; - static const TypeD *NEG_INF; + static const TypeD* ZERO; // positive zero only + static const TypeD* ONE; + static const TypeD* POS_INF; + static const TypeD* NEG_INF; #ifndef PRODUCT virtual void dump2( Dict &d, uint depth, outputStream *st ) const; #endif @@ -946,10 +939,8 @@ class TypeLong : public TypeInteger { static const TypeLong* ONE; static const TypeLong* NON_ZERO; static const TypeLong* POS; - static const TypeLong* NEG; static const TypeLong* LONG; static const TypeLong* INT; // 32-bit subrange [min_jint..max_jint] - static const TypeLong* UINT; // 32-bit unsigned [0..max_juint] static const TypeLong* TYPE_DOMAIN; // alias for TypeLong::LONG // static convenience methods. diff --git a/src/hotspot/share/opto/valuetypenode.hpp b/src/hotspot/share/opto/valuetypenode.hpp index b70a335c94a2..c53fbba3bb7c 100644 --- a/src/hotspot/share/opto/valuetypenode.hpp +++ b/src/hotspot/share/opto/valuetypenode.hpp @@ -40,6 +40,7 @@ class ValueTypeNode : public TypeNode { private: ValueTypeNode(ciValueKlass* vk, Node* oop, bool null_free) : TypeNode(TypeInstPtr::make(null_free ? TypePtr::NotNull : TypePtr::BotPTR, vk), Values + vk->nof_declared_nonstatic_fields()) { + assert(Arguments::is_valhalla_enabled(), "should not be used without value types"); init_class_id(Class_ValueType); init_req(Oop, oop); Compile::current()->add_value_type(this); @@ -208,6 +209,7 @@ class LoadFlatNode final : public SafePointNode { private: LoadFlatNode(ciValueKlass* vk, const TypeTuple* type, bool null_free, DecoratorSet decorators) : SafePointNode(TypeFunc::Parms + 2, nullptr, TypePtr::BOTTOM), _vk(vk), _type(type), _null_free(null_free), _decorators(decorators) { + assert(UseArrayFlattening || UseFieldFlattening, "should not be used without flattening enabled"); init_class_id(Class_LoadFlat); Compile::current()->add_flat_access(this); } @@ -253,6 +255,7 @@ class StoreFlatNode final : public SafePointNode { private: StoreFlatNode(bool null_free, DecoratorSet decorators) : SafePointNode(TypeFunc::Parms + 3, nullptr, TypePtr::BOTTOM), _null_free(null_free), _decorators(decorators) { + assert(UseArrayFlattening || UseFieldFlattening, "should not be used without flattening enabled"); init_class_id(Class_StoreFlat); Compile::current()->add_flat_access(this); } diff --git a/src/hotspot/share/opto/vectorIntrinsics.cpp b/src/hotspot/share/opto/vectorIntrinsics.cpp index 0b5bfd5e8d2e..3d413500ea73 100644 --- a/src/hotspot/share/opto/vectorIntrinsics.cpp +++ b/src/hotspot/share/opto/vectorIntrinsics.cpp @@ -279,7 +279,6 @@ bool LibraryCallKit::arch_supports_vector(int sopc, int num_elem, BasicType type is_supported = Matcher::match_rule_supported_vector_masked(sopc, num_elem, type); } } - is_supported |= Matcher::supports_vector_predicate_op_emulation(sopc, num_elem, type); if (!is_supported) { non_product_log_if_needed("Rejected vector mask predicate using (%s,%s,%d) because architecture does not support it", @@ -1366,31 +1365,55 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) { } // Check that the vector holding indices is supported by architecture - // For sub-word gathers expander receive index array. - if (!is_subword_type(elem_bt) && !arch_supports_vector(Op_LoadVector, idx_num_elem, T_INT, VecMaskNotUsed)) { + if (!arch_supports_vector(Op_LoadVector, idx_num_elem, T_INT, VecMaskNotUsed)) { log_if_needed(" ** not supported: arity=%d op=%s/loadindex vlen=%d etype=int is_masked_op=%d", is_scatter, is_scatter ? "scatter" : "gather", idx_num_elem, is_masked_op ? 1 : 0); return false; // not supported } + // Currently, subword is not supported. + // Note: we used to have an x64 subword gather implementation before, but it was buggy. + // JDK-8318650 had introduced the subword gather implementation: + // - We loaded from indexMap once to perform checkIndex + // - And then we loaded again from indexMap in the LoadVectorGather(Masked) + // backend implementation. But if a second thread mutated the indexMap, + // we would not have checked the index again, and possibly performed out + // of bounds accesses. + // - The reason for using indexMap directly was that subword gather requires + // an int-index per subword element, and so the indices vector would have + // to be larger than the output vector, or we would need multiple index + // vectors. Just loading from indexMap seemed like an elegant solution, + // but unfortunetely, it leads to TOCTOU bugs. + // + // JDK-8383689 removed subword gather again. + // + // In the future, we could consider re-implementing subword gather. We would + // have to ensure to only load the indices once, and perform checkIndex and + // indexing from that same index vector. We may also want to change the API, + // so that one can provide an index vector, rather than a indexMap array. + // Since vectors are immutable, the indexCheck and indexing would trivially + // be consuming the same indices. And the API user could compute arbitrary + // indices, and would not have to round-trip them via an indexMap array. + // However: so far, no hardware we are aware of can actually perform + // vectorized subword gather/scatter. So the implementation would actually + // need to be a scalar emulation. The JDK-8318650 performed scalar loads + // from the indexMap. A re-implementation would instead have to extract + // elements from the index vector. The performance impact has not yet been + // investigated. + if (is_subword_type(elem_bt)) { + log_if_needed(" ** not supported: subword type etype=%s", + type2name(elem_bt)); + return false; // not supported + } + Node* base = argument(6); Node* offset = ConvL2X(argument(7)); // Save state and restore on bailout SavedState old_state(this); - Node* addr = nullptr; - if (!is_subword_type(elem_bt)) { - addr = make_unsafe_address(base, offset, elem_bt, true); - } else { - assert(!is_scatter, "Only supports gather operation for subword types now"); - uint header = arrayOopDesc::base_offset_in_bytes(elem_bt); - assert(offset->is_Con() && offset->bottom_type()->is_long()->get_con() == header, - "offset must be the array base offset"); - Node* index = argument(15); - addr = array_element_address(base, index, elem_bt); - } + Node* addr = make_unsafe_address(base, offset, elem_bt, true); const TypeAryPtr* arr_type = gvn().type(addr)->isa_aryptr(); @@ -1417,18 +1440,13 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) { } // Get the indexes for gather/scatter. - Node* indexes = nullptr; + // Note: we have already loaded the indices from indexMap into a vector, + // and performed checkIndex on it. We must now use that same vector, + // and not reload from the indexMap again. const TypeInstPtr* vbox_idx_type = TypeInstPtr::make_exact(TypePtr::NotNull, vbox_idx_klass); - if (is_subword_type(elem_bt)) { - Node* indexMap = argument(16); - Node* indexM = argument(17); - indexes = array_element_address(indexMap, indexM, T_INT); - } else { - // Get the first index vector. - indexes = unbox_vector(argument(9), vbox_idx_type, T_INT, idx_num_elem); - if (indexes == nullptr) { - return false; - } + Node* indexes = unbox_vector(argument(9), vbox_idx_type, T_INT, idx_num_elem); + if (indexes == nullptr) { + return false; } // Get the vector mask value. @@ -1826,6 +1844,12 @@ bool LibraryCallKit::inline_vector_slice() { int num_elem = vlen->get_con(); BasicType elem_bt = get_vector_primitive_lane_type(vltype); + jint origin_con = origin->get_con(); + if (origin_con < 0 || origin_con > num_elem) { + log_if_needed(" ** vector slice origin %d out of range [0, %d]", origin_con, num_elem); + return false; + } + if (!arch_supports_vector(Op_VectorSlice, num_elem, elem_bt, VecMaskNotUsed)) { log_if_needed(" ** not supported: arity=2 op=slice vlen=%d etype=%s", num_elem, type2name(elem_bt)); @@ -1842,7 +1866,7 @@ bool LibraryCallKit::inline_vector_slice() { } // Defining origin in terms of number of bytes to make it type agnostic value. - Node* origin_node = gvn().intcon(origin->get_con() * type2aelembytes(elem_bt)); + Node* origin_node = gvn().intcon(origin_con * type2aelembytes(elem_bt)); const TypeVect* vector_type = TypeVect::make(elem_bt, num_elem); Node* operation = gvn().transform(trace_vector(new VectorSliceNode(v1, v2, origin_node, vector_type))); Node* box = box_vector(operation, vbox_type, elem_bt, num_elem); diff --git a/src/hotspot/share/opto/vectorization.cpp b/src/hotspot/share/opto/vectorization.cpp index 96ac4d78dcde..0fe022615300 100644 --- a/src/hotspot/share/opto/vectorization.cpp +++ b/src/hotspot/share/opto/vectorization.cpp @@ -223,13 +223,13 @@ VStatus VLoopMemorySlices::find_memory_slices() { if (n->is_memory_phi()) { // Memory slice with stores (and maybe loads) PhiNode* phi = n->as_Phi(); - int alias_idx = C->get_alias_index(phi->adr_type()); + int alias_idx = C->get_alias_index(phi->out_adr_type()); assert(_inputs.at(alias_idx) == nullptr, "did not yet touch this slice"); _inputs.at_put(alias_idx, phi->in(1)); _heads.at_put(alias_idx, phi); } else if (n->is_Load()) { LoadNode* load = n->as_Load(); - int alias_idx = C->get_alias_index(load->adr_type()); + int alias_idx = C->get_alias_index(load->in_adr_type()); PhiNode* head = _heads.at(alias_idx); if (head == nullptr) { // We did not find a phi on this slice yet -> must be a slice with only loads. @@ -248,7 +248,7 @@ VStatus VLoopMemorySlices::find_memory_slices() { } else if (n->is_Store()) { // Found a store. Make sure it is in a slice with a Phi. StoreNode* store = n->as_Store(); - int alias_idx = C->get_alias_index(store->adr_type()); + int alias_idx = C->get_alias_index(store->out_adr_type()); PhiNode* head = _heads.at(alias_idx); assert(head != nullptr, "should have found a mem phi for this slice"); #endif diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index fc0c35057bf3..d90b75e7579b 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -1130,18 +1130,18 @@ static Node* ideal_partial_operations(PhaseGVN* phase, Node* node, const TypeVec switch(vopc) { case Op_LoadVector: return new LoadVectorMaskedNode(node->in(0), node->in(1), node->in(2), - node->as_LoadVector()->adr_type(), vt, mask, + node->as_LoadVector()->in_adr_type(), vt, mask, node->as_LoadVector()->control_dependency()); case Op_LoadVectorGather: return new LoadVectorGatherMaskedNode(node->in(0), node->in(1), node->in(2), - node->as_LoadVector()->adr_type(), vt, + node->as_LoadVector()->in_adr_type(), vt, node->in(3), mask); case Op_StoreVector: return new StoreVectorMaskedNode(node->in(0), node->in(1), node->in(2), node->in(3), - node->as_StoreVector()->adr_type(), mask); + node->as_StoreVector()->in_adr_type(), mask); case Op_StoreVectorScatter: return new StoreVectorScatterMaskedNode(node->in(0), node->in(1), node->in(2), - node->as_StoreVector()->adr_type(), + node->as_StoreVector()->in_adr_type(), node->in(3), node->in(4), mask); default: // Add the mask as an additional input to the original vector node by default. @@ -1603,7 +1603,7 @@ Node* LoadVectorMaskedNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* ctr = in(MemNode::Control); Node* mem = in(MemNode::Memory); Node* adr = in(MemNode::Address); - return phase->transform(new LoadVectorNode(ctr, mem, adr, adr_type(), vect_type())); + return phase->transform(new LoadVectorNode(ctr, mem, adr, in_adr_type(), vect_type())); } } } @@ -1630,7 +1630,7 @@ Node* StoreVectorMaskedNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* mem = in(MemNode::Memory); Node* adr = in(MemNode::Address); Node* val = in(MemNode::ValueIn); - return phase->transform(new StoreVectorNode(ctr, mem, adr, adr_type(), val)); + return phase->transform(new StoreVectorNode(ctr, mem, adr, in_adr_type(), val)); } } } diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index 4657050af6ef..b5db43326539 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -1214,15 +1214,19 @@ class LoadVectorNode : public LoadNode { #endif }; -// Load Vector from memory via index map +// Gather: +// indices: int-vector of indices +// adr: memory address (e.g. array base plus offset) +// output[i] = adr[indices[i]] +// +// Note: assumes that indices are all in-bounds, performs no index checking. class LoadVectorGatherNode : public LoadVectorNode { public: LoadVectorGatherNode(Node* c, Node* mem, Node* adr, const TypePtr* at, const TypeVect* vt, Node* indices) : LoadVectorNode(c, mem, adr, at, vt) { init_class_id(Class_LoadVectorGather); add_req(indices); - DEBUG_ONLY(bool is_subword = is_subword_type(vt->element_basic_type())); - assert(is_subword || indices->bottom_type()->is_vect(), "indices must be in vector"); + assert(indices->bottom_type()->is_vect(), "indices must be in vector"); assert(req() == MemNode::ValueIn + 1, "match_edge expects that index input is in MemNode::ValueIn"); } @@ -1277,8 +1281,13 @@ class StoreVectorNode : public StoreNode { #endif }; -// Store Vector into memory via index map - class StoreVectorScatterNode : public StoreVectorNode { +// Scatter: +// indices: int-vector of indices +// adr: memory address (e.g. array base plus offset) +// adr[indices[i]] = val[i] +// +// Note: assumes that indices are all in-bounds, performs no index checking. +class StoreVectorScatterNode : public StoreVectorNode { public: enum { Indices = 4 }; StoreVectorScatterNode(Node* c, Node* mem, Node* adr, const TypePtr* at, Node* val, Node* indices) @@ -1338,7 +1347,12 @@ class LoadVectorMaskedNode : public LoadVectorNode { } }; -// Load Vector from memory via index map under the influence of a predicate register(mask). +// Masked Gather: +// indices: int-vector of indices +// adr: memory address (e.g. array base plus offset) +// output[i] = mask[i] ? adr[indices[i]] : 0 +// +// Note: assumes that indices are all in-bounds, performs no index checking. class LoadVectorGatherMaskedNode : public LoadVectorNode { public: LoadVectorGatherMaskedNode(Node* c, Node* mem, Node* adr, const TypePtr* at, const TypeVect* vt, Node* indices, Node* mask) @@ -1347,7 +1361,7 @@ class LoadVectorGatherMaskedNode : public LoadVectorNode { add_req(indices); add_req(mask); assert(req() == MemNode::ValueIn + 2, "match_edge expects that last input is in MemNode::ValueIn+1"); - assert(is_subword_type(vt->element_basic_type()) || indices->bottom_type()->is_vect(), "indices must be in vector"); + assert(indices->bottom_type()->is_vect(), "indices must be in vector"); } virtual int Opcode() const; @@ -1360,7 +1374,12 @@ class LoadVectorGatherMaskedNode : public LoadVectorNode { } }; -// Store Vector into memory via index map under the influence of a predicate register(mask). +// Masked Scatter: +// indices: int-vector of indices +// adr: memory address (e.g. array base plus offset) +// if (mask[i]) { adr[indices[i]] = val[i] } +// +// Note: assumes that indices are all in-bounds, performs no index checking. class StoreVectorScatterMaskedNode : public StoreVectorNode { public: enum { Indices = 4, @@ -2221,6 +2240,7 @@ class VectorUnboxNode : public VectorNode { private: uint size_of() const { return sizeof(*this); } bool depends_only_on_test_impl() const { return false; } + const TypePtr* in_adr_type_impl() const final { return TypePtr::BOTTOM; } }; // Lane-wise right rotation of the first input by the second input. diff --git a/src/hotspot/share/opto/vtransform.cpp b/src/hotspot/share/opto/vtransform.cpp index 93aefe0d5057..161438565f93 100644 --- a/src/hotspot/share/opto/vtransform.cpp +++ b/src/hotspot/share/opto/vtransform.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -972,10 +972,10 @@ float VTransformMemopScalarNode::cost(const VLoopAnalyzer& vloop_analyzer) const VTransformApplyResult VTransformMemopScalarNode::apply(VTransformApplyState& apply_state) const { apply_vtn_inputs_to_node(_node, apply_state); // The memory state has to be applied separately: the vtn does not hold it. This allows reordering. - Node* mem = apply_state.memory_state(_node->adr_type()); + Node* mem = apply_state.memory_state(_node->in_adr_type()); apply_state.phase()->igvn().replace_input_of(_node, 1, mem); if (_node->is_Store()) { - apply_state.set_memory_state(_node->adr_type(), _node); + apply_state.set_memory_state(_node->out_adr_type(), _node); } return VTransformApplyResult::make_scalar(_node); @@ -1016,7 +1016,7 @@ void VTransformPhiScalarNode::apply_backedge(VTransformApplyState& apply_state) if (_node->is_memory_phi()) { // Memory phi/backedge // The last memory state of that slice is the backedge. - Node* last_state = apply_state.memory_state(_node->adr_type()); + Node* last_state = apply_state.memory_state(_node->in_adr_type()); phase->igvn().replace_input_of(_node, 2, last_state); } else { // Data phi/backedge diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index b88874f5c451..c15c48042f6e 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -23,13 +23,13 @@ */ #include "cds/aotClassInitializer.hpp" +#include "cds/aotHeap.hpp" #include "cds/aotMetaspace.hpp" #include "cds/aotModules.hpp" #include "cds/cdsConfig.hpp" #include "cds/classListParser.hpp" #include "cds/classListWriter.hpp" #include "cds/dynamicArchive.hpp" -#include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/lambdaProxyClassDictionary.hpp" #include "classfile/classFileStream.hpp" @@ -3531,7 +3531,7 @@ JVM_END JVM_ENTRY(void, JVM_InitializeFromArchive(JNIEnv* env, jclass cls)) Klass* k = java_lang_Class::as_Klass(JNIHandles::resolve(cls)); - HeapShared::initialize_from_archived_subgraph(THREAD, k); + AOTHeap::initialize_from_archived_subgraph(THREAD, k); JVM_END JVM_ENTRY(void, JVM_RegisterLambdaProxyClassForArchiving(JNIEnv* env, diff --git a/src/hotspot/share/prims/jvmti.xml b/src/hotspot/share/prims/jvmti.xml index 6e6bed1e8495..8a5a1fc27a1b 100644 --- a/src/hotspot/share/prims/jvmti.xml +++ b/src/hotspot/share/prims/jvmti.xml @@ -3549,6 +3549,13 @@ err = (*jvmti)->Deallocate(jvmti, stack_info); describe the kind of reference being reported. +

+ Implementation Note: + In the HotSpot VM implementation, a reference from a class loader to each + non-hidden class defined by the class loader is reported with reference kind + . +

+ - Heap root reference: other heap root reference. + Other reference from an object to another object, or other heap root reference. @@ -4374,12 +4381,15 @@ class C2 extends C1 implements I2 { Points to the tag of the referrer object, or points to the zero if the referrer object is not tagged. - Null if the referrer in not an object (that is, + Null if the referrer is not an object (that is, this callback is reporting a heap root). To set the tag value to be associated with the referrer object the agent sets the jlong pointed to by the parameter. If this callback is reporting a reference from an object to itself, referrer_tag_ptr == tag_ptr. + For , this parameter can be + null or non-null, depending on whether the callback is reporting a heap + root reference or a reference from an object. @@ -10316,10 +10326,10 @@ myInit() { Set Extension Event Callback - Sets the callback function for an extension event and - enables the event. Or, if the callback is null, disables - the event. Note that unlike standard events, setting - the callback and enabling the event are a single operation. + Sets the callback function for an extension event. + If is null the callback is removed. + Use with + to enable or disable notification. new @@ -10338,12 +10348,12 @@ myInit() { jvmtiExtensionEvent - disable the event + remove the callback + If is null the callback is removed. If callback is non-null, - set callback to be the event callback function - and enable the event. + set callback to be the event callback function. @@ -14177,7 +14187,7 @@ myInit() { every 512KB).

If another sampling interval is required, the user can call - with a strictly positive integer value, + with a non-negative integer value, representing the new sampling interval.

This event is sent once the sampled allocation has been performed. It provides the object, stack trace diff --git a/src/hotspot/share/prims/jvmtiAgent.cpp b/src/hotspot/share/prims/jvmtiAgent.cpp index 5fe5378995b8..82c43f64e98d 100644 --- a/src/hotspot/share/prims/jvmtiAgent.cpp +++ b/src/hotspot/share/prims/jvmtiAgent.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -578,7 +578,7 @@ static bool invoke_Agent_OnAttach(JvmtiAgent* agent, outputStream* st) { #if INCLUDE_CDS static void check_cds_dump(JvmtiAgent* agent) { if (CDSConfig::new_aot_flags_used()) { // JEP 483 - // Agents are allowed with -XX:AOTMode=record and -XX:AOTMode=on/auto. + // Agents are allowed with -XX:AOTMode=record and -XX:AOTMode=required/auto. // Agents are completely disabled when -XX:AOTMode=create (see cdsConfig.cpp) assert(!CDSConfig::is_dumping_final_static_archive(), "agents should have been disabled with -XX:AOTMode=create"); } else if (CDSConfig::is_dumping_classic_static_archive() || CDSConfig::is_dumping_dynamic_archive()) { diff --git a/src/hotspot/share/prims/jvmtiEnv.cpp b/src/hotspot/share/prims/jvmtiEnv.cpp index 6f5280d3d5b1..c89f93371715 100644 --- a/src/hotspot/share/prims/jvmtiEnv.cpp +++ b/src/hotspot/share/prims/jvmtiEnv.cpp @@ -2514,8 +2514,11 @@ JvmtiEnv::SetBreakpoint(Method* method, jlocation location) { ResourceMark rm; JvmtiBreakpoint bp(method, location); JvmtiBreakpoints& jvmti_breakpoints = JvmtiCurrentBreakpoints::get_jvmti_breakpoints(); - if (jvmti_breakpoints.set(bp) == JVMTI_ERROR_DUPLICATE) - return JVMTI_ERROR_DUPLICATE; + + jvmtiError err = (jvmtiError) jvmti_breakpoints.set(bp); + if (err != JVMTI_ERROR_NONE) { + return err; + } if (TraceJVMTICalls) { jvmti_breakpoints.print(); diff --git a/src/hotspot/share/prims/jvmtiImpl.cpp b/src/hotspot/share/prims/jvmtiImpl.cpp index 9f7b1c70b3a0..ec5b157d59c4 100644 --- a/src/hotspot/share/prims/jvmtiImpl.cpp +++ b/src/hotspot/share/prims/jvmtiImpl.cpp @@ -261,6 +261,13 @@ int JvmtiBreakpoints::set(JvmtiBreakpoint& bp) { // Ensure that bp._method is not deallocated before VM_ChangeBreakpoints::doit(). methodHandle mh(Thread::current(), bp.method()); + + // Build MethodCounters on the calling JavaThread so the breakpoint count can't be dropped + // later in the VM operation. + if (mh->get_method_counters(Thread::current()) == nullptr) { + return JVMTI_ERROR_OUT_OF_MEMORY; + } + VM_ChangeBreakpoints set_breakpoint(VM_ChangeBreakpoints::SET_BREAKPOINT, &bp); VMThread::execute(&set_breakpoint); return JVMTI_ERROR_NONE; diff --git a/src/hotspot/share/prims/jvmtiTagMap.cpp b/src/hotspot/share/prims/jvmtiTagMap.cpp index 03d6c15cfbd1..0889976ff5c6 100644 --- a/src/hotspot/share/prims/jvmtiTagMap.cpp +++ b/src/hotspot/share/prims/jvmtiTagMap.cpp @@ -22,6 +22,7 @@ * */ +#include "classfile/classLoaderData.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/javaStackTraceClasses.hpp" @@ -1856,6 +1857,7 @@ class CallbackInvoker : AllStatic { // functions to report references static inline bool report_array_element_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index); + static inline bool report_other_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); static inline bool report_class_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); static inline bool report_class_loader_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); static inline bool report_signers_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree); @@ -2459,6 +2461,15 @@ inline bool CallbackInvoker::report_field_reference(const JvmtiHeapwalkObject& r } } +inline bool CallbackInvoker::report_other_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree) { + // Reporting classes from the class loader is not supported for old Heap 1.0 functions. + if (is_basic_heap_walk()) { + return true; + } else { + return invoke_advanced_object_reference_callback(JVMTI_HEAP_REFERENCE_OTHER, referrer, referree, -1); + } +} + // report an array referencing an element object inline bool CallbackInvoker::report_constant_pool_reference(const JvmtiHeapwalkObject& referrer, const JvmtiHeapwalkObject& referree, jint index) { if (is_basic_heap_walk()) { @@ -2785,8 +2796,11 @@ class VM_HeapWalkOperation: public VM_Operation { inline bool iterate_over_flat_array(const JvmtiHeapwalkObject& o); inline bool iterate_over_type_array(const JvmtiHeapwalkObject& o); inline bool iterate_over_class(const JvmtiHeapwalkObject& o); + inline bool iterate_over_class_loader(const JvmtiHeapwalkObject& o); inline bool iterate_over_object(const JvmtiHeapwalkObject& o); + class CollectDeclaredKlasses; + // root collection inline bool collect_simple_roots(); inline bool collect_stack_roots(); @@ -3086,6 +3100,43 @@ inline bool VM_HeapWalkOperation::iterate_over_class(const JvmtiHeapwalkObject& return true; } +class VM_HeapWalkOperation::CollectDeclaredKlasses : public KlassClosure { + GrowableArray* _klasses; + public: + CollectDeclaredKlasses(GrowableArray* klasses) : _klasses(klasses) {} + void do_klass(Klass* k) { + if (k->is_instance_klass() && InstanceKlass::cast(k)->is_loaded() && !k->is_hidden()) { + _klasses->push(k); + } + } +}; + +inline bool VM_HeapWalkOperation::iterate_over_class_loader(const JvmtiHeapwalkObject& o) { + assert(!o.is_flat(), "ClassLoaderKlass object cannot be flattened"); + bool res = iterate_over_object(o); + // For a class loader, iterate over the loaded classes also to maintain compatibility + // for when ClassLoader had a vector of loaded classes. + ClassLoaderData* data = java_lang_ClassLoader::loader_data_acquire(o.obj()); + + if (res && data != nullptr) { + ResourceMark rm; + GrowableArray* klasses = new GrowableArray(10); + CollectDeclaredKlasses itr(klasses); + data->classes_do(&itr); + + for (int i = 0; i < klasses->length(); i++) { + Klass* k = klasses->at(i); + JvmtiHeapwalkObject m(k->java_mirror()); + // Pretend the classes are referred indirectly by the class loader. They are + // root objects, so make them other references. + if (!CallbackInvoker::report_other_reference(o, m)) { + return false; + } + } + } + return res; +} + // an object references a class and its instance fields // (static fields are ignored here as we report these as // references from the class). @@ -3324,6 +3375,8 @@ bool VM_HeapWalkOperation::visit(const JvmtiHeapwalkObject& o) { // a java.lang.Class return iterate_over_class(o); } + } else if (java_lang_ClassLoader::is_instance(o.obj())) { + return iterate_over_class_loader(o); } else { // we report stack references only when initial object is not specified // (in the case we start from heap roots which include platform thread stack references) diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.cpp b/src/hotspot/share/prims/jvmtiTagMapTable.cpp index 31cb9890d74b..f5d238511285 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.cpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.cpp @@ -270,13 +270,6 @@ void JvmtiTagMapTable::add(const JvmtiHeapwalkObject& obj, jlong tag) { jlong* value = _table.put_if_absent(new_entry, tag, &is_added); *value = tag; // assign the new tag } - if (is_added) { - if (_table.maybe_grow(5, true /* use_large_table_sizes */)) { - int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0); - log_info(jvmti, table) ("JvmtiTagMap table resized to %d for %d entries max bucket %d", - _table.table_size(), _table.number_of_entries(), max_bucket_size); - } - } } bool JvmtiTagMapTable::update(const JvmtiHeapwalkObject& obj, jlong tag) { @@ -393,13 +386,6 @@ void JvmtiFlatTagMapTable::add(const JvmtiHeapwalkObject& obj, jlong tag) { bool is_added; jlong* value = _table.put_if_absent(entry, tag, &is_added); *value = tag; // assign the new tag - if (is_added) { - if (_table.maybe_grow(5, true /* use_large_table_sizes */)) { - int max_bucket_size = DEBUG_ONLY(_table.verify()) NOT_DEBUG(0); - log_info(jvmti, table) ("JvmtiFlatTagMapTable table resized to %d for %d entries max bucket %d", - _table.table_size(), _table.number_of_entries(), max_bucket_size); - } - } } jlong JvmtiFlatTagMapTable::remove(const JvmtiHeapwalkObject& obj) { diff --git a/src/hotspot/share/prims/jvmtiTagMapTable.hpp b/src/hotspot/share/prims/jvmtiTagMapTable.hpp index 07ed4cfc4430..ee1237b5b8d9 100644 --- a/src/hotspot/share/prims/jvmtiTagMapTable.hpp +++ b/src/hotspot/share/prims/jvmtiTagMapTable.hpp @@ -118,7 +118,9 @@ typedef ResizeableHashTable ResizableHT; + JvmtiTagMapKey::equals, + 5, /*load-factor*/ + true /*large table */> ResizableHT; // A supporting class for iterating over all entries in Hashmap class JvmtiTagMapKeyClosure { diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 18b6d00a0896..4c2dc0596aba 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -27,7 +27,6 @@ #include "cds/aotMetaspace.hpp" #include "cds/cdsConstants.hpp" #include "cds/filemap.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "classfile/classLoaderStats.hpp" @@ -123,6 +122,12 @@ #if INCLUDE_SERIALGC #include "gc/serial/serialHeap.hpp" #endif // INCLUDE_SERIALGC +#if INCLUDE_SHENANDOAHGC +#include "gc/shenandoah/shenandoahGenerationalHeap.hpp" +#include "gc/shenandoah/shenandoahHeap.hpp" +#include "gc/shenandoah/shenandoahHeapRegion.hpp" +#include "gc/shenandoah/shenandoahOldGeneration.hpp" +#endif // INCLUDE_SHENANDOAHGC #if INCLUDE_ZGC #include "gc/z/zAddress.inline.hpp" #include "gc/z/zHeap.inline.hpp" @@ -695,6 +700,37 @@ WB_END #endif // INCLUDE_G1GC +#if INCLUDE_SHENANDOAHGC + +WB_ENTRY(jint, WB_ShenandoahRegionSize(JNIEnv* env, jobject o)) + if (UseShenandoahGC) { + return ShenandoahHeapRegion::region_size_bytes_jint(); + } + THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_ShenandoahRegionSize: Shenandoah GC is not enabled"); +WB_END + +WB_ENTRY(jint, WB_ShenandoahRegionCount(JNIEnv* env, jobject o)) + if (UseShenandoahGC) { + return static_cast(ShenandoahHeap::heap()->num_regions()); + } + THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_ShenandoahRegionCount: Shenandoah GC is not enabled"); +WB_END + +WB_ENTRY(jlong, WB_ShenandoahOldMarkGC(JNIEnv* env, jobject o, jint timeout_seconds)) + if (UseShenandoahGC && ShenandoahHeap::heap()->mode()->is_generational()) { + if (timeout_seconds < 0) { + THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "WB_ShenandoahOldMarkGC: Timeout must be non-negative"); + } + if (ShenandoahGenerationalHeap::heap()->wait_for_old_collection(timeout_seconds)) { + return ShenandoahHeap::heap()->old_generation()->completed_gc_id(); + } + THROW_MSG_0(vmSymbols::java_lang_IllegalStateException(), "WB_ShenandoahOldMarkGC: Could not complete old GC cycle"); + } + THROW_MSG_0(vmSymbols::java_lang_UnsupportedOperationException(), "WB_ShenandoahOldMarkGC: Generational mode for Shenandoah GC is not enabled"); +WB_END + +#endif // INCLUDE_SHENANDOAHGC + // Alloc memory using the test memory tag so that we can use that to see if // NMT picks it up correctly WB_ENTRY(jlong, WB_NMTMalloc(JNIEnv* env, jobject o, jlong size)) @@ -2985,6 +3021,11 @@ static JNINativeMethod methods[] = { {CC"g1MemoryNodeIds", CC"()[I", (void*)&WB_G1MemoryNodeIds }, {CC"g1GetMixedGCInfo", CC"(I)[J", (void*)&WB_G1GetMixedGCInfo }, #endif // INCLUDE_G1GC +#if INCLUDE_SHENANDOAHGC + {CC"shenandoahRegionSize", CC"()I", (void*)&WB_ShenandoahRegionSize }, + {CC"shenandoahRegionCount", CC"()I", (void*)&WB_ShenandoahRegionCount }, + {CC"shenandoahOldMarkGC", CC"(I)J", (void*)&WB_ShenandoahOldMarkGC }, +#endif // INCLUDE_SHENANDOAHGC {CC"NMTMalloc", CC"(J)J", (void*)&WB_NMTMalloc }, {CC"NMTMallocWithPseudoStack", CC"(JI)J", (void*)&WB_NMTMallocWithPseudoStack}, {CC"NMTMallocWithPseudoStackAndType", CC"(JII)J", (void*)&WB_NMTMallocWithPseudoStackAndType}, diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 39f938de90fb..064ff2cabb73 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -537,6 +537,7 @@ static SpecialFlag const special_jvm_flags[] = { // -------------- Obsolete Flags - sorted by expired_in -------------- { "MetaspaceReclaimPolicy", JDK_Version::undefined(), JDK_Version::jdk(21), JDK_Version::undefined() }, + { "ShenandoahDegeneratedGC", JDK_Version::undefined(), JDK_Version::jdk(28), JDK_Version::jdk(29) }, #if defined(AARCH64) { "NearCpool", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::undefined() }, #endif diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp index 80f993eba70b..84cf7818aae7 100644 --- a/src/hotspot/share/runtime/arguments.hpp +++ b/src/hotspot/share/runtime/arguments.hpp @@ -25,6 +25,7 @@ #ifndef SHARE_RUNTIME_ARGUMENTS_HPP #define SHARE_RUNTIME_ARGUMENTS_HPP +#include "gc/shared/gcArguments.hpp" #include "jni.h" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" diff --git a/src/hotspot/share/runtime/globals_extension.hpp b/src/hotspot/share/runtime/globals_extension.hpp index e01e33eb306f..07ba06ce6c6b 100644 --- a/src/hotspot/share/runtime/globals_extension.hpp +++ b/src/hotspot/share/runtime/globals_extension.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -96,4 +96,16 @@ ALL_FLAGS(DEFINE_FLAG_MEMBER_SETTER, } \ } while (0) +#define FLAG_SET_ERGO_IF_DEFAULT_OR_VALUE(name, set_value, check_value) \ + do { \ + if (FLAG_IS_DEFAULT(name)) { \ + FLAG_SET_ERGO(name, set_value); \ + } else if (name == check_value) { \ + FLAG_SET_ERGO(name, set_value); \ + } \ + } while (0) + +#define FLAG_SET_ERGO_IF_DEFAULT_OR_ZERO(name, value) \ + FLAG_SET_ERGO_IF_DEFAULT_OR_VALUE(name, value, 0) + #endif // SHARE_RUNTIME_GLOBALS_EXTENSION_HPP diff --git a/src/hotspot/share/runtime/hotCodeSampler.cpp b/src/hotspot/share/runtime/hotCodeSampler.cpp index 33579d1b2685..7a73786a6aa8 100644 --- a/src/hotspot/share/runtime/hotCodeSampler.cpp +++ b/src/hotspot/share/runtime/hotCodeSampler.cpp @@ -89,9 +89,6 @@ bool ThreadSampler::sample_all_java_threads() { bool created = false; Pair* sampled_nm = _samples.put_if_absent(compile_id, Pair(nm, 0), &created); sampled_nm->second++; - if (created) { - _samples.maybe_grow(); - } } return true; } diff --git a/src/hotspot/share/runtime/park.cpp b/src/hotspot/share/runtime/park.cpp index 9cf29f96e556..82d59d573e9a 100644 --- a/src/hotspot/share/runtime/park.cpp +++ b/src/hotspot/share/runtime/park.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,8 +22,6 @@ * */ -#include "memory/allocation.inline.hpp" -#include "nmt/memTracker.hpp" #include "runtime/javaThread.hpp" #include "utilities/spinCriticalSection.hpp" @@ -74,7 +72,6 @@ ParkEvent * ParkEvent::Allocate (Thread * t) { } else { // Do this the hard way -- materialize a new ParkEvent. ev = new ParkEvent () ; - guarantee ((intptr_t(ev) & 0xFF) == 0, "invariant") ; } ev->reset() ; // courtesy to caller ev->AssociatedWith = t ; // Associate ev with t @@ -95,18 +92,10 @@ void ParkEvent::Release (ParkEvent * ev) { } } -// Override operator new and delete so we can ensure that the -// least significant byte of ParkEvent addresses is 0. -// Beware that excessive address alignment is undesirable -// as it can result in D$ index usage imbalance as -// well as bank access imbalance on Niagara-like platforms, -// although Niagara's hash function should help. - -void * ParkEvent::operator new (size_t sz) throw() { - return (void *) ((intptr_t (AllocateHeap(sz + 256, mtSynchronizer, CALLER_PC)) + 256) & -256); -} +// We currently allow a reference to a stale ParkEvent, even after the +// thread has died. Therefore we override operator delete to ensure that +// the ParkEvent is type-stable and immortal. void ParkEvent::operator delete (void * a) { - // ParkEvents are type-stable and immortal ... ShouldNotReachHere(); } diff --git a/src/hotspot/share/runtime/park.hpp b/src/hotspot/share/runtime/park.hpp index 4406a66da0d0..7369dc8b8b07 100644 --- a/src/hotspot/share/runtime/park.hpp +++ b/src/hotspot/share/runtime/park.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -135,11 +135,6 @@ class ParkEvent : public PlatformEvent { FreeNext = nullptr ; } - // We use placement-new to force ParkEvent instances to be - // aligned on 256-byte address boundaries. This ensures that the least - // significant byte of a ParkEvent address is always 0. - - void * operator new (size_t sz) throw(); void operator delete (void * a) ; public: diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 0f94937f67d6..910dce3e4229 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -23,11 +23,11 @@ * */ +#include "cds/aotHeap.hpp" #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotMetaspace.hpp" #include "cds/cds_globals.hpp" #include "cds/cdsConfig.hpp" -#include "cds/heapShared.inline.hpp" #include "classfile/classLoader.hpp" #include "classfile/javaClasses.hpp" #include "classfile/javaThreadStatus.hpp" @@ -100,6 +100,7 @@ #include "runtime/vmOperations.hpp" #include "sanitizers/address.hpp" #include "services/attachListener.hpp" +#include "services/diagnosticWorkers.hpp" #include "services/management.hpp" #include "services/threadIdTable.hpp" #include "services/threadService.hpp" @@ -375,7 +376,7 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_Thread(), CHECK); create_initial_thread(thread_group, main_thread, CHECK); - HeapShared::init_box_classes(CHECK); + AOTHeap::init_box_classes(CHECK); // The VM creates objects of this class. initialize_class(vmSymbols::java_lang_Module(), CHECK); @@ -398,7 +399,7 @@ void Threads::initialize_java_lang_classes(JavaThread* main_thread, TRAPS) { initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK); initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK); - HeapShared::materialize_thread_object(); + AOTHeap::materialize_thread_object(); // Phase 1 of the system initialization in the library, java.lang.System class initialization call_initPhase1(CHECK); @@ -445,7 +446,7 @@ void Threads::initialize_jsr292_core_classes(TRAPS) { initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK); if (UseSharedSpaces) { - HeapShared::initialize_java_lang_invoke(CHECK); + AOTHeap::initialize_java_lang_invoke(CHECK); } } @@ -720,7 +721,7 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { StubCodeDesc::freeze(); // Prepare AOT heap loader for GC. - HeapShared::enable_gc(); + AOTHeap::enable_gc(); #ifdef ADDRESS_SANITIZER Asan::initialize(); @@ -909,7 +910,7 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { WatcherThread::run_all_tasks(); // Finish materializing AOT objects - HeapShared::finish_materialize_objects(); + AOTHeap::finish_materialize_objects(); create_vm_timer.end(); #ifdef ASSERT @@ -1511,6 +1512,7 @@ void Threads::print_on_error(outputStream* st, Thread* current, char* buf, if (Universe::heap() != nullptr) { PrintOnErrorClosure print_closure(st, current, buf, buflen, &found_current); Universe::heap()->gc_threads_do(&print_closure); + DiagnosticWorkers::threads_do(&print_closure); num_other += print_closure.num_printed(); } diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index c72ec5d3834a..740de2a090c7 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -93,7 +93,6 @@ template(ShenandoahInitUpdateRefs) \ template(ShenandoahFinalUpdateRefs) \ template(ShenandoahFinalVerify) \ - template(ShenandoahDegeneratedGC) \ template(Exit) \ template(LinuxDllLoad) \ template(WhiteBoxOperation) \ diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index d6b26f209f62..17cd61ea55f9 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -193,6 +193,7 @@ nonstatic_field(ConstantPoolCache, _resolved_indy_entries, Array*) \ nonstatic_field(ResolvedIndyEntry, _cpool_index, u2) \ nonstatic_field(ValueFieldInfo, _klass, ValueKlass*) \ + nonstatic_field(ValueFieldInfo, _kind, LayoutKind) \ volatile_nonstatic_field(InstanceKlass, _array_klasses, ObjArrayKlass*) \ nonstatic_field(InstanceKlass, _methods, Array*) \ nonstatic_field(InstanceKlass, _default_methods, Array*) \ @@ -273,6 +274,7 @@ nonstatic_field(ConstMethod, _num_stack_arg_slots, u2) \ nonstatic_field(ObjArrayKlass, _element_klass, Klass*) \ nonstatic_field(ObjArrayKlass, _bottom_klass, Klass*) \ + nonstatic_field(FlatArrayKlass, _layout_kind, LayoutKind) \ volatile_nonstatic_field(Symbol, _hash_and_refcount, unsigned int) \ nonstatic_field(Symbol, _length, u2) \ unchecked_nonstatic_field(Symbol, _body, sizeof(u1)) /* NOTE: no type */ \ @@ -1203,6 +1205,7 @@ \ declare_integer_type(AOTCompressedPointers::narrowPtr) \ declare_integer_type(Bytecodes::Code) \ + declare_integer_type(LayoutKind) \ declare_integer_type(InstanceKlass::ClassState) \ declare_integer_type(Klass::KlassKind) \ declare_integer_type(JavaThreadState) \ @@ -1486,6 +1489,18 @@ \ declare_constant(InstanceKlass::enclosing_method_attribute_size) \ \ + /*******************/ \ + /* LayoutKind enum */ \ + /*******************/ \ + \ + declare_constant(LayoutKind::REFERENCE) \ + declare_constant(LayoutKind::BUFFERED) \ + declare_constant(LayoutKind::NULL_FREE_NON_ATOMIC_FLAT) \ + declare_constant(LayoutKind::NULL_FREE_ATOMIC_FLAT) \ + declare_constant(LayoutKind::NULLABLE_ATOMIC_FLAT) \ + declare_constant(LayoutKind::NULLABLE_NON_ATOMIC_FLAT) \ + declare_constant(LayoutKind::UNKNOWN) \ + \ /*********************************/ \ /* InstanceKlass ClassState enum */ \ /*********************************/ \ diff --git a/src/hotspot/share/services/diagnosticWorkers.cpp b/src/hotspot/share/services/diagnosticWorkers.cpp new file mode 100644 index 000000000000..bad6948be490 --- /dev/null +++ b/src/hotspot/share/services/diagnosticWorkers.cpp @@ -0,0 +1,70 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "gc/shared/workerThread.hpp" +#include "logging/log.hpp" +#include "runtime/os.hpp" +#include "runtime/safepoint.hpp" +#include "runtime/thread.hpp" +#include "services/diagnosticWorkers.hpp" + +Atomic DiagnosticWorkers::_workers{}; + +WorkerThreads* DiagnosticWorkers::workers() { + assert(Thread::current()->is_VM_thread(), "Must be the VM thread"); + assert_at_safepoint(); + if (_workers.load_relaxed() == nullptr) { + const uint max_workers = (uint)os::active_processor_count(); + if (max_workers > 1) { + WorkerThreads* pool = new WorkerThreads("DiagWorker", max_workers); + // Use release_store() because the error reporter can read _workers while + // the pool is being created. + _workers.release_store(pool); + log_info(gc, task)("Created diagnostic worker pool (max %u workers)", pool->max_workers()); + } + } + return _workers.load_relaxed(); +} + +void DiagnosticWorkers::threads_do(ThreadClosure* tc) { + // The error reporter can call this method outside a safepoint, so use load_acquire(). + WorkerThreads* workers = _workers.load_acquire(); + if (workers != nullptr) { + workers->threads_do(tc); + } +} + +uint DiagnosticWorkers::try_and_set_active_workers(uint num_workers) { + assert(Thread::current()->is_VM_thread(), "Must be the VM thread"); + assert_at_safepoint(); + if (num_workers == 0) { + return 0; + } + + WorkerThreads* pool = workers(); + if (pool == nullptr) { + return 0; + } + return pool->set_active_workers(MIN2(num_workers, pool->max_workers())); +} diff --git a/src/hotspot/share/services/diagnosticWorkers.hpp b/src/hotspot/share/services/diagnosticWorkers.hpp new file mode 100644 index 000000000000..3127f47e85ed --- /dev/null +++ b/src/hotspot/share/services/diagnosticWorkers.hpp @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_SERVICES_DIAGNOSTICWORKERS_HPP +#define SHARE_SERVICES_DIAGNOSTICWORKERS_HPP + +#include "memory/allStatic.hpp" +#include "runtime/atomic.hpp" +#include "utilities/globalDefinitions.hpp" + +class ThreadClosure; +class WorkerThreads; + +// Worker pool used by diagnostic tasks, for example heap dump and heap inspection. +// The pool is created lazily on the first parallel request and threads are added to +// the pool as each request needs them. Threads are reused and exist until the VM exits. +class DiagnosticWorkers : public AllStatic { +private: + // Written once by the VM thread at a safepoint. Also read by the error reporter, + // which can run outside a safepoint. + static Atomic _workers; + +public: + // Returns the diagnostic worker pool, creating it on first use and sizing it to the + // active processor count at that time. Returns nullptr if the pool hasn't been created + // and the active processor count is 1. Can only be called by the VM thread at a safepoint. + static WorkerThreads* workers(); + + // Applies the closure to each diagnostic worker if the worker pool has been created. + static void threads_do(ThreadClosure* tc); + + // Sets the number of active worker threads (capped at max_workers()) and returns the + // active thread count. Creates the worker pool on first use. Returns 0 if the pool + // wasn't created or num_workers is 0. Can only be called by the VM thread at a safepoint. + static uint try_and_set_active_workers(uint num_workers); +}; + +#endif // SHARE_SERVICES_DIAGNOSTICWORKERS_HPP diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 587ae2afe226..87c68894eb01 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -61,6 +61,7 @@ #include "runtime/vframe.hpp" #include "runtime/vmOperations.hpp" #include "runtime/vmThread.hpp" +#include "services/diagnosticWorkers.hpp" #include "services/heapDumper.hpp" #include "services/heapDumperCompression.hpp" #include "services/threadService.hpp" @@ -2298,7 +2299,7 @@ class DumperController : public CHeapObj { public: DumperController(uint number) : - // _lock and _global_writer_lock are used for synchronization between GC worker threads inside safepoint, + // _lock and _global_writer_lock are used for synchronization between diagnostic worker threads inside safepoint, // so we lock with _no_safepoint_check_flag. // signal_start() acquires _lock when global writer is locked, // its rank must be less than _global_writer_lock rank. @@ -2602,7 +2603,8 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask, public Unmounte } int dump_seq() { return _dump_seq; } bool is_parallel_dump() { return _num_dumper_threads > 1; } - void prepare_parallel_dump(WorkerThreads* workers); + // Returns the worker pool for a parallel dump or nullptr for a serial dump. + WorkerThreads* prepare_parallel_dump(); VMOp_Type type() const { return VMOp_HeapDumper; } virtual bool doit_prologue(); @@ -2645,14 +2647,21 @@ bool VM_HeapDumper::doit_prologue() { return VM_GC_Operation::doit_prologue(); } -void VM_HeapDumper::prepare_parallel_dump(WorkerThreads* workers) { - uint num_active_workers = workers != nullptr ? workers->active_workers() : 0; - uint num_requested_dump_threads = _num_dumper_threads; - // check if we can dump in parallel based on requested and active threads - if (num_active_workers <= 1 || num_requested_dump_threads <= 1) { +WorkerThreads* VM_HeapDumper::prepare_parallel_dump() { + const uint num_requested_dump_threads = _num_dumper_threads; + uint num_active_workers = 0; + WorkerThreads* workers = nullptr; + + if (Universe::heap()->supports_parallel_object_iteration() && _num_dumper_threads > 1) { + num_active_workers = DiagnosticWorkers::try_and_set_active_workers(_num_dumper_threads); + } + + // check if we can dump in parallel based on active threads + if (num_active_workers <= 1) { _num_dumper_threads = 1; } else { - _num_dumper_threads = clamp(num_requested_dump_threads, 2U, num_active_workers); + workers = DiagnosticWorkers::workers(); + _num_dumper_threads = num_active_workers; } _dumper_controller = new (std::nothrow) DumperController(_num_dumper_threads); bool can_parallel = _num_dumper_threads > 1; @@ -2660,6 +2669,7 @@ void VM_HeapDumper::prepare_parallel_dump(WorkerThreads* workers) { "actual dump threads %u, parallelism %s", num_requested_dump_threads, num_active_workers, _num_dumper_threads, can_parallel ? "true" : "false"); + return workers; } // The VM operation that dumps the heap. The dump consists of the following @@ -2701,8 +2711,7 @@ void VM_HeapDumper::doit() { } } - WorkerThreads* workers = ch->safepoint_workers(); - prepare_parallel_dump(workers); + WorkerThreads* workers = prepare_parallel_dump(); if (!is_parallel_dump()) { work(VMDumperId); diff --git a/src/hotspot/share/services/memoryService.hpp b/src/hotspot/share/services/memoryService.hpp index 1a947ec1d0c9..6541c6ea3870 100644 --- a/src/hotspot/share/services/memoryService.hpp +++ b/src/hotspot/share/services/memoryService.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -152,6 +152,8 @@ class TraceMemoryManagerStats : public StackObj { bool recordGCEndTime, bool countCollection); + void set_all_memory_pools_affected() { _allMemoryPoolsAffected = true; } + ~TraceMemoryManagerStats(); }; diff --git a/src/hotspot/share/utilities/objectBitSet.inline.hpp b/src/hotspot/share/utilities/objectBitSet.inline.hpp index e204841e6e20..6f1167d0022b 100644 --- a/src/hotspot/share/utilities/objectBitSet.inline.hpp +++ b/src/hotspot/share/utilities/objectBitSet.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -77,7 +77,6 @@ inline CHeapBitMap* ObjectBitSet::get_fragment_bits(uintptr_t addr) { bits = fragment->bits(); _fragment_list = fragment; _bitmap_fragments.put(granule, bits); - _bitmap_fragments.maybe_grow(); } _last_fragment_bits = bits; diff --git a/src/hotspot/share/utilities/resizableHashTable.hpp b/src/hotspot/share/utilities/resizableHashTable.hpp index ad51caff5e1d..62594a82947d 100644 --- a/src/hotspot/share/utilities/resizableHashTable.hpp +++ b/src/hotspot/share/utilities/resizableHashTable.hpp @@ -74,7 +74,9 @@ template< AnyObj::allocation_type ALLOC_TYPE = AnyObj::RESOURCE_AREA, MemTag MEM_TAG = mtInternal, unsigned (*HASH) (K const&) = primitive_hash, - bool (*EQUALS)(K const&, K const&) = primitive_equals + bool (*EQUALS)(K const&, K const&) = primitive_equals, + int LOAD_FACTOR = 8, + bool USE_LARGE_TABLE_SIZE = false > class ResizeableHashTable : public HashTableBase< ResizeableHashTableStorage, @@ -87,7 +89,7 @@ class ResizeableHashTable : public HashTableBase< NONCOPYABLE(ResizeableHashTable); // Calculate next "good" hashtable size based on requested count - int calculate_resize(bool use_large_table_sizes) const { + int calculate_resize() const { const int resize_factor = 2; // by how much we will resize using current number of entries // possible hashmap sizes - odd primes that roughly double in size. @@ -99,7 +101,7 @@ class ResizeableHashTable : public HashTableBase< const int large_array_size = sizeof(large_table_sizes)/sizeof(int); int requested = resize_factor * BASE::number_of_entries(); - int start_at = use_large_table_sizes ? 8 : 0; + int start_at = USE_LARGE_TABLE_SIZE ? 8 : 0; int newsize; for (int i = start_at; i < large_array_size; i++) { newsize = large_table_sizes[i]; @@ -110,19 +112,13 @@ class ResizeableHashTable : public HashTableBase< return requested; // greater than a size in the table } -public: - ResizeableHashTable(unsigned size, unsigned max_size) - : BASE(size), _max_size(max_size) { - assert(size <= 0x3fffffff && max_size <= 0x3fffffff, "avoid overflow in resize"); - } - - bool maybe_grow(int load_factor = 8, bool use_large_table_sizes = false) { + bool maybe_grow() { unsigned old_size = BASE::_table_size; if (old_size >= _max_size) { return false; } - if (BASE::number_of_entries() / int(old_size) > load_factor) { - unsigned new_size = MIN2(calculate_resize(use_large_table_sizes), _max_size); + if (BASE::number_of_entries() / int(old_size) > LOAD_FACTOR) { + unsigned new_size = MIN2(calculate_resize(), _max_size); resize(new_size); return true; } else { @@ -130,6 +126,12 @@ class ResizeableHashTable : public HashTableBase< } } +public: + ResizeableHashTable(unsigned size, unsigned max_size) + : BASE(size), _max_size(max_size) { + assert(size <= 0x3fffffff && max_size <= 0x3fffffff, "avoid overflow in resize"); + } + void resize(unsigned new_size) { Node** old_table = BASE::_table; Node** new_table = BASE::alloc_table(new_size); @@ -157,27 +159,37 @@ class ResizeableHashTable : public HashTableBase< BASE::_table_size = new_size; } -#ifdef ASSERT - int verify() { - Node** table = BASE::_table; - // Return max bucket size. If hashcode is broken, this will be - // too high. - int max_bucket_size = 0; - int index = 0; - Node* const* bucket = table; - while (bucket < &table[BASE::_table_size]) { - int count = 0; - Node* node = *bucket; - while (node != nullptr) { - count++; - node = node->_next; - } - max_bucket_size = MAX2(count, max_bucket_size); - ++bucket; + bool put(K const& key, V const& value) { + bool created = BASE::put(key, value); + if (created) { + maybe_grow(); + } + return created; + } + + bool put_when_absent(K const& key, V const& value) { + bool created = BASE::put_when_absent(key, value); + if (created) { + maybe_grow(); + } + return created; + } + + V* put_if_absent(K const& key, bool* p_created) { + V* node = BASE::put_if_absent(key, p_created); + if (*p_created) { + maybe_grow(); + } + return node; + } + + V* put_if_absent(K const& key, V const& value, bool* p_created) { + V* node = BASE::put_if_absent(key, value, p_created); + if (*p_created) { + maybe_grow(); } - return max_bucket_size; + return node; } -#endif // ASSERT }; #endif // SHARE_UTILITIES_RESIZABLEHASHTABLE_HPP diff --git a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java index 2f4df1eba03b..5c09b061a9c9 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/GaloisCounterMode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1603,8 +1603,9 @@ public int doFinal(ByteBuffer src, ByteBuffer dst) NIO_ACCESS.acquireSession(dst); try { Unsafe.getUnsafe().setMemory( - NIO_ACCESS.getBufferAddress(dst), - len + dst.position(), (byte) 0); + NIO_ACCESS.getBufferAddress(dst) + + dst.position(), + len, (byte) 0); } finally { NIO_ACCESS.releaseSession(dst); } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index 5335357f8b97..f71a3f8ea6a8 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -1211,7 +1211,7 @@ static int implKyberAddPoly(short[] result, short[] a, short[] b) { static void implKyberAddPolyJava(short[] result, short[] a, short[] b) { for (int m = 0; m < ML_KEM_N; m++) { int r = a[m] + b[m] + ML_KEM_Q; // This makes r > - ML_KEM_Q - a[m] = (short) r; + result[m] = (short) r; } } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java b/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java index a8ba711d658c..3aee3e633fe4 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/PKCS5Padding.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -91,25 +91,27 @@ public int unpad(byte[] in, int off, int len) { (len == 0)) { // this can happen if input is really a padded buffer return 0; } - int idx = Math.addExact(off, len); - byte lastByte = in[idx - 1]; - int padValue = (int)lastByte & 0x0ff; - if ((padValue < 0x01) - || (padValue > blockSize)) { - return -1; - } - int start = idx - padValue; - if (start < off) { - return -1; - } + int last = Math.addExact(off, len); + int padValue = in[last - 1] & 0x0ff; + // check padValue >= 1; negative if fail + int invalid = (padValue - 1); + // check padValue <= blockSize; negative if fail + invalid |= (blockSize - padValue); - for (int i = start; i < idx; i++) { - if (in[i] != lastByte) { - return -1; - } + // check the last block for the padding bytes + for (int i = last - blockSize; i < last ; i++) { + // mask = 0 for the first (blockSize - padValue) bytes in the block + // otherwise, use 0xffff to preserve the value + int mask = (last - padValue - i - 1) >> 8 ; + int valueCheck = ((in[i] & 0x0ff) ^ padValue) & mask; + // again, assign negative value if fail + invalid |= -valueCheck; } - return start; + // convert 'invalid' to -1 when 'invalid' is not 0 + // right shifting by 8 should be sufficient since value range is small + invalid >>= 8; // 0 or -1 + return (invalid | (last - padValue)); } /** diff --git a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java index 4b38bd55809e..c9e7191df33d 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/SunJCE.java @@ -792,7 +792,7 @@ void putEntries() { ps("KeyGenerator", "SunTlsMasterSecret", "com.sun.crypto.provider.TlsMasterSecretGenerator", - List.of("SunTls12MasterSecret", "SunTlsExtendedMasterSecret"), + List.of("SunTls12MasterSecret", "SunTlsExtendedMasterSecret", "SunTls12ExtendedMasterSecret"), null); ps("KeyGenerator", "SunTlsKeyMaterial", diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java index fdb5a80e2a2a..e21def8cfbe4 100644 --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2019, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -310,23 +310,12 @@ static boolean isRegistered(Class c) { // Shared among all packages with unsigned classes private static final Certificate[] nocerts = new Certificate[0]; - // The classes loaded by this class loader. The only purpose of this table - // is to keep the classes from being GC'ed until the loader is GC'ed. - private final ArrayList> classes = new ArrayList<>(); - // The "default" domain. Set as the default ProtectionDomain on newly // created classes. private final ProtectionDomain defaultDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, this, null); - // Invoked by the VM to record every loaded class with this loader. - void addClass(Class c) { - synchronized (classes) { - classes.add(c); - } - } - // The packages defined in this class loader. Each package name is // mapped to its corresponding NamedPackage object. // @@ -2646,8 +2635,6 @@ private void resetArchivedStates() { } reinitObjectField("package2certs", new ConcurrentHashMap<>()); - classes.clear(); - classes.trimToSize(); classLoaderValueMap = null; } } diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 5fee4809a700..6cfe3b5b3ba7 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -47,11 +47,11 @@ * It is not possible to synchronize on a value object. An attempt to {@code * synchronize} on a value object causes {@link IdentityException} to be thrown. *

- * The {@link #finalize()} method of a value class will never be invoked by - * the garbage collector. + * Value objects are never finalized, and it is an error to declare a + * {@code finalize} method in a value class. *

* A {@linkplain java.lang.ref.Reference Reference object} can only refer to an - * object with identity. Creating a reference object with a value object as + * object with identity. Creating a Reference object with a value object as * the referent throws {@code IdentityException}. * * @@ -598,12 +598,9 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio * system resources or to perform other cleanup. *

*
- * The {@code finalize} method of a value class is never directly - * invoked by the garbage collector. This includes the case where an - * abstract value class declares a {@code finalize} method and the - * class is extended by an identity class; the garbage collector never - * directly invokes the {@code finalize} method declared by the - * abstract value class. + * A value class cannot override the {@code finalize} method, and + * the garbage collector never invokes {@code finalize} on a value + * object. *
*
*

@@ -616,7 +613,7 @@ public final void wait(long timeoutMillis, int nanos) throws InterruptedExceptio * The general contract of {@code finalize} is that it is invoked * if and when the Java virtual * machine has determined that there is no longer any - * means by which this object can be accessed by any thread that has + * means by which this identity object can be accessed by any thread that has * not yet died, except as a result of an action taken by the * finalization of some other object or class which is ready to be * finalized. The {@code finalize} method may take any action, including diff --git a/src/java.base/share/classes/java/lang/foreign/Linker.java b/src/java.base/share/classes/java/lang/foreign/Linker.java index 5c3821a14ded..30a7c24560ae 100644 --- a/src/java.base/share/classes/java/lang/foreign/Linker.java +++ b/src/java.base/share/classes/java/lang/foreign/Linker.java @@ -83,15 +83,17 @@ * * {@snippet lang = java: * Linker linker = Linker.nativeLinker(); + * MemoryLayout C_SIZE_T = linker.canonicalLayouts().get("size_t"); + * MemoryLayout C_POINTER = linker.canonicalLayouts().get("void*"); * MethodHandle strlen = linker.downcallHandle( * linker.defaultLookup().findOrThrow("strlen"), - * FunctionDescriptor.of(JAVA_LONG, ADDRESS) + * FunctionDescriptor.of(C_SIZE_T, C_POINTER) * ); * } * * Note how the native linker also provides access, via its {@linkplain #defaultLookup() default lookup}, * to the native functions defined by the C libraries loaded with the Java runtime. - * Above, the default lookup is used to search the address of the {@code strlen} native + * Above, the default lookup is used to search for the address of the {@code strlen} native * function. That address is then passed, along with a platform-dependent description * of the signature of the function expressed as a {@link FunctionDescriptor} (more on * that below) to the native linker's {@link #downcallHandle(MemorySegment, FunctionDescriptor, Option...)} @@ -129,7 +131,7 @@ * {@linkplain AddressLayout#targetLayout() target layout}. For instance, a pointer that * is known to point to a C {@code int[2]} array can be modeled as an address layout * whose target layout is a sequence layout whose element count is 2, and whose - * element type is {@link ValueLayout#JAVA_INT}. + * element type is the canonical layout for {@code int}. *

* All native linker implementations are guaranteed to provide canonical layouts for the * following set of types: @@ -147,18 +149,17 @@ *

  • {@code void*}
  • * * As noted above, the specific canonical layout associated with each type can vary, - * depending on the data model supported by a given ABI. For instance, the C type - * {@code long} maps to the layout constant {@link ValueLayout#JAVA_LONG} on Linux/x64, - * but maps to the layout constant {@link ValueLayout#JAVA_INT} on Windows/x64. - * Similarly, the C type {@code size_t} maps to the layout constant - * {@link ValueLayout#JAVA_LONG} on 64-bit platforms, but maps to the layout constant - * {@link ValueLayout#JAVA_INT} on 32-bit platforms. + * depending on the data model supported by a given ABI. For instance, the canonical + * layout for the C type {@code long} is a {@link ValueLayout.OfLong} on Linux/x64, + * and a {@link ValueLayout.OfInt} on Windows/x64. + * Similarly, the canonical layout for the C type {@code size_t} is a + * {@link ValueLayout.OfLong} on 64-bit platforms, and a {@link ValueLayout.OfInt} + * on 32-bit platforms. *

    * A native linker typically does not provide canonical layouts for C's unsigned integral * types. Instead, they are modeled using the canonical layouts associated with their * corresponding signed integral types. For instance, the C type {@code unsigned long} - * maps to the layout constant {@link ValueLayout#JAVA_LONG} on Linux/x64, but maps to - * the layout constant {@link ValueLayout#JAVA_INT} on Windows/x64. + * maps to the canonical layout for {@code long}. *

    * The following table shows some examples of how C types are modeled in Linux/x64 * according to the "System V Application Binary Interface" @@ -169,71 +170,98 @@ * * * C type - * Layout + * Layout type * Java type + * Canonical layout name + * Alias in examples * * * * {@code bool} - * {@link ValueLayout#JAVA_BOOLEAN} + * {@link ValueLayout.OfBoolean} * {@code boolean} - * {@code char}
    {@code unsigned char} - * {@link ValueLayout#JAVA_BYTE} + * {@code bool} + * {@code C_BOOL} + * {@code char} + * {@link ValueLayout.OfByte} * {@code byte} - * {@code short}
    {@code unsigned short} - * {@link ValueLayout#JAVA_SHORT} + * {@code char} + * {@code C_CHAR} + * {@code short} + * {@link ValueLayout.OfShort} * {@code short} - * {@code int}
    {@code unsigned int} - * {@link ValueLayout#JAVA_INT} + * {@code short} + * {@code C_SHORT} + * {@code int} + * {@link ValueLayout.OfInt} + * {@code int} * {@code int} - * {@code long}
    {@code unsigned long} - * {@link ValueLayout#JAVA_LONG} + * {@code C_INT} + * {@code long} + * {@link ValueLayout.OfLong} + * {@code long} * {@code long} - * {@code long long}
    {@code unsigned long long} - * {@link ValueLayout#JAVA_LONG} + * {@code C_LONG} + * {@code long long} + * {@link ValueLayout.OfLong} * {@code long} + * {@code long long} + * {@code C_LONG_LONG} * {@code float} - * {@link ValueLayout#JAVA_FLOAT} + * {@link ValueLayout.OfFloat} * {@code float} + * {@code float} + * {@code C_FLOAT} * {@code double} - * {@link ValueLayout#JAVA_DOUBLE} + * {@link ValueLayout.OfDouble} + * {@code double} * {@code double} + * {@code C_DOUBLE} {@code size_t} - * {@link ValueLayout#JAVA_LONG} + * {@link ValueLayout.OfLong} * {@code long} + * {@code size_t} + * {@code C_SIZE_T} * {@code char*}, {@code int**}, {@code struct Point*} - * {@link ValueLayout#ADDRESS} + * {@link AddressLayout} * {@link MemorySegment} + * {@code void*} + * {@code C_POINTER} * {@code int (*ptr)[10]} * *

    - * ValueLayout.ADDRESS.withTargetLayout(
    - *     MemoryLayout.sequenceLayout(10,
    - *         ValueLayout.JAVA_INT)
    + * C_POINTER.withTargetLayout(
    + *     MemoryLayout.sequenceLayout(10, C_INT)
      * );
      * 
    * {@link MemorySegment} + * N/A + * N/A * struct Point { int x; long y; }; * *
      * MemoryLayout.structLayout(
    - *     ValueLayout.JAVA_INT.withName("x"),
    + *     C_INT.withName("x"),
      *     MemoryLayout.paddingLayout(4),
    - *     ValueLayout.JAVA_LONG.withName("y")
    + *     C_LONG.withName("y")
      * );
      * 
    * * {@link MemorySegment} + * N/A + * N/A * union Choice { float a; int b; } * *
      * MemoryLayout.unionLayout(
    - *     ValueLayout.JAVA_FLOAT.withName("a"),
    - *     ValueLayout.JAVA_INT.withName("b")
    + *     C_FLOAT.withName("a"),
    + *     C_INT.withName("b")
      * );
      * 
    * * {@link MemorySegment} + * N/A + * N/A * * *

    @@ -293,13 +321,13 @@ * Linker linker = Linker.nativeLinker(); * MethodHandle qsort = linker.downcallHandle( * linker.defaultLookup().findOrThrow("qsort"), - * FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS) + * FunctionDescriptor.ofVoid(C_POINTER, C_SIZE_T, C_SIZE_T, C_POINTER) * ); * } * - * As before, we use {@link ValueLayout#JAVA_LONG} to map the C type {@code size_t} type, - * and {@link ValueLayout#ADDRESS} for both the first pointer parameter (the array - * pointer) and the last parameter (the function pointer). + * We use the canonical layout for {@code "size_t"}, and the canonical layout for + * {@code "void*"} for both the first pointer parameter (the array pointer) and + * the last parameter (the function pointer). *

    * To invoke the {@code qsort} downcall handle obtained above, we need a function pointer * to be passed as the last parameter. That is, we need to create a function pointer out @@ -309,7 +337,7 @@ * {@snippet lang = java: * class Qsort { * static int qsortCompare(MemorySegment elem1, MemorySegment elem2) { - * return Integer.compare(elem1.get(JAVA_INT, 0), elem2.get(JAVA_INT, 0)); + * return Integer.compare(elem1.get(C_INT, 0), elem2.get(C_INT, 0)); * } * } * } @@ -317,9 +345,9 @@ * Now let's create a method handle for the comparator method defined above: * * {@snippet lang = java: - * FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT, - * ADDRESS.withTargetLayout(JAVA_INT), - * ADDRESS.withTargetLayout(JAVA_INT)); + * FunctionDescriptor comparDesc = FunctionDescriptor.of(C_INT, + * C_POINTER.withTargetLayout(C_INT), + * C_POINTER.withTargetLayout(C_INT)); * MethodHandle comparHandle = MethodHandles.lookup() * .findStatic(Qsort.class, "qsortCompare", * comparDesc.toMethodType()); @@ -327,7 +355,7 @@ * * First, we create a function descriptor for the function pointer type. Since we know * that the parameters passed to the comparator method will be pointers to elements of - * a C {@code int[]} array, we can specify {@link ValueLayout#JAVA_INT} as the target + * a C {@code int[]} array, we can specify the canonical layout for {@code "int"} as the target * layout for the address layouts of both parameters. This will allow the comparator * method to access the contents of the array elements to be compared. We then * {@linkplain FunctionDescriptor#toMethodType() turn} that function descriptor into @@ -339,9 +367,9 @@ * {@snippet lang = java: * try (Arena arena = Arena.ofConfined()) { * MemorySegment comparFunc = linker.upcallStub(comparHandle, comparDesc, arena); - * MemorySegment array = arena.allocateFrom(JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7); + * MemorySegment array = arena.allocateFrom(C_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7); * qsort.invokeExact(array, 10L, 4L, comparFunc); - * int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + * int[] sorted = array.toArray(C_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] * } * } * @@ -384,12 +412,12 @@ * * MethodHandle malloc = linker.downcallHandle( * linker.defaultLookup().findOrThrow("malloc"), - * FunctionDescriptor.of(ADDRESS, JAVA_LONG) + * FunctionDescriptor.of(C_POINTER, C_SIZE_T) * ); * * MethodHandle free = linker.downcallHandle( * linker.defaultLookup().findOrThrow("free"), - * FunctionDescriptor.ofVoid(ADDRESS) + * FunctionDescriptor.ofVoid(C_POINTER) * ); * } * @@ -517,7 +545,7 @@ * Linker linker = Linker.nativeLinker(); * MethodHandle printf = linker.downcallHandle( * linker.defaultLookup().findOrThrow("printf"), - * FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT), + * FunctionDescriptor.of(C_INT, C_POINTER, C_INT, C_INT, C_INT), * Linker.Option.firstVariadicArg(1) // first int is variadic * ); * } @@ -862,10 +890,10 @@ static Option captureCallState(String... capturedState) { return new LinkerOptions.CaptureCallState(mask); } - /** + /** * {@return a struct layout that represents the layout of the capture state - * segment that is passed to a downcall handle linked with - * {@link #captureCallState(String...)}} + * segment that is passed to a downcall handle linked with + * {@link #captureCallState(String...)}} *

    * The capture state layout is platform-dependent but is guaranteed to be * a {@linkplain StructLayout struct layout} containing only {@linkplain ValueLayout value layouts} diff --git a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java index 733714b57860..05d99ee8df27 100644 --- a/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java +++ b/src/java.base/share/classes/java/lang/invoke/StringConcatFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -632,7 +632,7 @@ private StringConcatFactory() { * without copying. */ private static final class InlineHiddenClassStrategy { - // The CLASS_NAME prefix must be the same as used by HeapShared::is_string_concat_klass() + // The CLASS_NAME prefix must be the same as used by AOTHeap::is_string_concat_klass() // in the HotSpot code. static final String CLASS_NAME = "java.lang.String$$StringConcat"; static final String METHOD_NAME = "concat"; diff --git a/src/java.base/share/classes/java/math/MutableBigInteger.java b/src/java.base/share/classes/java/math/MutableBigInteger.java index 1ede4cf32f8c..afee2aeeac74 100644 --- a/src/java.base/share/classes/java/math/MutableBigInteger.java +++ b/src/java.base/share/classes/java/math/MutableBigInteger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -383,24 +383,6 @@ private final int getLowestSetBit() { return ((intLen-1-j)<<5) + Integer.numberOfTrailingZeros(b); } - /** - * Return the int in use in this MutableBigInteger at the specified - * index. This method is not used because it is not inlined on all - * platforms. - */ - private final int getInt(int index) { - return value[offset+index]; - } - - /** - * Return a long which is equal to the unsigned value of the int in - * use in this MutableBigInteger at the specified index. This method is - * not used because it is not inlined on all platforms. - */ - private final long getLong(int index) { - return value[offset+index] & LONG_MASK; - } - /** * Ensure that the MutableBigInteger is in normal form, specifically * making sure that there are no leading zeros, and that if the @@ -426,18 +408,6 @@ final void normalize() { offset = (intLen == 0 ? 0 : offset+numZeros); } - /** - * If this MutableBigInteger cannot hold len words, increase the size - * of the value array to len words. - */ - private final void ensureCapacity(int len) { - if (value.length < len) { - value = new int[len]; - offset = 0; - intLen = len; - } - } - /** * Convert this MutableBigInteger into an int array with no leading * zeros, of a length that is equal to this MutableBigInteger's intLen. diff --git a/src/java.base/share/classes/java/net/SocksSocketImpl.java b/src/java.base/share/classes/java/net/SocksSocketImpl.java index 71794d7a0a66..5f8b974e12b6 100644 --- a/src/java.base/share/classes/java/net/SocksSocketImpl.java +++ b/src/java.base/share/classes/java/net/SocksSocketImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -143,13 +143,27 @@ private boolean authenticate(byte method, InputStream in, if (userName == null) return false; out.write(1); - out.write(userName.length()); - out.write(userName.getBytes(StandardCharsets.ISO_8859_1)); - if (password != null) { - out.write(password.length()); - out.write(password.getBytes(StandardCharsets.ISO_8859_1)); - } else + + var userNameBytes = userName.getBytes(StandardCharsets.ISO_8859_1); + // RFC 1929 allows usernames of at most 255 bytes + if (userNameBytes.length > 0xFF) { + throw new SocketException("SOCKS: username is too long"); + } + out.write(userNameBytes.length); + out.write(userNameBytes); + + if (password == null) { out.write(0); + } else { + var passwordBytes = password.getBytes(StandardCharsets.ISO_8859_1); + // RFC 1929 allows password of at most 255 bytes + if (passwordBytes.length > 0xFF) { + throw new SocketException("SOCKS: password is too long"); + } + out.write(passwordBytes.length); + out.write(passwordBytes); + } + out.flush(); byte[] data = new byte[2]; int i = readSocksReply(in, data, deadlineMillis); @@ -241,6 +255,15 @@ protected void connect(SocketAddress endpoint, int timeout) throws IOException { if (!(endpoint instanceof InetSocketAddress epoint)) throw new IllegalArgumentException("Unsupported address type"); + byte[] domainNameBytes = null; + if (epoint.isUnresolved()) { + domainNameBytes = epoint.getHostName().getBytes(StandardCharsets.ISO_8859_1); + // RFC 1928 allows FQDNs of at most 255 bytes + if (domainNameBytes.length > 0xFF) { + throw new SocketException("SOCKS: host name is too long"); + } + } + if (server == null) { // This is the general case // server is not null only when the socket was created with a @@ -366,8 +389,8 @@ protected void connect(SocketAddress endpoint, int timeout) throws IOException { /* Test for IPV4/IPV6/Unresolved */ if (epoint.isUnresolved()) { out.write(DOMAIN_NAME); - out.write(epoint.getHostName().length()); - out.write(epoint.getHostName().getBytes(StandardCharsets.ISO_8859_1)); + out.write(domainNameBytes.length); + out.write(domainNameBytes); out.write((epoint.getPort() >> 8) & 0xff); out.write((epoint.getPort() >> 0) & 0xff); } else if (epoint.getAddress() instanceof Inet6Address) { diff --git a/src/java.base/share/classes/java/text/CompactNumberFormat.java b/src/java.base/share/classes/java/text/CompactNumberFormat.java index 1df338bcad72..be453e74ed63 100644 --- a/src/java.base/share/classes/java/text/CompactNumberFormat.java +++ b/src/java.base/share/classes/java/text/CompactNumberFormat.java @@ -1300,17 +1300,17 @@ private Map buildPluralRulesMap() { } // Patterns for plurals syntax validation - private static final String EXPR = "([niftvwe])\\s*(([/%])\\s*(\\d+))*"; + private static final String EXPR = "([niftvwe])\\s*(([/%])\\s*(\\d+))*+"; private static final String RELATION = "(!?=)"; private static final String VALUE_RANGE = "((\\d+)\\.\\.(\\d+)|\\d+)"; private static final String CONDITION = EXPR + "\\s*" + RELATION + "\\s*" + VALUE_RANGE + "\\s*" + - "(,\\s*" + VALUE_RANGE + ")*"; + "(,\\s*" + VALUE_RANGE + ")*+"; private static final Pattern PLURALRULES_PATTERN = Pattern.compile("(zero|one|two|few|many):\\s*" + CONDITION + - "(\\s*(and|or)\\s*" + CONDITION + ")*"); + "(\\s*(and|or)\\s*" + CONDITION + ")*+"); /** * Validates a plural rule. diff --git a/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java b/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java index 787d8d5fecd8..736b284aa2d4 100644 --- a/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java +++ b/src/java.base/share/classes/java/util/concurrent/LinkedTransferQueue.java @@ -388,6 +388,10 @@ final void selfLinkItem() { ITEM.set(this, this); } + final Thread getVolatileWaiter() { + return (Thread)WAITER.getVolatile(this); + } + /** The number of times to spin when eligible */ private static final int SPINS = 1 << 7; @@ -486,12 +490,14 @@ public final boolean block() { // VarHandle mechanics static final VarHandle ITEM; static final VarHandle NEXT; + static final VarHandle WAITER; static { try { Class tn = DualNode.class; MethodHandles.Lookup l = MethodHandles.lookup(); ITEM = l.findVarHandle(tn, "item", Object.class); NEXT = l.findVarHandle(tn, "next", tn); + WAITER = l.findVarHandle(tn, "waiter", Thread.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } @@ -590,7 +596,7 @@ final Object xfer(Object e, long ns) { q = p.next; if (p.isData != haveData && haveData != (m != null)) { if (p.cmpExItem(m, e) == m) { - Thread w = p.waiter; // matched complementary node + Thread w = p.getVolatileWaiter(); // matched complementary node if (p != h && h == cmpExHead(h, (q == null) ? p : q)) h.next = h; // advance head; self-link old LockSupport.unpark(w); diff --git a/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java b/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java index 36224483f8b3..d4525f7da66c 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java +++ b/src/java.base/share/classes/java/util/concurrent/StructureViolationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,16 +24,13 @@ */ package java.util.concurrent; -import jdk.internal.javac.PreviewFeature; - /** * Thrown when a structure violation is detected. * * @see StructuredTaskScope#close() * - * @since 21 + * @since 28 */ -@PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) public final class StructureViolationException extends RuntimeException { @java.io.Serial private static final long serialVersionUID = -7705327650798235468L; diff --git a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java index 39ad6074b313..b05354284c13 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java +++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java @@ -30,7 +30,6 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.function.UnaryOperator; -import jdk.internal.javac.PreviewFeature; /** * An API for structured concurrency. {@code StructuredTaskScope} supports cases @@ -44,40 +43,26 @@ * and open a new {@code StructuredTaskScope}. It defines the {@link #close() close()} * method to close it. The API is designed to be used with the {@code try}-with-resources * statement where a {@code StructuredTaskScope} is opened as a resource and then closed - * automatically. The code inside the {@code try} block uses the {@link #fork(Callable)} - * method to fork subtasks. Each call to the {@code fork(Callable)} method starts - * a new {@link Thread} (typically a {@linkplain Thread##virtual-threads virtual thread}) - * to execute a subtask as a {@linkplain Callable value-returning method}. The subtask - * executes concurrently with the code inside the {@code try} block, and concurrently with - * other subtasks forked in the scope. After forking all subtasks, the code inside the - * block uses the {@link #join() join()} method to wait for all subtasks to finish (or - * some other outcome) as a single operation. The code after the {@code join()} method - * processes the outcome. Execution does not continue beyond the {@code try} block (or - * {@code close} method) until all threads started in the scope to execute subtasks have - * finished. - * - *

    To ensure correct usage, the {@link #fork(Callable)}, {@link #join()} and {@link - * #close()} methods may only be invoked by the owner thread (the thread that - * opened the {@code StructuredTaskScope}), the {@code fork(Callable)} method may not be - * called after {@code join()}, the {@code join()} method must be invoked to get the outcome - * after forking subtasks, and the {@code close()} method throws an exception after closing - * if the owner did not invoke the {@code join()} method after forking subtasks. + * automatically. The code inside the {@code try} block forks subtasks with the + * {@link #fork(Callable)} or {@link #fork(Runnable)} methods. Each call to {@code + * fork(Callable)} starts a new {@link Thread} (typically a {@linkplain + * Thread##virtual-threads virtual thread}) to execute a {@linkplain Callable + * value-returning method} as a subtask. Each call to {@code fork(Runnable)} starts a new + * {@link Thread} to execute a method that does not return a result as a subtask. The + * threads executing subtasks run concurrently with each other and with the code inside + * the {@code try} block. After forking all subtasks, the code inside the block uses the + * {@link #join() join()} method to wait for all subtasks to finish (or for another + * outcome) as a single operation. The code after the {@code join()} method processes the + * outcome. Execution does not continue beyond the {@code try} block (or {@code close} + * method) until all threads started in the scope to execute subtasks have finished. * *

    As a first example, consider a "main" task that splits into two subtasks to * concurrently fetch values from two remote services. The main task aggregates the results * of both subtasks. The example invokes {@link #fork(Callable)} to fork the two subtasks. * Each call to {@code fork(Callable)} returns a {@link Subtask Subtask} as a handle to - * the forked subtask. Both subtasks may complete successfully, one subtask may succeed - * and the other may fail, or both subtasks may fail. + * the forked subtask. Both subtasks may succeed, one may succeed and the other fail, or + * both may fail. * - *

    The main task in the example is interested in the successful result from both - * subtasks. It waits in the {@link #join()} method for both subtasks to complete - * successfully or for either subtask to fail. If both subtasks complete successfully then - * the {@code join()} method completes normally and the task uses the {@link Subtask#get() - * Subtask.get()} method to get the result of each subtask. If one of the subtasks fails - * then the other subtask is cancelled, and the {@code join()} method throws {@link - * ExecutionException} with the exception from the failed subtask as the {@linkplain - * Throwable#getCause() cause}. * {@snippet lang=java : * // @link substring="open()" target="#open()" : * try (var scope = StructuredTaskScope.open()) { @@ -97,19 +82,40 @@ * } // close * } * + *

    The main task in the example is interested in the successful result of both + * subtasks. It waits in the {@link #join()} method for both subtasks to complete + * successfully or for either subtask to fail. If both subtasks complete successfully then + * the {@code join()} method completes normally and the task uses the {@link Subtask#get() + * Subtask.get()} method to get the result of each subtask. If one of the subtasks fails + * then the other subtask is cancelled, and the {@code join()} method throws {@link + * ExecutionException} with the exception from the failed subtask as the {@linkplain + * Throwable#getCause() cause}. + * *

    The {@link #close() close()} method always waits for threads executing subtasks to * finish, even if the {@code join()} method throws, so that execution cannot continue - * beyond the {@code close()} method until the interrupted threads finish. + * beyond the {@code close()} method until all subtasks have finished. + * + *

    To ensure correct usage, the following restrictions are enforced: + *

      + *
    • The {@code fork} methods, the {@link #join()} method and {@link #close()} method + * may only be invoked by the owner thread (the thread that opened the {@code + * StructuredTaskScope}). + *
    • The {@code join()} method must be invoked to get the outcome after forking + * subtasks (the {@code close} method throws an exception after closing if the owner + * did not invoke the {@code join} method after forking subtasks). + *
    • Neither {@code fork} method may be called after {@code join()} is invoked. + *
    * *

    To allow for cancellation, subtasks must be coded so that they finish as soon as - * possible when interrupted. Subtasks that do not respond to interrupt, e.g. block on - * methods that are not interruptible, may delay the {@link #close() close()} method - * indefinitely. + * possible when {@link Thread##thread-interruption interrupted}. Subtasks that do not + * respond to interrupt, e.g. block on methods that are not interruptible, may delay the + * {@link #close() close()} method indefinitely. * *

    In the example, the subtasks produce results of different types ({@code String} and - * {@code Integer}). In other cases the subtasks may all produce results of the same type. - * If the example had used {@code StructuredTaskScope.open()} to open the scope - * then it could only be used to fork subtasks that return a {@code String} result. + * {@code Integer}). In other cases where the subtasks produce results of the same type, + * the scope can be opened with that type as the subtask result type. For example, + * {@code StructuredTaskScope.open()} opens a scope that can only be used to + * fork subtasks that produce a {@code String} result. * *

    Joiners

    * @@ -124,28 +130,29 @@ * is an exception, a {@code Joiner} may cause {@code join()} to throw an exception * other than {@code ExecutionException}. * - *

    A {@code Joiner} may cancel the scope (sometimes - * called "short-circuiting") when some condition is reached, e.g. a subtask fails, that - * does not require the outcome of other subtasks that are still executing. Cancelling the - * scope prevents new threads from being started in the scope, cancels subtasks in the - * scope that have not completed execution, and causes the {@code join()} method to wake up - * with the outcome (result or exception). In the above example, the outcome is that {@code - * join()} completes normally when all subtasks succeed. The scope is cancelled if any - * subtask fails and {@code join()} throws {@code ExecutionException} with the exception - * from the failed subtask as the {@linkplain Throwable#getCause() cause}. Other {@code - * Joiner} implementations may cancel the scope for other reasons, and may cause {@code - * join()} to throw a different exception when the outcome is an exception. + *

    Cancellation: A {@code Joiner} may cancel + * the scope (sometimes called "short-circuiting") when some condition is reached, for + * example, when a subtask fails, and the outcomes (results or exceptions) of other + * subtasks that are still executing are not required. Cancelling the scope prevents new + * threads from being started in the scope, cancels subtasks in the scope that have not + * completed execution (this will {@linkplain Thread#interrupt() interrupt} the threads + * executing the subtasks) and causes the {@code join()} method to wake up with the outcome. + * In the above example, the outcome is that {@code join()} completes normally when all + * subtasks succeed. The scope is cancelled if any subtask fails and {@code join()} throws + * {@code ExecutionException} with the exception from a failed subtask as the {@linkplain + * Throwable#getCause() cause}. Other {@code Joiner} implementations may cancel the scope + * for other reasons, and may cause {@code join()} to throw a different exception when + * the outcome is an exception. * *

    The {@link Joiner Joiner} interface defines static factory methods to create a * {@code Joiner} for a number of common cases. The interface can be implemented when a * more advanced or custom policy is required. A {@code Joiner} that returns a * non-{@code null} result may remove the need for bookkeeping and the need to keep * a reference to {@code Subtask} objects returned by the {@link #fork(Callable)} method. - - *

    Now consider another example where a main task splits into two subtasks. In this - * example, each subtask produces a {@code String} result and the main task is only - * interested in the result from the first subtask to complete successfully. The example - * uses {@link Joiner#anySuccessfulOrThrow() Joiner.anySuccessfulOrThrow()} to create a + * + *

    Now consider an example where a main task splits into two subtasks and where the + * main task is interested in the successful outcome of either subtask. The example uses + * {@link Joiner#anySuccessfulOrThrow() Joiner.anySuccessfulOrThrow()} to create a * {@code Joiner} that produces the result of any subtask that completes successfully. * {@snippet lang=java : * // @link substring="anySuccessfulOrThrow()" target="Joiner#anySuccessfulOrThrow()" : @@ -156,23 +163,22 @@ * scope.fork(callable2); * * // throws ExecutionException if both subtasks fail - * String firstResult = scope.join(); // @link substring="join" target="#join()" + * String anyResult = scope.join(); // @link substring="join" target="#join()" * * // @link substring="close" target="#close()" : * } // close * } * - *

    In the example, the task forks the two subtasks, then waits in the {@code - * join()} method for either subtask to complete successfully or for both subtasks to fail. - * If one of the subtasks completes successfully then the {@code Joiner} causes the other - * subtask to be cancelled (this will interrupt the thread executing the subtask), and - * the {@code join()} method returns the result from the successful subtask. Cancelling the - * other subtask avoids the task waiting for a result that it doesn't care about. If - * both subtasks fail then the {@code join()} method throws {@link ExecutionException} with - * the exception from one of the subtasks as the {@linkplain Throwable#getCause() cause}. - * {@link Joiner#anySuccessfulOrThrow(Function) Joiner.anySuccessfulOrThrow(Function)} can - * be used with a function that produces an exception other than {@code ExecutionException} - * to throw when all subtasks fail. + *

    The main task waits in the {@code join()} method for either subtask to complete + * successfully or for both subtasks to fail. If one of the subtasks completes successfully, + * then the {@code Joiner} causes the other subtask to be cancelled (this will interrupt + * the thread executing the subtask), and the {@code join()} method returns the result of + * the successful subtask. Cancelling the other subtask avoids the task waiting for a + * result that it doesn't care about. If both subtasks fail, then the {@code join()} method + * throws {@link ExecutionException} with the exception from one of the subtasks as the + * {@linkplain Throwable#getCause() cause}. {@link Joiner#anySuccessfulOrThrow(Function) + * Joiner.anySuccessfulOrThrow(Function)} can be used with a function that produces an + * exception other than {@code ExecutionException} to throw when all subtasks fail. * *

    Configuration

    * @@ -189,10 +195,10 @@ *

    The {@link #open(UnaryOperator)} and {@link #open(Joiner, UnaryOperator)} methods * can be used to create a {@code StructuredTaskScope} that uses a different {@code * ThreadFactory}, is named for monitoring and management purposes, or has a timeout that - * cancels the scope if the timeout expires before or while waiting for subtasks to - * complete. The {@code open} methods are called with an {@linkplain UnaryOperator operator} - * that is applied to the default configuration and returns a {@link Configuration - * Configuration} for the {@code StructuredTaskScope} under construction. + * cancels the scope if the timeout expires before the {@code join()} method is invoked + * or completes. The {@code open} methods are called with an {@linkplain UnaryOperator + * operator} that is applied to the default configuration and returns a {@link + * Configuration Configuration} for the {@code StructuredTaskScope} under construction. * *

    The following example opens a new {@code StructuredTaskScope} with a {@code * ThreadFactory} that creates virtual threads {@linkplain Thread#getName() named} @@ -215,8 +221,8 @@ *} * *

    A second example sets a timeout, represented by a {@link Duration}. The timeout - * starts when the new scope is opened. If the timeout expires before or while waiting in - * the {@link #join()} method then the scope is {@linkplain ##Cancellation cancelled} + * starts when the new scope is opened. If the timeout expires before the {@code join()} + * method is invoked or completes then the scope is {@linkplain ##Cancellation cancelled} * (this interrupts the threads executing the subtasks that have not completed), and the * {@code join()} method throws {@link ExecutionException} with a {@link * CancelledByTimeoutException CancelledByTimeoutException} as the cause. @@ -239,18 +245,19 @@ * *

    Exception handling

    * - *

    The outcome of the {@link #join()} method is a result or exception. When the outcome - * is an exception then its {@linkplain Throwable#getCause() cause} will typically be - * the exception from a failed subtask or {@link CancelledByTimeoutException + *

    The outcome of the {@link #join()} method is a result or exception. When the + * outcome is an exception, its {@linkplain Throwable#getCause() cause} will typically + * be the exception from a failed subtask or {@link CancelledByTimeoutException * CancelledByTimeoutException} if a timeout was configured. * *

    In some cases it may be useful to add a {@code catch} block to the * {@code try}-with-resources statement to handle the exception. The following example * uses the {@link #open(UnaryOperator)} method to open a scope with a timeout configured. * The {@code join()} method in this example throws {@link ExecutionException} if any - * subtask fails or the timeout expires. The exception cause is the exception from a failed - * subtask or {@code CancelledByTimeoutException}. The example uses the {@code switch} - * statement to select based on the cause. + * subtask fails or the timeout expires before the {@code join()} method is invoked or + * completes. The exception cause is the exception from a failed subtask or {@code + * CancelledByTimeoutException}. The example uses the {@code switch} statement to select + * based on the cause. * {@snippet lang=java : * try (var scope = StructuredTaskScope.open(cf -> cf.withTimeout(timeout))) { * @@ -273,9 +280,9 @@ * may be more appropriate to handle this in the subtask itself rather than the subtask * failing and the scope owner handling the exception. * - *

    The {@link #join()} method throws {@link InterruptedException} when interrupted - * before or while waiting in the {@code join()} method. - * The {@link Thread##thread-interruption Thread Interruption} section of the {@code Thread} + *

    The {@link #join()} method throws {@link InterruptedException} if the owner thread + * is interrupted while waiting in the {@code join()} method. The {@link + * Thread##thread-interruption Thread Interruption} section of the {@code Thread} * specification provides guidance on handling this exception. * *

    Inheritance of scoped value bindings

    @@ -298,7 +305,7 @@ * use structured concurrency. * *

    To ensure correctness, opening a new {@code StructuredTaskScope} captures the - * current thread's scoped value bindings. These are the scoped values bindings that are + * current thread's scoped value bindings. These are the scoped value bindings that are * inherited by the threads created to execute subtasks in the scope. Forking a * subtask checks that the bindings in effect at the time that the subtask is forked * match the bindings when the {@code StructuredTaskScope} was created. This check ensures @@ -311,10 +318,10 @@ *

    The following example demonstrates the inheritance of scoped value bindings. The * scoped value USERNAME is bound to the value "duke" for the bounded period of a lambda * expression by the thread executing it. The code in the block opens a {@code - * StructuredTaskScope} and forks two subtasks, it then waits in the {@code join()} method + * StructuredTaskScope} and forks two subtasks, then waits in the {@code join()} method * and aggregates the results from both subtasks. If code executed by the threads - * running subtask1 and subtask2 uses {@link ScopedValue#get()}, to get the value of - * USERNAME, then value "duke" will be returned. + * running subtask1 and subtask2 uses {@link ScopedValue#get()} to get the value of + * USERNAME, then the value "duke" will be returned. * {@snippet lang=java : * // @link substring="newInstance" target="ScopedValue#newInstance()" : * private static final ScopedValue USERNAME = ScopedValue.newInstance(); @@ -368,9 +375,8 @@ * @param the type of the result returned by the {@link #join() join()} method * @param the type of the exception thrown by the {@link #join() join()} method * @jls 17.4.5 Happens-before Order - * @since 21 + * @since 28 */ -@PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) public sealed interface StructuredTaskScope extends AutoCloseable permits StructuredTaskScopeImpl { @@ -381,26 +387,23 @@ public sealed interface StructuredTaskScope * *

    The scope owner can use the {@link #get() get()} method after {@linkplain * #join() joining} to obtain the result of a subtask that completed successfully. It - * can use the {@link #exception()} method to obtain the exception thrown by a subtask - * that failed. + * can use the {@link #exception()} method to obtain the exception of a failed subtask. * * @param the result type - * @since 21 + * @since 28 */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) sealed interface Subtask extends Supplier permits StructuredTaskScopeImpl.SubtaskImpl { /** * Represents the state of a {@link Subtask Subtask}. * @see Subtask#state() - * @since 21 + * @since 28 */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) enum State { /** - * The subtask result or exception is not available. This state indicates that - * the subtask was forked but has not completed, the subtask was forked after + * The subtask result or exception is not available. This state does not + * distinguish whether the subtask is still executing, was not started because * the scope was {@linkplain StructuredTaskScope##Cancellation cancelled}, or - * the subtask completed after the scope was cancelled. + * completed after the scope was cancelled. */ UNAVAILABLE, /** @@ -412,10 +415,12 @@ enum State { */ SUCCESS, /** - * The subtask failed with an exception. If the scope is {@linkplain - * StructuredTaskScope##Cancellation cancelled}, the subtask failed before - * the scope was cancelled. The {@link Subtask#exception() Subtask.exception()} - * method can be used to get the exception. This is a terminal state. + * The subtask failed with an exception or error, or a thread could not be + * created to execute the subtask. If the scope is {@linkplain + * StructuredTaskScope##Cancellation cancelled}, the subtask failed or + * the {@code fork} method failed before the scope was cancelled. The + * {@link Subtask#exception() Subtask.exception()} method can be used to + * get the exception or error. This is a terminal state. */ FAILED, } @@ -429,20 +434,19 @@ enum State { * Returns the result of this subtask if it completed successfully. If the scope * is {@linkplain StructuredTaskScope##Cancellation cancelled}, the subtask * completed successfully before the scope was cancelled. If the subtask was - * forked with {@link #fork(Callable) fork(Callable)} then the result from the + * forked with {@link #fork(Callable) fork(Callable)} then the result of the * {@link Callable#call() call()} method is returned. If the subtask was forked * with {@link #fork(Runnable) fork(Runnable)} then {@code null} is returned. * - *

    Code executing in the scope owner thread can use this method to get the - * result of a successful subtask after it has {@linkplain #join() joined}. - * - *

    Code executing in the {@code Joiner} {@link Joiner#onComplete(Subtask) - * onComplete(Subtask)} method should test that the {@linkplain #state() state} is - * {@link State#SUCCESS SUCCESS} before using this method to get the result. - * *

    This method may be invoked by any thread after the scope owner has joined. - * The only case where this method can be used to get the result before the scope - * owner has joined is when called from the {@code onComplete(Subtask)} method. + * The only case where this method may be used to get the result before the scope + * owner has joined is when called from a {@link Joiner Joiner}'s {@link + * Joiner#onComplete(Subtask) onComplete(Subtask)} method. The {@code + * onComplete(Subtask)} method should test that the {@linkplain #state() state} + * is {@link State#SUCCESS SUCCESS} before invoking the {@code get()} method to + * get the result. + * + * @apiNote This method does not wait for the subtask to complete. * * @return the possibly-null result * @throws IllegalStateException if the subtask has not completed or did not @@ -453,24 +457,20 @@ enum State { T get(); /** - * {@return the exception or error thrown by this subtask if it failed} + * {@return the exception or error for this subtask if it failed} * If the scope is {@linkplain StructuredTaskScope##Cancellation cancelled}, the - * subtask failed before the scope was cancelled. If the subtask was forked with - * {@link #fork(Callable) fork(Callable)} then the exception or error thrown by - * the {@link Callable#call() call()} method is returned. If the subtask was - * forked with {@link #fork(Runnable) fork(Runnable)} then the exception or error - * thrown by the {@link Runnable#run() run()} method is returned. - * - *

    Code executing in the scope owner thread can use this method to get the - * exception thrown by a failed subtask after it has {@linkplain #join() joined}. - * - *

    Code executing in a {@code Joiner} {@link Joiner#onComplete(Subtask) - * onComplete(Subtask)} method should test that the {@linkplain #state() state} is - * {@link State#FAILED FAILED} before using this method to get the exception. + * subtask failed before the scope was cancelled. If the subtask executed, then + * the exception or error thrown by the {@link Callable#call() Callable.call()} + * or {@link Runnable#run() Runnable.run()} method is returned. * *

    This method may be invoked by any thread after the scope owner has joined. - * The only case where this method can be used to get the exception before the scope - * owner has joined is when called from the {@code onComplete(Subtask)} method. + * The only case where this method may be used before the scope owner has joined + * is when called from a {@link Joiner Joiner}'s {@link Joiner#onComplete(Subtask) + * onComplete(Subtask)} method. The {@code onComplete(Subtask)} method should + * test that the {@linkplain #state() state} is {@link State#FAILED FAILED} + * before invoking this method. + * + * @apiNote This method does not wait for the subtask to complete. * * @throws IllegalStateException if the subtask has not completed or completed * with a result, or this method is invoked outside the context of the {@code @@ -505,7 +505,8 @@ enum State { * exception returned by an exception supplying function, if any subtask fails. *

  • {@link #anySuccessfulOrThrow()} and {@link #anySuccessfulOrThrow(Function)} * create a {@code Joiner} that produces the result of any successful subtask for - * {@code join()} to return. The {@code Joiner} causes {@code join()} to throw + * {@code join()} to return. The {@code Joiner} cancels the scope when any subtask + * completes successfully. The {@code Joiner} causes {@code join()} to throw * {@code ExecutionException}, or an exception returned by an exception supplying * function, if all subtasks fail. *
  • {@link #awaitAllSuccessfulOrThrow()} and {@link #awaitAllSuccessfulOrThrow(Function)} @@ -527,13 +528,14 @@ enum State { * *

    More advanced policies can be developed by implementing the {@code Joiner} * interface. The {@link #onFork(Subtask)} method is invoked when subtasks are forked. - * The {@link #onComplete(Subtask)} method is invoked when subtasks complete with a - * result or exception. These methods return a {@code boolean} to indicate whether the - * scope should be cancelled. These methods can be used to collect subtasks, results, - * or exceptions, and control when to cancel the scope. The {@link #result()} method - * must be implemented to produce the outcome (result or exception) for the {@code - * join()} method. The {@link #timeout()} method must be implemented to produce the - * outcome for the {@linkplain Configuration#withTimeout(Duration) timeout} case. + * The {@link #onComplete(Subtask)} method is invoked when subtasks complete or when + * a thread cannot be created to execute a subtask. These methods return a {@code + * boolean} to indicate whether the scope should be cancelled. These methods can be + * used to collect subtasks, results, or exceptions, and control when to cancel the + * scope. The {@link #result()} method must be implemented to produce the outcome + * (result or exception) for the {@code join()} method. The {@link #timeout()} method + * must be implemented to produce the outcome for the + * {@linkplain Configuration#withTimeout(Duration) timeout} case. * *

    Unless otherwise specified, passing a {@code null} argument to a method * in this class will cause a {@link NullPointerException} to be thrown. @@ -548,37 +550,35 @@ enum State { * Configuration#withTimeout(Duration) timeouts}. In many cases, a timeout will cause * the {@code join()} method to throw an exception with {@link * CancelledByTimeoutException CancelledByTimeoutException} as the cause. Some - * {@code Joiner} implementation may be capable of returning a result for the + * {@code Joiner} implementations may be capable of returning a result for the * timeout case. * - *

    Designing a {@code Joiner} should take into account the code at the use-site + *

    Designing a {@code Joiner} should take into account the code at the use site * where the results from the {@link StructuredTaskScope#join() join()} method are - * processed. It should be clear what the {@code Joiner} does vs. the application - * code at the use-site. In general, the {@code Joiner} implementation is not the - * place for "business logic". A {@code Joiner} should be designed to be as general - * purpose as possible. + * processed. It should be clear what the {@code Joiner} does as opposed to the + * application code at the use site. In general, the {@code Joiner} implementation is + * not the place for "business logic". A {@code Joiner} should be designed to be as + * general purpose as possible. * * @apiNote It is very important that a new {@code Joiner} object is created for each * {@code StructuredTaskScope}. {@code Joiner} objects should never be shared with - * different scopes or re-used after a scope is closed. + * different scopes or reused after a scope is closed. * * @param the result type of subtasks {@linkplain #fork(Callable) forked} in the scope * @param the type of the result returned by the {@link #join() join()} method * @param the type of the exception thrown by the {@link #join() join()} method - * @since 25 + * @since 28 * @see #open(Joiner) */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) interface Joiner { /** - * Invoked by {@link #fork(Callable) fork(Callable)} and {@link #fork(Runnable) - * fork(Runnable)} when forking a subtask. The method is invoked before a thread - * is created to execute the subtask. + * Invoked by the scope owner thread when {@linkplain #fork(Callable) forking} a + * subtask. The method is invoked before a thread is created to execute the subtask. * * @implSpec The default implementation throws {@code NullPointerException} if the * subtask is {@code null}. It throws {@code IllegalArgumentException} if the - * subtask is not in the {@link Subtask.State#UNAVAILABLE UNAVAILABLE} state, it - * otherwise returns {@code false}. + * subtask is not in the {@link Subtask.State#UNAVAILABLE UNAVAILABLE} state, + * and otherwise returns {@code false}. * * @apiNote This method is invoked by the {@code fork} methods. It should not be * invoked directly. @@ -594,18 +594,27 @@ default boolean onFork(Subtask subtask) { } /** - * Invoked by the thread that executed a subtask after the subtask completes - * successfully or fails with an exception. This method is not invoked by subtasks - * that complete after the scope is {@linkplain StructuredTaskScope##Cancellation - * cancelled}. + * Invoked by the thread that executed a subtask after the subtask completes, or + * by the scope owner thread when {@linkplain #fork(Callable) forking} is unable + * to create a thread to execute the subtask. + * + *

    This method is not invoked for subtasks that are forked or complete after + * the scope is {@linkplain StructuredTaskScope##Cancellation cancelled}. + * + *

    If {@link #fork(Callable) fork(Callable)} or {@link #fork(Runnable) + * fork(Runnable)} fails to create a thread to execute a subtask, the {@code + * fork} method invokes this method directly with the subtask in the {@link + * Subtask.State#FAILED FAILED} state. In this case, the {@link + * Subtask#exception() Subtask.exception()} method returns the exception or error + * that prevented the thread from being created. * * @implSpec The default implementation throws {@code NullPointerException} if the * subtask is {@code null}. It throws {@code IllegalArgumentException} if the * subtask is not in the {@link Subtask.State#SUCCESS SUCCESS} or {@link - * Subtask.State#FAILED FAILED} state, it otherwise returns {@code false}. + * Subtask.State#FAILED FAILED} state, and otherwise returns {@code false}. * - * @apiNote This method is invoked by subtasks when they complete. It should not - * be invoked directly. + * @apiNote This method is invoked by the thread that executed a subtask or by a + * {@code fork} method. It should not be invoked directly. * * @param subtask the subtask * @return {@code true} to cancel the scope, otherwise {@code false} @@ -618,13 +627,15 @@ default boolean onComplete(Subtask subtask) { } /** - * Invoked by the {@link #join() join()} method to produce the outcome (result or - * exception) after waiting for all subtasks to complete or the scope is {@linkplain + * Invoked by the {@link #join() join()} method to produce the outcome (result + * or exception) after all subtasks complete or the scope is {@linkplain * StructuredTaskScope##Cancellation cancelled}. This method is not invoked if the - * scope was opened with a timeout and the timeout expires before or while waiting. + * scope was opened with a timeout and the timeout expires before the {@code join()} + * method is invoked or completes. * - *

    This method will be called at most once, by the {@code join()} method, to - * produce the outcome. The behavior of this method when invoked directly is undefined. + *

    This method will be called at most once by the {@code join()} method to + * produce the outcome. The behavior of this method when invoked directly is + * undefined. * * @apiNote This method is invoked by the {@code join()} method. It should not be * invoked directly. @@ -636,15 +647,16 @@ default boolean onComplete(Subtask subtask) { /** * Invoked by the {@link #join() join()} method to produce the outcome (result or - * exception) when the scope was opened with a timeout and the timeout expires before - * or while waiting in the {@code join()} method. + * exception) when the scope was opened with a timeout and the timeout expires + * before the {@code join()} method is invoked or completes. * *

    If the outcome is an exception, this method throws the exception with a * {@link CancelledByTimeoutException CancelledByTimeoutException} as the * {@link Throwable#getCause() cause}. * - *

    This method will be called at most once, by the {@code join()} method, to - * produce the outcome. The behavior of this method when invoked directly is undefined. + *

    This method will be called at most once by the {@code join()} method to + * produce the outcome. The behavior of this method when invoked directly is + * undefined. * * @apiNote This method is invoked by the {@code join()} method. It should not be * invoked directly. @@ -652,7 +664,6 @@ default boolean onComplete(Subtask subtask) { * @return the result * @throws R_X with a cause of {@code CancelledByTimeoutException}, if the outcome * is an exception - * @since 27 */ R timeout() throws R_X; @@ -668,7 +679,7 @@ default boolean onComplete(Subtask subtask) { * An empty list is returned if no subtasks were forked. If any subtask fails then * the {@code Joiner} causes the {@code join()} method to throw the exception * returned by the given exception supplying function when {@linkplain - * Function#apply(Object) applied} to the exception from the first subtask to fail. + * Function#apply(Object) applied} to the exception from a failed subtask. * The function should return an exception with the exception from the failed * subtask (the function argument) as the {@linkplain Throwable#getCause() cause}. * If the function returns {@code null} then it causes the {@code join()} method @@ -676,19 +687,20 @@ default boolean onComplete(Subtask subtask) { * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for all subtasks to complete successfully, then the {@code Joiner} - * causes the {@code join()} method to throw the exception returned by the - * exception supplying function when applied to a {@link CancelledByTimeoutException + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} + * causes the {@code join()} method to throw the exception returned by the exception + * supplying function when applied to a {@link CancelledByTimeoutException * CancelledByTimeoutException}. * - * @apiNote Joiners returned by this method are suited to cases where all subtasks - * return a result of the same type. It removes the need for bookkeeping - * and the need to keep a reference to the {@link Subtask Subtask} objects returned - * by the {@link #fork(Callable) fork(Callable)} method. Joiners returned by {@link - * #awaitAllSuccessfulOrThrow(Function)} and {@link #awaitAllSuccessfulOrThrow()} - * are suited to cases where the subtasks return results of different types and - * where it is necessary to keep a reference to the {@code Subtask} objects. + * @apiNote A Joiner returned by this method is suited to cases where all subtasks + * return a result of the same type. The Joiner removes the need for + * bookkeeping and the need to keep a reference to the {@link Subtask Subtask} + * objects returned by the {@link #fork(Callable) fork(Callable)} method. Joiners + * returned by {@link #awaitAllSuccessfulOrThrow(Function)} and + * {@link #awaitAllSuccessfulOrThrow()} are suited to cases where the subtasks + * return results of different types and where it is necessary to keep a reference + * to the {@code Subtask} objects. * *

    The following example is a method that opens a {@code StructuredTaskScope} * with a Joiner created with {@code allSuccessfulOrThrow(Function)}. The method @@ -696,8 +708,7 @@ default boolean onComplete(Subtask subtask) { * #fork(Callable) forks} a subtask to execute each callable, waits in {@link * #join() join()} for all subtasks to complete successfully, and then returns a * list of the results. It throws the runtime exception {@link CompletionException} - * if any subtask fails, with the exception from the first subtask to fail as the - * cause. + * if any subtask fails, with the exception from a failed subtask as the cause. * {@snippet lang=java : * List invokeAll(Collection> tasks) throws InterruptedException { * try (var scope = StructuredTaskScope.open( @@ -712,7 +723,6 @@ default boolean onComplete(Subtask subtask) { * @param esf the exception supplying function * @param the result type of subtasks * @param the type of the exception thrown by the {@link #join() join()} method - * @since 27 * @see #allSuccessfulOrThrow() */ static Joiner, R_X> @@ -732,18 +742,18 @@ default boolean onComplete(Subtask subtask) { * were {@linkplain #fork(Callable) forked}, when all subtasks complete successfully. * An empty list is returned if no subtasks were forked. If any subtask fails then * the Joiner causes the {@code join()} method to throw {@code ExecutionException} - * with the exception from the first subtask to fail as the {@linkplain - * Throwable#getCause() cause}. + * with the exception from a failed subtask as the {@linkplain Throwable#getCause() + * cause}. * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for all subtasks to complete successfully, then the {@code Joiner} - * causes the {@code join()} method to throw {@code ExecutionException} with - * a {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} + * causes the {@code join()} method to throw {@code ExecutionException} with a + * {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. * - * @implSpec A Joiner returned by this method is equivalent to invoking {@link - * #allSuccessfulOrThrow(Function) allSuccessfulOrThrow(ExecutionException::new)}. + *

    This method is equivalent to invoking {@link #allSuccessfulOrThrow(Function) + * allSuccessfulOrThrow(ExecutionException::new)}. * * @param the result type of subtasks */ @@ -764,7 +774,7 @@ static Joiner, ExecutionException> allSuccessfulOrThrow() { * {@code join()} method to throw the exception returned by the given exception * supplying function when {@linkplain Function#apply(Object) applied} to the * exception from one of the failed subtasks. The function should return an - * exception with the exception from the failed subtask (the function argument) as + * exception with the exception from a failed subtask (the function argument) as * the {@linkplain Throwable#getCause() cause}. If the function returns {@code null} * then it causes the {@code join()} method to throw {@code NullPointerException}. * If no subtasks were forked then the Joiner causes the {@code join()} method to @@ -773,10 +783,10 @@ static Joiner, ExecutionException> allSuccessfulOrThrow() { * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for a subtask to complete successfully, then the {@code Joiner} - * causes the {@code join()} method to throw the exception returned by the - * exception supplying function when applied to a {@link CancelledByTimeoutException + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} + * causes the {@code join()} method to throw the exception returned by the exception + * supplying function when applied to a {@link CancelledByTimeoutException * CancelledByTimeoutException}. * * @apiNote The following example is a method that opens a {@code StructuredTaskScope} @@ -800,7 +810,6 @@ static Joiner, ExecutionException> allSuccessfulOrThrow() { * @param esf the exception supplying function * @param the result type of subtasks * @param the type of the exception thrown by the {@link #join() join()} method - * @since 27 * @see #anySuccessfulOrThrow() */ static Joiner anySuccessfulOrThrow(Function esf) { @@ -817,24 +826,23 @@ static Joiner anySuccessfulOrThrow(Functio * with this Joiner returns the result of a successful subtask. If a subtask * completes successfully then the scope is cancelled and the {@code join()} method * returns its result. If all subtasks fail then the Joiner causes the {@code - * join()} method to throw {@code ExecutionException} with the exception from one - * of the failed subtasks as the {@linkplain Throwable#getCause() cause}. If no - * subtasks were forked then the {@code Joiner} causes the {@code join()} method - * to throw {@code ExecutionException} with {@link java.util.NoSuchElementException} - * as the cause. + * join()} method to throw {@code ExecutionException} with the exception from a + * failed subtask as the {@linkplain Throwable#getCause() cause}. If no subtasks + * were forked then the {@code Joiner} causes the {@code join()} method to throw + * {@code ExecutionException} with {@link java.util.NoSuchElementException} as + * the cause. * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for a subtask to complete successfully, then the {@code Joiner} + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} * causes the {@code join()} method to throw {@code ExecutionException} with a * {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. * - * @implSpec A Joiner returned by this method is equivalent to invoking {@link - * #anySuccessfulOrThrow(Function) anySuccessfulOrThrow(ExecutionException::new)}. + *

    This method is equivalent to invoking {@link #anySuccessfulOrThrow(Function) + * anySuccessfulOrThrow(ExecutionException::new)}. * * @param the result type of subtasks - * @since 26 */ static Joiner anySuccessfulOrThrow() { return anySuccessfulOrThrow(ExecutionException::new); @@ -850,7 +858,7 @@ static Joiner anySuccessfulOrThrow() { * with this Joiner returns {@code null} when all subtasks complete successfully. * If any subtask fails then the Joiner causes the {@code join()} method to throw * the exception returned by the given exception supplying function when {@linkplain - * Function#apply(Object) applied} to the exception from the first subtask to fail. + * Function#apply(Object) applied} to the exception from a failed subtask. * The function should return an exception with the exception from the failed * subtask (the function argument) as the {@linkplain Throwable#getCause() cause}. * If the function returns {@code null} then it causes the {@code join()} method @@ -858,10 +866,10 @@ static Joiner anySuccessfulOrThrow() { * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for all subtasks to complete successfully, then the {@code Joiner} - * causes the {@code join()} method to throw the exception returned by the - * exception supplying function when applied to a {@link CancelledByTimeoutException + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} + * causes the {@code join()} method to throw the exception returned by the exception + * supplying function when applied to a {@link CancelledByTimeoutException * CancelledByTimeoutException}. * * @apiNote Joiners returned by this method are suited to cases where subtasks @@ -894,7 +902,6 @@ static Joiner anySuccessfulOrThrow() { * @param esf the exception supplying function * @param the result type of subtasks * @param the type of the exception thrown by the {@link #join() join()} method - * @since 27 */ static Joiner awaitAllSuccessfulOrThrow(Function esf) { @@ -911,18 +918,18 @@ static Joiner anySuccessfulOrThrow() { *

    The {@link #join() join()} method of a {@link StructuredTaskScope} opened * with this Joiner returns {@code null} when all subtasks complete successfully. * If any subtask fails then the Joiner causes the {@code join()} method to throw - * {@code ExecutionException} with the exception from the first subtask to fail - * as the {@linkplain Throwable#getCause() cause}. + * {@code ExecutionException} with the exception from a failed subtask as the + * {@linkplain Throwable#getCause() cause}. * *

    Timeout Handling: The {@code Joiner} cannot produce a result when * the scope is cancelled by a timeout. If the scope was opened with a {@linkplain - * Configuration#withTimeout(Duration) timeout}, and the timeout expires before or - * while waiting for all subtasks to complete successfully, then the {@code Joiner} + * Configuration#withTimeout(Duration) timeout}, and the timeout expires before + * the {@code join()} method is invoked or completes, then the {@code Joiner} * causes the {@code join()} method to throw {@code ExecutionException} with a * {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. * - * @implSpec A Joiner returned by this method is equivalent to invoking {@link - * #awaitAllSuccessfulOrThrow(Function) awaitAllSuccessfulOrThrow(ExecutionException::new)}. + *

    This method is equivalent to invoking {@link #awaitAllSuccessfulOrThrow(Function) + * awaitAllSuccessfulOrThrow(ExecutionException::new)}. * * @param the result type of subtasks * @see #open() @@ -934,8 +941,8 @@ static Joiner awaitAllSuccessfulOrThrow() { /** * {@return a new Joiner that produces a list of all subtasks when all subtasks - * complete or {@linkplain Predicate#test(Object) evaluating} a predicate on a - * completed subtask causes the scope to be {@linkplain + * complete, or when {@linkplain Predicate#test(Object) evaluation} of a predicate + * on a completed subtask causes the scope to be {@linkplain * StructuredTaskScope##Cancellation cancelled}} * The {@code Joiner} does not cause the {@link #join() join()} method to throw * if subtasks fail or a configured {@linkplain Configuration#withTimeout(Duration) @@ -950,18 +957,25 @@ static Joiner awaitAllSuccessfulOrThrow() { * or subtasks in the {@link Subtask.State#UNAVAILABLE UNAVAILABLE} state if * they were forked or completed after the scope was cancelled. The scope is * cancelled if the given predicate {@linkplain Predicate#test(Object) evaluates} - * to {@code true}, or a configured timeout expires before or while waiting in the - * {@code join()} method. + * to {@code true}, or a configured timeout expires before the {@code join()} + * method is invoked or completes. * - *

    The given {@code Predicate}'s {@link Predicate#test(Object) test(Object)} - * method is invoked on a completed subtask by the thread that executed the subtask. + *

    If a subtask completes before the scope is cancelled, the thread that + * executed the subtask invokes the given {@code Predicate}'s {@link + * Predicate#test(Object) test(Object)} method with the completed subtask. * The method is invoked after the subtask completes (successfully or with an - * exception) before the thread terminates. The scope is cancelled if the {@code - * test} method returns {@code true}. The {@code test} method must be thread safe. - * It may be invoked concurrently from several threads as multiple subtasks can - * complete at the same time. If the method throws an exception or error, the thread - * invokes the {@linkplain Thread.UncaughtExceptionHandler uncaught exception handler} - * with the exception or error before the thread terminates. + * exception) and before the thread terminates. The scope is cancelled if the + * {@code test} method returns {@code true}. The {@code test} method must be + * thread-safe. It may be invoked concurrently from several threads as multiple + * subtasks can complete at the same time. If the method throws an exception or + * error, the thread invokes the {@linkplain Thread.UncaughtExceptionHandler + * uncaught exception handler} with the exception or error before the thread + * terminates. + * + *

    If {@link #fork(Callable) fork(Callable)} or {@link #fork(Runnable) + * fork(Runnable)} fails to create a thread to execute a subtask, the {@code fork} + * method invokes the predicate directly with the subtask in the {@link + * Subtask.State#FAILED FAILED} state. * *

    Timeout Handling: If used with a scope that has a {@linkplain * Configuration#withTimeout(Duration) timeout} set, and the timeout expires before @@ -1004,10 +1018,10 @@ static Joiner awaitAllSuccessfulOrThrow() { * } * *

    The following example is a method that uses {@code allUntil} to create a - * Joiner that does not cancel the scope. The method waits for all subtasks to - * complete or a timeout to expire. It returns a list of all subtasks, in the + * Joiner that does not itself cancel the scope. The method waits for all subtasks + * to complete or a timeout to expire. It returns a list of all subtasks, in the * same order as the collection of callables, even if the timeout expires before - * or while waiting in {@code join()}. + * the {@code join()} method is invoked or completes. * {@snippet lang=java : * List> invokeAll(Collection> tasks, Duration timeout) throws InterruptedException { * // @link substring="withTimeout" target="Configuration#withTimeout(Duration)" : @@ -1045,9 +1059,8 @@ static Joiner awaitAllSuccessfulOrThrow() { *

    Unless otherwise specified, passing a {@code null} argument to a method * in this class will cause a {@link NullPointerException} to be thrown. * - * @since 25 + * @since 28 */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) sealed interface Configuration permits StructuredTaskScopeImpl.ConfigImpl { /** * {@return a new {@code Configuration} object with the given thread factory} @@ -1092,10 +1105,9 @@ sealed interface Configuration permits StructuredTaskScopeImpl.ConfigImpl { * throws because the scope was {@linkplain StructuredTaskScope##Cancellation cancelled} * by a timeout. * - * @since 27 + * @since 28 * @see Configuration#withTimeout(Duration) */ - @PreviewFeature(feature = PreviewFeature.Feature.STRUCTURED_CONCURRENCY) final class CancelledByTimeoutException extends RuntimeException { @java.io.Serial static final long serialVersionUID = 705788143955048766L; @@ -1121,23 +1133,23 @@ public CancelledByTimeoutException() { } * method with the default configuration to produce the configuration for the new * scope: *

      - *
    • If the {@code apply} method returns a {@code Configuration} with a {@link - * ThreadFactory}, set using {@link Configuration#withThreadFactory(ThreadFactory) - * withThreadFactory(ThreadFactory)}, its {@link ThreadFactory#newThread(Runnable) - * newThread(Runnable)} method will be invoked to create threads when {@linkplain - * #fork(Callable) forking} subtasks in the scope. If a {@code ThreadFactory} is not set - * then forking subtasks will create an unnamed virtual thread for each subtask.
    • - *
    • If the {@code apply} method returns a {@code Configuration} with a timeout, set - * using {@link Configuration#withTimeout(Duration) withTimeout(Duration)}, the timeout - * will start when the scope is opened. If the timeout expires before or while waiting in - * {@link #join()} then the scope will be {@linkplain ##Cancellation cancelled}. It is - * {@link Joiner Joiner} specific as to whether the {@code join()} method returns a - * result or throws an exception when a timeout occurs. If the outcome is an exception - * then it will be thrown with a {@link CancelledByTimeoutException - * CancelledByTimeoutException} as the {@linkplain Throwable#getCause() cause}.
    • - *
    • If the {@code apply} method returns a {@code Configuration} with a name, set - * using {@linkplain Configuration#withName(String) withName(String)}, the name will - * be used for monitoring and management purposes.
    • + *
    • If the {@code apply} method returns a configuration with a {@linkplain + * Configuration#withThreadFactory(ThreadFactory) ThreadFactory}, the {@code + * ThreadFactory}'s {@link ThreadFactory#newThread(Runnable) newThread(Runnable)} + * method is invoked to create threads when {@linkplain #fork(Callable) forking} + * subtasks in the scope. If a {@code ThreadFactory} is not set then forking subtasks + * will create an unnamed virtual thread for each subtask.
    • + *
    • If the {@code apply} method returns a configuration with a {@linkplain + * Configuration#withTimeout(Duration) timeout}, the timeout starts when the scope is + * opened. If the timeout expires before {@link #join()} is invoked or completes then + * the scope is {@linkplain ##Cancellation cancelled} asynchronously. Whether the + * {@code join()} method returns a result or throws an exception when a timeout occurs + * is Joiner-specific. If the outcome is an exception then it will be thrown with a + * {@link CancelledByTimeoutException CancelledByTimeoutException} as the {@linkplain + * Throwable#getCause() cause}.
    • + *
    • If the {@code apply} method returns a configuration with a {@linkplain + * Configuration#withName(String) name}, the new scope is created with this name for + * monitoring and management purposes.
    • *
    • If the {@code apply} method throws an exception or error then it is propagated * by this method.
    • *
    • If the {@code apply} method returns {@code null} then {@code NullPointerException} @@ -1157,7 +1169,6 @@ public CancelledByTimeoutException() { } * @param the result type of subtasks {@linkplain #fork(Callable) forked} in the scope * @param the type of the result returned by the {@link #join() join()} method * @param the type of the exception thrown by the {@link #join() join()} method - * @since 26 */ static StructuredTaskScope open(Joiner joiner, UnaryOperator configOperator) { @@ -1175,6 +1186,10 @@ public CancelledByTimeoutException() { } * unnamed {@linkplain Thread##virtual-threads virtual threads}, does not name the * scope, and has no timeout. * + *

      This method is equivalent to invoking the {@linkplain #open(Joiner, UnaryOperator) + * 2-arg open} method with the given Joiner and the {@linkplain UnaryOperator#identity() + * identity operator}. + * *

      The new scope is owned by the current thread. Only code executing in this * thread can {@linkplain #fork(Callable) fork}, {@linkplain #join() join}, or * {@linkplain #close close} the scope. @@ -1182,16 +1197,11 @@ public CancelledByTimeoutException() { } *

      Construction captures the current thread's {@linkplain ScopedValue scoped * value} bindings for inheritance by threads forked in the scope. * - * @implSpec This factory method is equivalent to invoking the - * {@linkplain #open(Joiner, UnaryOperator) 2-arg open} method with the given Joiner - * and the {@linkplain UnaryOperator#identity() identity operator}. - * * @param joiner the Joiner * @return a new scope * @param the result type of subtasks {@linkplain #fork(Callable) forked} in the scope * @param the type of the result returned by the {@link #join() join()} method * @param the type of the exception thrown by the {@link #join() join()} method - * @since 25 */ static StructuredTaskScope open(Joiner joiner) { @@ -1207,13 +1217,17 @@ public CancelledByTimeoutException() { } * method. * *

      The {@link #join()} method of the new scope waits for all subtasks to succeed - * or any subtask to fail. The {@code join() method} returns {@code null} if all + * or any subtask to fail. The {@code join()} method returns {@code null} if all * subtasks complete successfully. It throws {@link ExecutionException} if any subtask - * fails, with the exception from the first subtask to fail as the {@linkplain - * Throwable#getCause() cause}. If a {@linkplain Configuration#withTimeout(Duration) - * timeout} is configured, and the timeout expires before or while waiting in the - * {@code join()} method, it throws {@code ExecutionException} with a {@link - * CancelledByTimeoutException CancelledByTimeoutException} as the cause. + * fails, with the exception from a failed subtask as the {@linkplain Throwable#getCause() + * cause}. If a {@linkplain Configuration#withTimeout(Duration) timeout} is configured, + * and the timeout expires before the {@link #join()} method is invoked or completes, + * it throws {@code ExecutionException} with a {@link CancelledByTimeoutException + * CancelledByTimeoutException} as the cause. + * + *

      This method is equivalent to invoking the {@linkplain #open(Joiner, UnaryOperator) + * 2-arg open} method with a Joiner created with {@link Joiner#awaitAllSuccessfulOrThrow() + * Joiner.awaitAllSuccessfulOrThrow()} and the given configuration operator. * *

      The new scope is owned by the current thread. Only code executing in this * thread can {@linkplain #fork(Callable) fork}, {@linkplain #join() join}, or @@ -1222,15 +1236,9 @@ public CancelledByTimeoutException() { } *

      Construction captures the current thread's {@linkplain ScopedValue scoped * value} bindings for inheritance by threads forked in the scope. * - * @implSpec This factory method is equivalent to invoking the {@linkplain - * #open(Joiner, UnaryOperator) 2-arg open} method with a Joiner created with {@link - * Joiner#awaitAllSuccessfulOrThrow() awaitAllSuccessfulOrThrow()} and the given - * configuration operator. - * * @param configOperator the operator to produce the configuration * @return a new scope * @param the result type of subtasks forked in the scope - * @since 27 */ static StructuredTaskScope open(UnaryOperator configOperator) { @@ -1241,7 +1249,7 @@ public CancelledByTimeoutException() { } * Opens a new {@code StructuredTaskScope} where {@link #join()} waits for all subtasks * to succeed or any subtask to fail. The {@code join()} method returns {@code null} * if all subtasks complete successfully. It throws {@link ExecutionException} if any - * subtask fails, with the exception from the first subtask to fail as the {@linkplain + * subtask fails, with the exception from a failed subtask as the {@linkplain * Throwable#getCause() cause}. * *

      The scope is created with the {@linkplain ##DefaultConfiguration default @@ -1249,6 +1257,11 @@ public CancelledByTimeoutException() { } * unnamed {@linkplain Thread##virtual-threads virtual threads}, does not name the * scope, and has no timeout. * + *

      This method is equivalent to invoking the {@linkplain #open(Joiner, UnaryOperator) + * 2-arg open} method with a Joiner created with {@link Joiner#awaitAllSuccessfulOrThrow() + * Joiner.awaitAllSuccessfulOrThrow()} and the {@linkplain UnaryOperator#identity() + * identity operator}. + * *

      The new scope is owned by the current thread. Only code executing in this * thread can {@linkplain #fork(Callable) fork}, {@linkplain #join() join}, or * {@linkplain #close close} the scope. @@ -1256,14 +1269,8 @@ public CancelledByTimeoutException() { } *

      Construction captures the current thread's {@linkplain ScopedValue scoped * value} bindings for inheritance by threads forked in the scope. * - * @implSpec This factory method is equivalent to invoking the - * {@linkplain #open(Joiner, UnaryOperator) 2-arg open} method with a Joiner created - * with {@link Joiner#awaitAllSuccessfulOrThrow() awaitAllSuccessfulOrThrow()} and - * the {@linkplain UnaryOperator#identity() identity operator}. - * * @param the result type of subtasks * @return a new scope - * @since 25 */ static StructuredTaskScope open() { return open(Joiner.awaitAllSuccessfulOrThrow(), UnaryOperator.identity()); @@ -1272,7 +1279,7 @@ static StructuredTaskScope open() { /** * Forks a subtask by starting a new thread in this scope to execute a value-returning * method. The new thread executes the subtask concurrently with the current thread. - * The parameter to this method is a {@link Callable}, the new thread executes its + * The parameter to this method is a {@link Callable}. The new thread executes its * {@link Callable#call() call()} method. The thread inherits the current thread's * {@linkplain ScopedValue scoped value bindings} that must match the bindings * captured when the scope was opened. @@ -1282,53 +1289,62 @@ static StructuredTaskScope open() { * Configuration#withThreadFactory(ThreadFactory) set}, then its {@link * ThreadFactory#newThread(Runnable) newThread(Runnable)} method is invoked to create * the thread that will execute the subtask. {@link RejectedExecutionException} is - * thrown if the {@code newThread(Runnable)} method returns {@code null}. - * If a {@code ThreadFactory} is not set, the {@code fork(Callable)} method creates an - * unnamed {@linkplain Thread##virtual-threads virtual thread} to execute the subtask. + * thrown if the {@code newThread(Runnable)} method returns {@code null}. If the + * {@code newThread(Runnable)} method throws an exception or error then it is + * propagated by this method. + * + *

      If a {@code ThreadFactory} is not set, the {@code fork(Callable)} method + * creates an unnamed {@linkplain Thread##virtual-threads virtual thread} to execute + * the subtask. * *

      This method returns a {@link Subtask Subtask} object as a handle to the * forked subtask. If the scope is {@linkplain ##Cancellation cancelled}, the * {@code Subtask} is returned in the {@link Subtask.State#UNAVAILABLE UNAVAILABLE} - * state without creating a thread. In some usages, {@code Subtask} object will be used - * by the "main" task (the scope owner) to get the subtask's outcome (result or - * exception) after it has invoked {@link #join() join()} to wait for all subtasks to - * complete. In other usages, the scope is created with a {@link Joiner Joiner} that + * state without creating a thread. In some usages, the {@code Subtask} object will be + * used by the "main" task (the scope owner) to get the subtask's outcome (result or + * exception) after it has invoked {@link #join() join()}. + * In other usages, the scope is created with a {@link Joiner Joiner} that * produces the outcome for the main task to process after joining. A {@code Joiner} * that produces a result reduces the need for bookkeeping and the need for the * main task to retain references to {@code Subtask} objects for correlation purposes. * - *

      To ensure correct usage, the {@link Subtask#get() Subtask.get()} method may - * only be called by the scope owner to get the result of a successful subtask after - * it has waited for subtasks to complete with the {@link #join() join()} method. - * Similarly, the {@link Subtask#exception() Subtask.exception()} method may only be - * called by the scope owner to get the exception (or error) of a failed subtask after - * it has joined. + *

      After the scope owner has {@linkplain #join() joined} to obtain the + * scope's outcome, {@link Subtask#get() Subtask.get()} and {@link Subtask#exception() + * Subtask.exception()} can be used to get the subtask's result or exception. * *

      This method may only be invoked by the scope owner. * *

      Joiner Implementers: * - *

      If the scope was opened with a {@link Joiner Joiner}, its {@link + *

      If the scope was opened with a {@link Joiner Joiner}, the Joiner's {@link * Joiner#onFork(Subtask) onFork(Subtask)} method is invoked with the newly created - * {@code Subtask} object before the thread is created. It is invoked with the subtask - * in the {@code UNAVAILABLE} state. If the method throws an exception or error then - * it is propagated by the {@code fork(Callable)} method without creating a thread to - * execute the subtask. If the scope is not already cancelled, and the {@code onFork} - * method return {@code false}, then a thread is created and {@linkplain Thread#start() - * scheduled} to start execution of the subtask. If the scope is cancelled, or the - * {@code onFork} method returns {@code true} to cancel the scope, {@code fork(Callable)} - * returns the subtask in the {@code UNAVAILABLE} state. + * {@code Subtask} object in the {@code UNAVAILABLE} state and before a thread is + * created to execute it. If the {@code onFork(Subtask)} method returns {@code true} + * then the scope is cancelled if not already cancelled. This causes the {@code + * fork(Callable)} method to return the {@code Subtask} in the {@code UNAVAILABLE} state + * without creating a thread. If the {@code onFork(Subtask)} method returns {@code + * false}, and the scope is not cancelled, then the {@code fork(Callable)} method + * continues on to create a thread to execute the subtask. If the {@code + * onFork(Subtask)} method completes with an exception or error, then it is propagated + * by the {@code fork(Callable)} method without creating a thread to execute the subtask. * *

      If the subtask executes and completes (successfully or with an exception) before * the scope is cancelled, then the thread invokes the Joiner's {@link * Joiner#onComplete(Subtask) onComplete(Subtask)} method with the subtask in the * {@link Subtask.State#SUCCESS SUCCESS} or {@link Subtask.State#FAILED FAILED} state. - * If the {@code onComplete(Subtask}) method returns {@code true} then the scope is - * cancelled, if not already cancelled. If the {@code onComplete(Subtask)} method - * completes with an exception or error, then the thread executes the {@linkplain + * If the {@code onComplete(Subtask)} method returns {@code true}, then the scope is + * cancelled, if not already cancelled. If the Joiner's {@code onComplete(Subtask)} + * method completes with an exception or error, then the thread executes the {@linkplain * Thread.UncaughtExceptionHandler uncaught exception handler} before the thread * terminates. * + *

      The subtask is not executed if the {@code fork} method is unable to create a + * thread to execute the subtask. In that case, the {@code fork} method invokes the + * Joiner's {@link Joiner#onComplete(Subtask) onComplete(Subtask)} method directly + * with the subtask in the {@link Subtask.State#FAILED FAILED} state. The Joiner can + * invoke the subtask's {@link Subtask#exception() exception()} method to get the + * exception or error encountered when attempting to create the thread. + * * @param task the value-returning task for the thread to execute * @param the result type * @return the subtask @@ -1348,7 +1364,7 @@ static StructuredTaskScope open() { * does not return a result. * *

      This method works exactly the same as {@link #fork(Callable)} except that the - * parameter to this method is a {@link Runnable}, the new thread executes its + * parameter to this method is a {@link Runnable}. The new thread executes its * {@link Runnable#run() run()} method, and {@link Subtask#get() Subtask.get()} returns * {@code null} if the subtask completes successfully. * @@ -1362,27 +1378,26 @@ static StructuredTaskScope open() { * the same as when the scope was created * @throws RejectedExecutionException if the thread factory rejected creating a * thread to execute the subtask - * @since 25 */ Subtask fork(Runnable task); /** - * Returns a result, or throws, after waiting for all subtasks to complete or the - * scope to be {@linkplain ##Cancellation cancelled}. + * Returns a result or throws an exception after waiting for all subtasks to complete + * or the scope to be {@linkplain ##Cancellation cancelled}. * *

      If the scope was opened with the {@link #open()} or {@link #open(UnaryOperator)} - * method, then {@code join()} waits for all subtasks to succeed or any subtask to fail. + * method then {@code join()} waits for all subtasks to succeed or any subtask to fail. * It returns {@code null} if all subtasks complete successfully. It throws {@link - * ExecutionException} if any subtask fails, with exception from the first subtask to - * fail as the {@linkplain Throwable#getCause() cause}. If a {@linkplain + * ExecutionException} if any subtask fails, with the exception from any failed + * subtask as the {@linkplain Throwable#getCause() cause}. If a {@linkplain * Configuration#withTimeout(Duration) timeout} is configured and the timeout expires - * before or while waiting, it throws {@code ExecutionException} with a {@link - * CancelledByTimeoutException CancelledByTimeoutException} as the cause. + * before {@code join()} is invoked or completes, it throws {@code ExecutionException} + * with a {@link CancelledByTimeoutException CancelledByTimeoutException} as the cause. * - *

      If the scope was opened with a {@link Joiner Joiner}, it is invoked after - * waiting or cancellation to produce the outcome (result or exception). This includes - * the timeout case where a timeout is configured and it expires before or - * while waiting in the {@code join()} method. + *

      If the scope was opened with a {@link Joiner Joiner}, the {@code Joiner} is + * invoked after waiting or cancellation to produce the outcome (result or exception). + * This includes the timeout case where a timeout is configured and the + * timeout expires before this method completes. * *

      This method may only be invoked by the scope owner. It may only be invoked once * to get the result, exception or timeout outcome, unless the previous invocation @@ -1390,7 +1405,7 @@ static StructuredTaskScope open() { * *

      Joiner Implementers: * - *

      When all subtasks complete, or the scope cancelled, this method invokes the + *

      When all subtasks complete, or the scope is cancelled, this method invokes the * {@code Joiner}'s {@link Joiner#result() result()} or {@link Joiner#timeout() timeout()} * method to produce the outcome (result or exception) for the {@code join()} method. * The {@code result()} method is invoked for the "no timeout" case. The {@code timeout()} @@ -1400,17 +1415,14 @@ static StructuredTaskScope open() { * @apiNote When the outcome is an exception, the {@linkplain Throwable#getStackTrace() * stack trace} will be the stack trace of the call to the {@code join()} method. * Its {@linkplain Throwable#getCause() cause} will typically be the exception - * thrown by a failed subtask with the stack trace of the failed subtask. - * + * thrown by a failed subtask. * @return the result * @throws WrongThreadException if the current thread is not the scope owner * @throws IllegalStateException if already joined or this scope is closed * @throws R_X when the outcome is an exception * @throws InterruptedException if the current thread is {@linkplain Thread#interrupt() - * interrupted} while waiting or this method is invoked with the current thread's - * {@linkplain Thread#isInterrupted() interrupted status} set. The current thread's - * interrupted status is cleared when this exception is thrown. - * @since 25 + * interrupted} while waiting. The current thread's interrupted status is cleared + * when this exception is thrown. * @see Thread##thread-interruption Thread Interruption */ R join() throws R_X, InterruptedException; @@ -1419,7 +1431,7 @@ static StructuredTaskScope open() { * {@return {@code true} if this scope is {@linkplain ##Cancellation cancelled} or in * the process of being cancelled, otherwise {@code false}} * - *

      Cancelling the scope prevents new threads from starting in the scope and + *

      Cancelling the scope prevents new threads from being started in the scope and * {@linkplain Thread#interrupt() interrupts} threads executing unfinished subtasks. * It may take some time before the interrupted threads finish execution; this * method may return {@code true} before all threads have been interrupted or before @@ -1429,22 +1441,21 @@ static StructuredTaskScope open() { * that forks subtasks before the {@link #join()} method is invoked) may use this * method to avoid doing work in cases where the scope is cancelled by the completion * of a previously forked subtask or a timeout. - * - * @since 25 */ boolean isCancelled(); /** * Closes this scope. * - *

      This method first {@linkplain ##Cancellation cancels} the scope, if not already - * cancelled. This {@linkplain Thread#interrupt() interrupts} the threads executing - * unfinished subtasks. This method then waits for all threads to finish. If interrupted - * while waiting then it will continue to wait until the threads finish, before - * completing with the {@linkplain Thread#isInterrupted() interrupted status} set. + *

      If the {@link #join()} method has not completed with an outcome, this method + * first {@linkplain ##Cancellation cancels} the scope, if not already cancelled. + * This {@linkplain Thread#interrupt() interrupts} the threads executing unfinished + * subtasks. This method then waits for all threads to finish. If interrupted + * while waiting, it continues to wait until the threads finish, then completes + * with the {@linkplain Thread#isInterrupted() interrupted status} set. If the scope + * is already closed then this method has no effect. * - *

      This method may only be invoked by the scope owner. If the scope - * is already closed then the scope owner invoking this method has no effect. + *

      This method may only be invoked by the scope owner. * *

      A {@code StructuredTaskScope} is intended to be used in a structured * manner. If this method is called to close a scope before nested scopes are diff --git a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScopeImpl.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScopeImpl.java index fbd58fc2a211..5b813b6c435d 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScopeImpl.java +++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScopeImpl.java @@ -46,10 +46,12 @@ final class StructuredTaskScopeImpl private final ThreadFlock flock; // scope state, set by owner thread, read by any thread - private static final int ST_FORKED = 1, // subtasks forked, need to join - ST_JOIN_STARTED = 2, // join started, can no longer fork - ST_JOIN_COMPLETED = 3, // join completed - ST_CLOSED = 4; // closed + private static final int ST_FORKED = 1, // subtasks forked, need to join + ST_JOIN_STARTED = 2, // join started, can no longer fork + ST_JOIN_COMPLETED = 3, // join completed + ST_CLOSED_BEFORE_JOIN = 4, // closed, join did not complete + ST_CLOSED_AFTER_JOIN = 5; // closed, join completed + private volatile int state; // set or read by any thread @@ -112,7 +114,8 @@ private void ensureOwner() { * Returns true if join has been invoked and there is an outcome. */ private boolean isJoinCompleted() { - return state >= ST_JOIN_COMPLETED; + int s = state; + return (s == ST_JOIN_COMPLETED || s == ST_CLOSED_AFTER_JOIN); } /** @@ -200,9 +203,15 @@ public Subtask fork(Callable task) { if (!cancelled) { // create thread to run task - Thread thread = threadFactory.newThread(subtask); - if (thread == null) { - throw new RejectedExecutionException("Rejected by thread factory"); + Thread thread; + try { + thread = threadFactory.newThread(subtask); + if (thread == null) { + throw new RejectedExecutionException("Rejected by thread factory"); + } + } catch (Throwable e) { + subtask.notRun(e); + throw e; } // attempt to start the thread @@ -210,8 +219,14 @@ public Subtask fork(Callable task) { try { flock.start(thread); } catch (IllegalStateException e) { - // shutdown by another thread, or underlying flock is shutdown due - // to unstructured use + // thread flock may be shutdown + if (!cancelled) { + subtask.notRun(e); + throw e; + } + } catch (Throwable e) { + subtask.notRun(e); + throw e; } } @@ -265,7 +280,7 @@ public boolean isCancelled() { public void close() { ensureOwner(); int s = state; - if (s == ST_CLOSED) { + if (s == ST_CLOSED_BEFORE_JOIN || s == ST_CLOSED_AFTER_JOIN) { return; } @@ -279,7 +294,7 @@ public void close() { try { flock.close(); } finally { - state = ST_CLOSED; + state = (s == ST_JOIN_COMPLETED) ? ST_CLOSED_AFTER_JOIN : ST_CLOSED_BEFORE_JOIN; } // throw ISE if the owner didn't join after forking @@ -309,6 +324,7 @@ private record AltResult(Subtask.State state, Throwable exception) { private final Callable task; private volatile Object result; @Stable private Thread thread; + private Thread caller; SubtaskImpl(StructuredTaskScopeImpl scope, Callable task) { this.scope = scope; @@ -360,6 +376,20 @@ public void run() { scope.onComplete(this); } + void notRun(Throwable ex) { + result = new AltResult(State.FAILED, ex); + caller = Thread.currentThread(); + try { + scope.onComplete(this); + } catch (Throwable e) { + if (e != ex) { + ex.addSuppressed(e); + } + } finally { + caller = null; + } + } + @Override public Subtask.State state() { Object result = this.result; @@ -390,7 +420,11 @@ public T get() { @Override public Throwable exception() { - ensureJoinedIfNotSubtask(); + // allow main task to invoke Subtask::exception from onComplete callback + Thread caller = this.caller; + if (caller == null || caller != Thread.currentThread()) { + ensureJoinedIfNotSubtask(); + } Object result = this.result; if (result instanceof AltResult alt && alt.state() == State.FAILED) { return alt.exception(); diff --git a/src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java b/src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java index 49efe5d5c2c0..230c5d2ec39c 100644 --- a/src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java +++ b/src/java.base/share/classes/java/util/concurrent/SynchronousQueue.java @@ -177,7 +177,7 @@ final Object xferLifo(Object e, long ns) { else if (p.cmpExItem(m, e) != m) p = head; // missed; restart else { // matched complementary node - Thread w = p.waiter; + Thread w = p.getVolatileWaiter(); cmpExHead(p, p.next); LockSupport.unpark(w); break outer; diff --git a/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java b/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java index b83bc8e941f9..e3bf10cafe69 100644 --- a/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java +++ b/src/java.base/share/classes/jdk/internal/icu/impl/UBiDiProps.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -66,8 +66,8 @@ private void readData(ByteBuffer bytes) throws IOException { // read indexes[] int i, count; count=bytes.getInt(); - if(count bytes.remaining() / Integer.BYTES) { + throw new IOException("Invalid index count in " + DATA_FILE_NAME); } indexes=new int[count]; @@ -88,22 +88,31 @@ private void readData(ByteBuffer bytes) throws IOException { // read mirrors[] count=indexes[IX_MIRROR_LENGTH]; - if(count>0) { - mirrors=new int[count]; - for(i=0; i bytes.remaining() / Integer.BYTES) { + throw new IOException("Invalid mirrors length in " + DATA_FILE_NAME); + } + mirrors=new int[count]; + for(i=0; i bytes.remaining()) { + throw new IOException("Invalid joining-group length in " + DATA_FILE_NAME); + } + count = (int)jgArrayLength; jgArray=new byte[count]; for(i=0; i bytes.remaining()) { + throw new IOException("Invalid joining-group 2 length in " + DATA_FILE_NAME); + } + count = (int)jgArrayLength; jgArray2=new byte[count]; for(i=0; i= 0) { + return true; + } + return hasDotOrDotDot(name) || hasWinDrive(name); + } + + private static boolean hasDotOrDotDot(String name) { + int index = 0; + while (index < name.length()) { + int start = index; + while (index < name.length() && name.charAt(index) != '/') { + index++; + } + if (name.charAt(start) == '.') { + int length = index - start; + if (length == 1 || (length == 2 && name.charAt(start + 1) == '.')) { + return true; + } + } + index++; + } + return false; + } + + private static boolean isWinDriveLetter(char c) { + return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); + } + + private static boolean hasWinDrive(String name) { + int i = 0; + while (i + 1 < name.length()) { + if (name.charAt(i + 1) == ':' && isWinDriveLetter(name.charAt(i))) { + return true; + } + i = name.indexOf('/', i); + if (i < 0) { + return false; + } + i++; + } + return false; + } } private final Path file; diff --git a/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java b/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java index 1d6876f760e7..1951dbc925a1 100644 --- a/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java +++ b/src/java.base/share/classes/sun/net/ftp/impl/FtpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -526,7 +526,7 @@ private boolean issueCommand(String cmd) throws IOException, // ignore... } } - if (cmd.indexOf('\n') != -1) { + if (cmd.indexOf('\n') != -1 || cmd.indexOf('\r') != -1) { sun.net.ftp.FtpProtocolException ex = new sun.net.ftp.FtpProtocolException("Illegal FTP command"); ex.initCause(new IllegalArgumentException("Illegal carriage return")); diff --git a/src/java.base/share/classes/sun/net/smtp/SmtpClient.java b/src/java.base/share/classes/sun/net/smtp/SmtpClient.java index 494bcf199879..73968ddf1926 100644 --- a/src/java.base/share/classes/sun/net/smtp/SmtpClient.java +++ b/src/java.base/share/classes/sun/net/smtp/SmtpClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,7 +74,7 @@ private void toCanonical(String s) throws IOException { } public void to(String s) throws IOException { - if (s.indexOf('\n') != -1) { + if (s.indexOf('\n') != -1 || s.indexOf('\r') != -1) { throw new IOException("Illegal SMTP command", new IllegalArgumentException("Illegal carriage return")); } @@ -120,7 +120,7 @@ else if (st == pos) } public void from(String s) throws IOException { - if (s.indexOf('\n') != -1) { + if (s.indexOf('\n') != -1 || s.indexOf('\r') != -1) { throw new IOException("Illegal SMTP command", new IllegalArgumentException("Illegal carriage return")); } diff --git a/src/java.base/share/classes/sun/nio/ch/Net.java b/src/java.base/share/classes/sun/nio/ch/Net.java index 4564579d0546..80e6e409e327 100644 --- a/src/java.base/share/classes/sun/nio/ch/Net.java +++ b/src/java.base/share/classes/sun/nio/ch/Net.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,8 +67,6 @@ public String name() { // set to true if exclusive binding is on for Windows private static final boolean EXCLUSIVE_BIND; - // set to true if the fast tcp loopback should be enabled on Windows - private static final boolean FAST_LOOPBACK; // -- Miscellaneous utilities -- @@ -458,11 +456,6 @@ static Object getSocketOption(FileDescriptor fd, ProtocolFamily family, SocketOp } } - private static boolean isFastTcpLoopbackRequested() { - String loopbackProp = System.getProperty("jdk.net.useFastTcpLoopback", "false"); - return loopbackProp.isEmpty() || Boolean.parseBoolean(loopbackProp); - } - // -- Socket operations -- private static native boolean isIPv6Available0(); @@ -491,7 +484,7 @@ static FileDescriptor socket() throws IOException { static FileDescriptor socket(ProtocolFamily family, boolean stream) throws IOException { boolean preferIPv6 = isIPv6Available() && (family != StandardProtocolFamily.INET); - return IOUtil.newFD(socket0(preferIPv6, stream, false, FAST_LOOPBACK)); + return IOUtil.newFD(socket0(preferIPv6, stream, false)); } static FileDescriptor serverSocket() { @@ -501,12 +494,11 @@ static FileDescriptor serverSocket() { static FileDescriptor serverSocket(ProtocolFamily family) { boolean preferIPv6 = isIPv6Available() && (family != StandardProtocolFamily.INET); - return IOUtil.newFD(socket0(preferIPv6, true, true, FAST_LOOPBACK)); + return IOUtil.newFD(socket0(preferIPv6, true, true)); } // Due to oddities SO_REUSEADDR on Windows reuse is ignored - private static native int socket0(boolean preferIPv6, boolean stream, boolean reuse, - boolean fastLoopback); + private static native int socket0(boolean preferIPv6, boolean stream, boolean reuse); public static void bind(FileDescriptor fd, InetAddress addr, int port) throws IOException @@ -797,8 +789,6 @@ static native int blockOrUnblock6(boolean block, FileDescriptor fd, byte[] group EXCLUSIVE_BIND = false; } - FAST_LOOPBACK = isFastTcpLoopbackRequested(); - IPV6_AVAILABLE = isIPv6Available0(); SO_REUSEPORT_AVAILABLE = isReusePortAvailable0(); } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java index 1cc29e29477a..c40c9bc432fc 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java @@ -126,8 +126,12 @@ final class SSLConfiguration implements Cloneable { if (supportExtendedMasterSecret) { try { KeyGenerator.getInstance("SunTlsExtendedMasterSecret"); - } catch (NoSuchAlgorithmException nae) { - supportExtendedMasterSecret = false; + } catch (NoSuchAlgorithmException _) { + try { + KeyGenerator.getInstance("SunTls12ExtendedMasterSecret"); + } catch (NoSuchAlgorithmException _) { + supportExtendedMasterSecret = false; + } } } useExtendedMasterSecret = supportExtendedMasterSecret; diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java index c325890a3b66..5c596932dc27 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtensions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -68,8 +68,17 @@ final class SSLExtensions { } encodedLength = len + 2; // 2: the length of the extensions. + Set extIdSet = new HashSet<>(); // To check for duplicates. + while (len > 0) { int extId = Record.getInt16(m); + + if (!extIdSet.add(extId)) { + throw hm.handshakeContext.conContext.fatal( + Alert.ILLEGAL_PARAMETER, + "Duplicate extension with ID: " + extId); + } + int extLen = Record.getInt16(m); if (extLen > m.remaining()) { throw hm.handshakeContext.conContext.fatal( diff --git a/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java index 533b7f9a52de..829c35f5155f 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLMasterKeyDerivation.java @@ -121,7 +121,11 @@ public SecretKey deriveKey(String typeNotUsed) throws IOException { TlsMasterSecretParameterSpec spec; if (context.handshakeSession.useExtendedMasterSecret) { // reset to use the extended master secret algorithm - masterAlg = "SunTlsExtendedMasterSecret"; + if (protocolVersion.id == ProtocolVersion.TLS12.id) { + masterAlg = "SunTls12ExtendedMasterSecret"; + } else { + masterAlg = "SunTlsExtendedMasterSecret"; + } // For the session hash, use the handshake messages up to and // including the ClientKeyExchange message. diff --git a/src/java.base/share/classes/sun/security/util/HostnameChecker.java b/src/java.base/share/classes/sun/security/util/HostnameChecker.java index b5a6e48e5707..a9fafd43acf7 100644 --- a/src/java.base/share/classes/sun/security/util/HostnameChecker.java +++ b/src/java.base/share/classes/sun/security/util/HostnameChecker.java @@ -125,7 +125,7 @@ private static boolean isIpAddress(String name) { /** * Check if the certificate allows use of the given IP address. - * + *

      * From RFC2818: * In some cases, the URI is specified as an IP address rather than a * hostname. In this case, the iPAddress subjectAltName must be present @@ -140,26 +140,30 @@ private static void matchIP(String expectedIP, X509Certificate cert) } for (List next : subjAltNames) { // For IP address, it needs to be exact match - if (((Integer)next.get(0)).intValue() == ALTNAME_IP) { + if ((int) next.get(0) == ALTNAME_IP) { String ipAddress = (String)next.get(1); + if (expectedIP.equalsIgnoreCase(ipAddress)) { return; } else { - // compare InetAddress objects in order to ensure - // equality between a long IPv6 address and its - // abbreviated form. + // Compare InetAddress objects if literal check failed. + // InetAddress.getByName() normalizes IP address, thus + // the following is insured: + // - Equality between a long IPv6 address and its + // abbreviated form. + // - Equality between IPv4-Mapped IPv6 address and its + // regular IPv4 form. try { if (InetAddress.getByName(expectedIP).equals( InetAddress.getByName(ipAddress))) { return; } - } catch (UnknownHostException e) {} + } catch (UnknownHostException _) {} } } } throw new CertificateException("No subject alternative " + - "names matching " + "IP address " + - expectedIP + " found"); + "names matching IP address " + expectedIP + " found"); } /** diff --git a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java index 44c9ed2d0754..c1afe4246125 100644 --- a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java @@ -36,12 +36,9 @@ */ enum CADistrustPolicy { /** - * Distrust TLS Server certificates anchored by a Symantec root CA and - * issued after April 16, 2019 (with exceptions for a couple of subordinate - * CAs, see the jdk.security.caDistrustPolicies definition in the - * java.security file for more details). If enabled, this policy is - * currently enforced by the PKIX and SunX509 TrustManager implementations - * of the SunJSSE provider implementation. + * Distrust TLS Server certificates anchored by a Symantec root CA. + * If enabled, this policy is currently enforced by the PKIX and SunX509 + * TrustManager implementations of the SunJSSE provider implementation. */ SYMANTEC_TLS { void checkDistrust(String variant, X509Certificate[] chain) @@ -54,10 +51,9 @@ void checkDistrust(String variant, X509Certificate[] chain) }, /** - * Distrust TLS Server certificates anchored by an Entrust root CA and - * issued after November 11, 2024. If enabled, this policy is currently - * enforced by the PKIX and SunX509 TrustManager implementations - * of the SunJSSE provider implementation. + * Distrust TLS Server certificates anchored by an Entrust root CA. + * If enabled, this policy is currently enforced by the PKIX and SunX509 + * TrustManager implementations of the SunJSSE provider implementation. */ ENTRUST_TLS { void checkDistrust(String variant, X509Certificate[] chain) @@ -70,10 +66,9 @@ void checkDistrust(String variant, X509Certificate[] chain) }, /** - * Distrust TLS Server certificates anchored by a CAMERFIRMA root CA and - * issued after April 15, 2025. If enabled, this policy is currently - * enforced by the PKIX and SunX509 TrustManager implementations - * of the SunJSSE provider implementation. + * Distrust TLS Server certificates anchored by a CAMERFIRMA root CA. + * If enabled, this policy is currently enforced by the PKIX and SunX509 + * TrustManager implementations of the SunJSSE provider implementation. */ CAMERFIRMA_TLS { void checkDistrust(String variant, X509Certificate[] chain) @@ -87,9 +82,10 @@ void checkDistrust(String variant, X509Certificate[] chain) /** * Distrust TLS Server certificates anchored by the Chunghwa ePKI root CA - * and issued after March 17, 2026. If enabled, this policy is currently - * enforced by the PKIX and SunX509 TrustManager implementations - * of the SunJSSE provider implementation. + * and issued after March 17, 2026. After APRIL 19, 2027, certificates will + * be distrusted regardless of the issued date in the certificate. + * If enabled, this policy is currently enforced by the PKIX and SunX509 + * TrustManager implementations of the SunJSSE provider implementation. */ CHUNGHWA_TLS { void checkDistrust(String variant, X509Certificate[] chain) diff --git a/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java index 984f5a0ab47f..fb6db1126893 100644 --- a/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/CamerfirmaTLSPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,6 @@ package sun.security.validator; import java.security.cert.X509Certificate; -import java.time.LocalDate; -import java.time.Month; -import java.time.ZoneOffset; -import java.util.Date; -import java.util.Map; -import java.util.Set; import sun.security.util.Debug; import sun.security.x509.X509CertImpl; @@ -53,9 +47,7 @@ final class CamerfirmaTLSPolicy { "063E4AFAC491DFD332F3089B8542E94617D893D7FE944E10A7937EE29D9693C0"; // Any TLS Server certificate that is anchored by one of the Camerfirma - // roots above and is issued after this date will be distrusted. - private static final LocalDate APRIL_15_2025 = - LocalDate.of(2025, Month.APRIL, 15); + // roots above will be distrusted. /** * This method assumes the eeCert is a TLS Server Cert and chains back to @@ -74,11 +66,10 @@ static void checkDistrust(X509Certificate[] chain) + "trust anchor of TLS server certificate"); } if (FINGERPRINT.equalsIgnoreCase(fp)) { - Date notBefore = chain[0].getNotBefore(); - LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), - ZoneOffset.UTC); - // reject if certificate is issued after April 15, 2025 - checkNotBefore(ldNotBefore, APRIL_15_2025, anchor); + throw new ValidatorException + ("TLS Server certificate anchored by a distrusted legacy " + + "Camerfirma root CA: "+ anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); } } @@ -86,17 +77,5 @@ private static String fingerprint(X509Certificate cert) { return X509CertImpl.getFingerprint("SHA-256", cert, debug); } - private static void checkNotBefore(LocalDate notBeforeDate, - LocalDate distrustDate, X509Certificate anchor) - throws ValidatorException { - if (notBeforeDate.isAfter(distrustDate)) { - throw new ValidatorException - ("TLS Server certificate issued after " + distrustDate + - " and anchored by a distrusted legacy Camerfirma root CA: " - + anchor.getSubjectX500Principal(), - ValidatorException.T_UNTRUSTED_CERT, anchor); - } - } - private CamerfirmaTLSPolicy() {} } diff --git a/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java index 114f5043fbf9..e7f0642f8606 100644 --- a/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java @@ -29,8 +29,6 @@ import java.time.Month; import java.time.ZoneOffset; import java.util.Date; -import java.util.Map; -import java.util.Set; import sun.security.util.Debug; import sun.security.x509.X509CertImpl; @@ -55,6 +53,12 @@ final class ChunghwaTLSPolicy { private static final LocalDate MARCH_17_2026 = LocalDate.of(2026, Month.MARCH, 17); + // Any TLS Server certificate that is anchored by the Chunghwa + // root above is distrusted after APRIL_19_2027. + // APRIL_19_2027 is MARCH_17_2026 plus 398 days. + private static final LocalDate APRIL_19_2027 = + LocalDate.of(2027, Month.APRIL, 19); + /** * This method assumes the eeCert is a TLS Server Cert and chains back to * the anchor. @@ -72,6 +76,13 @@ static void checkDistrust(X509Certificate[] chain) + "trust anchor of TLS server certificate"); } if (FINGERPRINT.equalsIgnoreCase(fp)) { + if (LocalDate.now().isAfter(APRIL_19_2027)) { + throw new ValidatorException + ("TLS Server certificate anchored by a distrusted legacy " + + "Chunghwa root CA: " + anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); + } + Date notBefore = chain[0].getNotBefore(); LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), ZoneOffset.UTC); diff --git a/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java index 831d1088ed35..efb84ac8bc99 100644 --- a/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java @@ -25,11 +25,6 @@ package sun.security.validator; import java.security.cert.X509Certificate; -import java.time.LocalDate; -import java.time.Month; -import java.time.ZoneOffset; -import java.util.Date; -import java.util.Map; import java.util.Set; import sun.security.util.Debug; @@ -70,9 +65,7 @@ final class EntrustTLSPolicy { ); // Any TLS Server certificate that is anchored by one of the Entrust - // roots above and is issued after this date will be distrusted. - private static final LocalDate NOVEMBER_11_2024 = - LocalDate.of(2024, Month.NOVEMBER, 11); + // roots above will be distrusted. /** * This method assumes the eeCert is a TLS Server Cert and chains back to @@ -91,11 +84,10 @@ static void checkDistrust(X509Certificate[] chain) + "trust anchor of TLS server certificate"); } if (FINGERPRINTS.contains(fp)) { - Date notBefore = chain[0].getNotBefore(); - LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), - ZoneOffset.UTC); - // reject if certificate is issued after November 11, 2024 - checkNotBefore(ldNotBefore, NOVEMBER_11_2024, anchor); + throw new ValidatorException + ("TLS Server certificate anchored by a distrusted legacy " + + "Entrust root CA: "+ anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); } } @@ -103,17 +95,5 @@ private static String fingerprint(X509Certificate cert) { return X509CertImpl.getFingerprint("SHA-256", cert, debug); } - private static void checkNotBefore(LocalDate notBeforeDate, - LocalDate distrustDate, X509Certificate anchor) - throws ValidatorException { - if (notBeforeDate.isAfter(distrustDate)) { - throw new ValidatorException - ("TLS Server certificate issued after " + distrustDate + - " and anchored by a distrusted legacy Entrust root CA: " - + anchor.getSubjectX500Principal(), - ValidatorException.T_UNTRUSTED_CERT, anchor); - } - } - private EntrustTLSPolicy() {} } diff --git a/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java index a481a05c752b..01c090e7eb9f 100644 --- a/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/SymantecTLSPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,11 +25,6 @@ package sun.security.validator; import java.security.cert.X509Certificate; -import java.time.LocalDate; -import java.time.Month; -import java.time.ZoneOffset; -import java.util.Date; -import java.util.Map; import java.util.Set; import sun.security.util.Debug; @@ -123,28 +118,8 @@ final class SymantecTLSPolicy { "2399561127A57125DE8CEFEA610DDF2FA078B5C8067F4E828290BFB860E84B3C" ); - private static final LocalDate DECEMBER_31_2019 = - LocalDate.of(2019, Month.DECEMBER, 31); - // SHA-256 certificate fingerprints of subCAs with later distrust dates - private static final Map EXEMPT_SUBCAS = Map.of( - // Subject DN: C=US, O=Apple Inc., OU=Certification Authority, - // CN=Apple IST CA 2 - G1 - // Issuer DN: CN=GeoTrust Global CA, O=GeoTrust Inc., C=US - "AC2B922ECFD5E01711772FEA8ED372DE9D1E2245FCE3F57A9CDBEC77296A424B", - DECEMBER_31_2019, - // Subject DN: C=US, O=Apple Inc., OU=Certification Authority, - // CN=Apple IST CA 8 - G1 - // Issuer DN: CN=GeoTrust Primary Certification Authority - G2, - // OU=(c) 2007 GeoTrust Inc. - For authorized use only, - // O=GeoTrust Inc., C=US - "A4FE7C7F15155F3F0AEF7AAA83CF6E06DEB97CA3F909DF920AC1490882D488ED", - DECEMBER_31_2019 - ); - // Any TLS Server certificate that is anchored by one of the Symantec - // roots above and is issued after this date will be distrusted. - private static final LocalDate APRIL_16_2019 = - LocalDate.of(2019, Month.APRIL, 16); + // roots above will be distrusted. /** * This method assumes the eeCert is a TLS Server Cert and chains back to @@ -163,26 +138,10 @@ static void checkDistrust(X509Certificate[] chain) + "trust anchor of TLS server certificate"); } if (FINGERPRINTS.contains(fp)) { - Date notBefore = chain[0].getNotBefore(); - LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), - ZoneOffset.UTC); - // check if chain goes through one of the subCAs - if (chain.length > 2) { - X509Certificate subCA = chain[chain.length-2]; - fp = fingerprint(subCA); - if (fp == null) { - throw new ValidatorException("Cannot generate fingerprint " - + "for intermediate CA of TLS server certificate"); - } - LocalDate distrustDate = EXEMPT_SUBCAS.get(fp); - if (distrustDate != null) { - // reject if certificate is issued after specified date - checkNotBefore(ldNotBefore, distrustDate, anchor); - return; // success - } - } - // reject if certificate is issued after April 16, 2019 - checkNotBefore(ldNotBefore, APRIL_16_2019, anchor); + throw new ValidatorException( + "TLS Server certificate anchored by a distrusted legacy " + + "Symantec root CA: " +anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); } } @@ -190,17 +149,5 @@ private static String fingerprint(X509Certificate cert) { return X509CertImpl.getFingerprint("SHA-256", cert, debug); } - private static void checkNotBefore(LocalDate notBeforeDate, - LocalDate distrustDate, X509Certificate anchor) - throws ValidatorException { - if (notBeforeDate.isAfter(distrustDate)) { - throw new ValidatorException - ("TLS Server certificate issued after " + distrustDate + - " and anchored by a distrusted legacy Symantec root CA: " - + anchor.getSubjectX500Principal(), - ValidatorException.T_UNTRUSTED_CERT, anchor); - } - } - private SymantecTLSPolicy() {} } diff --git a/src/java.base/share/classes/sun/security/x509/IPAddressName.java b/src/java.base/share/classes/sun/security/x509/IPAddressName.java index f372d349a573..7a26fe3c4a23 100644 --- a/src/java.base/share/classes/sun/security/x509/IPAddressName.java +++ b/src/java.base/share/classes/sun/security/x509/IPAddressName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ import java.lang.Integer; import java.net.InetAddress; import java.util.Arrays; +import sun.net.util.IPAddressUtil; import sun.security.util.HexDumpEncoder; import sun.security.util.BitArray; import sun.security.util.DerOutputStream; @@ -69,11 +70,12 @@ */ public class IPAddressName implements GeneralNameInterface { private byte[] address; - private final boolean isIPv4; private String name; /** * Create the IPAddressName object from the passed encoded Der value. + *

      + * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4. * * @param derValue the encoded DER IPAddressName. * @exception IOException on error. @@ -84,24 +86,44 @@ public IPAddressName(DerValue derValue) throws IOException { /** * Create the IPAddressName object with the specified octets. + *

      + * A valid address must consist of 4 bytes of address and + * optional 4 bytes of mask, or 16 bytes of address and + * optional 16 bytes of mask. + *

      + * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4. * * @param address the IP address * @throws IOException if address is not a valid IPv4 or IPv6 address */ public IPAddressName(byte[] address) throws IOException { - /* - * A valid address must consist of 4 bytes of address and - * optional 4 bytes of 4 bytes of mask, or 16 bytes of address - * and optional 16 bytes of mask. - */ - if (address.length == 4 || address.length == 8) { - isIPv4 = true; - } else if (address.length == 16 || address.length == 32) { - isIPv4 = false; - } else { - throw new IOException("Invalid IPAddressName"); - } - this.address = address; + // Treat IPv4-Mapped IPv6 address as a regular IPv4. + this.address = switch (address.length) { + case 4, 8 -> address; // IPv4 address/subnet + case 16 -> { + // Single IPv6 address. + byte[] newAddr = IPAddressUtil.convertFromIPv4MappedAddress( + address); + yield newAddr != null ? newAddr : address; + } + case 32 -> { + // IPv6 subnet. + byte[] newAddr = IPAddressUtil.convertFromIPv4MappedAddress( + Arrays.copyOfRange(address, 0, 16)); + + if (newAddr == null) { + yield address; + } + + byte[] ret = new byte[8]; + System.arraycopy(newAddr, 0, ret, 0, 4); + System.arraycopy(ipv4MaskFromIpv6Mask( + Arrays.copyOfRange(address, 16, 32)), + 0, ret, 4, 4); + yield ret; + } + default -> throw new IOException("Invalid IPAddressName"); + }; } /** @@ -118,6 +140,8 @@ public IPAddressName(byte[] address) throws IOException { * of the address. If /n is used, n is a decimal number indicating how many * of the leftmost contiguous bits of the address comprise the prefix for * this subnet. Internally, a mask value is created using the prefix length. + *

      + * IPv4-Mapped IPv6 addresses/subnets normalized to IPv4. * * @param name String form of IPAddressName * @throws IOException if name can not be converted to a valid IPv4 or IPv6 @@ -137,11 +161,9 @@ public IPAddressName(String name) throws IOException { // Parse name into byte-value address components and optional // prefix parseIPv6(name); - isIPv4 = false; } else if (name.indexOf('.') >= 0) { //name is IPv4: uses dots as value separators parseIPv4(name); - isIPv4 = true; } else { throw new IOException("Invalid IPAddress: " + name); } @@ -150,7 +172,7 @@ public IPAddressName(String name) throws IOException { /** * Parse an IPv4 address. * - * @param name IPv4 address with optional mask values + * @param name IPv4 address with an optional mask in dotted IP notation. * @throws IOException on error */ private void parseIPv4(String name) throws IOException { @@ -178,41 +200,76 @@ private void parseIPv4(String name) throws IOException { /** * Parse an IPv6 address. * - * @param name String IPv6 address with optional / - * If / is present, address[] array will - * be 32 bytes long, otherwise 16. + * @param name IPv6 address with an optional mask in CIDR notation. * @throws IOException on error */ - private static final int MASKSIZE = 16; private void parseIPv6(String name) throws IOException { - int slashNdx = name.indexOf('/'); + if (slashNdx == -1) { address = InetAddress.getByName(name).getAddress(); } else { - address = new byte[32]; byte[] base = InetAddress.getByName (name.substring(0, slashNdx)).getAddress(); - System.arraycopy(base, 0, address, 0, 16); - - // append a mask corresponding to the num of prefix bits specified - int prefixLen = Integer.parseInt(name.substring(slashNdx+1)); - if (prefixLen < 0 || prefixLen > 128) { - throw new IOException("IPv6Address prefix length (" + - prefixLen + ") in out of valid range [0,128]"); + byte[] mask = ipv6MaskFromString(name.substring(slashNdx + 1)); + + if (base.length == 16) { + // IPv6 address. + address = new byte[32]; + System.arraycopy(base, 0, address, 0, 16); + System.arraycopy(mask, 0, address, 16, 16); + } else if (base.length == 4) { + // IPv4-Mapped IPv6 address. + address = new byte[8]; + System.arraycopy(base, 0, address, 0, 4); + System.arraycopy(ipv4MaskFromIpv6Mask(mask), 0, address, 4, 4); + } else { + throw new IOException("Invalid IPAddress subnet: " + name); } + } + } + + private static byte[] ipv6MaskFromString(String ipv6Mask) + throws IOException { + int prefixLen; + + try { + prefixLen = Integer.parseInt(ipv6Mask); + } catch (NumberFormatException e) { + throw new IOException(e); + } + + if (prefixLen < 0 || prefixLen > 128) { + throw new IOException("IPv6Address prefix length (" + + prefixLen + ") is out of valid range [0,128]"); + } - // create new bit array initialized to zeros - BitArray bitArray = new BitArray(MASKSIZE * 8); + // create new bit array initialized to zeros + BitArray bitArray = new BitArray(128); - // set all most significant bits up to prefix length - for (int i = 0; i < prefixLen; i++) - bitArray.set(i, true); - byte[] maskArray = bitArray.toByteArray(); + // set all most significant bits up to prefix length + for (int i = 0; i < prefixLen; i++) { + bitArray.set(i, true); + } + + return bitArray.toByteArray(); + } + + private static byte[] ipv4MaskFromIpv6Mask(byte[] ipv6Mask) + throws IOException { + if (ipv6Mask == null || ipv6Mask.length != 16) { + throw new IOException("IPv6 mask must be 16 bytes"); + } - // copy mask bytes into mask portion of address - System.arraycopy(maskArray, 0, address, MASKSIZE, MASKSIZE); + // For a mapped IPv4 subnet, the first 96 bits must be set. + for (int i = 0; i < 12; i++) { + if ((ipv6Mask[i] & 0xFF) != 0xFF) { + throw new IOException("Not an IPv4-mapped IPv6 mask: first 96" + + " bits are not set"); + } } + + return Arrays.copyOfRange(ipv6Mask, 12, 16); } /** @@ -256,8 +313,8 @@ public String getName() throws IOException { if (name != null) return name; - if (isIPv4) { - //IPv4 address or subdomain + if (address.length == 4 || address.length == 8) { + //IPv4 address or subnet byte[] host = new byte[4]; System.arraycopy(address, 0, host, 0, 4); name = InetAddress.getByAddress(host).getHostAddress(); @@ -268,14 +325,14 @@ public String getName() throws IOException { InetAddress.getByAddress(mask).getHostAddress(); } } else { - //IPv6 address or subdomain + //IPv6 address or subnet byte[] host = new byte[16]; System.arraycopy(address, 0, host, 0, 16); name = InetAddress.getByAddress(host).getHostAddress(); if (address.length == 32) { - // IPv6 subdomain: display prefix length + // IPv6 subnet: display prefix length - // copy subdomain into new array and convert to BitArray + // copy subnet into new array and convert to BitArray byte[] maskBytes = new byte[16]; System.arraycopy(address, 16, maskBytes, 0, 16); BitArray ba = new BitArray(16*8, maskBytes); @@ -289,7 +346,7 @@ public String getName() throws IOException { // Verify remaining bits 0 for (; i < 16*8; i++) { if (ba.get(i)) { - throw new IOException("Invalid IPv6 subdomain - set " + + throw new IOException("Invalid IPv6 subnet - set " + "bit " + i + " not contiguous"); } } diff --git a/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java b/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java index 5b34a7267967..07989fea25b5 100644 --- a/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java +++ b/src/java.base/share/classes/sun/security/x509/NameConstraintsExtension.java @@ -449,7 +449,14 @@ public boolean verify(X509Certificate cert) throws IOException { } } else { if (!hasNameType(altNames, GeneralNameInterface.NAME_DNS)) { - altNames.add(new GeneralName(new DNSName(cn))); + // Replacing wildcard character '*' with 'z' to check + // the domain name template validity. + new DNSName(cn.replace('*', 'z')); + // If the above line doesn't throw, use Der constructor + // which performs no format checks: we want to accept + // DNS name with any wildcard here. + altNames.add(new GeneralName(new DNSName(new DerValue( + DerValue.tag_IA5String, cn)))); } } } catch (IOException ioe) { diff --git a/src/java.base/share/classes/sun/security/x509/URIName.java b/src/java.base/share/classes/sun/security/x509/URIName.java index c6d0d1790569..4a021ccbea59 100644 --- a/src/java.base/share/classes/sun/security/x509/URIName.java +++ b/src/java.base/share/classes/sun/security/x509/URIName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ import java.net.URI; import java.net.URISyntaxException; +import sun.net.util.IPAddressUtil; import sun.security.util.*; /** @@ -131,14 +132,21 @@ public URIName(String name) throws IOException { try { hostDNS = new DNSName(host); } catch (IOException ioe) { - // Not a valid DNSName; see if it is a valid IPv4 - // IPAddressName - try { - hostIP = new IPAddressName(host); - } catch (Exception ioe2) { + // Not a valid DNSName; only accept host as an + // IPv4 literal. + byte[] ipV4 = IPAddressUtil.textToNumericFormatV4(host); + if (ipV4 != null) { + try { + hostIP = new IPAddressName(ipV4); + } catch (IOException ioe2) { + throw new IOException("invalid URI name (host " + + "portion is not a valid DNSName or IPv4 address" + + "):" + name, ioe2); + } + } else { throw new IOException("invalid URI name (host " + - "portion is not a valid DNSName, IPv4 address," + - " or IPv6 address):" + name); + "portion is not a valid DNSName or IPv4 address" + + "):" + name); } } } diff --git a/src/java.base/share/classes/sun/text/RuleBasedBreakIterator.java b/src/java.base/share/classes/sun/text/RuleBasedBreakIterator.java index d2ed0efb29e1..0e7c062ec867 100644 --- a/src/java.base/share/classes/sun/text/RuleBasedBreakIterator.java +++ b/src/java.base/share/classes/sun/text/RuleBasedBreakIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -387,6 +387,26 @@ private void setupTables(String ruleFile, ByteBuffer bb) { int additionalDataLength = bb.getInt(); checksum = bb.getLong(); + // Check the data length + if (stateTableLength < 0 || + backwardsStateTableLength < 0 || + endStatesLength < 0 || + lookaheadStatesLength < 0 || + BMPdataLength < 0 || + nonBMPdataLength < 0 || + additionalDataLength < 0 || + bb.remaining() != + 2L * stateTableLength + + 2L * backwardsStateTableLength + + endStatesLength + + lookaheadStatesLength + + 2L * BMP_INDICES_LENGTH + + BMPdataLength + + 4L * nonBMPdataLength + + additionalDataLength) { + throw new MissingResourceException("Corrupt rule data file", ruleFile, ""); + } + /* Read stateTable[numCategories * numRows] */ stateTable = new short[stateTableLength]; for (int i = 0; i < stateTableLength; i++) { diff --git a/src/java.base/share/classes/sun/util/calendar/ZoneInfoFile.java b/src/java.base/share/classes/sun/util/calendar/ZoneInfoFile.java index c4dfef0fbe83..759384d5fc08 100644 --- a/src/java.base/share/classes/sun/util/calendar/ZoneInfoFile.java +++ b/src/java.base/share/classes/sun/util/calendar/ZoneInfoFile.java @@ -312,8 +312,15 @@ private static void load(DataInputStream dis) throws IOException { /////////////////////////Ser///////////////////////////////// public static ZoneInfo getZoneInfo(DataInput in, String zoneId) throws Exception { byte type = in.readByte(); - // TBD: assert ZRULES: + if (type != (byte)1) { + throw new StreamCorruptedException( + "Not a ZoneRules object: " + type); + } int stdSize = in.readInt(); + if (stdSize < 0 || stdSize > 1024) { + throw new StreamCorruptedException( + "Invalid standard transition count: " + stdSize); + } long[] stdTrans = new long[stdSize]; for (int i = 0; i < stdSize; i++) { stdTrans[i] = readEpochSec(in); @@ -323,6 +330,10 @@ public static ZoneInfo getZoneInfo(DataInput in, String zoneId) throws Exception stdOffsets[i] = readOffset(in); } int savSize = in.readInt(); + if (savSize < 0 || savSize > 1024) { + throw new StreamCorruptedException( + "Invalid saving count: " + savSize); + } long[] savTrans = new long[savSize]; for (int i = 0; i < savSize; i++) { savTrans[i] = readEpochSec(in); @@ -332,6 +343,10 @@ public static ZoneInfo getZoneInfo(DataInput in, String zoneId) throws Exception savOffsets[i] = readOffset(in); } int ruleSize = in.readByte(); + if (ruleSize < 0 || ruleSize > 16) { + throw new StreamCorruptedException( + "Invalid rule size: " + ruleSize); + } ZoneOffsetTransitionRule[] rules = new ZoneOffsetTransitionRule[ruleSize]; for (int i = 0; i < ruleSize; i++) { rules[i] = new ZoneOffsetTransitionRule(in); diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index f16f975f101f..56eb39915da7 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1,4 +1,4 @@ -# + # This is the "master security properties file". # # An alternate java.security properties file may be specified @@ -1457,23 +1457,18 @@ jdk.sasl.disabledMechanisms= # The supported values are: # # SYMANTEC_TLS : Distrust TLS Server certificates anchored by a Symantec -# root CA and issued after April 16, 2019 unless issued by one of the -# following subordinate CAs which have a later distrust date: -# 1. Apple IST CA 2 - G1, SHA-256 fingerprint: -# AC2B922ECFD5E01711772FEA8ED372DE9D1E2245FCE3F57A9CDBEC77296A424B -# Distrust after December 31, 2019. -# 2. Apple IST CA 8 - G1, SHA-256 fingerprint: -# A4FE7C7F15155F3F0AEF7AAA83CF6E06DEB97CA3F909DF920AC1490882D488ED -# Distrust after December 31, 2019. +# root CA. # # ENTRUST_TLS : Distrust TLS Server certificates anchored by -# an Entrust root CA and issued after November 11, 2024. +# an Entrust root CA. # # CAMERFIRMA_TLS : Distrust TLS Server certificates anchored by -# a Camerfirma root CA and issued after April 15, 2025. +# a Camerfirma root CA. # # CHUNGHWA_TLS : Distrust TLS Server certificates anchored by # a Chunghwa root CA and issued after March 17, 2026. +# After April 19, 2027, certificates will be distrusted regardless +# of the date the certificate was issued. # # Leading and trailing whitespace surrounding each value are ignored. # Unknown values are ignored. If the property is commented out or set to the diff --git a/src/java.base/share/data/blockedcertsconverter/blocked.certs.pem b/src/java.base/share/data/blockedcertsconverter/blocked.certs.pem index 4a3b33b03079..59b08d76b8c4 100644 --- a/src/java.base/share/data/blockedcertsconverter/blocked.certs.pem +++ b/src/java.base/share/data/blockedcertsconverter/blocked.certs.pem @@ -747,3 +747,433 @@ lZA8SFd5CTDy3EBUU5UwzXUc5HoIRUxXSPycu3aIBWawg3sCdKiAoikScPAWj0bM 0vmsP/8QSlTOBqO+QFQ6R82BtTvBNU3qbVICV4QObsxib++FAFL56NApPqskg7Vz LfNIAjKabHUcjbuZkmg6jr4BfYW7+oQDHCsYgADjjKGdKz/8U/fP -----END CERTIFICATE----- + +// Subject: CN=EM DV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// a0:24:fd:f4:ba:66:5c:78:5c:e4:a0:20:48:38:58:b7 +-----BEGIN CERTIFICATE----- +MIIDzjCCAragAwIBAgIRAKAk/fS6Zlx4XOSgIEg4WLcwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVkaHJhIEJW +MRswGQYDVQQDDBJFTSBEViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAATnntnASpdzxHNeUREdqFrCvV5kqzdLgOEsvCA47B5PDjuBL8mFnh5nr7BO +L4vJf3SFumWqkR7opORbYtXMA9Vr5nizw4y03JN8okYOI4RTyJKTXSnUT5HhVga2 +PM7atGijggE1MIIBMTAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQU4h4v5xwZOQ5VS2LBICRXNSSKJRQwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDEGA1UdIAQqMCgwCAYGZ4EMAQIBMA0GCysGAQQBg44hAQABMA0GCysGAQQBg44h +AQJkMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FB +QUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEF +BQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBCwUAA4IB +AQBzQ1IoqpdotM6gFrR4Q3YrKifOLeaj5XXunldpVE/vZn2TxC2y0s8oYiLR5ICE +wqVXSB7N6BM1Mp+Nif7Jbhb2IswAfSR4Iik8+B0iRmi7lYoNlycCgHjmsjOmZaxM +QkPmjXAJbG5f4V41OsvJZmS7lI3r0IuvEDVX3GAZ13on6IImSs9ybNpHQ7NCqkMQ +7wH4uAO1dtpFycgJSdHaCOhHjrox2BTYGLdpUrYwY3EOAjlZ8UGgsxs7mG+xquMl +bN1LFURwfnkVMP3T/DBIZ/ys2+lNFyxK3FlArrV4/000uTnVB1lO8oGORYdXoT5V +GUyRCEIDhF5TBUuKF4E3kf1h +-----END CERTIFICATE----- + +// Subject: CN=EM EV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// d8:f8:68:5e:4f:2b:d9:d8:24:02:a8:5a:1c:1e:b7:31 +-----BEGIN CERTIFICATE----- +MIIDzTCCArWgAwIBAgIRANj4aF5PK9nYJAKoWhwetzEwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVkaHJhIEJW +MRswGQYDVQQDDBJFTSBFViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQl26haZNOX2gsQUunqVG5Ie+/qnJ2daKDF6tz+73Wmm7XW/AH0K/vKbrQL +zWTAdn42/9Bdl6gTxaoMbJUw55z18IZkaPE4BkIKpchRhAseJSVA55DktlTDhS7W +SvHoZX2jggE0MIIBMDAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQUPtwg2YlGmId7hz/YgUeyXBXYRwYwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDAGA1UdIAQpMCcwBwYFZ4EMAQEwDQYLKwYBBAGDjiEBAAEwDQYLKwYBBAGDjiEB +AngwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFB +Q2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUF +BzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQELBQADggEB +AJNFuAFW9Gnw/8+kCO4KIlZdnM1Sh2u+8cK0EYAHhXpb5FlGvYkcARigo5oS7VoM +7FOYlTueV6yICZKNAYawZPJPhNCGK+rp3cQBEz6d0n8OZEYZov9lb3ll8ORgSDuf +FVIW6Gagg/UnOamLBTOR8eD5V73SCme/B2cHUDw+m5E8cc+0Ujzz3Cn3ex5JrhXk +2T3s8PoVzEmhTgGboFfcWO1EYhPPrYwdxPYRQIXAXUXhLEOVNL9ASbZJLFI8E1IM +m7NwxVN2s38jWpaCUaaF0tg7hfiy7oe13rzJfHsdqldJVTTUTPWVx8TC4ju0VLet +bAlwhwFT6AP8uIVMGUHNw24= +-----END CERTIFICATE----- + +// Subject: CN=EM OV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// e9:42:23:bd:e9:2f:91:4e:5a:b9:ac:ab:fd:ac:37:10 +-----BEGIN CERTIFICATE----- +MIIDzjCCAragAwIBAgIRAOlCI73pL5FOWrmsq/2sNxAwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVkaHJhIEJW +MRswGQYDVQQDDBJFTSBPViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAARziXqg6zz9yosSQC/68KvE4SErr43ud4rJ7i1tuD+sKAxeBWOK56Ks1xwy +Ko42HN659pxDArndjXPRzGyqsswK7k4hbOl53VGKSvS2JuVoTN1lgDftDIVOTMEv +TPhkbQ2jggE1MIIBMTAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQUm9hlUa/n5yw3rQjuaRnZvzeErgowDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDEGA1UdIAQqMCgwCAYGZ4EMAQICMA0GCysGAQQBg44hAQABMA0GCysGAQQBg44h +AQJuMEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FB +QUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEF +BQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBCwUAA4IB +AQALRoOSvkzRa+tN5Rje9W5GiXd/qsicd9N6Gnh0SWC7VyWH+HycIqAnFCMYZznj +CeGusomFYHE42SHWr85JDIbgzy6y3dO4i9n9w0Bx7yrXWBbDzFS8VgA3+EY4Cg7y +mtoC3RPOJVtaXXPZ+68X804g1Mj+3vCSHj2lNPVgco0OzSIOJ/KMg1R9MLBkiKzb +bERAGZHejY0HBhYd76IINZN8z6bBNah2UdFYxsOisLynPQUkol6gRLum024/FzVl +CCDfnUusrkr+mAfPAvD/Vb8bhpA7jYtepuU+2mIdShj5lLlvAwZphn+ypWycuo2x +0k9mldz2dqI9nhCiY+TTIOOR +-----END CERTIFICATE----- + +// Subject: CN=EM DV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// 91:f3:47:f1:a5:b7:d3:99:62:b9:5e:0e:e4:e2:99:d1 +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIRAJHzR/Glt9OZYrleDuTimdEwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu +YzEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0CAQYFK4EE +ACIDYgAEOnp4YsjETTJN3WkDOmeCwO/u/kBV5ooSP6A3G6O9kZRRTuFypDPjYw8C +vRiAN9nlFJ+bd9uUooHfmHsv6oiLdL3+j9pMEJnubKGVZv4+8OX2sJpVbMyLpQVh +3xsqRB0Bo4IBNTCCATEwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQw +HQYDVR0OBBYEFK5+EFw8KZbBqEcnBVnx4D3U5QrzMA4GA1UdDwEB/wQEAwIBBjAS +BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAxBgNVHSAEKjAoMAgGBmeBDAECATANBgsrBgEEAYOOIQEAATANBgsrBgEEAYOO +IQECZDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9B +QUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYB +BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOC +AQEAr7b6Mx11TIJgVzHt6EH5qGNWeiiqoMrp8E5/k8s+jvupx1n083pZ5XOzUzND +wEUe0W4C4FIGUHNQ4XDwqOw0yK42xqm2dvCKgsko2UvDlT0haZVtTCeWMQOe5J/J +ziaMfZwsZNfZ3aaI6XPtWDZtwY8AaNfyaeCpbtm5GcGnyNWk4jSNpbtpo6NjfXuP +CFOm3rTjJ4zytP7Tso6iMm0L01cYFOlTmoPv4515R2r3SPnKDtijoB/D/vgxzojv +DafgyG9afX8+laO9NbMMz4sqC1vZx3984i/sBNFiugm3Vvdq5OdJRjuGC1QILIs6 +dg/K3i8VQqRpbfaM9kIlMzZ3dQ== +-----END CERTIFICATE----- + +// Subject: CN=EM OV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// 8d:5a:48:11:93:9e:f4:47:d7:71:11:cd:d9:84:9a:e9 +-----BEGIN CERTIFICATE----- +MIIDzzCCAregAwIBAgIRAI1aSBGTnvRH13ERzdmEmukwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu +YzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0CAQYFK4EE +ACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZfjpRIOHv +1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+N7ulaVSf +HAKycO1no4IBNTCCATEwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQw +HQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMA4GA1UdDwEB/wQEAwIBBjAS +BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD +AjAxBgNVHSAEKjAoMAgGBmeBDAECAjANBgsrBgEEAYOOIQEAATANBgsrBgEEAYOO +IQECbjBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9B +QUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYB +BQUHMAGGGGh0dHA6Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOC +AQEAHlbhZpjhkPEAQVhPHRtBDaHqiisVD20pQ1TUeSx3l62Qe7NQ0b7vWQy7DN0i +KOu7BGpP5EnIjvXbz9FhpfPJ4FhKEt3tQ51rqXz2oD8vRaryvExWPobDUz3j7HN6 +OzBsVMIe3PD7AAkpky0x8zMSi/ep5R9OMzybmNptbtppyuB5ytgq+h7lQdRIP+CQ +TaJX3qTgfMqzntYre5GljmEB5hU2/rSn8zoSU2APkJidlZFpghtgbD4I2VRHZvCw +FFr/SrFM9pwrgbJNZ1MtyTSVgI5NlNb0aMfLR+/r6jJt3ROeY2yh2QQ2dx+Yqgdb +uL5ChFWOQU+ErSDnUq7yr+pjBQ== +-----END CERTIFICATE----- + +// Subject: CN=EM OV CS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// d6:a5:ef:61:3b:39:0f:32:ef:9c:df:34:9f:77:28:41 +-----BEGIN CERTIFICATE----- +MIIDxTCCAq2gAwIBAgIRANal72E7OQ8y75zfNJ93KEEwDQYJKoZIhvcNAQELBQAw +ezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV +BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0yNTA1MTQwMDAwMDBaFw0y +ODEyMzEyMzU5NTlaMD8xCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVkaHJhIElu +YzEaMBgGA1UEAwwRRU0gT1YgQ1MgQ0EgLSBHM0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAS58OBjRwKmnKaq3UQ8/WLEG5uksgkkaX15dOdpGhJvuWr7PjTC/TvF9IMG +HyZdwqWENjRzI1EENrKPBPOGI2MGeYDiUD4CUy1lRiNacWWiWaHvSW90G1/e53hy +COS4+u+jggEsMIIBKDAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQU7m9zKY1vv6ezG8OVNPYVVkR4ZrcwDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwMgYDVR0gBCsw +KTAIBgZngQwBBAEwDQYLKwYBBAGDjiEBAAEwDgYMKwYBBAGDjiEBAoFIMEMGA1Ud +HwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0FBQUNlcnRpZmlj +YXRlU2VydmljZXMuY3JsMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYYaHR0 +cDovL29jc3AuY29tb2RvY2EuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBgGbZ+NDkn +2Fr5dCVs/51+6cT5iEPP1stglX4TI2UaNh9LmPdltxXw3crV8gK5L8i+d+ScGibF +y0obtBHNTldXDgLnAHIzY3n+pceyKj7O2ae9CYnyuu5RBifeI3XYKowWE+oXnVGF +Oy0CvCJ/JsdP6aYgoCa9hGnAYicYQQL6MOMQZ5PuhcSM8t4giIzrpgrEkUP93GYE +emDG5Z8BYwKs80lLeQNBnwg/VHJ3iEF/o84RXYwrlrKqJ+33C094o6LNikMJCqOK +/WYHZSR8Xyhf8GXFAj5pZuI6Bcpug2V/E7i42j8OBhc25aNYDqK+B0qmb90sjNUB +g3tJeSIb/599 +-----END CERTIFICATE----- + +// Subject: CN=EM OV CS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// 41:e3:2b:73:ac:18:5d:6c:e0:d7:48:0b:ac:f8:4e:69 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIQQeMrc6wYXWzg10gLrPhOaTANBgkqhkiG9w0BAQsFADB7 +MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD +VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE +AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTI1MDUxNDAwMDAwMFoXDTI4 +MTIzMTIzNTk1OVowPjELMAkGA1UEBhMCTkwxEzARBgNVBAoMCmVNdWRocmEgQlYx +GjAYBgNVBAMMEUVNIE9WIENTIENBIC0gRzNCMHYwEAYHKoZIzj0CAQYFK4EEACID +YgAEqrsPsGZqz88B+tUr/P1MK/LLPDSX3nOVDlgZth/mlQmHQcjEkUObNKBDHK/+ +W/m0wcjorgAB+fTcitgsWfNjnpbNbD1NsM7+wmw9YlF7N3YTcnQ/jordd7EAZPKr +5eBQo4IBLDCCASgwHwYDVR0jBBgwFoAUoBEKIz6W8Qfs4q8p74Klf9AwpLQwHQYD +VR0OBBYEFHn66OzP2w11jaRY1p+27dx8Sd7mMA4GA1UdDwEB/wQEAwIBBjASBgNV +HRMBAf8ECDAGAQH/AgEAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMDIGA1UdIAQrMCkw +CAYGZ4EMAQQBMA0GCysGAQQBg44hAQABMA4GDCsGAQQBg44hAQKBSDBDBgNVHR8E +PDA6MDigNqA0hjJodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9BQUFDZXJ0aWZpY2F0 +ZVNlcnZpY2VzLmNybDA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6 +Ly9vY3NwLmNvbW9kb2NhLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAIh0OzWsdBYhY +7+yxPGyosRL+uUSN6/Mrd30fBuougs5FVWQ3cRgLhImnUUgXADY9buugWhMmfT3c +yU5T1MFXZXClCILm5mfg+oqm/ikUWJl3Ayf2GvYf2O++6vzqvkn418nI5Lpdt0O7 +Li524YS/3CZHDA+zZQ7rrHpZXgQ9Hmp8AUQ4FneaGjNanFoYxiZxwCyW5z4u7AoK +0bmOVqKtcDFoVNz5++G9TYDJdhWOXmKKgzqRo1MK0PjazNT7MGHqknP93SqMVJ1g +mX/qNWxYteAjlvFanQxBNX/+TRxDjuFsEHO/7BvqSpIIck0UEEfizhN8zNpWLbcl +EqVgl0g39A== +-----END CERTIFICATE----- + +// Subject: CN=EM DV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// a4:ae:e6:78:f2:33:62:b4:c0:e4:60:b7:72:f2:14 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIQAKSu5njyM2K0wORgt3LyFDAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTU1 +NDA4WhcNNDAwNDI4MTgzMDAwWjA/MQswCQYDVQQGEwJOTDETMBEGA1UECgwKZU11 +ZGhyYSBCVjEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNCMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAE557ZwEqXc8RzXlERHahawr1eZKs3S4DhLLwgOOweTw47gS/J +hZ4eZ6+wTi+LyX90hbplqpEe6KTkW2LVzAPVa+Z4s8OMtNyTfKJGDiOEU8iSk10p +1E+R4VYGtjzO2rRoo4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFOIeL+ccGTkOVUtiwSAkVzUkiiUUMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAmQwCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYi5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRFZUTFNDQUczQi5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIwLmsGXi6oFMJ2klkcEMXMug6gvxQWZWkewiDWVzQyl2L6z6DuQfPO +Ibh1grvdULDCAjEAxZvLlxUavG69J6wnSjC8XBbqNIQsJ6CE2QdNOe84Ptxb6VG8 +gt2TlfJbHii+KNvK +-----END CERTIFICATE----- + +// Subject: CN=EM DV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 58:8b:2e:3c:2b:b8:cd:a1:3e:1a:12:09:25:43:21 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIPWIsuPCu4zaE+GhIJJUMhMAoGCCqGSM49BAMDMFYxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM1 +MDlaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk +aHJhIEluYzEbMBkGA1UEAwwSRU0gRFYgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAEOnp4YsjETTJN3WkDOmeCwO/u/kBV5ooSP6A3G6O9kZRRTuFy +pDPjYw8CvRiAN9nlFJ+bd9uUooHfmHsv6oiLdL3+j9pMEJnubKGVZv4+8OX2sJpV +bMyLpQVh3xsqRB0Bo4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFK5+EFw8KZbBqEcnBVnx4D3U5QrzMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAmQwCAYGZ4EMAQIBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRFZUTFNDQUczQy5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIxAMEN2HWMocbuWdy1d33pH6JpMvP/V/DIS5PtZbv1Q7TXEbxrcyxg +yuddOJscYDJJlAIwB4sH1BpbScDzkdD4tJBSwrG/1Mx82vb8YaySehNuu1+1oXNd +AEKwaJ0mVzScy2+6 +-----END CERTIFICATE----- + +// Subject: CN=EM EV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// c7:c4:1e:fe:92:d8:25:f0:5e:ae:84:1a:4a:82:c1 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu2gAwIBAgIQAMfEHv6S2CXwXq6EGkqCwTAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTYw +MzMxWhcNNDAwNDI4MTgzMDAwWjA/MQswCQYDVQQGEwJOTDETMBEGA1UECgwKZU11 +ZGhyYSBCVjEbMBkGA1UEAwwSRU0gRVYgVExTIENBIC0gRzNCMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAEJduoWmTTl9oLEFLp6lRuSHvv6pydnWigxerc/u91ppu11vwB +9Cv7ym60C81kwHZ+Nv/QXZeoE8WqDGyVMOec9fCGZGjxOAZCCqXIUYQLHiUlQOeQ +5LZUw4Uu1krx6GV9o4IBlTCCAZEwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFD7cINmJRpiHe4c/2IFHslwV2EcGMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBcBgNVHSAEVTBTMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAngwBwYFZ4EMAQEwdQYIKwYBBQUHAQEEaTBnMCQGCCsGAQUFBzABhhho +dHRwOi8vb2NzcC1iLmVtc2lnbi5jb20wPwYIKwYBBQUHMAKGM2h0dHA6Ly9yZXBv +c2l0b3J5LmVtc2lnbi5jb20vY2VydHMvRU1FVlRMU0NBRzNCLnA3YzA3BgNVHR8E +MDAuMCygKqAohiZodHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3R0bHNjYWczLmNy +bDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQD +AwNpADBmAjEA9Jq999oaOUKhFwopw33iCmt0OHIn3gjT43GfuOHR/YckXdwoNdDA +lIEhz0NgRO3/AjEAvY/a3OMvktIZLYjn+WLKmp0QhrIMV+e6EpS7j4GVeOKouNp6 +MN/P+ByGeiRMghzB +-----END CERTIFICATE----- + +// Subject: CN=EM EV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 80:09:92:8a:64:10:76:01:af:05:fa:2f:19:f1:09 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIQAIAJkopkEHYBrwX6LxnxCTAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJJTjElMCMGA1UECgwcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl +ZDEgMB4GA1UEAwwXZW1TaWduIFJvb3QgVExTIENBIC0gRzMwHhcNMjUwNDMwMTUz +OTMxWhcNNDAwNDI4MTgzMDAwWjBAMQswCQYDVQQGEwJVUzEUMBIGA1UECgwLZU11 +ZGhyYSBJbmMxGzAZBgNVBAMMEkVNIEVWIFRMUyBDQSAtIEczQzB2MBAGByqGSM49 +AgEGBSuBBAAiA2IABC1W6Cmm+4LGSkaX2WCvlR6QZ66R+SbTQwbaSKiq63hptqZ+ +bSdCbgRCqgOzbhmI3/Y2UU7hzwf1uks/8qBmRa59MdWGatMao9JG6wsxd+6Zy3Vm +xRbOdICKZo+R8MR/yqOCAZUwggGRMB8GA1UdIwQYMBaAFOE6qyF88eNNVK7Ucrf9 +i1lCIJBUMB0GA1UdDgQWBBSmJV6AqU2385Z8G8eR35K87kvrXzAdBgNVHSUEFjAU +BggrBgEFBQcDAQYIKwYBBQUHAwIwXAYDVR0gBFUwUzA5BgsrBgEEAYOOIQEAATAq +MCgGCCsGAQUFBwIBFhxodHRwOi8vcmVwb3NpdG9yeS5lbXNpZ24uY29tMA0GCysG +AQQBg44hAQJ4MAcGBWeBDAEBMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNRVZUTFNDQUczQy5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIwBy1wTnzH4lXDTy2PD91ysLjvnlVN3JbJmc1BIMEU8TOLkYVHibMc +9HeyonikFSfmAjEA6uE3QRxlrZTz/1/lXPTEMUhoauAtycagtlNFp79kjhvNa2/f +Aal1GNxsOutLnr0Y +-----END CERTIFICATE----- + +// Subject: CN=EM OV CS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=emSign Root CS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 78:18:93:3f:8f:85:b4:61:9a:2b:35:fb:a8:3d:0a +-----BEGIN CERTIFICATE----- +MIIDWzCCAuCgAwIBAgIPeBiTP4+FtGGaKzX7qD0KMAoGCCqGSM49BAMDMFUxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MR8wHQYDVQQDDBZlbVNpZ24gUm9vdCBDUyBDQSAtIEczMB4XDTI1MDQzMDE2MDgy +N1oXDTQwMDQyODE4MzAwMFowPjELMAkGA1UEBhMCTkwxEzARBgNVBAoMCmVNdWRo +cmEgQlYxGjAYBgNVBAMMEUVNIE9WIENTIENBIC0gRzNCMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAEqrsPsGZqz88B+tUr/P1MK/LLPDSX3nOVDlgZth/mlQmHQcjEkUOb +NKBDHK/+W/m0wcjorgAB+fTcitgsWfNjnpbNbD1NsM7+wmw9YlF7N3YTcnQ/jord +d7EAZPKr5eBQo4IBizCCAYcwHwYDVR0jBBgwFoAUaHQD+IXKjVRQVx0W4MjeRQ2r +zS0wHQYDVR0OBBYEFHn66OzP2w11jaRY1p+27dx8Sd7mMBMGA1UdJQQMMAoGCCsG +AQUFBwMDMF4GA1UdIARXMFUwOQYLKwYBBAGDjiEBAAEwKjAoBggrBgEFBQcCARYc +aHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbTAOBgwrBgEEAYOOIQECgUgwCAYG +Z4EMAQQBMHQGCCsGAQUFBwEBBGgwZjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3At +Yi5lbXNpZ24uY29tMD4GCCsGAQUFBzAChjJodHRwOi8vcmVwb3NpdG9yeS5lbXNp +Z24uY29tL2NlcnRzL0VNT1ZDU0NBRzNCLnA3YzA2BgNVHR8ELzAtMCugKaAnhiVo +dHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3Rjc2NhZzMuY3JsMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMAoGCCqGSM49BAMDA2kAMGYCMQC/zr03 +YEELsNKqzUtH2dVFi4BaKFdyrr/6G1dIWp8KAVgsw2/u4lyyboUDaC9jDvICMQC9 +HTxku84tNX9dmHOtH7aC2Nu/JM0jTf6KjZAvovCJzzv1IFWSZpVLZibynMLXOrM= +-----END CERTIFICATE----- + +// Subject: CN=EM OV CS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=emSign Root CS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 54:4b:7f:92:2c:31:ae:22:0a:4a:92:29:16:f6:e6 +-----BEGIN CERTIFICATE----- +MIIDWzCCAuGgAwIBAgIPVEt/kiwxriIKSpIpFvbmMAoGCCqGSM49BAMDMFUxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MR8wHQYDVQQDDBZlbVNpZ24gUm9vdCBDUyBDQSAtIEczMB4XDTI1MDQzMDE2Mzg0 +MFoXDTQwMDQyODE4MzAwMFowPzELMAkGA1UEBhMCVVMxFDASBgNVBAoMC2VNdWRo +cmEgSW5jMRowGAYDVQQDDBFFTSBPViBDUyBDQSAtIEczQzB2MBAGByqGSM49AgEG +BSuBBAAiA2IABLnw4GNHAqacpqrdRDz9YsQbm6SyCSRpfXl052kaEm+5avs+NML9 +O8X0gwYfJl3CpYQ2NHMjUQQ2so8E84YjYwZ5gOJQPgJTLWVGI1pxZaJZoe9Jb3Qb +X97neHII5Lj676OCAYswggGHMB8GA1UdIwQYMBaAFGh0A/iFyo1UUFcdFuDI3kUN +q80tMB0GA1UdDgQWBBTub3MpjW+/p7Mbw5U09hVWRHhmtzATBgNVHSUEDDAKBggr +BgEFBQcDAzBeBgNVHSAEVzBVMDkGCysGAQQBg44hAQABMCowKAYIKwYBBQUHAgEW +HGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDgYMKwYBBAGDjiEBAoFIMAgG +BmeBDAEEATB0BggrBgEFBQcBAQRoMGYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw +LWMuZW1zaWduLmNvbTA+BggrBgEFBQcwAoYyaHR0cDovL3JlcG9zaXRvcnkuZW1z +aWduLmNvbS9jZXJ0cy9FTU9WQ1NDQUczQy5wN2MwNgYDVR0fBC8wLTAroCmgJ4Yl +aHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290Y3NjYWczLmNybDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQDAwNoADBlAjEA8yPg +8sntOhLwV2QXJ5LEaKSpo/fnE3uOgTadK73fDhyjiqr0Io9GVuQTYqBPbQ2TAjBg +euApmBS486+iei03lRrp139dPw6k28jjGNvWsAU7A1N86J1zPJVDEyVH+nMAWC0= +-----END CERTIFICATE----- + +// Subject: CN=EM OV TLS CA - G3B, O=eMudhra BV, C=NL +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 6c:ad:a3:67:fb:1c:10:00:88:12:0e:a2:02:d5:68 +-----BEGIN CERTIFICATE----- +MIIDZjCCAu2gAwIBAgIPbK2jZ/scEACIEg6iAtVoMAoGCCqGSM49BAMDMFYxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNjAw +MTJaFw00MDA0MjgxODMwMDBaMD8xCzAJBgNVBAYTAk5MMRMwEQYDVQQKDAplTXVk +aHJhIEJWMRswGQYDVQQDDBJFTSBPViBUTFMgQ0EgLSBHM0IwdjAQBgcqhkjOPQIB +BgUrgQQAIgNiAARziXqg6zz9yosSQC/68KvE4SErr43ud4rJ7i1tuD+sKAxeBWOK +56Ks1xwyKo42HN659pxDArndjXPRzGyqsswK7k4hbOl53VGKSvS2JuVoTN1lgDft +DIVOTMEvTPhkbQ2jggGWMIIBkjAfBgNVHSMEGDAWgBThOqshfPHjTVSu1HK3/YtZ +QiCQVDAdBgNVHQ4EFgQUm9hlUa/n5yw3rQjuaRnZvzeErgowHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMF0GA1UdIARWMFQwOQYLKwYBBAGDjiEBAAEwKjAo +BggrBgEFBQcCARYcaHR0cDovL3JlcG9zaXRvcnkuZW1zaWduLmNvbTANBgsrBgEE +AYOOIQECbjAIBgZngQwBAgIwdQYIKwYBBQUHAQEEaTBnMCQGCCsGAQUFBzABhhho +dHRwOi8vb2NzcC1iLmVtc2lnbi5jb20wPwYIKwYBBQUHMAKGM2h0dHA6Ly9yZXBv +c2l0b3J5LmVtc2lnbi5jb20vY2VydHMvRU1PVlRMU0NBRzNCLnA3YzA3BgNVHR8E +MDAuMCygKqAohiZodHRwOi8vY3JsLmVtc2lnbi5jb20vP3Jvb3R0bHNjYWczLmNy +bDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAKBggqhkjOPQQD +AwNnADBkAjBAk+5BizdJhrED3wb7F2Gm7bWI60dlNd075nghT2TrR4NWKZtJZcbc +28xxIY/NONICMF6mPdsjosaqmvEMWqKcBh4vsIvXSKnQj5o8zrFB4oKS+accjYTe +8Dz3bQjauvy9uw== +-----END CERTIFICATE----- + +// Subject: CN=EM OV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=emSign Root TLS CA - G3, O=eMudhra Technologies Limited, C=IN +// Serial Number: +// 29:69:26:c2:27:a3:a1:52:63:92:b1:94:82:a5:04 +-----BEGIN CERTIFICATE----- +MIIDaDCCAu6gAwIBAgIPKWkmwiejoVJjkrGUgqUEMAoGCCqGSM49BAMDMFYxCzAJ +BgNVBAYTAklOMSUwIwYDVQQKDBxlTXVkaHJhIFRlY2hub2xvZ2llcyBMaW1pdGVk +MSAwHgYDVQQDDBdlbVNpZ24gUm9vdCBUTFMgQ0EgLSBHMzAeFw0yNTA0MzAxNTM3 +NDFaFw00MDA0MjgxODMwMDBaMEAxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtlTXVk +aHJhIEluYzEbMBkGA1UEAwwSRU0gT1YgVExTIENBIC0gRzNDMHYwEAYHKoZIzj0C +AQYFK4EEACIDYgAEE4JRzVKskpjByKrZdzId65fnCb0LTKCspV3IrVHc/J0ViaFZ +fjpRIOHv1jHGPhg1ysrimGmmLhYx6KKBgatWYW3EXZGKvAnzqw5jz3kqpNztc4e+ +N7ulaVSfHAKycO1no4IBljCCAZIwHwYDVR0jBBgwFoAU4TqrIXzx401UrtRyt/2L +WUIgkFQwHQYDVR0OBBYEFDTq6gzryQra+dDF4u2beMmHnhtsMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjBdBgNVHSAEVjBUMDkGCysGAQQBg44hAQABMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9yZXBvc2l0b3J5LmVtc2lnbi5jb20wDQYLKwYB +BAGDjiEBAm4wCAYGZ4EMAQICMHUGCCsGAQUFBwEBBGkwZzAkBggrBgEFBQcwAYYY +aHR0cDovL29jc3AtYy5lbXNpZ24uY29tMD8GCCsGAQUFBzAChjNodHRwOi8vcmVw +b3NpdG9yeS5lbXNpZ24uY29tL2NlcnRzL0VNT1ZUTFNDQUczQy5wN2MwNwYDVR0f +BDAwLjAsoCqgKIYmaHR0cDovL2NybC5lbXNpZ24uY29tLz9yb290dGxzY2FnMy5j +cmwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwCgYIKoZIzj0E +AwMDaAAwZQIxAMSZt2xsPeumRdeSMSQ3CbzIZeQ/q7GDCWfeE1Pq+mhgA6gRM44c +NVF3xJ+wsxJk+wIwFJbaDMokD8Ei6eNOiA8yUMYvfzQyngmrhZQMdPCK1SewrR79 +4rSb9vGqxZzelQth +-----END CERTIFICATE----- + +// Subject: CN=EM EV TLS CA - G3C, O=eMudhra Inc, C=US +// Issuer: CN=AAA Certificate Services, O=Comodo CA Limited, L=Salford, ST=Greater Manchester, C=GB +// Serial Number: +// 05:f0:78:75:2d:ec:e7:a1:bb:85:95:d3:03:16:71:71 +-----BEGIN CERTIFICATE----- +MIIDzTCCArWgAwIBAgIQBfB4dS3s56G7hZXTAxZxcTANBgkqhkiG9w0BAQsFADB7 +MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD +VQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UE +AwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTI1MDUxNDAwMDAwMFoXDTI4 +MTIzMTIzNTk1OVowQDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC2VNdWRocmEgSW5j +MRswGQYDVQQDDBJFTSBFViBUTFMgQ0EgLSBHM0MwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQtVugppvuCxkpGl9lgr5UekGeukfkm00MG2kioqut4abamfm0nQm4EQqoD +s24ZiN/2NlFO4c8H9bpLP/KgZkWufTHVhmrTGqPSRusLMXfumct1ZsUWznSAimaP +kfDEf8qjggE0MIIBMDAfBgNVHSMEGDAWgBSgEQojPpbxB+zirynvgqV/0DCktDAd +BgNVHQ4EFgQUpiVegKlNt/OWfBvHkd+SvO5L618wDgYDVR0PAQH/BAQDAgEGMBIG +A1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC +MDAGA1UdIAQpMCcwBwYFZ4EMAQEwDQYLKwYBBAGDjiEBAAEwDQYLKwYBBAGDjiEB +AngwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFB +Q2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUF +BzABhhhodHRwOi8vb2NzcC5jb21vZG9jYS5jb20wDQYJKoZIhvcNAQELBQADggEB +AIFfHiaMSjnrdZygRYV4ekRBmG7ybcFk4hGQWMMyMFi0cAEk2+dGfz5bl8IaIbLq +z+uVJzhOgVEX7WqIfgbWfFsfPYHYjPHBmy7SlZ3BwA86IQ6qF/etpX2/qL9A5yRN +N3nVowUpaqV6H3aCpkAh3wR2ZNkvAbLDvPz3J0l+sEWjWv+ZISOV4R+hXDTdPXV0 +1lKxnbwqEsdo/sE2QkCDKwFrZh/0uKjWspydJj+jSaDLTrGNx50Dgmn7VEcD1ZI5 +FXjknVUiO25YlmS1dtVZSBbyUKSjlrP/yDwPcZunoAsmCAAtCKsFLviLaymzvsKI +eSSDGxpSySp2VCxompGDk8g= +-----END CERTIFICATE----- diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 25a98eab5971..ecf042234f69 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -2933,6 +2933,11 @@ Java HotSpot VM. some throughput cost. Pause times are independent of what heap size is used. Supports heap sizes from 8MB to 16TB. +[`-XX:+ZAdaptiveHeapSizing`]{#-XX__ZAdaptiveHeapSizing} +: Enables Adaptive Heap Sizing for ZGC, which dynamically adjusts the heap + size based on memory availability and system resources. By default, this + option is disabled. + [`-XX:ZAllocationSpikeTolerance=`]{#-XX_ZAllocationSpikeTolerance}*factor* : Sets the allocation spike tolerance for ZGC. By default, this option is set to 2.0. This factor describes the level of allocation spikes to expect. @@ -2949,6 +2954,13 @@ Java HotSpot VM. heap to be compacted more aggressively, to reclaim more memory at the cost of using more CPU time. +[`-XX:ZGCIntensity=`]{#-XX_ZGCIntensity}*intensity* +: Configures the trade-off between memory and CPU usage on the system when + Adaptive Heap Sizing is enabled with `-XX:+ZAdaptiveHeapSizing`. When + enabled, the default value is 5, striking a balance between memory and CPU + that is reasonable for most systems. When Adaptive Heap Sizing is disabled, + the effective value is 0. + [`-XX:+ZProactive`]{#-XX__ZProactive} : Enables proactive GC cycles when using ZGC. By default, this option is enabled. ZGC will start a proactive GC cycle if doing so is expected to @@ -3031,6 +3043,9 @@ they're used. : Enables the use of Java Flight Recorder (JFR) during the runtime of the application. Since JDK 8u40 this option has not been required to use JFR. +[`-XX:AOTMode=on`]{#-XX__AOTModeOn} +: Requires the AOTCache to be loaded. Use `-XX:AOTMode=required` instead. + ## Obsolete Java Options These `java` options are still accepted but ignored, and a warning is issued @@ -4165,8 +4180,8 @@ The AOT cache can be used with the following command-line options: : Specifies the location of the AOT cache. The standard extension for *cachefile* is `.aot`. This option cannot be used together with `-XX:AOTCacheOutput`. - This option is compatible with `AOTMode` settings of `on`, `create`, or `auto` (the default). - The *cachefile* is read in AOT modes `on` and `auto`, and is ignored by mode `off`. + This option is compatible with `AOTMode` settings of `required`, `create`, or `auto` (the default). + The *cachefile* is read in AOT modes `required` and `auto`, and is ignored by mode `off`. The *cachefile* is written by AOT mode `create`. In that case, this option is equivalent to `-XX:AOTCacheOutput=`*cachefile*. @@ -4186,11 +4201,7 @@ The AOT cache can be used with the following command-line options: [`-XX:AOTMode=`]{#-XX_AOTMode}*mode* : Specifies the AOT Mode for this run. - *mode* must be one of the following: `auto`, `off`, `record`, `create`, `on`, or `required`. - - Note that `on` is an alias for `required`. All discussion below about `required` - also applies to `on`. The use of `on` is discouraged. The support of `on` may be deprecated and - removed in future releases. + *mode* must be one of the following: `auto`, `off`, `record`, `create`, or `required`. - `auto`: This AOT mode is the default, and takes effect if no `-XX:AOTMode` option is present. It automatically sets the AOT mode to `record`, `required`, or `off`, as follows: diff --git a/src/java.base/share/native/libnet/net_util.h b/src/java.base/share/native/libnet/net_util.h index 92b812b18686..8983e51eb64f 100644 --- a/src/java.base/share/native/libnet/net_util.h +++ b/src/java.base/share/native/libnet/net_util.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -82,14 +82,6 @@ extern jfieldID ni_addrsID; extern jfieldID ni_descID; extern jmethodID ni_ctrID; -/* DatagramPacket fields */ -extern jfieldID dp_addressID; -extern jfieldID dp_portID; -extern jfieldID dp_bufID; -extern jfieldID dp_offsetID; -extern jfieldID dp_lengthID; -extern jfieldID dp_bufLengthID; - /* Inet6Address fields */ extern jclass ia6_class; extern jfieldID ia6_holder6ID; @@ -172,9 +164,6 @@ NET_SetSockOpt(int fd, int level, int opt, const void *arg, int len); JNIEXPORT int JNICALL NET_Bind(int fd, SOCKETADDRESS *sa, int len); -JNIEXPORT jint JNICALL -NET_EnableFastTcpLoopback(int fd); - unsigned short in_cksum(unsigned short *addr, int len); jint NET_Wait(JNIEnv *env, jint fd, jint flags, jint timeout); diff --git a/src/java.base/share/native/libzip/zlib/gzwrite.c b/src/java.base/share/native/libzip/zlib/gzwrite.c index b11c318d5432..5719bbffe00a 100644 --- a/src/java.base/share/native/libzip/zlib/gzwrite.c +++ b/src/java.base/share/native/libzip/zlib/gzwrite.c @@ -266,8 +266,11 @@ local z_size_t gz_write(gz_statep state, voidpc buf, z_size_t len) { n -= state->strm.avail_in; state->x.pos += n; len -= n; - if (ret == -1) + if (ret == -1) { + state->strm.avail_in = 0; + state->strm.next_in = state->in; return state->again ? put - len : 0; + } } while (len); } @@ -407,7 +410,8 @@ local int gz_vacate(gz_statep state) { z_streamp strm; strm = &(state->strm); - if (strm->next_in + strm->avail_in <= state->in + state->size) + if (strm->next_in == NULL || + strm->next_in + strm->avail_in <= state->in + state->size) return 0; (void)gz_comp(state, Z_NO_FLUSH); if (strm->avail_in == 0) { @@ -467,6 +471,7 @@ int ZEXPORTVA gzvprintf(gzFile file, const char *format, va_list va) { a Z_BUF_ERROR to let the application know that this gzprintf() needs to be retried. */ gz_error(state, Z_BUF_ERROR, "stalled write on gzprintf"); + return state->err; } if (!state->again) return state->err; diff --git a/src/java.base/unix/native/libnet/net_util_md.c b/src/java.base/unix/native/libnet/net_util_md.c index d629c697ffe2..45fb2c172b73 100644 --- a/src/java.base/unix/native/libnet/net_util_md.c +++ b/src/java.base/unix/native/libnet/net_util_md.c @@ -33,7 +33,6 @@ #if defined(__linux__) #include #include -#include #endif #if defined(MACOSX) @@ -234,11 +233,6 @@ void NET_ThrowUnknownHostExceptionWithGaiError(JNIEnv *env, } } -JNIEXPORT jint JNICALL -NET_EnableFastTcpLoopback(int fd) { - return 0; -} - /** * See net_util.h for documentation */ @@ -594,29 +588,12 @@ NET_SetSockOpt(int fd, int level, int opt, const void *arg, /* * Wrapper for bind system call - performs any necessary pre/post * processing to deal with OS specific issues :- - * - * Linux allows a socket to bind to 127.0.0.255 which must be - * caught. */ int NET_Bind(int fd, SOCKETADDRESS *sa, int len) { int rv; -#ifdef __linux__ - /* - * ## get bugId for this issue - goes back to 1.2.2 port ## - * ## When IPv6 is enabled this will be an IPv4-mapped - * ## with family set to AF_INET6 - */ - if (sa->sa.sa_family == AF_INET) { - if ((ntohl(sa->sa4.sin_addr.s_addr) & 0x7f0000ff) == 0x7f0000ff) { - errno = EADDRNOTAVAIL; - return -1; - } - } -#endif - rv = bind(fd, &sa->sa, len); return rv; diff --git a/src/java.base/unix/native/libnet/net_util_md.h b/src/java.base/unix/native/libnet/net_util_md.h index 639cf00515fa..18c563f98a4e 100644 --- a/src/java.base/unix/native/libnet/net_util_md.h +++ b/src/java.base/unix/native/libnet/net_util_md.h @@ -54,17 +54,6 @@ #endif #endif -/* - * On 64-bit JDKs we use a much larger stack and heap buffer. - */ -#ifdef _LP64 -#define MAX_BUFFER_LEN 65536 -#define MAX_HEAP_BUFFER_LEN 131072 -#else -#define MAX_BUFFER_LEN 8192 -#define MAX_HEAP_BUFFER_LEN 65536 -#endif - typedef union { struct sockaddr sa; struct sockaddr_in sa4; diff --git a/src/java.base/unix/native/libnio/ch/Net.c b/src/java.base/unix/native/libnio/ch/Net.c index fd93f7c28967..948646fb80c3 100644 --- a/src/java.base/unix/native/libnio/ch/Net.c +++ b/src/java.base/unix/native/libnio/ch/Net.c @@ -42,11 +42,6 @@ #include "nio_util.h" #include "nio.h" -#ifdef _AIX -#include -#include -#endif - /** * IP_MULTICAST_ALL supported since 2.6.31 but may not be available at * build time. @@ -234,7 +229,7 @@ Java_sun_nio_ch_Net_canUseIPv6OptionsWithIPv4LocalAddress0(JNIEnv* env, jclass c JNIEXPORT jint JNICALL Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6, - jboolean stream, jboolean reuse, jboolean ignored) + jboolean stream, jboolean reuse) { int fd; int type = (stream ? SOCK_STREAM : SOCK_DGRAM); diff --git a/src/java.base/windows/native/libnet/Inet4AddressImpl.c b/src/java.base/windows/native/libnet/Inet4AddressImpl.c index 91b307375006..8ea34cfbd4b6 100644 --- a/src/java.base/windows/native/libnet/Inet4AddressImpl.c +++ b/src/java.base/windows/native/libnet/Inet4AddressImpl.c @@ -24,6 +24,9 @@ */ #include #include +#include +#include +#include #include "net_util.h" diff --git a/src/java.base/windows/native/libnet/Inet6AddressImpl.c b/src/java.base/windows/native/libnet/Inet6AddressImpl.c index 812693299ba2..029d2c588e90 100644 --- a/src/java.base/windows/native/libnet/Inet6AddressImpl.c +++ b/src/java.base/windows/native/libnet/Inet6AddressImpl.c @@ -24,6 +24,9 @@ */ #include #include +#include +#include +#include #include "net_util.h" diff --git a/src/java.base/windows/native/libnet/NetworkInterface.c b/src/java.base/windows/native/libnet/NetworkInterface.c index 838921567c3b..b166c1cc4cff 100644 --- a/src/java.base/windows/native/libnet/NetworkInterface.c +++ b/src/java.base/windows/native/libnet/NetworkInterface.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,9 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +#include +#include +#include #include "net_util.h" #include "NetworkInterface.h" diff --git a/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c b/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c index f3bc27f67ca3..c195b4151714 100644 --- a/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c +++ b/src/java.base/windows/native/libnet/ResolverConfigurationImpl.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include "net_util.h" #include "jni_util.h" @@ -36,14 +38,6 @@ #define BUFF_SIZE 15360 #define MAX_TRIES 3 -#define STS_NO_CONFIG 0x0 /* no configuration found */ -#define STS_SL_FOUND 0x1 /* search list found */ -#define STS_NS_FOUND 0x2 /* name servers found */ -#define STS_ERROR -1 /* error return lodConfig failed memory allccation failure*/ - -#define IS_SL_FOUND(sts) (sts & STS_SL_FOUND) -#define IS_NS_FOUND(sts) (sts & STS_NS_FOUND) - /* JNI ids */ static jfieldID searchlistID; static jfieldID nameserversID; @@ -148,7 +142,7 @@ void strappend(char *s1, char *s2) { * Use DNS server addresses returned by GetAdaptersAddresses for currently * active interfaces. */ -static int loadConfig(JNIEnv *env, char *sl, char *ns) { +static BOOL loadConfig(JNIEnv *env, char *sl, char *ns) { IP_ADAPTER_ADDRESSES *adapters, *adapter; IP_ADAPTER_DNS_SERVER_ADDRESS *dnsServer; WCHAR *suffix; @@ -179,7 +173,7 @@ static int loadConfig(JNIEnv *env, char *sl, char *ns) { ret = getAdapters(env, flags, &adapters); if (ret != ERROR_SUCCESS) { - return STS_ERROR; + return FALSE; } adapter = adapters; @@ -227,7 +221,7 @@ static int loadConfig(JNIEnv *env, char *sl, char *ns) { free(adapters); - return STS_SL_FOUND & STS_NS_FOUND; + return TRUE; } @@ -242,6 +236,7 @@ Java_sun_net_dns_ResolverConfigurationImpl_init0(JNIEnv *env, jclass cls) CHECK_NULL(searchlistID); nameserversID = (*env)->GetStaticFieldID(env, cls, "os_nameservers", "Ljava/lang/String;"); + CHECK_NULL(nameserversID); } /* @@ -259,7 +254,7 @@ Java_sun_net_dns_ResolverConfigurationImpl_loadDNSconfig0(JNIEnv *env, jclass cl searchlist[0] = '\0'; nameservers[0] = '\0'; - if (loadConfig(env, searchlist, nameservers) != STS_ERROR) { + if (loadConfig(env, searchlist, nameservers) == TRUE) { /* * Populate static fields in sun.net.DefaultResolverConfiguration diff --git a/src/java.base/windows/native/libnet/net_util_md.c b/src/java.base/windows/native/libnet/net_util_md.c index 5abdd8d4c2e3..52d5003b5063 100644 --- a/src/java.base/windows/native/libnet/net_util_md.c +++ b/src/java.base/windows/native/libnet/net_util_md.c @@ -22,14 +22,13 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ +#include +#include #include "net_util.h" #include "java_net_InetAddress.h" #include "java_net_SocketOptions.h" -// Taken from mstcpip.h in Windows SDK 8.0 or newer. -#define SIO_LOOPBACK_FAST_PATH _WSAIOW(IOC_VENDOR,16) - #ifndef IPTOS_TOS_MASK #define IPTOS_TOS_MASK 0x1e #endif @@ -37,9 +36,6 @@ #define IPTOS_PREC_MASK 0xe0 #endif -/* true if SO_RCVTIMEO is supported */ -jboolean isRcvTimeoutSupported = JNI_TRUE; - /* * Table of Windows Sockets errors, the specific exception we * throw for the error, and the error text. @@ -287,17 +283,7 @@ JNIEXPORT int JNICALL NET_GetSockOpt(int s, int level, int optname, void *optval, int *optlen) { - int rv; - - if (level == IPPROTO_IPV6 && optname == IPV6_TCLASS) { - int *intopt = (int *)optval; - *intopt = 0; - *optlen = sizeof(*intopt); - return 0; - } - - rv = getsockopt(s, level, optname, optval, optlen); - + int rv = getsockopt(s, level, optname, optval, optlen); /* * IPPROTO_IP/IP_TOS is not supported on some Windows @@ -394,25 +380,6 @@ void dumpAddr (char *str, void *addr) { } } -/** - * Enables SIO_LOOPBACK_FAST_PATH - */ -JNIEXPORT jint JNICALL -NET_EnableFastTcpLoopback(int fd) { - int enabled = 1; - DWORD result_byte_count = -1; - int result = WSAIoctl(fd, - SIO_LOOPBACK_FAST_PATH, - &enabled, - sizeof(enabled), - NULL, - 0, - &result_byte_count, - NULL, - NULL); - return result == SOCKET_ERROR ? WSAGetLastError() : 0; -} - int IsWindows10RS3OrGreater() { OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; diff --git a/src/java.base/windows/native/libnet/net_util_md.h b/src/java.base/windows/native/libnet/net_util_md.h index 255988c461e3..6a661e70bdda 100644 --- a/src/java.base/windows/native/libnet/net_util_md.h +++ b/src/java.base/windows/native/libnet/net_util_md.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,32 +24,6 @@ */ #include #include -#include -#include -#include - -/* used to disable connection reset messages on Windows XP */ -#ifndef SIO_UDP_CONNRESET -#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) -#endif - -#ifndef IN6_IS_ADDR_ANY -#define IN6_IS_ADDR_ANY(a) \ - (((a)->s6_words[0] == 0) && ((a)->s6_words[1] == 0) && \ - ((a)->s6_words[2] == 0) && ((a)->s6_words[3] == 0) && \ - ((a)->s6_words[4] == 0) && ((a)->s6_words[5] == 0) && \ - ((a)->s6_words[6] == 0) && ((a)->s6_words[7] == 0)) -#endif - -#ifndef IPV6_V6ONLY -#define IPV6_V6ONLY 27 /* Treat wildcard bind as AF_INET6-only. */ -#endif - -#define MAX_BUFFER_LEN 2048 -#define MAX_HEAP_BUFFER_LEN 65536 - -/* true if SO_RCVTIMEO is supported by underlying provider */ -extern jboolean isRcvTimeoutSupported; typedef union { struct sockaddr sa; @@ -57,24 +31,6 @@ typedef union { struct sockaddr_in6 sa6; } SOCKETADDRESS; -#define SOCKETADDRESS_COPY(DST,SRC) { \ - if ((SRC)->sa_family == AF_INET6) { \ - memcpy ((DST), (SRC), sizeof (struct sockaddr_in6)); \ - } else { \ - memcpy ((DST), (SRC), sizeof (struct sockaddr_in)); \ - } \ -} - -#define SET_PORT(X,Y) { \ - if ((X)->sa.sa_family == AF_INET) { \ - (X)->sa4.sin_port = (Y); \ - } else { \ - (X)->sa6.sin6_port = (Y); \ - } \ -} - -#define GET_PORT(X) ((X)->sa.sa_family == AF_INET ? (X)->sa4.sin_port : (X)->sa6.sin6_port) - /** * With dual socket implementation the * IPv4 addresseses might be mapped as IPv6. @@ -116,34 +72,3 @@ JNIEXPORT int JNICALL NET_WinBind(int s, SOCKETADDRESS *sa, int len, JNIEXPORT jint JNICALL NET_EnableFastTcpLoopbackConnect(int fd); -/* XP versions of the native routines */ - -JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByName0_XP - (JNIEnv *env, jclass cls, jstring name); - -JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByIndex0_XP - (JNIEnv *env, jclass cls, jint index); - -JNIEXPORT jobject JNICALL Java_java_net_NetworkInterface_getByInetAddress0_XP - (JNIEnv *env, jclass cls, jobject iaObj); - -JNIEXPORT jobjectArray JNICALL Java_java_net_NetworkInterface_getAll_XP - (JNIEnv *env, jclass cls); - -JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_supportsMulticast0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); - -JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isUp0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); - -JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isP2P0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); - -JNIEXPORT jbyteArray JNICALL Java_java_net_NetworkInterface_getMacAddr0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); - -JNIEXPORT jint JNICALL Java_java_net_NetworkInterface_getMTU0_XP - (JNIEnv *env, jclass class, jstring name, jint index); - -JNIEXPORT jboolean JNICALL Java_java_net_NetworkInterface_isLoopback0_XP - (JNIEnv *env, jclass cls, jstring name, jint index); diff --git a/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c b/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c index 54a4133a3b22..ce3144d5c045 100644 --- a/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c +++ b/src/java.base/windows/native/libnio/ch/DatagramChannelImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ */ #include +#include #include "jni.h" #include "jni_util.h" diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c index aff73441cfd7..12c1b9b3a2df 100644 --- a/src/java.base/windows/native/libnio/ch/Net.c +++ b/src/java.base/windows/native/libnio/ch/Net.c @@ -25,6 +25,7 @@ #include #include +#include #include "jni.h" #include "jni_util.h" @@ -37,31 +38,6 @@ #include "sun_nio_ch_Net.h" #include "sun_nio_ch_PollArrayWrapper.h" -/** - * Definitions to allow for building with older SDK include files. - */ - -#ifndef MCAST_BLOCK_SOURCE - -#define MCAST_BLOCK_SOURCE 43 -#define MCAST_UNBLOCK_SOURCE 44 -#define MCAST_JOIN_SOURCE_GROUP 45 -#define MCAST_LEAVE_SOURCE_GROUP 46 - -#endif /* MCAST_BLOCK_SOURCE */ - -struct my_ip_mreq_source { - IN_ADDR imr_multiaddr; - IN_ADDR imr_sourceaddr; - IN_ADDR imr_interface; -}; - -struct my_group_source_req { - ULONG gsr_interface; - SOCKADDR_STORAGE gsr_group; - SOCKADDR_STORAGE gsr_source; -}; - /** * Copy IPv6 address as jbytearray to target */ @@ -152,7 +128,7 @@ Java_sun_nio_ch_Net_canUseIPv6OptionsWithIPv4LocalAddress0(JNIEnv* env, jclass c JNIEXPORT jint JNICALL Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6, - jboolean stream, jboolean reuse, jboolean fastLoopback) + jboolean stream, jboolean reuse) { SOCKET s; int domain = (preferIPv6) ? AF_INET6 : AF_INET; @@ -170,23 +146,6 @@ Java_sun_nio_ch_Net_socket0(JNIEnv *env, jclass cl, jboolean preferIPv6, setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&opt, sizeof(opt)); } - /* Enable SIO_LOOPBACK_FAST_PATH on TCP sockets if possible */ - if (stream && fastLoopback) { - static int loopback_available = 1; - if (loopback_available) { - int rv = NET_EnableFastTcpLoopback((jint)s); - if (rv) { - if (rv == WSAEOPNOTSUPP) { - loopback_available = 0; - } else { - NET_ThrowNew(env, rv, "fastLoopback"); - closesocket(s); - return IOS_THROWN; - } - } - } - } - /* Disable WSAECONNRESET errors for initially unconnected UDP sockets */ if (!stream) { setConnectionReset(s, FALSE); @@ -380,6 +339,13 @@ Java_sun_nio_ch_Net_getIntOption0(JNIEnv *env, jclass clazz, jobject fdo, char *arg; int arglen, n; + /** + * IPV6_TCLASS is not supported on Windows + */ + if (level == IPPROTO_IPV6 && opt == IPV6_TCLASS) { + return 0; + } + if (level == SOL_SOCKET && opt == SO_LINGER) { arg = (char *)&linger; arglen = sizeof(linger); @@ -389,11 +355,11 @@ Java_sun_nio_ch_Net_getIntOption0(JNIEnv *env, jclass clazz, jobject fdo, } /** - * HACK: IP_TOS is deprecated on Windows and querying the option + * IP_TOS is deprecated on Windows and querying the option * returns a protocol error. NET_GetSockOpt handles this and uses - * a fallback mechanism. Same applies to IPV6_TCLASS + * a fallback mechanism. */ - if ((level == IPPROTO_IP && opt == IP_TOS) || (level == IPPROTO_IPV6 && opt == IPV6_TCLASS)) { + if (level == IPPROTO_IP && opt == IP_TOS) { mayNeedConversion = JNI_TRUE; } @@ -421,6 +387,13 @@ Java_sun_nio_ch_Net_setIntOption0(JNIEnv *env, jclass clazz, jobject fdo, char *parg; int arglen, n; + /** + * IPV6_TCLASS is not supported on Windows + */ + if (level == IPPROTO_IPV6 && opt == IPV6_TCLASS) { + return; + } + if (level == SOL_SOCKET && opt == SO_LINGER) { parg = (char *)&linger; arglen = sizeof(linger); @@ -436,9 +409,15 @@ Java_sun_nio_ch_Net_setIntOption0(JNIEnv *env, jclass clazz, jobject fdo, arglen = sizeof(arg); } - if (level == IPPROTO_IPV6 && opt == IPV6_TCLASS) { - /* No op */ - return; + /** + * Setting IP_TOS option requires clearing the + * lowest bit (representing the "unused" bit) + * of the value. NET_SetSockOpt handles the + * value conversion as well as a fallback + * mechanism if the value cannot be set. + */ + if (level == IPPROTO_IP && opt == IP_TOS) { + mayNeedConversion = JNI_TRUE; } if (mayNeedConversion) { @@ -447,7 +426,7 @@ Java_sun_nio_ch_Net_setIntOption0(JNIEnv *env, jclass clazz, jobject fdo, n = setsockopt(fdval(env, fdo), level, opt, parg, arglen); } if (n == SOCKET_ERROR) - NET_ThrowNew(env, WSAGetLastError(), "setsocketopt"); + NET_ThrowNew(env, WSAGetLastError(), "setsockopt"); } JNIEXPORT jint JNICALL @@ -455,7 +434,7 @@ Java_sun_nio_ch_Net_joinOrDrop4(JNIEnv *env, jobject this, jboolean join, jobjec jint group, jint interf, jint source) { struct ip_mreq mreq; - struct my_ip_mreq_source mreq_source; + struct ip_mreq_source mreq_source; int opt, n, optlen; void* optval; @@ -478,7 +457,7 @@ Java_sun_nio_ch_Net_joinOrDrop4(JNIEnv *env, jobject this, jboolean join, jobjec if (n == SOCKET_ERROR) { if (join && (WSAGetLastError() == WSAENOPROTOOPT)) return IOS_UNAVAILABLE; - NET_ThrowNew(env, WSAGetLastError(), "setsocketopt"); + NET_ThrowNew(env, WSAGetLastError(), "setsockopt"); } return 0; } @@ -487,7 +466,7 @@ JNIEXPORT jint JNICALL Java_sun_nio_ch_Net_blockOrUnblock4(JNIEnv *env, jobject this, jboolean block, jobject fdo, jint group, jint interf, jint source) { - struct my_ip_mreq_source mreq_source; + struct ip_mreq_source mreq_source; int n; int opt = (block) ? IP_BLOCK_SOURCE : IP_UNBLOCK_SOURCE; @@ -518,7 +497,7 @@ static int setGroupSourceReqOption(JNIEnv* env, jint index, jbyteArray source) { - struct my_group_source_req req; + struct group_source_req req; struct sockaddr_in6* sin6; req.gsr_interface = (ULONG)index; @@ -565,7 +544,7 @@ Java_sun_nio_ch_Net_blockOrUnblock6(JNIEnv *env, jobject this, jboolean block, j int opt = (block) ? MCAST_BLOCK_SOURCE : MCAST_UNBLOCK_SOURCE; int n = setGroupSourceReqOption(env, fdo, opt, group, index, source); if (n == SOCKET_ERROR) { - NET_ThrowNew(env, WSAGetLastError(), "setsocketopt to block or unblock source"); + NET_ThrowNew(env, WSAGetLastError(), "setsockopt to block or unblock source"); } return 0; } diff --git a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c index 2c6de95ac65f..90737969c4a5 100644 --- a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c +++ b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousServerSocketChannelImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include #include +#include #include "jni.h" #include "jni_util.h" @@ -36,29 +37,7 @@ #include "sun_nio_ch_WindowsAsynchronousServerSocketChannelImpl.h" -#ifndef WSAID_ACCEPTEX -#define WSAID_ACCEPTEX {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} -#endif - -#ifndef SO_UPDATE_ACCEPT_CONTEXT -#define SO_UPDATE_ACCEPT_CONTEXT 0x700B -#endif - - -typedef BOOL (*AcceptEx_t) -( - SOCKET sListenSocket, - SOCKET sAcceptSocket, - PVOID lpOutputBuffer, - DWORD dwReceiveDataLength, - DWORD dwLocalAddressLength, - DWORD dwRemoteAddressLength, - LPDWORD lpdwBytesReceived, - LPOVERLAPPED lpOverlapped -); - - -static AcceptEx_t AcceptEx_func; +static LPFN_ACCEPTEX AcceptEx_func; JNIEXPORT void JNICALL diff --git a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c index b325c34c9d85..b91dde31e3e4 100644 --- a/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c +++ b/src/java.base/windows/native/libnio/ch/WindowsAsynchronousSocketChannelImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include #include +#include #include #include "jni.h" @@ -36,26 +37,7 @@ #include "sun_nio_ch_WindowsAsynchronousSocketChannelImpl.h" -#ifndef WSAID_CONNECTEX -#define WSAID_CONNECTEX {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}} -#endif - -#ifndef SO_UPDATE_CONNECT_CONTEXT -#define SO_UPDATE_CONNECT_CONTEXT 0x7010 -#endif - -typedef BOOL (PASCAL *ConnectEx_t) -( - SOCKET s, - const struct sockaddr* name, - int namelen, - PVOID lpSendBuffer, - DWORD dwSendDataLength, - LPDWORD lpdwBytesSent, - LPOVERLAPPED lpOverlapped -); - -static ConnectEx_t ConnectEx_func; +static LPFN_CONNECTEX ConnectEx_func; JNIEXPORT void JNICALL diff --git a/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java b/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java index cfb89118ff71..5eb12e4b8731 100644 --- a/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java +++ b/src/java.datatransfer/share/classes/java/awt/datatransfer/DataFlavor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,6 @@ import java.io.InputStreamReader; import java.io.ObjectInput; import java.io.ObjectOutput; -import java.io.OptionalDataException; import java.io.Reader; import java.io.Serial; import java.io.StringReader; @@ -1320,19 +1319,7 @@ public synchronized void readExternal(ObjectInput is) throws IOException , Class } } - try { - representationClass = (Class)is.readObject(); - } catch (OptionalDataException ode) { - if (!ode.eof || ode.length != 0) { - throw ode; - } - // Ensure backward compatibility. - // Old versions didn't write the representation class to the stream. - if (rcn != null) { - representationClass = - DataFlavor.tryToLoadClass(rcn, getClass().getClassLoader()); - } - } + representationClass = (Class)is.readObject(); } /** diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java index d3f262852559..6cf49b5b9ff9 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CImage.java @@ -83,7 +83,6 @@ public static CImage createFromImage(final Image image, CTrayIcon.IconObserver o } public static final class Creator { - CTrayIcon.IconObserver observer; Creator() { } @@ -131,7 +130,7 @@ public Image createImageFromName(final String name) { return createImageUsingNativeSize(nativeCreateNSImageFromImageName(name)); } - private static int[] imageToArray(Image image, boolean prepareImage, CTrayIcon.IconObserver observer) { + private static BufferedImage getAsBufferedImage(Image image, boolean prepareImage, CTrayIcon.IconObserver observer) { if (image == null) return null; if (prepareImage && !(image instanceof BufferedImage)) { @@ -163,17 +162,18 @@ private static int[] imageToArray(Image image, boolean prepareImage, CTrayIcon.I g2.drawImage(image, 0, 0, observer); g2.dispose(); - return ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData(); + return bimg; } public byte[] getPlatformImageBytes(final Image image) { - int[] buffer = imageToArray(image, false, null); + BufferedImage bimg = getAsBufferedImage(image, false, null); - if (buffer == null) { + if (bimg == null) { return null; } + int[] buffer = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData(); - return nativeGetPlatformImageBytes(buffer, image.getWidth(null), image.getHeight(null)); + return nativeGetPlatformImageBytes(buffer, bimg.getWidth(), bimg.getHeight()); } /** @@ -205,11 +205,13 @@ private CImage createFromImage(final Image image, final boolean prepareImage, CT return createFromImages(resolutionVariants, prepareImage); } - int[] buffer = imageToArray(image, prepareImage, observer); - if (buffer == null) { + BufferedImage bimg = getAsBufferedImage(image, prepareImage, observer); + + if (bimg == null) { return null; } - return new CImage(nativeCreateNSImageFromArray(buffer, image.getWidth(null), image.getHeight(null))); + int[] buffer = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData(); + return new CImage(nativeCreateNSImageFromArray(buffer, bimg.getWidth(), bimg.getHeight())); } public CImage createFromImages(final List images) { @@ -230,13 +232,14 @@ private CImage createFromImages(final List images, final boolean prepareI num = 0; for (final Image img : images) { - buffers[num] = imageToArray(img, prepareImage, null); - if (buffers[num] == null) { + BufferedImage bimg = getAsBufferedImage(img, prepareImage, null); + if (bimg == null) { // Unable to process the image continue; } - w[num] = img.getWidth(null); - h[num] = img.getHeight(null); + buffers[num] = ((DataBufferInt)bimg.getRaster().getDataBuffer()).getData(); + w[num] = bimg.getWidth(); + h[num] = bimg.getHeight(); num++; } diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m index fa534fff2759..3c5bb10cd253 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CImage.m @@ -75,6 +75,11 @@ static NSBitmapImageRep* CImage_CreateImageRep(JNIEnv *env, jintArray buffer, jint width, jint height) { + jlong bufferLength = (*env)->GetArrayLength(env, buffer); + if (((jlong)width * height) > bufferLength) { + return 0L; + } + NSBitmapImageRep* imageRep = [[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:width pixelsHigh:height diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java index 69c1bc63f3f6..e584769f3e8f 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/gif/GIFImageReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,6 +35,7 @@ import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.PixelInterleavedSampleModel; import java.awt.image.SampleModel; +import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; import java.nio.ByteOrder; @@ -650,7 +651,8 @@ private int locateImage(int imageIndex) throws IIOException { // Read blocks of 1-255 bytes, stop at a 0-length block private byte[] concatenateBlocks() throws IOException { - byte[] data = new byte[0]; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(255); + byte[] data = new byte[255]; while (true) { int length = stream.readUnsignedByte(); if (length == 0) { @@ -660,15 +662,10 @@ private byte[] concatenateBlocks() throws IOException { stream.skipBytes(length); continue; } - byte[] subBlockData = - ReaderUtil.staggeredReadByteStream(stream, length); - byte[] newData = new byte[data.length + length]; - System.arraycopy(data, 0, newData, 0, data.length); - System.arraycopy(subBlockData, 0, newData, - data.length, length); - data = newData; + stream.readFully(data, 0, length); + buffer.write(data, 0, length); } - return data; + return buffer.toByteArray(); } // Stream must be positioned at start of metadata for 'currIndex' diff --git a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java index 1d6bf2dbf8a4..da815eda4e52 100644 --- a/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java +++ b/src/java.desktop/share/classes/com/sun/imageio/plugins/jpeg/JPEGImageReader.java @@ -1258,9 +1258,15 @@ private Raster readInternal(int imageIndex, int[] sBands = param.getSourceBands(); if (sBands != null) { - srcBands = sBands; + srcBands = sBands.clone(); numRasterBands = srcBands.length; } + for (int b = 0; b < srcBands.length; b++) { + if (srcBands[b] < 0 || srcBands[b] >= numComponents) { + throw new IllegalArgumentException( + "ImageReadParam out of range source band : " + srcBands[b]); + } + } if (!wantRaster) { // ignore dest bands for Raster destinationBands = param.getDestinationBands(); } diff --git a/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java b/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java index 0581b0ce3ca6..7f89ec1e422e 100644 --- a/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java +++ b/src/java.desktop/share/classes/com/sun/media/sound/StandardMidiFileReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package com.sun.media.sound; import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.EOFException; import java.io.File; @@ -229,6 +230,10 @@ final class SMFParser { private static final boolean DEBUG = false; + private static final int MAX_ALLOWED_ALLOC = 1024*1024; // Maximum allowed memory preallocation + + private static final int TRACK_BUFFER_SIZE = 10*1024; // Read large tracks in 10 KB chunks + int tracks; // number of tracks DataInputStream stream; // the stream to read from @@ -286,19 +291,45 @@ boolean nextTrack() throws IOException, InvalidMidiDataException { } } // now read track in a byte array - try { - trackData = new byte[trackLength]; - } catch (final OutOfMemoryError oom) { - throw new IOException("Track length too big", oom); - } - try { - // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection - stream.readFully(trackData); - } catch (EOFException eof) { - if (!STRICT_PARSER) { - return false; + if (trackLength <= MAX_ALLOWED_ALLOC) { + try { + trackData = new byte[trackLength]; + } catch (final OutOfMemoryError oom) { + throw new IOException("Track length too big", oom); + } + try { + // $$fb 2003-08-20: fix for 4910986: MIDI file parser breaks up on http connection + stream.readFully(trackData); + } catch (EOFException eof) { + if (!STRICT_PARSER) { + return false; + } + throw new EOFException("invalid MIDI file"); + } + } else { + try { + byte[] buffer = new byte[TRACK_BUFFER_SIZE]; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + long bytesRead = 0; + + while (bytesRead < trackLength) { + int bytesToRead = (int) Math.min(buffer.length, trackLength - bytesRead); + try { + stream.readFully(buffer, 0, bytesToRead); + } catch (EOFException eof) { + if (!STRICT_PARSER) { + return false; + } + throw new EOFException("invalid MIDI file"); + } + + out.write(buffer, 0, bytesToRead); + bytesRead += bytesToRead; + } + trackData = out.toByteArray(); + } catch (final OutOfMemoryError oom) { + throw new IOException("Track length too big", oom); } - throw new EOFException("invalid MIDI file"); } pos = 0; return true; diff --git a/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java b/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java index 234a58de4ece..c894f434e2fa 100644 --- a/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java +++ b/src/java.desktop/share/classes/javax/swing/event/EventListenerList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -301,9 +301,12 @@ private void readObject(ObjectInputStream s) ClassLoader cl = Thread.currentThread().getContextClassLoader(); EventListener l = (EventListener)s.readObject(); String name = (String) listenerTypeOrNull; - @SuppressWarnings("unchecked") - Class tmp = (Class)Class.forName(name, true, cl); - add(tmp, l); + Class c = Class.forName(name, false, cl); + if (EventListener.class.isAssignableFrom(c)) { + @SuppressWarnings("unchecked") + Class tmp = (Class) c; + add(tmp, l); + } } } diff --git a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java index 1c3ad7db47fd..d67c12580904 100644 --- a/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java +++ b/src/java.desktop/share/classes/sun/font/StandardGlyphVector.java @@ -430,15 +430,27 @@ public Shape getOutline(float x, float y) { // relative to gv origin public Shape getGlyphOutline(int ix) { + if (ix < 0 || ix >= glyphs.length) { + throw new IndexOutOfBoundsException("ix = " + ix); + } + return getGlyphsOutline(ix, 1, 0, 0); } // relative to gv origin offset by x, y public Shape getGlyphOutline(int ix, float x, float y) { + if (ix < 0 || ix >= glyphs.length) { + throw new IndexOutOfBoundsException("ix = " + ix); + } + return getGlyphsOutline(ix, 1, x, y); } public Point2D getGlyphPosition(int ix) { + if (ix < 0 || ix > glyphs.length) { + throw new IndexOutOfBoundsException("ix = " + ix); + } + initPositions(); ix *= 2; diff --git a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c index 3bd2b3d07ae5..b294d8f396d4 100644 --- a/src/java.desktop/share/native/libfontmanager/freetypeScaler.c +++ b/src/java.desktop/share/native/libfontmanager/freetypeScaler.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -864,10 +864,12 @@ static void CopyGrey4ToGrey8(const void* srcImage, int srcRowBytes, srcRow += srcRowBytes; dstRow += dstRowBytes; - for (i = 0; i < width; i++) { + for (i = 0; i < width; i += 2) { srcValue = *src8++; - *dstByte++ = Grey4ToAlpha255(srcValue & 0x0f); *dstByte++ = Grey4ToAlpha255(srcValue >> 4); + if (i < width-1) { + *dstByte++ = Grey4ToAlpha255(srcValue & 0x0f); + } } } } diff --git a/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h b/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h index a8ad65e7c4b9..a87f79c9b157 100644 --- a/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h +++ b/src/java.desktop/share/native/libmlib_image/mlib_image_proto.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,9 +31,6 @@ #include #include #include "jni.h" -#if defined ( __MEDIALIB_OLD_NAMES_ADDED ) -#include <../include/mlib_image_proto.h> -#endif /* defined ( __MEDIALIB_OLD_NAMES_ADDED ) */ #ifdef __cplusplus extern "C" { @@ -43,2603 +40,41 @@ extern "C" { #include "j2d_names.h" #endif // __USE_J2D_NAMES -#if defined ( _MSC_VER ) -#if ! defined ( __MEDIALIB_OLD_NAMES ) -#define __MEDIALIB_OLD_NAMES -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -#endif /* defined ( _MSC_VER ) */ - -/* Arithmetic Operations ( arith ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAbs mlib_ImageAbs -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAbs(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAbs_Fp mlib_ImageAbs_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAbs_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAbs_Fp_Inp mlib_ImageAbs_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAbs_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAbs_Inp mlib_ImageAbs_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAbs_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAdd mlib_ImageAdd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAdd(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAdd_Fp mlib_ImageAdd_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAdd_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAdd_Fp_Inp mlib_ImageAdd_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAdd_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAdd_Inp mlib_ImageAdd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAdd_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAve mlib_ImageAve -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAve(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAve_Fp mlib_ImageAve_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAve_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAve_Fp_Inp mlib_ImageAve_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAve_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAve_Inp mlib_ImageAve_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAve_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend mlib_ImageBlend -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - const mlib_image *alpha); - -/* src1dst = src1dst * alpha + src2 * (1 - alpha) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend1_Fp_Inp mlib_ImageBlend1_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend1_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2, - const mlib_image *alpha); - -/* src1dst = src1dst * alpha + src2 * (1 - alpha) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend1_Inp mlib_ImageBlend1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend1_Inp(mlib_image *src1dst, - const mlib_image *src2, - const mlib_image *alpha); - -/* src2dst = src1 * alpha + src2dst * (1 - alpha) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend2_Fp_Inp mlib_ImageBlend2_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend2_Fp_Inp(mlib_image *src2dst, - const mlib_image *src1, - const mlib_image *alpha); - -/* src2dst = src1 * alpha + src2dst * (1 - alpha) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend2_Inp mlib_ImageBlend2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend2_Inp(mlib_image *src2dst, - const mlib_image *src1, - const mlib_image *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlend_Fp mlib_ImageBlend_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlend_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - const mlib_image *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlendMulti mlib_ImageBlendMulti -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlendMulti(mlib_image *dst, - const mlib_image **srcs, - const mlib_image **alphas, - const mlib_s32 *c, - mlib_s32 n); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlendMulti_Fp mlib_ImageBlendMulti_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlendMulti_Fp(mlib_image *dst, - const mlib_image **srcs, - const mlib_image **alphas, - const mlib_d64 *c, - mlib_s32 n); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlendRGBA2ARGB mlib_ImageBlendRGBA2ARGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlendRGBA2ARGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageBlendRGBA2BGRA mlib_ImageBlendRGBA2BGRA -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageBlendRGBA2BGRA(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorBlend mlib_ImageColorBlend -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorBlend(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *color, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorBlend_Fp mlib_ImageColorBlend_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorBlend_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *color, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorBlend_Fp_Inp mlib_ImageColorBlend_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorBlend_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *color, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorBlend_Inp mlib_ImageColorBlend_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorBlend_Inp(mlib_image *srcdst, - const mlib_s32 *color, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAdd mlib_ImageConstAdd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAdd(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAdd_Fp mlib_ImageConstAdd_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAdd_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAdd_Fp_Inp mlib_ImageConstAdd_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAdd_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAdd_Inp mlib_ImageConstAdd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAdd_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDiv mlib_ImageConstDiv -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDiv(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDivShift mlib_ImageConstDivShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDivShift(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDivShift_Inp mlib_ImageConstDivShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDivShift_Inp(mlib_image *srcdst, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDiv_Fp mlib_ImageConstDiv_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDiv_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDiv_Fp_Inp mlib_ImageConstDiv_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDiv_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstDiv_Inp mlib_ImageConstDiv_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstDiv_Inp(mlib_image *srcdst, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMul mlib_ImageConstMul -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMul(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMulShift mlib_ImageConstMulShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMulShift(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMulShift_Inp mlib_ImageConstMulShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMulShift_Inp(mlib_image *srcdst, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMul_Fp mlib_ImageConstMul_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMul_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMul_Fp_Inp mlib_ImageConstMul_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMul_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstMul_Inp mlib_ImageConstMul_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstMul_Inp(mlib_image *srcdst, - const mlib_d64 *consts); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstSub mlib_ImageConstSub -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstSub(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstSub_Fp mlib_ImageConstSub_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstSub_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstSub_Fp_Inp mlib_ImageConstSub_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstSub_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstSub_Inp mlib_ImageConstSub_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstSub_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDiv1_Fp_Inp mlib_ImageDiv1_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDiv1_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDiv2_Fp_Inp mlib_ImageDiv2_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDiv2_Fp_Inp(mlib_image *src2dst, - const mlib_image *src1); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivAlpha mlib_ImageDivAlpha -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivAlpha(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivAlpha_Fp mlib_ImageDivAlpha_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivAlpha_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivAlpha_Fp_Inp mlib_ImageDivAlpha_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivAlpha_Fp_Inp(mlib_image *img, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivAlpha_Inp mlib_ImageDivAlpha_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivAlpha_Inp(mlib_image *img, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivConstShift mlib_ImageDivConstShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivConstShift(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivConstShift_Inp mlib_ImageDivConstShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivConstShift_Inp(mlib_image *srcdst, - const mlib_s32 *consts, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivShift mlib_ImageDivShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivShift(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivShift1_Inp mlib_ImageDivShift1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivShift1_Inp(mlib_image *src1dst, - const mlib_image *src2, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDivShift2_Inp mlib_ImageDivShift2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDivShift2_Inp(mlib_image *src2dst, - const mlib_image *src1, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDiv_Fp mlib_ImageDiv_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDiv_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExp mlib_ImageExp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExp_Fp mlib_ImageExp_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExp_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExp_Fp_Inp mlib_ImageExp_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExp_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExp_Inp mlib_ImageExp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInvert mlib_ImageInvert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageInvert(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInvert_Fp mlib_ImageInvert_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageInvert_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInvert_Fp_Inp mlib_ImageInvert_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageInvert_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInvert_Inp mlib_ImageInvert_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageInvert_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLog mlib_ImageLog -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLog(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLog_Fp mlib_ImageLog_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLog_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLog_Fp_Inp mlib_ImageLog_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLog_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLog_Inp mlib_ImageLog_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLog_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMax mlib_ImageMax -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMax(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMax_Fp mlib_ImageMax_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMax_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMax_Fp_Inp mlib_ImageMax_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMax_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMax_Inp mlib_ImageMax_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMax_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMin mlib_ImageMin -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMin(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMin_Fp mlib_ImageMin_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMin_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMin_Fp_Inp mlib_ImageMin_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMin_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMin_Inp mlib_ImageMin_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMin_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulAlpha mlib_ImageMulAlpha -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulAlpha(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulAlpha_Fp mlib_ImageMulAlpha_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulAlpha_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulAlpha_Fp_Inp mlib_ImageMulAlpha_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulAlpha_Fp_Inp(mlib_image *img, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulAlpha_Inp mlib_ImageMulAlpha_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulAlpha_Inp(mlib_image *img, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulShift mlib_ImageMulShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulShift(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMulShift_Inp mlib_ImageMulShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMulShift_Inp(mlib_image *src1dst, - const mlib_image *src2, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMul_Fp mlib_ImageMul_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMul_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMul_Fp_Inp mlib_ImageMul_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMul_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScalarBlend mlib_ImageScalarBlend -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScalarBlend(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - const mlib_s32 *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScalarBlend_Fp mlib_ImageScalarBlend_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScalarBlend_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2, - const mlib_d64 *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScalarBlend_Fp_Inp mlib_ImageScalarBlend_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScalarBlend_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2, - const mlib_d64 *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScalarBlend_Inp mlib_ImageScalarBlend_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScalarBlend_Inp(mlib_image *src1dst, - const mlib_image *src2, - const mlib_s32 *alpha); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale mlib_ImageScale -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *alpha, - const mlib_s32 *beta, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale2 mlib_ImageScale2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale2(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *alpha, - const mlib_d64 *beta); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale2_Inp mlib_ImageScale2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale2_Inp(mlib_image *srcdst, - const mlib_d64 *alpha, - const mlib_d64 *beta); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale_Fp mlib_ImageScale_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *alpha, - const mlib_d64 *beta); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale_Fp_Inp mlib_ImageScale_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *alpha, - const mlib_d64 *beta); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageScale_Inp mlib_ImageScale_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageScale_Inp(mlib_image *srcdst, - const mlib_s32 *alpha, - const mlib_s32 *beta, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSqrShift mlib_ImageSqrShift -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSqrShift(mlib_image *dst, - const mlib_image *src, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSqrShift_Inp mlib_ImageSqrShift_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSqrShift_Inp(mlib_image *srcdst, - mlib_s32 shift); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSqr_Fp mlib_ImageSqr_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSqr_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSqr_Fp_Inp mlib_ImageSqr_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSqr_Fp_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub mlib_ImageSub -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - -/* src1dst = src1dst - src2 */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub1_Fp_Inp mlib_ImageSub1_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub1_Fp_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* src1dst = src1dst - src2 */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub1_Inp mlib_ImageSub1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub1_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* src2dst = src1 - src2dst */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub2_Fp_Inp mlib_ImageSub2_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub2_Fp_Inp(mlib_image *src2dst, - const mlib_image *src1); - -/* src2dst = src1 - src2dst */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub2_Inp mlib_ImageSub2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub2_Inp(mlib_image *src2dst, - const mlib_image *src1); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSub_Fp mlib_ImageSub_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSub_Fp(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - -/* Color Space Conversion ( color ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorConvert1 mlib_ImageColorConvert1 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorConvert1(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *cmat); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorConvert1_Fp mlib_ImageColorConvert1_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorConvert1_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *cmat); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorConvert2 mlib_ImageColorConvert2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorConvert2(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *cmat, - const mlib_d64 *offset); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorConvert2_Fp mlib_ImageColorConvert2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorConvert2_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *cmat, - const mlib_d64 *offset); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorHSL2RGB mlib_ImageColorHSL2RGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorHSL2RGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorHSL2RGB_Fp mlib_ImageColorHSL2RGB_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorHSL2RGB_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorHSV2RGB mlib_ImageColorHSV2RGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorHSV2RGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorHSV2RGB_Fp mlib_ImageColorHSV2RGB_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorHSV2RGB_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2CIEMono mlib_ImageColorRGB2CIEMono -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2CIEMono(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2CIEMono_Fp mlib_ImageColorRGB2CIEMono_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2CIEMono_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2HSL mlib_ImageColorRGB2HSL -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2HSL(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2HSL_Fp mlib_ImageColorRGB2HSL_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2HSL_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2HSV mlib_ImageColorRGB2HSV -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2HSV(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2HSV_Fp mlib_ImageColorRGB2HSV_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2HSV_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2Mono mlib_ImageColorRGB2Mono -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2Mono(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *weight); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2Mono_Fp mlib_ImageColorRGB2Mono_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2Mono_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *weight); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2XYZ mlib_ImageColorRGB2XYZ -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2XYZ(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2XYZ_Fp mlib_ImageColorRGB2XYZ_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2XYZ_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2YCC mlib_ImageColorRGB2YCC -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2YCC(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorRGB2YCC_Fp mlib_ImageColorRGB2YCC_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorRGB2YCC_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorXYZ2RGB mlib_ImageColorXYZ2RGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorXYZ2RGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorXYZ2RGB_Fp mlib_ImageColorXYZ2RGB_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorXYZ2RGB_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorYCC2RGB mlib_ImageColorYCC2RGB -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorYCC2RGB(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorYCC2RGB_Fp mlib_ImageColorYCC2RGB_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorYCC2RGB_Fp(mlib_image *dst, - const mlib_image *src); - -/* Image Creation, Deletion and Query ( common ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCreate mlib_ImageCreate -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -JNIEXPORT mlib_image * -__mlib_ImageCreate(mlib_type type, - mlib_s32 channels, - mlib_s32 width, - mlib_s32 height); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCreateStruct mlib_ImageCreateStruct -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -JNIEXPORT mlib_image * -__mlib_ImageCreateStruct(mlib_type type, - mlib_s32 channels, - mlib_s32 width, - mlib_s32 height, - mlib_s32 stride, - const void *data); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCreateSubimage mlib_ImageCreateSubimage -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_image * __mlib_ImageCreateSubimage(mlib_image *img, - mlib_s32 x, - mlib_s32 y, - mlib_s32 w, - mlib_s32 h); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDelete mlib_ImageDelete -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -JNIEXPORT void -__mlib_ImageDelete(mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSetPaddings mlib_ImageSetPaddings -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSetPaddings(mlib_image *img, - mlib_u8 left, - mlib_u8 top, - mlib_u8 right, - mlib_u8 bottom); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSetFormat mlib_ImageSetFormat -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSetFormat(mlib_image *img, - mlib_format format); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetType mlib_ImageGetType -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_type __mlib_ImageGetType(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetChannels mlib_ImageGetChannels -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetChannels(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetWidth mlib_ImageGetWidth -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetWidth(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetHeight mlib_ImageGetHeight -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetHeight(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetStride mlib_ImageGetStride -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetStride(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetData mlib_ImageGetData -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static void * __mlib_ImageGetData(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetFlags mlib_ImageGetFlags -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetFlags(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetPaddings mlib_ImageGetPaddings -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_u8 * __mlib_ImageGetPaddings(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetBitOffset mlib_ImageGetBitOffset -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageGetBitOffset(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGetFormat mlib_ImageGetFormat -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_format __mlib_ImageGetFormat(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotAligned2 mlib_ImageIsNotAligned2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotAligned2(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotAligned4 mlib_ImageIsNotAligned4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotAligned4(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotAligned64 mlib_ImageIsNotAligned64 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotAligned64(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotAligned8 mlib_ImageIsNotAligned8 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotAligned8(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotHeight2X mlib_ImageIsNotHeight2X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotHeight2X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotHeight4X mlib_ImageIsNotHeight4X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotHeight4X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotHeight8X mlib_ImageIsNotHeight8X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotHeight8X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotOneDvector mlib_ImageIsNotOneDvector -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotOneDvector(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotStride8X mlib_ImageIsNotStride8X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotStride8X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotWidth2X mlib_ImageIsNotWidth2X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotWidth2X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotWidth4X mlib_ImageIsNotWidth4X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotWidth4X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsNotWidth8X mlib_ImageIsNotWidth8X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsNotWidth8X(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageIsUserAllocated mlib_ImageIsUserAllocated -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageIsUserAllocated(const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageTestFlags mlib_ImageTestFlags -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -static mlib_s32 __mlib_ImageTestFlags(const mlib_image *img, - mlib_s32 flags); - -/* Image Copying and Clearing ( copy ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageClear mlib_ImageClear -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageClear(mlib_image *img, - const mlib_s32 *color); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageClearEdge mlib_ImageClearEdge -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageClearEdge(mlib_image *img, - mlib_s32 dx, - mlib_s32 dy, - const mlib_s32 *color); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageClearEdge_Fp mlib_ImageClearEdge_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageClearEdge_Fp(mlib_image *img, - mlib_s32 dx, - mlib_s32 dy, - const mlib_d64 *color); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageClear_Fp mlib_ImageClear_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageClear_Fp(mlib_image *img, - const mlib_d64 *color); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopy mlib_ImageCopy -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopy(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopyArea mlib_ImageCopyArea -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopyArea(mlib_image *img, - mlib_s32 x, - mlib_s32 y, - mlib_s32 w, - mlib_s32 h, - mlib_s32 dx, - mlib_s32 dy); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopyMask mlib_ImageCopyMask -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopyMask(mlib_image *dst, - const mlib_image *src, - const mlib_image *mask, - const mlib_s32 *thresh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopyMask_Fp mlib_ImageCopyMask_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopyMask_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_image *mask, - const mlib_d64 *thresh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCopySubimage mlib_ImageCopySubimage -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCopySubimage(mlib_image *dst, - const mlib_image *src, - mlib_s32 xd, - mlib_s32 yd, - mlib_s32 xs, - mlib_s32 ys, - mlib_s32 w, - mlib_s32 h); - -/* Data Format Conversion ( format ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelCopy mlib_ImageChannelCopy -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelCopy(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelExtract mlib_ImageChannelExtract -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelExtract(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelInsert mlib_ImageChannelInsert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelInsert(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelMerge mlib_ImageChannelMerge -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelMerge(mlib_image *dst, - const mlib_image ** srcs); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageChannelSplit mlib_ImageChannelSplit -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageChannelSplit(mlib_image ** dsts, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDataTypeConvert mlib_ImageDataTypeConvert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDataTypeConvert(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageReformat mlib_ImageReformat -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageReformat(void **dstData, - const void **srcData, - mlib_s32 numChannels, - mlib_s32 xSize, - mlib_s32 ySize, - mlib_type dstDataType, - const mlib_s32 *dstBandoffsets, - mlib_s32 dstScanlinestride, - mlib_s32 dstPixelstride, - mlib_type srcDataType, - const mlib_s32 *srcBandoffsets, - mlib_s32 srcScanlinestride, - mlib_s32 srcPixelstride); - -/* Fourier Transformation ( fourier ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFourierTransform mlib_ImageFourierTransform -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFourierTransform(mlib_image *dst, - const mlib_image *src, - mlib_fourier_mode mode); - -/* Geometric Operations ( geom ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffine mlib_ImageAffine -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -JNIEXPORT mlib_status -__mlib_ImageAffine(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffineTable mlib_ImageAffineTable -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffineTable(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffineTable_Fp mlib_ImageAffineTable_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffineTable_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffineTransform mlib_ImageAffineTransform -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffineTransform(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffineTransform_Fp mlib_ImageAffineTransform_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffineTransform_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAffine_Fp mlib_ImageAffine_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAffine_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *mtx, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFilteredSubsample mlib_ImageFilteredSubsample -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFilteredSubsample(mlib_image *dst, - const mlib_image *src, - mlib_s32 scaleX, - mlib_s32 scaleY, - mlib_s32 transX, - mlib_s32 transY, - const mlib_d64 *hKernel, - const mlib_d64 *vKernel, - mlib_s32 hSize, - mlib_s32 vSize, - mlib_s32 hParity, - mlib_s32 vParity, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFilteredSubsample_Fp mlib_ImageFilteredSubsample_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFilteredSubsample_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 scaleX, - mlib_s32 scaleY, - mlib_s32 transX, - mlib_s32 transY, - const mlib_d64 *hKernel, - const mlib_d64 *vKernel, - mlib_s32 hSize, - mlib_s32 vSize, - mlib_s32 hParity, - mlib_s32 vParity, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipAntiDiag mlib_ImageFlipAntiDiag -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipAntiDiag(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipAntiDiag_Fp mlib_ImageFlipAntiDiag_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipAntiDiag_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipMainDiag mlib_ImageFlipMainDiag -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipMainDiag(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipMainDiag_Fp mlib_ImageFlipMainDiag_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipMainDiag_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipX mlib_ImageFlipX -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipX(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipX_Fp mlib_ImageFlipX_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipX_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipY mlib_ImageFlipY -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipY(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageFlipY_Fp mlib_ImageFlipY_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageFlipY_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGridWarp mlib_ImageGridWarp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGridWarp(mlib_image *dst, - const mlib_image *src, - const mlib_f32 *xWarpPos, - const mlib_f32 *yWarpPos, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_s32 xStart, - mlib_s32 xStep, - mlib_s32 xNumCells, - mlib_s32 yStart, - mlib_s32 yStep, - mlib_s32 yNumCells, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGridWarpTable mlib_ImageGridWarpTable -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGridWarpTable(mlib_image *dst, - const mlib_image *src, - const mlib_f32 *xWarpPos, - const mlib_f32 *yWarpPos, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_s32 xStart, - mlib_s32 xStep, - mlib_s32 xNumCells, - mlib_s32 yStart, - mlib_s32 yStep, - mlib_s32 yNumCells, - const void *table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGridWarpTable_Fp mlib_ImageGridWarpTable_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGridWarpTable_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_f32 *xWarpPos, - const mlib_f32 *yWarpPos, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_s32 xStart, - mlib_s32 xStep, - mlib_s32 xNumCells, - mlib_s32 yStart, - mlib_s32 yStep, - mlib_s32 yNumCells, - const void *table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGridWarp_Fp mlib_ImageGridWarp_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGridWarp_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_f32 *xWarpPos, - const mlib_f32 *yWarpPos, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_s32 xStart, - mlib_s32 xStep, - mlib_s32 xNumCells, - mlib_s32 yStart, - mlib_s32 yStep, - mlib_s32 yNumCells, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInterpTableCreate mlib_ImageInterpTableCreate -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -void * __mlib_ImageInterpTableCreate(mlib_type type, - mlib_s32 width, - mlib_s32 height, - mlib_s32 leftPadding, - mlib_s32 topPadding, - mlib_s32 subsampleBitsH, - mlib_s32 subsampleBitsV, - mlib_s32 precisionBits, - const void *dataH, - const void *dataV); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageInterpTableDelete mlib_ImageInterpTableDelete -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -void __mlib_ImageInterpTableDelete(void *interp_table); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImagePolynomialWarp mlib_ImagePolynomialWarp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImagePolynomialWarp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *xCoeffs, - const mlib_d64 *yCoeffs, - mlib_s32 n, - mlib_d64 preShiftX, - mlib_d64 preShiftY, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_d64 preScaleX, - mlib_d64 preScaleY, - mlib_d64 postScaleX, - mlib_d64 postScaleY, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImagePolynomialWarpTable mlib_ImagePolynomialWarpTable -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImagePolynomialWarpTable(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *xCoeffs, - const mlib_d64 *yCoeffs, - mlib_s32 n, - mlib_d64 preShiftX, - mlib_d64 preShiftY, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_d64 preScaleX, - mlib_d64 preScaleY, - mlib_d64 postScaleX, - mlib_d64 postScaleY, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImagePolynomialWarpTable_Fp mlib_ImagePolynomialWarpTable_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImagePolynomialWarpTable_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *xCoeffs, - const mlib_d64 *yCoeffs, - mlib_s32 n, - mlib_d64 preShiftX, - mlib_d64 preShiftY, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_d64 preScaleX, - mlib_d64 preScaleY, - mlib_d64 postScaleX, - mlib_d64 postScaleY, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImagePolynomialWarp_Fp mlib_ImagePolynomialWarp_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImagePolynomialWarp_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *xCoeffs, - const mlib_d64 *yCoeffs, - mlib_s32 n, - mlib_d64 preShiftX, - mlib_d64 preShiftY, - mlib_d64 postShiftX, - mlib_d64 postShiftY, - mlib_d64 preScaleX, - mlib_d64 preScaleY, - mlib_d64 postScaleX, - mlib_d64 postScaleY, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate mlib_ImageRotate -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate(mlib_image *dst, - const mlib_image *src, - mlib_d64 angle, - mlib_d64 xcenter, - mlib_d64 ycenter, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate180 mlib_ImageRotate180 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate180(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate180_Fp mlib_ImageRotate180_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate180_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate270 mlib_ImageRotate270 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate270(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate270_Fp mlib_ImageRotate270_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate270_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate90 mlib_ImageRotate90 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate90(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate90_Fp mlib_ImageRotate90_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate90_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRotate_Fp mlib_ImageRotate_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRotate_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 angle, - mlib_d64 xcenter, - mlib_d64 ycenter, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSubsampleAverage mlib_ImageSubsampleAverage -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSubsampleAverage(mlib_image *dst, - const mlib_image *src, - mlib_d64 scalex, - mlib_d64 scaley); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSubsampleAverage_Fp mlib_ImageSubsampleAverage_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSubsampleAverage_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 scalex, - mlib_d64 scaley); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSubsampleBinaryToGray mlib_ImageSubsampleBinaryToGray -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSubsampleBinaryToGray(mlib_image *dst, - const mlib_image *src, - mlib_d64 xscale, - mlib_d64 yscale, - const mlib_u8 *lutGray); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomIn2X mlib_ImageZoomIn2X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomIn2X(mlib_image *dst, - const mlib_image *src, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomIn2X_Fp mlib_ImageZoomIn2X_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomIn2X_Fp(mlib_image *dst, - const mlib_image *src, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomOut2X mlib_ImageZoomOut2X -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomOut2X(mlib_image *dst, - const mlib_image *src, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomOut2X_Fp mlib_ImageZoomOut2X_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomOut2X_Fp(mlib_image *dst, - const mlib_image *src, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslate mlib_ImageZoomTranslate -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslate(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslateTable mlib_ImageZoomTranslateTable -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslateTable(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslateTable_Fp mlib_ImageZoomTranslateTable_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslateTable_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - const void *interp_table, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslateToGray mlib_ImageZoomTranslateToGray -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslateToGray(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - mlib_filter filter, - mlib_edge edge, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoomTranslate_Fp mlib_ImageZoomTranslate_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoomTranslate_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_d64 tx, - mlib_d64 ty, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoom mlib_ImageZoom -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoom(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_filter filter, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageZoom_Fp mlib_ImageZoom_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageZoom_Fp(mlib_image *dst, - const mlib_image *src, - mlib_d64 zoomx, - mlib_d64 zoomy, - mlib_filter filter, - mlib_edge edge); - -/* Logical Operations ( logic ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAnd mlib_ImageAnd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAnd(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAndNot mlib_ImageAndNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAndNot(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - -/* src1dst = src1dst & (~src2) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAndNot1_Inp mlib_ImageAndNot1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAndNot1_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* src2dst = src1 & (~src2dst) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAndNot2_Inp mlib_ImageAndNot2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAndNot2_Inp(mlib_image *src2dst, - const mlib_image *src1); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAnd_Inp mlib_ImageAnd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAnd_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAnd mlib_ImageConstAnd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAnd(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAndNot mlib_ImageConstAndNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAndNot(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - -/* srcdst = (~srcdst) & c */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAndNot_Inp mlib_ImageConstAndNot_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAndNot_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstAnd_Inp mlib_ImageConstAnd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstAnd_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotAnd mlib_ImageConstNotAnd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotAnd(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotAnd_Inp mlib_ImageConstNotAnd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotAnd_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotOr mlib_ImageConstNotOr -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotOr(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotOr_Inp mlib_ImageConstNotOr_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotOr_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotXor mlib_ImageConstNotXor -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotXor(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstNotXor_Inp mlib_ImageConstNotXor_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstNotXor_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstOr mlib_ImageConstOr -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstOr(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstOrNot mlib_ImageConstOrNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstOrNot(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *c); - -/* srcdst = (~srcdst) | c */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstOrNot_Inp mlib_ImageConstOrNot_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstOrNot_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstOr_Inp mlib_ImageConstOr_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstOr_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstXor mlib_ImageConstXor -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstXor(mlib_image *dst, - mlib_image *src, - mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConstXor_Inp mlib_ImageConstXor_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConstXor_Inp(mlib_image *srcdst, - const mlib_s32 *c); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNot mlib_ImageNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNot(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotAnd mlib_ImageNotAnd -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotAnd(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotAnd_Inp mlib_ImageNotAnd_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotAnd_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotOr mlib_ImageNotOr -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotOr(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotOr_Inp mlib_ImageNotOr_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotOr_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotXor mlib_ImageNotXor -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotXor(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNotXor_Inp mlib_ImageNotXor_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNotXor_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageNot_Inp mlib_ImageNot_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageNot_Inp(mlib_image *srcdst); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOr mlib_ImageOr -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOr(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOrNot mlib_ImageOrNot -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOrNot(mlib_image *dst, - const mlib_image *src1, - const mlib_image *src2); - -/* src1dst = src1dst | (~src2) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOrNot1_Inp mlib_ImageOrNot1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOrNot1_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* src2dst = src1 | (~src2dst) */ - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOrNot2_Inp mlib_ImageOrNot2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOrNot2_Inp(mlib_image *src2dst, - const mlib_image *src1); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageOr_Inp mlib_ImageOr_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageOr_Inp(mlib_image *src1dst, - const mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageXor mlib_ImageXor -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageXor(mlib_image *dst, - mlib_image *src1, - mlib_image *src2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageXor_Inp mlib_ImageXor_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageXor_Inp(mlib_image *src1dst, - const mlib_image *src2); - -/* Radiometric Operations ( radio ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorReplace mlib_ImageColorReplace -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorReplace(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *color1, - const mlib_s32 *color2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorReplace_Fp mlib_ImageColorReplace_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorReplace_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *color1, - const mlib_d64 *color2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorReplace_Fp_Inp mlib_ImageColorReplace_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorReplace_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *color1, - const mlib_d64 *color2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageColorReplace_Inp mlib_ImageColorReplace_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageColorReplace_Inp(mlib_image *srcdst, - const mlib_s32 *color1, - const mlib_s32 *color2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageHistogram mlib_ImageHistogram -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageHistogram(mlib_s32 ** histo, - const mlib_image *img); +JNIEXPORT mlib_image * +mlib_ImageCreate(mlib_type type, + mlib_s32 channels, + mlib_s32 width, + mlib_s32 height); -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageHistogram2 mlib_ImageHistogram2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageHistogram2(mlib_s32 ** histo, - const mlib_image *img, - const mlib_s32 *numBins, - const mlib_s32 *lowValue, - const mlib_s32 *highValue, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod); +JNIEXPORT mlib_image * +mlib_ImageCreateStruct(mlib_type type, + mlib_s32 channels, + mlib_s32 width, + mlib_s32 height, + mlib_s32 stride, + const void *data); +JNIEXPORT void +mlib_ImageDelete(mlib_image *img); -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLookUp mlib_ImageLookUp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ JNIEXPORT mlib_status -__mlib_ImageLookUp(mlib_image *dst, +mlib_ImageAffine(mlib_image *dst, const mlib_image *src, - const void **table); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLookUp2 mlib_ImageLookUp2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLookUp2(mlib_image *dst, - const mlib_image *src, - const void **table, - const mlib_s32 *offsets, - mlib_s32 channels); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLookUpMask mlib_ImageLookUpMask -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLookUpMask(mlib_image *dst, - const mlib_image *src, - const void **table, - mlib_s32 channels, - mlib_s32 cmask); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageLookUp_Inp mlib_ImageLookUp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageLookUp_Inp(mlib_image *srcdst, - const void **table); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh1 mlib_ImageThresh1 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh1(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thresh, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh1_Fp mlib_ImageThresh1_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh1_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thresh, - const mlib_d64 *ghigh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh1_Fp_Inp mlib_ImageThresh1_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh1_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thresh, - const mlib_d64 *ghigh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh1_Inp mlib_ImageThresh1_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh1_Inp(mlib_image *srcdst, - const mlib_s32 *thresh, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh2 mlib_ImageThresh2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh2(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thresh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh2_Fp mlib_ImageThresh2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh2_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thresh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh2_Fp_Inp mlib_ImageThresh2_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh2_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thresh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh2_Inp mlib_ImageThresh2_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh2_Inp(mlib_image *srcdst, - const mlib_s32 *thresh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh3 mlib_ImageThresh3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh3(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thresh, - const mlib_s32 *ghigh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh3_Fp mlib_ImageThresh3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh3_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thresh, - const mlib_d64 *ghigh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh3_Fp_Inp mlib_ImageThresh3_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh3_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thresh, - const mlib_d64 *ghigh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh3_Inp mlib_ImageThresh3_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh3_Inp(mlib_image *srcdst, - const mlib_s32 *thresh, - const mlib_s32 *ghigh); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh4 mlib_ImageThresh4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh4(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thigh, - const mlib_s32 *tlow, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh4_Fp mlib_ImageThresh4_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh4_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thigh, - const mlib_d64 *tlow, - const mlib_d64 *ghigh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh4_Fp_Inp mlib_ImageThresh4_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh4_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thigh, - const mlib_d64 *tlow, - const mlib_d64 *ghigh, - const mlib_d64 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh4_Inp mlib_ImageThresh4_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh4_Inp(mlib_image *srcdst, - const mlib_s32 *thigh, - const mlib_s32 *tlow, - const mlib_s32 *ghigh, - const mlib_s32 *glow); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh5 mlib_ImageThresh5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh5(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *thigh, - const mlib_s32 *tlow, - const mlib_s32 *gmid); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh5_Fp mlib_ImageThresh5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh5_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *thigh, - const mlib_d64 *tlow, - const mlib_d64 *gmid); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh5_Fp_Inp mlib_ImageThresh5_Fp_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh5_Fp_Inp(mlib_image *srcdst, - const mlib_d64 *thigh, - const mlib_d64 *tlow, - const mlib_d64 *gmid); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageThresh5_Inp mlib_ImageThresh5_Inp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageThresh5_Inp(mlib_image *srcdst, - const mlib_s32 *thigh, - const mlib_s32 *tlow, - const mlib_s32 *gmid); - -/* Linear and Not-Linear Spatial Operations, Morphological Operations ( spatial ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv2x2 mlib_ImageConv2x2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv2x2(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv2x2_Fp mlib_ImageConv2x2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv2x2_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv3x3 mlib_ImageConv3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv3x3(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv3x3_Fp mlib_ImageConv3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv3x3_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv4x4 mlib_ImageConv4x4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv4x4(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv4x4_Fp mlib_ImageConv4x4_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv4x4_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv5x5 mlib_ImageConv5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv5x5(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv5x5_Fp mlib_ImageConv5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv5x5_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv7x7 mlib_ImageConv7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv7x7(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *kernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConv7x7_Fp mlib_ImageConv7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConv7x7_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 cmask, - mlib_edge edge); - + const mlib_d64 *mtx, + mlib_filter filter, + mlib_edge edge); -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConvKernelConvert mlib_ImageConvKernelConvert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ JNIEXPORT mlib_status -__mlib_ImageConvKernelConvert(mlib_s32 *ikernel, +mlib_ImageConvKernelConvert(mlib_s32 *ikernel, mlib_s32 *iscale, const mlib_d64 *fkernel, mlib_s32 m, mlib_s32 n, mlib_type type); - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConvMxN mlib_ImageConvMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ JNIEXPORT mlib_status -__mlib_ImageConvMxN(mlib_image *dst, +mlib_ImageConvMxN(mlib_image *dst, const mlib_image *src, const mlib_s32 *kernel, mlib_s32 m, @@ -2650,744 +85,10 @@ __mlib_ImageConvMxN(mlib_image *dst, mlib_s32 cmask, mlib_edge edge); - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageConvolveMxN mlib_ImageConvolveMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageConvolveMxN(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *kernel, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge); - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDilate4 mlib_ImageDilate4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDilate4(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDilate4_Fp mlib_ImageDilate4_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDilate4_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDilate8 mlib_ImageDilate8 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDilate8(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageDilate8_Fp mlib_ImageDilate8_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageDilate8_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageErode4 mlib_ImageErode4 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageErode4(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageErode4_Fp mlib_ImageErode4_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageErode4_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageErode8 mlib_ImageErode8 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageErode8(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageErode8_Fp mlib_ImageErode8_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageErode8_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGradient3x3 mlib_ImageGradient3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGradient3x3(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hmask, - const mlib_d64 *vmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGradient3x3_Fp mlib_ImageGradient3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGradient3x3_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hmask, - const mlib_d64 *vmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGradientMxN mlib_ImageGradientMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGradientMxN(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hmask, - const mlib_d64 *vmask, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageGradientMxN_Fp mlib_ImageGradientMxN_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageGradientMxN_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hmask, - const mlib_d64 *vmask, - mlib_s32 m, - mlib_s32 n, - mlib_s32 dm, - mlib_s32 dn, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter3x3 mlib_ImageMaxFilter3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter3x3(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter3x3_Fp mlib_ImageMaxFilter3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter3x3_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter5x5 mlib_ImageMaxFilter5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter5x5(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter5x5_Fp mlib_ImageMaxFilter5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter5x5_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter7x7 mlib_ImageMaxFilter7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter7x7(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaxFilter7x7_Fp mlib_ImageMaxFilter7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaxFilter7x7_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter3x3 mlib_ImageMedianFilter3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter3x3(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter3x3_Fp mlib_ImageMedianFilter3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter3x3_Fp(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter3x3_US mlib_ImageMedianFilter3x3_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter3x3_US(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter5x5 mlib_ImageMedianFilter5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter5x5(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter5x5_Fp mlib_ImageMedianFilter5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter5x5_Fp(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter5x5_US mlib_ImageMedianFilter5x5_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter5x5_US(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter7x7 mlib_ImageMedianFilter7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter7x7(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter7x7_Fp mlib_ImageMedianFilter7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter7x7_Fp(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilter7x7_US mlib_ImageMedianFilter7x7_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilter7x7_US(mlib_image *dst, - const mlib_image *src, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilterMxN mlib_ImageMedianFilterMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilterMxN(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilterMxN_Fp mlib_ImageMedianFilterMxN_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilterMxN_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMedianFilterMxN_US mlib_ImageMedianFilterMxN_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMedianFilterMxN_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_median_mask mmask, - mlib_s32 cmask, - mlib_edge edge, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter3x3 mlib_ImageMinFilter3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter3x3(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter3x3_Fp mlib_ImageMinFilter3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter3x3_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter5x5 mlib_ImageMinFilter5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter5x5(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter5x5_Fp mlib_ImageMinFilter5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter5x5_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter7x7 mlib_ImageMinFilter7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter7x7(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinFilter7x7_Fp mlib_ImageMinFilter7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinFilter7x7_Fp(mlib_image *dst, - const mlib_image *src); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter3x3 mlib_ImageRankFilter3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter3x3(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter3x3_Fp mlib_ImageRankFilter3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter3x3_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter3x3_US mlib_ImageRankFilter3x3_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter3x3_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter5x5 mlib_ImageRankFilter5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter5x5(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter5x5_Fp mlib_ImageRankFilter5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter5x5_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter5x5_US mlib_ImageRankFilter5x5_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter5x5_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter7x7 mlib_ImageRankFilter7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter7x7(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter7x7_Fp mlib_ImageRankFilter7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter7x7_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilter7x7_US mlib_ImageRankFilter7x7_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilter7x7_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 rank, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilterMxN mlib_ImageRankFilterMxN -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilterMxN(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilterMxN_Fp mlib_ImageRankFilterMxN_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilterMxN_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_s32 rank); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageRankFilterMxN_US mlib_ImageRankFilterMxN_US -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageRankFilterMxN_US(mlib_image *dst, - const mlib_image *src, - mlib_s32 m, - mlib_s32 n, - mlib_s32 rank, - mlib_s32 bits); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv3x3 mlib_ImageSConv3x3 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv3x3(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *hkernel, - const mlib_s32 *vkernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv3x3_Fp mlib_ImageSConv3x3_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv3x3_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hkernel, - const mlib_d64 *vkernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv5x5 mlib_ImageSConv5x5 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv5x5(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *hkernel, - const mlib_s32 *vkernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv5x5_Fp mlib_ImageSConv5x5_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv5x5_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hkernel, - const mlib_d64 *vkernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv7x7 mlib_ImageSConv7x7 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv7x7(mlib_image *dst, - const mlib_image *src, - const mlib_s32 *hkernel, - const mlib_s32 *vkernel, - mlib_s32 scale, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConv7x7_Fp mlib_ImageSConv7x7_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConv7x7_Fp(mlib_image *dst, - const mlib_image *src, - const mlib_d64 *hkernel, - const mlib_d64 *vkernel, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSConvKernelConvert mlib_ImageSConvKernelConvert -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSConvKernelConvert(mlib_s32 *ihkernel, - mlib_s32 *ivkernel, - mlib_s32 *iscale, - const mlib_d64 *fhkernel, - const mlib_d64 *fvkernel, - mlib_s32 m, - mlib_s32 n, - mlib_type type); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSobel mlib_ImageSobel -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSobel(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask, - mlib_edge edge); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageSobel_Fp mlib_ImageSobel_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageSobel_Fp(mlib_image *dst, - const mlib_image *src, - mlib_s32 cmask, - mlib_edge edge); - -/* Image Staistics ( stat ) */ - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAutoCorrel mlib_ImageAutoCorrel -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAutoCorrel(mlib_d64 *correl, - const mlib_image *img, - mlib_s32 dx, - mlib_s32 dy); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageAutoCorrel_Fp mlib_ImageAutoCorrel_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageAutoCorrel_Fp(mlib_d64 *correl, - const mlib_image *img, - mlib_s32 dx, - mlib_s32 dy); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCrossCorrel mlib_ImageCrossCorrel -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCrossCorrel(mlib_d64 *correl, - const mlib_image *img1, - const mlib_image *img2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageCrossCorrel_Fp mlib_ImageCrossCorrel_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageCrossCorrel_Fp(mlib_d64 *correl, - const mlib_image *img1, - const mlib_image *img2); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExtrema2 mlib_ImageExtrema2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExtrema2(mlib_s32 *min, - mlib_s32 *max, - const mlib_image *img, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExtrema2_Fp mlib_ImageExtrema2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExtrema2_Fp(mlib_d64 *min, - mlib_d64 *max, - const mlib_image *img, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExtremaLocations mlib_ImageExtremaLocations -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExtremaLocations(mlib_s32 *min, - mlib_s32 *max, - const mlib_image *img, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod, - mlib_s32 saveLocations, - mlib_s32 maxRuns, - mlib_s32 *minCounts, - mlib_s32 *maxCounts, - mlib_s32 **minLocations, - mlib_s32 **maxLocations, - mlib_s32 len); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageExtremaLocations_Fp mlib_ImageExtremaLocations_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageExtremaLocations_Fp(mlib_d64 *min, - mlib_d64 *max, - const mlib_image *img, - mlib_s32 xStart, - mlib_s32 yStart, - mlib_s32 xPeriod, - mlib_s32 yPeriod, - mlib_s32 saveLocations, - mlib_s32 maxRuns, - mlib_s32 *minCounts, - mlib_s32 *maxCounts, - mlib_s32 **minLocations, - mlib_s32 **maxLocations, - mlib_s32 len); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaximum mlib_ImageMaximum -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaximum(mlib_s32 *max, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMaximum_Fp mlib_ImageMaximum_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMaximum_Fp(mlib_d64 *max, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMean mlib_ImageMean -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMean(mlib_d64 *mean, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMean_Fp mlib_ImageMean_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMean_Fp(mlib_d64 *mean, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinimum mlib_ImageMinimum -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinimum(mlib_s32 *min, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMinimum_Fp mlib_ImageMinimum_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMinimum_Fp(mlib_d64 *min, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMoment2 mlib_ImageMoment2 -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMoment2(mlib_d64 *moment, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageMoment2_Fp mlib_ImageMoment2_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageMoment2_Fp(mlib_d64 *moment, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageStdDev mlib_ImageStdDev -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageStdDev(mlib_d64 *sdev, - const mlib_image *img, - const mlib_d64 *mean); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageStdDev_Fp mlib_ImageStdDev_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageStdDev_Fp(mlib_d64 *sdev, - const mlib_image *img, - const mlib_d64 *mean); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageXProj mlib_ImageXProj -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageXProj(mlib_d64 *xproj, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageXProj_Fp mlib_ImageXProj_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageXProj_Fp(mlib_d64 *xproj, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageYProj mlib_ImageYProj -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageYProj(mlib_d64 *yproj, - const mlib_image *img); - - -#if defined ( __MEDIALIB_OLD_NAMES ) -#define __mlib_ImageYProj_Fp mlib_ImageYProj_Fp -#endif /* ! defined ( __MEDIALIB_OLD_NAMES ) */ -mlib_status __mlib_ImageYProj_Fp(mlib_d64 *yproj, - const mlib_image *img); +JNIEXPORT mlib_status +mlib_ImageLookUp(mlib_image *dst, + const mlib_image *src, + const void **table); #ifdef __cplusplus } diff --git a/src/java.desktop/unix/classes/sun/awt/X11GraphicsDevice.java b/src/java.desktop/unix/classes/sun/awt/X11GraphicsDevice.java index ff25120b6593..4a3a1b4bdba2 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11GraphicsDevice.java +++ b/src/java.desktop/unix/classes/sun/awt/X11GraphicsDevice.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -155,6 +155,14 @@ public Rectangle getBounds() { return bounds.getBounds(); } + /** + * Returns the screen bounds in native pixels, + * ignoring UI scaling and Xwayland display mode emulation. + */ + public Rectangle getNativeBounds() { + return pGetBounds(getScreen()); + } + public Insets getInsets() { return insets; } diff --git a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java index a8f7cd41a0e6..7f136552f58c 100644 --- a/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java +++ b/src/java.desktop/unix/classes/sun/awt/screencast/ScreencastHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,13 +26,11 @@ package sun.awt.screencast; import sun.awt.UNIXToolkit; -import sun.java2d.pipe.Region; +import sun.awt.X11GraphicsDevice; -import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Toolkit; -import java.awt.geom.AffineTransform; import java.util.Arrays; import java.util.List; import java.util.Set; @@ -115,19 +113,7 @@ private static List getSystemScreensBounds() { .stream(GraphicsEnvironment .getLocalGraphicsEnvironment() .getScreenDevices()) - .map(graphicsDevice -> { - GraphicsConfiguration gc = - graphicsDevice.getDefaultConfiguration(); - Rectangle screen = gc.getBounds(); - AffineTransform tx = gc.getDefaultTransform(); - - return new Rectangle( - Region.clipRound(screen.x * tx.getScaleX()), - Region.clipRound(screen.y * tx.getScaleY()), - Region.clipRound(screen.width * tx.getScaleX()), - Region.clipRound(screen.height * tx.getScaleY()) - ); - }) + .map(d -> ((X11GraphicsDevice) d).getNativeBounds()) .toList(); } diff --git a/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c b/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c index 0d352c36bbeb..d0b60f82be03 100644 --- a/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c +++ b/src/java.desktop/unix/native/common/java2d/x11/X11FontScaler_md.c @@ -28,7 +28,6 @@ #include #include -#include #include #include diff --git a/src/java.desktop/unix/native/common/java2d/x11/X11SurfaceData.c b/src/java.desktop/unix/native/common/java2d/x11/X11SurfaceData.c index b134623a0bd1..565225006b0a 100644 --- a/src/java.desktop/unix/native/common/java2d/x11/X11SurfaceData.c +++ b/src/java.desktop/unix/native/common/java2d/x11/X11SurfaceData.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -176,6 +176,9 @@ Java_sun_java2d_x11_XSurfaceData_isDrawableValid(JNIEnv *env, jobject this) #ifndef HEADLESS X11SDOps *xsdo = X11SurfaceData_GetOps(env, this); + if (xsdo == NULL) { + return JNI_FALSE; + } AWT_LOCK(); if (xsdo->drawable != 0 || X11SD_InitWindow(env, xsdo) == SD_SUCCESS) { diff --git a/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c b/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c index 5862ab1f47bd..dadd04b6bfad 100644 --- a/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c +++ b/src/java.desktop/unix/native/libawt/awt/awt_Mlib.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/awt_GraphicsEnv.c b/src/java.desktop/unix/native/libawt_xawt/awt/awt_GraphicsEnv.c index 5985dd93226f..08b0011e76fd 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/awt_GraphicsEnv.c +++ b/src/java.desktop/unix/native/libawt_xawt/awt/awt_GraphicsEnv.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1153,19 +1153,19 @@ JNIEnv *env, jobject this, jint visualNum, jint screen) DASSERT(screen >= 0 && screen < awt_numScreens); AwtGraphicsConfigData *adata = NULL; - AwtScreenData asd = x11Screens[screen]; + AwtScreenData *asd = &x11Screens[screen]; int i; int depth; XImage * tempImage; /* If haven't gotten all of the configs yet, do it now. */ - if (asd.numConfigs == 0) { - getAllConfigs (env, screen, &asd); + if (asd->numConfigs == 0) { + getAllConfigs (env, screen, asd); } /* Check the graphicsConfig for this visual */ - for (i = 0; i < asd.numConfigs; i++) { - AwtGraphicsConfigDataPtr agcPtr = asd.configs[i]; + for (i = 0; i < asd->numConfigs; i++) { + AwtGraphicsConfigDataPtr agcPtr = asd->configs[i]; if ((jint)agcPtr->awt_visInfo.visualid == visualNum) { adata = agcPtr; break; @@ -1188,6 +1188,10 @@ JNIEnv *env, jobject this, jint visualNum, jint screen) tempImage = XCreateImage(awt_display, adata->awt_visInfo.visual, depth, ZPixmap, 0, NULL, 1, 1, 32, 0); + if (tempImage == NULL) { + JNU_ThrowIllegalArgumentException(env, "XCreateImage failed"); + return; + } adata->pixelStride = (tempImage->bits_per_pixel + 7) / 8; (*env)->SetIntField(env, this, x11GraphicsConfigIDs.bitsPerPixel, (jint)tempImage->bits_per_pixel); diff --git a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c index 13cf21994f91..e42a5f0124ab 100644 --- a/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c +++ b/src/java.desktop/unix/native/libawt_xawt/awt/screencast_pipewire.c @@ -28,6 +28,7 @@ #endif #include +#include #include "jni_util.h" #include "awt.h" @@ -75,14 +76,17 @@ int (*fp_pw_core_disconnect)(struct pw_core *core); struct pw_context * (*fp_pw_context_new)(struct pw_loop *main_loop, struct pw_properties *props, size_t user_data_size); +void (*fp_pw_context_destroy)(struct pw_context *context); struct pw_thread_loop * (*fp_pw_thread_loop_new)(const char *name, const struct spa_dict *props); struct pw_loop * (*fp_pw_thread_loop_get_loop)(struct pw_thread_loop *loop); void (*fp_pw_thread_loop_signal)(struct pw_thread_loop *loop, bool wait_for_accept); -void (*fp_pw_thread_loop_wait)(struct pw_thread_loop *loop); -void (*fp_pw_thread_loop_accept)(struct pw_thread_loop *loop); +int (*fp_pw_thread_loop_timed_wait_full)(struct pw_thread_loop *loop, + const struct timespec *abstime); +int (*fp_pw_thread_loop_get_time)(struct pw_thread_loop *loop, + struct timespec *abstime, int64_t timeout); int (*fp_pw_thread_loop_start)(struct pw_thread_loop *loop); void (*fp_pw_thread_loop_stop)(struct pw_thread_loop *loop); void (*fp_pw_thread_loop_destroy)(struct pw_thread_loop *loop); @@ -103,6 +107,9 @@ int DEBUG_SCREENCAST_ENABLED = FALSE; (*env)->ExceptionDescribe(env); \ } +// 10 s timeout for format negotiation and frames from all requested screens. +static const int64_t PIPEWIRE_TIMEOUT_NS = 10LL * 1000 * 1000 * 1000; + static gboolean hasPipewireFailed = FALSE; static gboolean sessionClosed = TRUE; static GString *activeSessionToken; @@ -158,6 +165,10 @@ static void doCleanup() { for (int i = 0; i < screenSpace.screenCount; ++i) { struct ScreenProps *screenProps = &screenSpace.screens[i]; + if (screenProps->captureDataPixbuf) { + gtk->g_object_unref(screenProps->captureDataPixbuf); + screenProps->captureDataPixbuf = NULL; + } if (screenProps->data) { if (screenProps->data->stream) { fp_pw_thread_loop_lock(pw.loop); @@ -183,6 +194,11 @@ static void doCleanup() { pw.core = NULL; } + if (pw.context) { + fp_pw_context_destroy(pw.context); + pw.context = NULL; + } + if (pw.loop) { fp_pw_thread_loop_destroy(pw.loop); pw.loop = NULL; @@ -281,7 +297,7 @@ static void onStreamParamChanged( data->rawFormat.size.height); data->hasFormat = TRUE; - fp_pw_thread_loop_signal(pw.loop, TRUE); + fp_pw_thread_loop_signal(pw.loop, FALSE); } static void onStreamProcess(void *userdata) { @@ -299,7 +315,6 @@ static void onStreamProcess(void *userdata) { if ( !data->hasFormat || !screen->shouldCapture - || screen->captureDataReady ) { return; } @@ -313,6 +328,11 @@ static void onStreamProcess(void *userdata) { return; } + if (screen->captureDataReady) { + fp_pw_stream_queue_buffer(data->stream, pwBuffer); + return; + } + spaBuffer = pwBuffer->buffer; if (!spaBuffer || spaBuffer->n_datas < 1 @@ -344,7 +364,6 @@ static void onStreamProcess(void *userdata) { ); GdkRectangle captureArea = screen->captureArea; - GdkRectangle screenBounds = screen->bounds; GdkPixbuf *pixbuf = gtk->gdk_pixbuf_new_from_data(spaData.data, GDK_COLORSPACE_RGB, @@ -355,9 +374,14 @@ static void onStreamProcess(void *userdata) { spaData.chunk->stride, NULL, NULL); + if (!pixbuf) { + ERR("Cannot create a pixbuf from stream data.\n"); + goto fail; + } - if (screen->bounds.width != streamWidth - || screen->bounds.height != streamHeight) { + gboolean needsScaling = screen->bounds.width != streamWidth + || screen->bounds.height != streamHeight; + if (needsScaling) { DEBUG_SCREEN_PREFIX(screen, "scaling stream data %dx%d -> %dx%d\n", streamWidth, streamHeight, @@ -369,37 +393,39 @@ static void onStreamProcess(void *userdata) { screen->bounds.height, GDK_INTERP_BILINEAR); + if (!scaled) { + ERR("Cannot scale the stream pixbuf.\n"); + goto fail; + } gtk->g_object_unref(pixbuf); pixbuf = scaled; } - GdkPixbuf *cropped = NULL; - if (captureArea.width != screenBounds.width - || captureArea.height != screenBounds.height) { - - cropped = gtk->gdk_pixbuf_new(GDK_COLORSPACE_RGB, + if (needsScaling + && captureArea.x == 0 && captureArea.y == 0 + && captureArea.width == screen->bounds.width + && captureArea.height == screen->bounds.height) { + screen->captureDataPixbuf = pixbuf; + } else { + GdkPixbuf *cropped = gtk->gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, captureArea.width, captureArea.height); - if (cropped) { - gtk->gdk_pixbuf_copy_area(pixbuf, + if (!cropped) { + ERR("Cannot create a new pixbuf.\n"); + goto fail; + } + gtk->gdk_pixbuf_copy_area(pixbuf, captureArea.x, captureArea.y, captureArea.width, captureArea.height, cropped, 0, 0); - } else { - ERR("Cannot create a new pixbuf.\n"); - } gtk->g_object_unref(pixbuf); - pixbuf = NULL; - - data->screenProps->captureDataPixbuf = cropped; - } else { - data->screenProps->captureDataPixbuf = pixbuf; + screen->captureDataPixbuf = cropped; } screen->captureDataReady = TRUE; @@ -408,6 +434,15 @@ static void onStreamProcess(void *userdata) { fp_pw_stream_queue_buffer(data->stream, pwBuffer); fp_pw_thread_loop_signal(pw.loop, FALSE); + return; + + fail: + if (pixbuf) { + gtk->g_object_unref(pixbuf); + } + fp_pw_stream_queue_buffer(data->stream, pwBuffer); + hasPipewireFailed = TRUE; + fp_pw_thread_loop_signal(pw.loop, FALSE); } static void onStreamStateChanged( @@ -485,10 +520,13 @@ static bool startStream( } /** + * The caller must hold the PipeWire thread loop lock. + * * @param index of a screen + * @param captureDeadline shared deadline for format negotiation and screen capture * @return TRUE on success */ -static gboolean connectStream(int index) { +static gboolean connectStream(int index, const struct timespec *captureDeadline) { DEBUG_SCREENCAST("@@@ using screen %i\n", index); if (index >= screenSpace.screenCount) { DEBUG_SCREENCAST("!!! Wrong index for screen\n", NULL); @@ -500,9 +538,7 @@ static gboolean connectStream(int index) { data->screenProps = &screenSpace.screens[index]; if (!sessionClosed && data->stream) { - fp_pw_thread_loop_lock(pw.loop); int result = fp_pw_stream_set_active(data->stream, TRUE); - fp_pw_thread_loop_unlock(pw.loop); DEBUG_SCREEN_PREFIX(data->screenProps, "stream %p: activate result |%i|\n", @@ -527,7 +563,6 @@ static gboolean connectStream(int index) { if (!data->stream) { DEBUG_SCREEN_PREFIX(data->screenProps, "!!! Could not create a pipewire stream\n", NULL); - fp_pw_thread_loop_unlock(pw.loop); return FALSE; } @@ -543,18 +578,20 @@ static gboolean connectStream(int index) { if (!startStream(data->stream, screenSpace.screens[index].id)){ DEBUG_SCREEN_PREFIX(data->screenProps, "!!! Could not start a pipewire stream\n", NULL); - fp_pw_thread_loop_unlock(pw.loop); return FALSE; } - while (!data->hasFormat) { - fp_pw_thread_loop_wait(pw.loop); - fp_pw_thread_loop_accept(pw.loop); - if (hasPipewireFailed) { - fp_pw_thread_loop_unlock(pw.loop); + while (!hasPipewireFailed && !data->hasFormat) { + int result = fp_pw_thread_loop_timed_wait_full(pw.loop, captureDeadline); + if (result < 0) { + DEBUG_SCREEN_PREFIX(data->screenProps, + "Format wait failed: %d\n", result); return FALSE; } } + if (hasPipewireFailed) { + return FALSE; + } DEBUG_SCREEN_PREFIX(data->screenProps, "frame size: %dx%d\n", @@ -636,7 +673,7 @@ static const struct pw_core_events coreEvents = { * @param requestedArea requested screenshot area * @return TRUE on success */ -static gboolean doLoop(GdkRectangle requestedArea) { +static gboolean doLoop(GdkRectangle requestedArea, struct timespec *captureDeadline) { gboolean isLoopLockTaken = FALSE; if (!pw.loop && !sessionClosed) { pw.loop = fp_pw_thread_loop_new("AWT Pipewire Thread", NULL); @@ -688,6 +725,13 @@ static gboolean doLoop(GdkRectangle requestedArea) { } pw_core_add_listener(pw.core, &pw.coreListener, &coreEvents, NULL); + } else if (pw.loop) { + fp_pw_thread_loop_lock(pw.loop); + isLoopLockTaken = TRUE; + } + + if (fp_pw_thread_loop_get_time(pw.loop, captureDeadline, PIPEWIRE_TIMEOUT_NS) < 0) { + goto fail; } for (int i = 0; i < screenSpace.screenCount; ++i) { @@ -707,7 +751,7 @@ static gboolean doLoop(GdkRectangle requestedArea) { DEBUG_SCREEN_PREFIX(screen, "@@@ adding screen %i\n", i); if (checkScreen(i, requestedArea)) { - if (!connectStream(i)){ + if (!connectStream(i, captureDeadline)){ goto fail; } } @@ -781,11 +825,12 @@ static gboolean loadSymbols() { LOAD_SYMBOL(fp_pw_context_connect_fd, "pw_context_connect_fd"); LOAD_SYMBOL(fp_pw_core_disconnect, "pw_core_disconnect"); LOAD_SYMBOL(fp_pw_context_new, "pw_context_new"); + LOAD_SYMBOL(fp_pw_context_destroy, "pw_context_destroy"); LOAD_SYMBOL(fp_pw_thread_loop_new, "pw_thread_loop_new"); LOAD_SYMBOL(fp_pw_thread_loop_get_loop, "pw_thread_loop_get_loop"); LOAD_SYMBOL(fp_pw_thread_loop_signal, "pw_thread_loop_signal"); - LOAD_SYMBOL(fp_pw_thread_loop_wait, "pw_thread_loop_wait"); - LOAD_SYMBOL(fp_pw_thread_loop_accept, "pw_thread_loop_accept"); + LOAD_SYMBOL(fp_pw_thread_loop_timed_wait_full, "pw_thread_loop_timed_wait_full"); + LOAD_SYMBOL(fp_pw_thread_loop_get_time, "pw_thread_loop_get_time"); LOAD_SYMBOL(fp_pw_thread_loop_start, "pw_thread_loop_start"); LOAD_SYMBOL(fp_pw_thread_loop_stop, "pw_thread_loop_stop"); LOAD_SYMBOL(fp_pw_thread_loop_destroy, "pw_thread_loop_destroy"); @@ -952,19 +997,27 @@ static int makeScreencast( return pw.pwFd; } - if (!doLoop(*requestedArea)) { + struct timespec captureDeadline; + if (!doLoop(*requestedArea, &captureDeadline)) { return RESULT_ERROR; } - while (!isAllDataReady()) { - fp_pw_thread_loop_lock(pw.loop); - fp_pw_thread_loop_wait(pw.loop); - fp_pw_thread_loop_unlock(pw.loop); - if (hasPipewireFailed) { - doCleanup(); - return RESULT_ERROR; + fp_pw_thread_loop_lock(pw.loop); + int waitResult = 0; + while (!hasPipewireFailed && !isAllDataReady()) { + waitResult = fp_pw_thread_loop_timed_wait_full(pw.loop, &captureDeadline); + if (waitResult < 0) { + DEBUG_SCREENCAST("Frame wait failed: %d\n", waitResult); + break; } } + gboolean failed = hasPipewireFailed || waitResult < 0; + fp_pw_thread_loop_unlock(pw.loop); + + if (failed) { + doCleanup(); + return RESULT_ERROR; + } return RESULT_OK; } @@ -1095,17 +1148,15 @@ JNIEXPORT jint JNICALL Java_sun_awt_screencast_ScreencastHelper_getRGBPixelsImpl } } + fp_pw_thread_loop_lock(pw.loop); if (screenProps->captureDataPixbuf) { gtk->g_object_unref(screenProps->captureDataPixbuf); screenProps->captureDataPixbuf = NULL; } screenProps->shouldCapture = FALSE; - - fp_pw_thread_loop_lock(pw.loop); fp_pw_stream_set_active(screenProps->data->stream, FALSE); - fp_pw_thread_loop_unlock(pw.loop); - screenProps->captureDataReady = FALSE; + fp_pw_thread_loop_unlock(pw.loop); } } diff --git a/src/java.desktop/unix/native/libfontmanager/X11FontScaler.c b/src/java.desktop/unix/native/libfontmanager/X11FontScaler.c index 6e836fa111c3..e4cc3a69d844 100644 --- a/src/java.desktop/unix/native/libfontmanager/X11FontScaler.c +++ b/src/java.desktop/unix/native/libfontmanager/X11FontScaler.c @@ -34,7 +34,6 @@ #include #include #include -#include #include #include diff --git a/src/java.management/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java b/src/java.management/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java index fd7bf8fbcaed..541757813ca9 100644 --- a/src/java.management/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java +++ b/src/java.management/share/classes/com/sun/jmx/mbeanserver/JmxMBeanServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1068,111 +1068,6 @@ public boolean isInstanceOf(ObjectName name, String className) return mbsInterceptor.isInstanceOf(cloneObjectName(name), className); } - /** - * De-serializes a byte array in the context of the class loader - * of an MBean. - * - * @param name The name of the MBean whose class loader should - * be used for the de-serialization. - * @param data The byte array to be de-sererialized. - * - * @return The de-serialized object stream. - * - * @exception InstanceNotFoundException The MBean specified is not - * found. - * @exception OperationsException Any of the usual Input/Output - * related exceptions. - * - */ - @Deprecated - public ObjectInputStream deserialize(ObjectName name, byte[] data) - throws InstanceNotFoundException, OperationsException { - - final ClassLoader loader = getClassLoaderFor(name); - - return instantiator.deserialize(loader, data); - } - - /** - * De-serializes a byte array in the context of a given MBean class loader. - * The class loader is the one that loaded the class with name "className". - * - * @param className The name of the class whose class loader should be - * used for the de-serialization. - * @param data The byte array to be de-sererialized. - * - * @return The de-serialized object stream. - * - * @exception OperationsException Any of the usual Input/Output - * related exceptions. - * @exception ReflectionException The specified class could not be - * loaded by the default loader repository - * - */ - @Deprecated - public ObjectInputStream deserialize(String className, byte[] data) - throws OperationsException, ReflectionException { - - if (className == null) { - throw new RuntimeOperationsException( - new IllegalArgumentException(), - "Null className passed in parameter"); - } - - final ClassLoaderRepository clr = getClassLoaderRepository(); - - Class theClass; - try { - if (clr == null) throw new ClassNotFoundException(className); - theClass = clr.loadClass(className); - } catch (ClassNotFoundException e) { - throw new ReflectionException(e, - "The given class could not be " + - "loaded by the default loader " + - "repository"); - } - - return instantiator.deserialize(theClass.getClassLoader(), data); - } - - /** - * De-serializes a byte array in the context of a given MBean class loader. - * The class loader is the one that loaded the class with name "className". - * The name of the class loader to be used for loading the specified - * class is specified. - * If null, the MBean Server's class loader will be used. - * - * @param className The name of the class whose class loader should be - * used for the de-serialization. - * @param data The byte array to be de-sererialized. - * @param loaderName The name of the class loader to be used for - * loading the specified class. - * If null, the MBean Server's class loader will be used. - * - * @return The de-serialized object stream. - * - * @exception InstanceNotFoundException The specified class loader - * MBean is not found. - * @exception OperationsException Any of the usual Input/Output - * related exceptions. - * @exception ReflectionException The specified class could not - * be loaded by the specified class loader. - * - */ - @Deprecated - public ObjectInputStream deserialize(String className, - ObjectName loaderName, - byte[] data) throws - InstanceNotFoundException, OperationsException, ReflectionException { - - // Clone ObjectName - // - loaderName = cloneObjectName(loaderName); - - ClassLoader myLoader = outerShell.getClass().getClassLoader(); - return instantiator.deserialize(className, loaderName, data, myLoader); - } - /** * Initializes this MBeanServer, registering the MBeanServerDelegate. *

      This method must be called once, before using the MBeanServer. diff --git a/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanInstantiator.java b/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanInstantiator.java index b1fcf7999c62..269648268619 100644 --- a/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanInstantiator.java +++ b/src/java.management/share/classes/com/sun/jmx/mbeanserver/MBeanInstantiator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -321,29 +321,7 @@ public Object instantiate(Class theClass, Object params[], public ObjectInputStream deserialize(ClassLoader loader, byte[] data) throws OperationsException { - // Check parameter validity - if (data == null) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Null data passed in parameter"); - } - if (data.length == 0) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Empty data passed in parameter"); - } - - // Object deserialization - ByteArrayInputStream bIn; - ObjectInputStream objIn; - - bIn = new ByteArrayInputStream(data); - try { - objIn = new ObjectInputStreamWithLoader(bIn,loader); - } catch (IOException e) { - throw new OperationsException( - "An IOException occurred trying to de-serialize the data"); - } - - return objIn; + throw new UnsupportedOperationException("Not supported"); } /** @@ -378,56 +356,7 @@ public ObjectInputStream deserialize(String className, OperationsException, ReflectionException { - // Check parameter validity - if (data == null) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Null data passed in parameter"); - } - if (data.length == 0) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Empty data passed in parameter"); - } - if (className == null) { - throw new RuntimeOperationsException(new - IllegalArgumentException(), "Null className passed in parameter"); - } - - Class theClass; - if (loaderName == null) { - // Load the class using the agent class loader - theClass = findClass(className, loader); - - } else { - // Get the class loader MBean - try { - ClassLoader instance = null; - - instance = getClassLoader(loaderName); - if (instance == null) - throw new ClassNotFoundException(className); - theClass = Class.forName(className, false, instance); - } - catch (ClassNotFoundException e) { - throw new ReflectionException(e, - "The MBean class could not be loaded by the " + - loaderName.toString() + " class loader"); - } - } - - // Object deserialization - ByteArrayInputStream bIn; - ObjectInputStream objIn; - - bIn = new ByteArrayInputStream(data); - try { - objIn = new ObjectInputStreamWithLoader(bIn, - theClass.getClassLoader()); - } catch (IOException e) { - throw new OperationsException( - "An IOException occurred trying to de-serialize the data"); - } - - return objIn; + throw new UnsupportedOperationException("Not supported"); } diff --git a/src/java.management/share/classes/javax/management/openmbean/OpenType.java b/src/java.management/share/classes/javax/management/openmbean/OpenType.java index de2663bdc7fe..ed59311e3f8c 100644 --- a/src/java.management/share/classes/javax/management/openmbean/OpenType.java +++ b/src/java.management/share/classes/javax/management/openmbean/OpenType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -113,7 +113,7 @@ public abstract class OpenType implements Serializable { /** * @deprecated Use {@link #ALLOWED_CLASSNAMES_LIST ALLOWED_CLASSNAMES_LIST} instead. */ - @Deprecated + @Deprecated(since="6", forRemoval=true) public static final String[] ALLOWED_CLASSNAMES = ALLOWED_CLASSNAMES_LIST.toArray(new String[0]); diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java index 5259cb638014..936ec08a6d13 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapBindingEnumeration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -65,7 +65,7 @@ final class LdapBindingEnumeration try { obj = NamingManagerHelper.getDirObjectInstance(obj, cn, homeCtx, - homeCtx.envprops, attrs, ObjectFactoriesFilter::checkLdapFilter); + homeCtx.envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter()); } catch (NamingException e) { throw e; diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java index 636d8f5fe2b6..90bf789de1ff 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapCtx.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -388,6 +388,16 @@ public LdapCtx(String dn, String host, int port_number, initEnv(); } + /** + * Whether this LDAP provider implementation trusts serial data. + * See {@linkplain java.naming/ java.naming module info} for more info. + * + * @return true if this LDAP provider implementation trusts serial data + */ + public static boolean trustSerialData() { + return Obj.trustSerialData(); + } + public LdapContext newInstance(Control[] reqCtls) throws NamingException { LdapContext clone = new LdapCtx(this, currentDN); @@ -1113,7 +1123,7 @@ protected Object c_lookup(Name name, Continuation cont) try { return NamingManagerHelper.getDirObjectInstance(obj, name, this, - envprops, attrs, ObjectFactoriesFilter::checkLdapFilter); + envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter()); } catch (NamingException e) { throw cont.fillInException(e); diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java index f3f937029803..095cc4185e79 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapReferralContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -118,7 +118,7 @@ final class LdapReferralContext implements DirContext, LdapContext { Object obj; try { obj = NamingManagerHelper.getObjectInstance(ref, null, null, - env, ObjectFactoriesFilter::checkLdapFilter); + env, ObjectFactoriesFilter.ldapFactoryFilter()); } catch (NamingException e) { if (handleReferrals == LdapClient.LDAP_REF_THROW) { diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java index 6f1981df1028..714481d151c2 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/LdapSearchEnumeration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -124,7 +124,7 @@ protected SearchResult createItem(String dn, Attributes attrs, // rcn is either relative to homeCtx or a fully qualified DN obj = NamingManagerHelper.getDirObjectInstance( obj, rcn, (relative ? homeCtx : null), - homeCtx.envprops, attrs, ObjectFactoriesFilter::checkLdapFilter); + homeCtx.envprops, attrs, ObjectFactoriesFilter.ldapFactoryFilter()); } catch (NamingException e) { throw e; } catch (Exception e) { diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java index 5e34f9543021..6065273a829e 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -119,6 +119,14 @@ final class Obj { static final int SER_OBJECT = 3; // auxiliary serialized object class static final int MAR_OBJECT = 4; // auxiliary marshalled object class + /** + * Whether deserialization of serial data is allowed + * @return true if deserialization of serial data is allowed + */ + static boolean trustSerialData() { + return trustSerialData; + } + /** * Encode an object in LDAP attributes. * Supports binding Referenceable or Reference, Serializable, diff --git a/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java b/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java index 856a1b0a4446..43eeb788e400 100644 --- a/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java +++ b/src/java.naming/share/classes/com/sun/naming/internal/NamingManagerHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -88,7 +88,7 @@ public static Object getObjectInstance(Object refInfo, Name name, Context nameCt // if reference has no factory, check for addresses // containing URLs - answer = processURLAddrs(ref, name, nameCtx, environment); + answer = processURLAddrs(ref, name, nameCtx, environment, factoryFilter); if (answer != null) { return answer; } @@ -156,7 +156,7 @@ public static Object getDirObjectInstance(Object refInfo, Name name, Context nam // containing URLs // ignore name & attrs params; not used in URL factory // RMI references from ' - answer = processURLAddrs(ref, name, nameCtx, environment); + answer = processURLAddrs(ref, name, nameCtx, environment, factoryFilter); if (answer != null) { return answer; } @@ -239,7 +239,8 @@ private static Object createObjectFromFactories(Object obj, Name name, * invoking a factory. */ static Object processURLAddrs(Reference ref, Name name, Context nameCtx, - Hashtable environment) + Hashtable environment, + Predicate> factoryFilter) throws NamingException { for (int i = 0; i < ref.size(); i++) { @@ -248,7 +249,7 @@ static Object processURLAddrs(Reference ref, Name name, Context nameCtx, addr.getType().equalsIgnoreCase("URL")) { String url = (String)addr.getContent(); - Object answer = processURL(url, name, nameCtx, environment); + Object answer = processURL(url, name, nameCtx, environment, factoryFilter); if (answer != null) { return answer; } @@ -258,7 +259,8 @@ static Object processURLAddrs(Reference ref, Name name, Context nameCtx, } private static Object processURL(Object refInfo, Name name, - Context nameCtx, Hashtable environment) + Context nameCtx, Hashtable environment, + Predicate> factoryFilter) throws NamingException { Object answer; @@ -269,7 +271,7 @@ private static Object processURL(Object refInfo, Name name, String scheme = getURLScheme(url); if (scheme != null) { answer = getURLObject(scheme, refInfo, name, nameCtx, - environment); + environment, factoryFilter); if (answer != null) { return answer; } @@ -285,7 +287,7 @@ private static Object processURL(Object refInfo, Name name, String scheme = getURLScheme(urls[i]); if (scheme != null) { answer = getURLObject(scheme, refInfo, name, nameCtx, - environment); + environment, factoryFilter); if (answer != null) return answer; } @@ -327,11 +329,16 @@ private static String getURLScheme(String str) { * @param nameCtx Context whose provider resource file will be searched * for package prefix values (or null if none) * @param environment Environment properties for creating the context + * @param factoryFilter an object factory filter. If the factoryFilter is + * an instance of {@link ObjectFactoriesFilter.ContextFactoryFilter} + * it will be used to check whether the selected URL Context Factory + * is allowed to process a URL Reference of the given URL scheme. * @see javax.naming.InitialContext */ private static Object getURLObject(String scheme, Object urlInfo, Name name, Context nameCtx, - Hashtable environment) + Hashtable environment, + Predicate> factoryFilter) throws NamingException { // e.g. "ftpURLContextFactory" @@ -342,6 +349,10 @@ private static Object getURLObject(String scheme, Object urlInfo, if (factory == null) return null; + if (!ObjectFactoriesFilter.checkURLContextFactory(scheme, factory, factoryFilter)) { + return null; + } + // Found object factory try { return factory.getObjectInstance(urlInfo, name, nameCtx, environment); diff --git a/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java b/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java index c7d50778d7bf..5c5267f2275f 100644 --- a/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java +++ b/src/java.naming/share/classes/com/sun/naming/internal/ObjectFactoriesFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,19 @@ package com.sun.naming.internal; +import com.sun.jndi.ldap.LdapCtx; import sun.security.util.SecurityProperties; import javax.naming.Reference; +import javax.naming.spi.ObjectFactory; import java.io.ObjectInputFilter; import java.io.ObjectInputFilter.FilterInfo; import java.io.ObjectInputFilter.Status; +import java.util.List; +import java.util.Locale; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; /** * This class implements the filter that validates object factories classes instantiated @@ -47,6 +54,133 @@ */ public final class ObjectFactoriesFilter { + /** + * Defines an Object Factory Filter that can be used to {@linkplain + * #test(Class) validate usage of regular ObjectFactory classes} as + * well as {@linkplain #checkURLContextFactory(String, Class) validate + * usage of URL Context Factory classes}. + */ + public static final class ContextFactoryFilter implements Predicate> { + private final Predicate> factoryFilter; + private final BiPredicate> urlFactoryFilter; + + /** + * Construct a new {@code ContextFactoryFilter} from the given + * {@code factoryFilter} and {@code urlFactoryFilter} + * @param factoryFilter A predicate that tells whether a given + * object factory class can be used to reconstruct an object + * from a given Reference. + * This is typically {@link #checkLdapFilter(Class) + * ObjectFactoriesFilter::checkLdapFilter} + * @param urlFactoryFilter A bi-predicate that tells whether + * a URL reference of a given scheme should be processed + * using the selected URL Context Factory. + * This is typically {@link #checkURLContextFactory(String, Class) + * ObjectFactoriesFilter::checkURLContextFactory} + */ + private ContextFactoryFilter(Predicate> factoryFilter, + BiPredicate> urlFactoryFilter) { + this.factoryFilter = factoryFilter; + this.urlFactoryFilter = urlFactoryFilter; + } + + /** + * Checks whether a URL reference of the given scheme should be processed + * using the selected URL Context Factory. + * @param scheme the URL scheme + * @param urlContextFactoryClass the URL Context Factory class + * @return true if the URL Reference can be processed + */ + public boolean checkURLContextFactory(String scheme, Class urlContextFactoryClass) { + return urlFactoryFilter.test(scheme, urlContextFactoryClass); + } + + @Override + public boolean test(Class factoryClass) { + return factoryFilter.test(factoryClass); + } + + @Override + public ContextFactoryFilter negate() { + return new ContextFactoryFilter(factoryFilter.negate(), urlFactoryFilter.negate()); + } + + private static final ContextFactoryFilter LDAP_CONTEXT_FILTER = + new ContextFactoryFilter(ObjectFactoriesFilter::checkLdapFilter, + ObjectFactoriesFilter::ldapFollowsURL); + } + + private static final class URLSchemes { + private static final String LDAP_DISABLED_URL_SCHEMES_PROP = + "jdk.jndi.ldap.disabledURLSchemes"; + private static final String DEFAULT_LDAP_DISABLED_URL_SCHEMES = + LdapCtx.trustSerialData() ? "" : "rmi"; + private static final String LDAP_DISABLED_URL_SCHEMES = + System.getProperty(LDAP_DISABLED_URL_SCHEMES_PROP, + DEFAULT_LDAP_DISABLED_URL_SCHEMES); + static final List LDAP_DISABLED_URL_SCHEMES_LIST; + + static { + String[] protocols = LDAP_DISABLED_URL_SCHEMES.split(","); + LDAP_DISABLED_URL_SCHEMES_LIST = Stream.of(protocols) + .filter(Predicate.not(String::isEmpty)) + .map(s -> s.toLowerCase(Locale.ROOT)) + .toList(); + } + } + + /** + * Returns a {@code ContextFactoryFilter} used to check whether Reference + * objects retrieved from an LDAP context should be processed. + * The returned {@code ContextFactoryFilter} should be used to check + * whether a given object factory class can be used to reconstruct an object + * from a Reference, or whether a URL Reference of a given scheme can be + * processed using a given URL Context Factory class. + * + * @return a {@code ContextFactoryFilter} used to check whether Reference + * objects retrieved from an LDAP context should be processed. + */ + public static ContextFactoryFilter ldapFactoryFilter() { + return ContextFactoryFilter.LDAP_CONTEXT_FILTER; + } + + /** + * Checks whether URL References of the given scheme should be + * processed with the given URL Context Factory class, when the + * URL Reference was retrieved from LDAP. + * @param scheme the URL scheme + * @param urlContextFactory the URL Context Factory class + * @return true if the URL Reference should be processed by the + * given urlContextFactory class. + */ + public static boolean ldapFollowsURL(String scheme, Class urlContextFactory) { + // do not follow URLs if they are disabled; + for (String p : URLSchemes.LDAP_DISABLED_URL_SCHEMES_LIST) { + if (p.equalsIgnoreCase(scheme)) return false; + } + return true; + } + + /** + * This method is called from NamingManagerHelper to verify whether + * a URL Reference of the given URL scheme can be processed by the + * given ObjectFactory. + * @param scheme the URL scheme + * @param urlContextFactory the URL Context Factory selected for that scheme + * @param factoryFilter the factory filter. If the Reference was retrieved from + * LDAP this is typically an instance of ContextFactoryFilter. + * @return true if a URL Reference of the given URL scheme is allowed to be processed + * by the given ObjectFactory. + */ + public static boolean checkURLContextFactory(String scheme, + ObjectFactory urlContextFactory, + Predicate factoryFilter) { + if (factoryFilter instanceof ContextFactoryFilter ucf) { + return ucf.checkURLContextFactory(scheme, urlContextFactory.getClass()); + } + return true; + } + /** * Checks if serial filter configured with {@code "jdk.jndi.object.factoriesFilter"} * system property value allows instantiation of the specified objects factory class. diff --git a/src/java.naming/share/classes/module-info.java b/src/java.naming/share/classes/module-info.java index 5a731000194c..a31e808588d8 100644 --- a/src/java.naming/share/classes/module-info.java +++ b/src/java.naming/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -89,18 +89,34 @@ *

      The following implementation specific system properties are supported by the * default LDAP Naming Service Provider implementation in the JDK: *

        - *
      • {@systemProperty com.sun.jndi.ldap.object.trustSerialData}: + *
      • {@systemProperty com.sun.jndi.ldap.object.trustSerialData}: *
        The value of this system property is the string representation of a boolean value * that controls the deserialization of java objects from the {@code javaSerializedData} LDAP * attribute, reconstruction of RMI references from the {@code javaRemoteLocation} LDAP attribute, and * reconstruction of {@linkplain javax.naming.BinaryRefAddr binary reference addresses} from - * the {@code javaReferenceAddress} LDAP attribute. + * the {@code javaReferenceAddress} LDAP attribute. It also controls whether RMI URL References + * obtained from LDAP will be followed. * To allow the deserialization or reconstruction of java objects from {@code javaSerializedData}, * {@code javaRemoteLocation} or {@code javaReferenceAddress} attributes, the system property value * can be set to {@code true} (case insensitive). *
        If the property is not specified the deserialization of java objects * from the {@code javaSerializedData}, the {@code javaRemoteLocation}, or {@code javaReferenceAddress} - * attributes is not allowed. + * attributes is not allowed. In addition URL References obtained from LDAP that use the + * {@code rmi} scheme will not be followed by default, unless the default value of the {@link + * ##disabledURLSchemes jdk.jndi.ldap.disabledURLSchemes} system property is overridden on the + * java command line to allow it. + *
      • + *
      • {@systemProperty jdk.jndi.ldap.disabledURLSchemes}: + *
        The value of this system property is a comma separated list of URL schemes for which + * use of a URL Context Factory is disabled when processing {@linkplain javax.naming.StringRefAddr + * string reference addresses} in the built-in JNDI/LDAP provider. If the value of that property + * is empty then all schemes are allowed. + *
        The default value of that property is empty if {@linkplain ##trustSerialData + * deserialization of java objects} is allowed, and {@code "rmi"} if deserialization of java + * objects is not allowed: in that latter case {@linkplain javax.naming.StringRefAddr string reference + * addresses} of {@linkplain javax.naming.StringRefAddr#getType() type} {@code "URL"} whose {@linkplain + * javax.naming.StringRefAddr#getContent() content} is an URL string with scheme {@code "rmi"} + * will not be followed. *
      • *
      • {@systemProperty jdk.jndi.object.factoriesFilter}: *
        The value of this system property defines a filter used by diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java index 278e2bf23b14..05f0a4cb7c2d 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/AuthenticationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,7 +49,7 @@ * Implementation of Http Basic authentication. */ class AuthenticationFilter implements HeaderFilter { - volatile MultiExchange exchange; + private static final Base64.Encoder encoder = Base64.getEncoder(); static final int DEFAULT_RETRY_LIMIT = 3; @@ -67,12 +67,12 @@ class AuthenticationFilter implements HeaderFilter { // A public no-arg constructor is required by FilterFactory public AuthenticationFilter() {} - private PasswordAuthentication getCredentials(String header, + private PasswordAuthentication getCredentials(HttpClientImpl client, + String header, boolean proxy, HttpRequestImpl req) throws IOException { - HttpClientImpl client = exchange.client(); java.net.Authenticator auth = client.authenticator() .orElseThrow(() -> new IOException("No authenticator set")); @@ -144,33 +144,51 @@ private URI getProxyURI(HttpRequestImpl r) { } @Override - public void request(HttpRequestImpl r, MultiExchange e) throws IOException { + public void request(HttpRequestImpl r, MultiExchange exchange) throws IOException { // use preemptive authentication if an entry exists. - Cache cache = getCache(e); - this.exchange = e; + Cache cache = getCache(exchange.client()); + AuthInfo serverAuth = exchange.serverauth; + AuthInfo proxyAuth = exchange.proxyauth; + + if (serverAuth != null && !sameOriginAndScheme(serverAuth.requestor, r.uri())) { + serverAuth = exchange.serverauth = null; + } + if (proxyAuth != null && !sameOriginAndScheme(proxyAuth.requestor, getProxyURI(r))) { + proxyAuth = exchange.proxyauth = null; + } // Proxy - if (exchange.proxyauth == null) { + if (proxyAuth == null) { URI proxyURI = getProxyURI(r); if (proxyURI != null) { CacheEntry ca = cache.get(proxyURI, true); if (ca != null) { - exchange.proxyauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8); + exchange.proxyauth = new AuthInfo(true, ca.scheme, proxyURI, null, ca, ca.isUTF8); addBasicCredentials(r, true, ca.value, ca.isUTF8); } } } // Server - if (exchange.serverauth == null) { - CacheEntry ca = cache.get(r.uri(), false); + if (serverAuth == null) { + URI requestor = r.uri(); + CacheEntry ca = cache.get(requestor, false); if (ca != null) { - exchange.serverauth = new AuthInfo(true, ca.scheme, null, ca, ca.isUTF8); + exchange.serverauth = new AuthInfo(true, ca.scheme, requestor, null, ca, ca.isUTF8); addBasicCredentials(r, false, ca.value, ca.isUTF8); } } } + private static boolean sameOriginAndScheme(URI uri, URI other) { + // in this context we don't need to use equalsIgnoreCase. + if (uri == other) return true; + if (uri == null || other == null) return false; + if (!Objects.equals(uri.getScheme(), other.getScheme())) return false; + if (!Objects.equals(uri.getRawAuthority(), other.getRawAuthority())) return false; + return true; + } + // TODO: refactor into per auth scheme class private static void addBasicCredentials(HttpRequestImpl r, boolean proxy, @@ -202,6 +220,7 @@ private static void addBasicCredentials(HttpRequestImpl r, static class AuthInfo { final boolean fromcache; final String scheme; + final URI requestor; int retries; PasswordAuthentication credentials; // used in request CacheEntry cacheEntry; // if used @@ -209,19 +228,24 @@ static class AuthInfo { AuthInfo(boolean fromcache, String scheme, - PasswordAuthentication credentials, boolean isUTF8) { + URI requestor, + PasswordAuthentication credentials, + boolean isUTF8) { this.fromcache = fromcache; this.scheme = scheme; this.credentials = credentials; + this.requestor = requestor; this.retries = 1; this.isUTF8 = isUTF8; } AuthInfo(boolean fromcache, String scheme, + URI requestor, PasswordAuthentication credentials, - CacheEntry ca, boolean isUTF8) { - this(fromcache, scheme, credentials, isUTF8); + CacheEntry ca, + boolean isUTF8) { + this(fromcache, scheme, requestor, credentials, isUTF8); assert credentials == null || (ca != null && ca.value == null); cacheEntry = ca; } @@ -230,7 +254,9 @@ AuthInfo retryWithCredentials(PasswordAuthentication pw, boolean isUTF8) { // If the info was already in the cache we need to create a new // instance with fromCache==false so that it's put back in the // cache if authentication succeeds - AuthInfo res = fromcache ? new AuthInfo(false, scheme, pw, isUTF8) : this; + AuthInfo res = fromcache + ? new AuthInfo(false, scheme, requestor, pw, isUTF8) + : this; res.credentials = Objects.requireNonNull(pw); res.retries = retries; return res; @@ -239,7 +265,9 @@ AuthInfo retryWithCredentials(PasswordAuthentication pw, boolean isUTF8) { @Override public HttpRequestImpl response(Response r) throws IOException { - Cache cache = getCache(exchange); + MultiExchange exchange = r.exchange.multi; + HttpClientImpl client = exchange.client(); + Cache cache = getCache(client); int status = r.statusCode(); HttpHeaders hdrs = r.headers(); HttpRequestImpl req = r.request(); @@ -253,28 +281,37 @@ public HttpRequestImpl response(Response r) throws IOException { } if (status != PROXY_UNAUTHORIZED) { - if (exchange.proxyauth != null && !exchange.proxyauth.fromcache) { - AuthInfo au = exchange.proxyauth; + AuthInfo au = exchange.proxyauth; + if (au != null && !au.fromcache) { URI proxyURI = getProxyURI(req); if (proxyURI != null) { - exchange.proxyauth = null; - cache.store(au.scheme, proxyURI, true, au.credentials, au.isUTF8); + if (sameOriginAndScheme(au.requestor, proxyURI)) { + exchange.proxyauth = null; + cache.store(au.scheme, au.requestor, true, au.credentials, au.isUTF8); + } } } if (status != UNAUTHORIZED) { + au = exchange.serverauth; // check if any authentication succeeded for first time - if (exchange.serverauth != null && !exchange.serverauth.fromcache) { - AuthInfo au = exchange.serverauth; - cache.store(au.scheme, req.uri(), false, au.credentials, au.isUTF8); + if (au != null && !au.fromcache) { + URI requestor = req.uri(); + if (sameOriginAndScheme(au.requestor, requestor)) { + cache.store(au.scheme, au.requestor, false, au.credentials, au.isUTF8); + } } + assert status != PROXY_UNAUTHORIZED && status != UNAUTHORIZED; return null; } + assert status == UNAUTHORIZED; } + assert status == PROXY_UNAUTHORIZED || status == UNAUTHORIZED; + boolean proxy = status == PROXY_UNAUTHORIZED; String authname = proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; List authvals = hdrs.allValues(authname); - if (authvals.isEmpty() && exchange.client().authenticator().isPresent()) { + if (authvals.isEmpty() && client.authenticator().isPresent()) { throw new IOException(authname + " header missing for response code " + status); } String authval = null; @@ -310,16 +347,17 @@ public HttpRequestImpl response(Response r) throws IOException { } AuthInfo au = proxy ? exchange.proxyauth : exchange.serverauth; - if (au == null) { + URI requestor = proxy ? getProxyURI(req) : req.uri(); + if (au == null || !sameOriginAndScheme(au.requestor, requestor)) { // if no authenticator, let the user deal with 407/401 - if (exchange.client().authenticator().isEmpty()) return null; + if (client.authenticator().isEmpty()) return null; - PasswordAuthentication pw = getCredentials(authval, proxy, req); + PasswordAuthentication pw = getCredentials(client, authval, proxy, req); if (pw == null) { throw new IOException("No credentials provided"); } // No authentication in request. Get credentials from user - au = new AuthInfo(false, "Basic", pw, isUTF8); + au = new AuthInfo(false, "Basic",requestor, pw, isUTF8); if (proxy) { exchange.proxyauth = au; } else { @@ -332,16 +370,17 @@ public HttpRequestImpl response(Response r) throws IOException { throw new IOException("too many authentication attempts. Limit: " + retry_limit); } else { + assert sameOriginAndScheme(au.requestor, requestor); // we sent credentials, but they were rejected if (au.fromcache) { cache.remove(au.cacheEntry); } // if no authenticator, let the user deal with 407/401 - if (exchange.client().authenticator().isEmpty()) return null; + if (client.authenticator().isEmpty()) return null; // try again - PasswordAuthentication pw = getCredentials(authval, proxy, req); + PasswordAuthentication pw = getCredentials(client, authval, proxy, req); if (pw == null) { throw new IOException("No credentials provided"); } @@ -362,8 +401,7 @@ public HttpRequestImpl response(Response r) throws IOException { // be garbage collected when no longer referenced. static final WeakHashMap caches = new WeakHashMap<>(); - static synchronized Cache getCache(MultiExchange exchange) { - HttpClientImpl client = exchange.client(); + static synchronized Cache getCache(HttpClientImpl client) { Cache c = caches.get(client); if (c == null) { c = new Cache(); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java b/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java new file mode 100644 index 000000000000..fc2b3c428f8a --- /dev/null +++ b/src/java.net.http/share/classes/jdk/internal/net/http/CheckFilename.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.net.http; + +import java.util.Set; +import java.util.List; +import java.util.Locale; +import jdk.internal.util.OperatingSystem; + +/** + * Checks for disallowed filenames that may be provided by a server + * which we do not want to write to. On Windows we disallow any filename + * that looks like a DOS legacy device. No restrictions on other platforms. + */ +final class CheckFilename { + + private CheckFilename() { + throw new InternalError(); + } + + private static final boolean isWindows = OperatingSystem.isWindows(); + + /** + * Reserved names documented at: + * https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file + * and other Microsoft api documentation + */ + private static final Set WINDOWS_RESERVED_NAMES = + Set.of("AUX", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", + "COM7", "COM8", "COM9", "COM¹", "COM²", "COM³", "CON", "CONIN$", + "CONOUT$", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", + "LPT9", "LPT¹", "LPT²", "LPT³", "NUL", "PRN"); + + /** + * name must be the final component of the pathname only. + * Possibly including an extension. + */ + public static boolean isAllowed(String name) { + if (!isWindows) + return true; + + // Microsoft documents reserved device names followed by extensions as equivalent to the device name. + // `CON.txt`, `NUL.tar.gz`, `COM1.txt`, etc. should not be allowed. + int dot = name.indexOf('.'); + if (dot != -1) { + name = name.substring(0, dot); + } + name = name.toUpperCase(Locale.ROOT) + .stripTrailing(); + + return !WINDOWS_RESERVED_NAMES.contains(name); + } +} diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java index 024de6bd49b3..cb7b4889a672 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http3Connection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -668,13 +668,27 @@ void close(final Http3Error error, final String logMsg, closeCause == null ? "" : closeCause.toString()); debug.log("State is: " + describeClosedState(closedState)); } + // record the error in each unfinished exchange exchanges.values().forEach(e -> e.recordError(closeCause)); // close the underlying QUIC connection connection.close(error.code(), logMsg, closeCause); final TerminationCause tc = connection.quicConnection.terminationCause(); assert tc != null : "termination cause is null"; - // close all HTTP streams - exchanges.values().forEach(exchange -> exchange.cancelImpl(tc.getCloseCause(), error)); + + // Figure out whether this is an orderly close. + boolean orderlyClose = tc.isAppLayer() && tc.getCloseCode() == H3_NO_ERROR.code(); + if (orderlyClose) { + // For an orderly close, we will let the caller pull the bytes + // from exchanges which have received all promised bytes + // (but haven't processed them yet), but cancel all exchanges + // for which not all bytes have been received. + exchanges.values().stream() + .filter(Http3ExchangeImpl::shouldCancelOnOrderlyClose) + .forEach(exchange -> exchange.cancelImpl(tc.getCloseCause(), error)); + } else { + // close all HTTP streams + exchanges.values().forEach(exchange -> exchange.cancelImpl(tc.getCloseCause(), error)); + } pushManager.cancelAllPromises(tc.getCloseCause(), error); discardConnectionState(); // No longer wait for reading HTTP/3 stream types: @@ -717,6 +731,13 @@ void connectionError(Http3Stream exchange, Throwable throwable, long errorCod final Optional error = Http3Error.fromCode(errorCode); assert error.isPresent() : "not a HTTP3 error code: " + errorCode; close(error.get(), logMsg, throwable); + if (exchange instanceof Http3ExchangeImpl impl) { + // if the error originates from the exchange itself, + // makes sure it gets cancelled even if it has buffered + // all data, as processing buffered data can still trigger + // malformed frame errors + impl.cancelImpl(throwable, error.get()); + } } public String toString() { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http3ExchangeImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http3ExchangeImpl.java index 4625968682f1..b8970293b362 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http3ExchangeImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http3ExchangeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,6 +84,8 @@ import static jdk.internal.net.http.common.Utils.readContentLength; import static jdk.internal.net.http.http3.ConnectionSettings.UNLIMITED_MAX_FIELD_SECTION_SIZE; +import static jdk.internal.net.http.quic.streams.QuicReceiverStream.ReceivingStreamState.DATA_READ; +import static jdk.internal.net.http.quic.streams.QuicReceiverStream.ReceivingStreamState.DATA_RECVD; /** * This class represents an HTTP/3 Request/Response stream. @@ -196,7 +198,12 @@ Http3Connection http3Connection() { } void recordError(Throwable closeCause) { - errorRef.compareAndSet(null, closeCause); + if (!allDataReceivedButStillBuffered()) { + // only record the error if we have not received + // all bytes; Otherwise we can let the caller + // pull them. + errorRef.compareAndSet(null, closeCause); + } } private sealed class HeadersConsumer extends StreamHeadersConsumer permits PushHeadersConsumer { @@ -1724,6 +1731,31 @@ void onSubscriptionError(Throwable t) { readScheduler.runOrSchedule(); } + // We do not want to cancel the exchange on orderly + // close if all data has been received but some of + // it is still buffered. In that case we will + // let the client pull the buffered data even + // after the underlying connection has closed. + boolean shouldCancelOnOrderlyClose() { + return !allDataReceivedButStillBuffered(); + } + + private boolean allDataReceivedButStillBuffered() { + // if responseReceived is true, no data is still + // buffered + if (responseReceived) return false; + + // if the headerFrameReader is blocked, the data + // the QPack decoder is waiting for may not be present + // in the decoder stream, or may never arrive: + // in this case, we will consider that not all data has + // arrived, regardless of our receiving stream state. + if (headerFrameReader.isBlocked()) return false; + + var state = stream.receivingState(); + return state == DATA_RECVD || state == DATA_READ; + } + // This loop is triggered to push response body data into // the body subscriber. It is called from the processQuicData // loop. However, we cannot call onNext() if we have no demands. diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java index aacfb27e99ad..1fdc5ffc7994 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java @@ -1839,8 +1839,8 @@ public String toString() { } private void initFilters() { - addFilter(AuthenticationFilter.class); addFilter(RedirectFilter.class); + addFilter(AuthenticationFilter.class); if (this.cookieHandler != null) { addFilter(CookieFilter.class); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java b/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java index a3c588a8f922..a88860232136 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/RedirectFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,23 +28,15 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; -import java.net.http.HttpClient; +import java.net.http.HttpClient.Redirect; import java.net.http.HttpHeaders; -import java.util.concurrent.locks.ReentrantLock; import jdk.internal.net.http.common.Log; import jdk.internal.net.http.common.Utils; class RedirectFilter implements HeaderFilter { - private final ReentrantLock stateLock = new ReentrantLock(); - HttpRequestImpl request; - HttpClientImpl client; - HttpClient.Redirect policy; - String method; - MultiExchange exchange; static final int DEFAULT_MAX_REDIRECTS = 5; - URI uri; /* * NOT_MODIFIED status code results from a conditional GET where @@ -62,28 +54,11 @@ public RedirectFilter() {} @Override public void request(HttpRequestImpl r, MultiExchange e) throws IOException { - stateLock.lock(); - try { - this.request = r; - this.client = e.client(); - this.policy = client.followRedirects(); - - this.method = r.method(); - this.uri = r.uri(); - this.exchange = e; - } finally { - stateLock.unlock(); - } } @Override public HttpRequestImpl response(Response r) throws IOException { - stateLock.lock(); - try { - return handleResponse(r); - } finally { - stateLock.unlock(); - } + return handleResponse(r); } private static String redirectedMethod(int statusCode, String orig) { @@ -120,7 +95,9 @@ private static boolean isRedirecting(int statusCode) { */ private HttpRequestImpl handleResponse(Response r) { int rcode = r.statusCode(); - if (rcode == 200 || policy == HttpClient.Redirect.NEVER) { + MultiExchange exchange = r.exchange.multi; + Redirect policy = exchange.client.followRedirects(); + if (rcode == 200 || policy == Redirect.NEVER) { return null; } @@ -128,10 +105,12 @@ private HttpRequestImpl handleResponse(Response r) { return null; if (isRedirecting(rcode)) { - URI redir = getRedirectedURI(r.headers()); - String newMethod = redirectedMethod(rcode, method); + HttpRequestImpl request = r.request(); + URI from = request.uri(); + URI redir = getRedirectedURI(from, r.headers()); + String newMethod = redirectedMethod(rcode, request.method()); Log.logTrace("response code: {0}, redirected URI: {1}", rcode, redir); - if (canRedirect(redir) && ++exchange.numberOfRedirects < max_redirects) { + if (canRedirect(from, redir, policy) && ++exchange.numberOfRedirects < max_redirects) { Log.logTrace("redirect to: {0} with method: {1}", redir, newMethod); return HttpRequestImpl.newInstanceForRedirection(redir, newMethod, request, rcode != 303); } else { @@ -142,7 +121,7 @@ private HttpRequestImpl handleResponse(Response r) { return null; } - private URI getRedirectedURI(HttpHeaders headers) { + private URI getRedirectedURI(URI uri, HttpHeaders headers) { URI redirectedURI; redirectedURI = headers.firstValue("Location") .map(URI::create) @@ -155,7 +134,7 @@ private URI getRedirectedURI(HttpHeaders headers) { return redirectedURI; } - private boolean canRedirect(URI redir) { + private boolean canRedirect(URI uri, URI redir, Redirect policy) { String newScheme = redir.getScheme(); String oldScheme = uri.getScheme(); return switch (policy) { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java index a74c632c2185..d1f5b21e054f 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java @@ -320,7 +320,7 @@ public BodySubscriber apply(ResponseInfo responseInfo) { filenameParam = filenameParam.trim(); - if (PROHIBITED.contains(filenameParam)) { + if (PROHIBITED.contains(filenameParam) || !CheckFilename.isAllowed(filenameParam)) { throw unchecked(responseInfo, "Prohibited Content-Disposition filename parameter:" + filenameParam); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java index 8c2c095ba0fa..e6933a3c2aae 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseContent.java @@ -26,6 +26,7 @@ package jdk.internal.net.http; import java.io.IOException; +import java.net.ProtocolException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -141,7 +142,7 @@ class ChunkedBodyParser implements BodyParser { volatile boolean cr = false; // tryReadChunkLength has found CR volatile int chunkext = 0; // number of bytes already read in the chunk extension volatile int digits = 0; // number of chunkLength bytes already read - volatile int bytesToConsume; // number of bytes that still need to be consumed before proceeding + volatile int crlfBytesToConsume; // number of CRLF bytes that still need to be consumed volatile ChunkState state = ChunkState.READING_LENGTH; // current state volatile AbstractSubscription sub; ChunkedBodyParser(Consumer onComplete) { @@ -252,8 +253,8 @@ public void accept(ByteBuffer b) { private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException { assert state == ChunkState.READING_LENGTH; while (chunkbuf.hasRemaining()) { - if (chunkext + digits >= MAX_CHUNK_HEADER_SIZE) { - throw new IOException("Chunk header size too long: " + (chunkext + digits)); + if (chunkext >= MAX_CHUNK_HEADER_SIZE - digits) { + throw new IOException("Chunk header size too long: " + (chunkext + (long) digits)); } int c = chunkbuf.get(); if (cr) { @@ -265,8 +266,8 @@ private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException { } if (c == CR) { cr = true; - if (digits == 0 && debug.on()) { - debug.log("tryReadChunkLen: invalid chunk header? No digits in chunkLen?"); + if (digits == 0) { + throw new ProtocolException("Chunk header doesn't contain any digits"); } } else if (cr == false && chunkext > 0) { // we have seen a non digit character after the chunk length. @@ -291,41 +292,33 @@ private int tryReadChunkLen(ByteBuffer chunkbuf) throws IOException { } } else { digits++; - partialChunklen = partialChunklen * 16 + digit; + long nextPartialChunklen = partialChunklen * 16L + digit; + if (nextPartialChunklen > Integer.MAX_VALUE) { + throw new ProtocolException("chunk length too big: " + nextPartialChunklen); + } + partialChunklen = (int) nextPartialChunklen; } } } return -1; } - - // try to consume as many bytes as specified by bytesToConsume. - // returns the number of bytes that still need to be consumed. - // In practice this method is only called to consume one CRLF pair - // with bytesToConsume set to 2, so it will only return 0 (if completed), - // 1, or 2 (if chunkbuf doesn't have the 2 chars). - private int tryConsumeBytes(ByteBuffer chunkbuf) throws IOException { - int n = bytesToConsume; - if (n > 0) { - int e = Math.min(chunkbuf.remaining(), n); - - // verifies some assertions - // this methods is called only to consume CRLF - if (Utils.ASSERTIONSENABLED) { - assert n <= 2 && e <= 2; - ByteBuffer tmp = chunkbuf.slice(); - // if n == 2 assert that we will first consume CR - assert (n == 2 && e > 0) ? tmp.get() == CR : true; - // if n == 1 || n == 2 && e == 2 assert that we then consume LF - assert (n == 1 || e == 2) ? tmp.get() == LF : true; + private boolean tryConsumeCRLF(ByteBuffer buffer) throws ProtocolException { + assert crlfBytesToConsume >= 0 && crlfBytesToConsume <= 2; + for (; crlfBytesToConsume > 0 && buffer.remaining() > 0; crlfBytesToConsume--) { + byte b = buffer.get(); + if (crlfBytesToConsume == 1) { // 1 byte to go (implies already consumed CR), check LF + if (b != LF) { + throw new ProtocolException("LF expected"); + } + } else { // 2 bytes to go, check CR + assert crlfBytesToConsume == 2; + if (b != CR) { + throw new ProtocolException("CR expected"); + } } - - chunkbuf.position(chunkbuf.position() + e); - n -= e; - bytesToConsume = n; } - assert n >= 0; - return n; + return crlfBytesToConsume == 0; } /** @@ -338,7 +331,7 @@ private int tryConsumeBytes(ByteBuffer chunkbuf) throws IOException { */ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { int unfulfilled = bytesremaining; - int toconsume = bytesToConsume; + int toconsume = crlfBytesToConsume; ChunkState st = state; if (st == ChunkState.READING_LENGTH && chunklen == -1) { if (debug.on()) debug.log(() -> "Trying to read chunk len" @@ -349,7 +342,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { if (debug.on()) debug.log("Got chunk len %d", clen); cr = false; partialChunklen = 0; unfulfilled = bytesremaining = clen; - if (clen == 0) toconsume = bytesToConsume = 2; // that was the last chunk + if (clen == 0) toconsume = crlfBytesToConsume = 2; // that was the last chunk else st = state = ChunkState.READING_DATA; // read the data } @@ -357,12 +350,12 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { if (debug.on()) debug.log("Trying to consume bytes: %d (remaining in buffer: %s)", toconsume, chunk.remaining()); - if (tryConsumeBytes(chunk) > 0) { + if (!tryConsumeCRLF(chunk)) { return READMORE; } } - toconsume = bytesToConsume; + toconsume = crlfBytesToConsume; assert toconsume == 0; @@ -394,7 +387,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { debug.log( "Returning chunk bytes: %d", bytes2return); returnBuffer = Utils.sliceWithLimitedCapacity(chunk, bytes2return).asReadOnlyBuffer(); unfulfilled = bytesremaining -= bytes2return; - if (unfulfilled == 0) bytesToConsume = 2; + if (unfulfilled == 0) crlfBytesToConsume = 2; } assert unfulfilled >= 0; @@ -408,7 +401,7 @@ ByteBuffer tryReadOneHunk(ByteBuffer chunk) throws IOException { // then we will come back here later - skipping the block // that reads data because remaining==0, and finding // that the two bytes are now consumed. - if (tryConsumeBytes(chunk) == 0) { + if (tryConsumeCRLF(chunk)) { // we're done for this chunk! reset all states and // prepare to read the next chunk. chunklen = -1; diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java index 9826b280438d..779040bb89ac 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -130,7 +130,7 @@ class Stream extends ExchangeImpl { final HeadersConsumer rspHeadersConsumer; final HttpHeadersBuilder responseHeadersBuilder; final HttpHeaders requestPseudoHeaders; - volatile HttpResponse.BodySubscriber responseSubscriber; + volatile BodySubscriber responseSubscriber; final HttpRequest.BodyPublisher requestPublisher; volatile RequestSubscriber requestSubscriber; volatile int responseCode; @@ -139,7 +139,7 @@ class Stream extends ExchangeImpl { private final AtomicReference errorRef = new AtomicReference<>(); final CompletableFuture requestBodyCF = new MinimalFuture<>(); volatile CompletableFuture responseBodyCF; - volatile HttpResponse.BodySubscriber pendingResponseSubscriber; + volatile BodySubscriber pendingResponseSubscriber; volatile boolean stopRequested; /** True if END_STREAM has been seen in a frame received on this stream. */ @@ -182,13 +182,18 @@ HttpConnection connection() { return connection.connection; } + private CompletableFuture getBody(BodySubscriber subscriber) { + var bodyCf = ResponseSubscribers.getBodyAsync(exchange.parentExecutor, subscriber); + return bodyCf == null ? null : bodyCf.toCompletableFuture(); + } + /** * Invoked either from incoming() -> {receiveDataFrame() or receiveResetFrame() } * of after user subscription window has re-opened, from SubscriptionBase.request() */ private void schedule() { boolean onCompleteCalled = false; - HttpResponse.BodySubscriber subscriber = responseSubscriber; + BodySubscriber subscriber = responseSubscriber; // prevents drainInputQueue() from running concurrently inputQLock.lock(); try { @@ -203,7 +208,22 @@ private void schedule() { if (debug.on()) debug.log("subscribing user subscriber"); subscriber.onSubscribe(userSubscription); } - while (!inputQ.isEmpty() && errorRef.get() == null) { + + CompletableFuture bodyCf = null; + while (!inputQ.isEmpty()) { + if (errorRef.get() != null) { + if (bodyCf == null) bodyCf = getBody(subscriber); + + // if the bodyCf is completed exceptionally, or cancelled, + // there is no point in pulling the data. + // if bodyCf is null just stop here; this is not expected. + if (bodyCf == null || bodyCf.isCompletedExceptionally()) break; + + // otherwise, we just ignore the error for now to give a chance + // to the caller to pull the buffered data. The error will + // eventually be reported when the buffered data has been pulled, + // unless the entire response was successfully decoded and completed. + } Http2Frame frame = inputQ.peek(); if (frame instanceof ResetFrame rf) { inputQ.remove(); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/qpack/readers/HeaderFrameReader.java b/src/java.net.http/share/classes/jdk/internal/net/http/qpack/readers/HeaderFrameReader.java index a4ea55661fe6..78949bfb481e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/qpack/readers/HeaderFrameReader.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/qpack/readers/HeaderFrameReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -321,6 +321,10 @@ private void resetPrefixVars() { fieldSectionSizeTracker.set(0); } + public boolean isBlocked() { + return state == State.AWAITING_DT_INSERT_COUNT; + } + public FieldSectionPrefix decodedSectionPrefix() { if (deltaBase == -1L) { throw new IllegalStateException("Field Section Prefix not parsed yet"); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java index ed14fc49f19d..f225afe6d0b1 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicConnectionImpl.java @@ -1122,12 +1122,16 @@ public void increaseProcessedData(long diff) { } private long bumpMaxData() { - long newMaxData = processedData + desiredBufferSize; + var processedData = this.processedData; + long newMaxData = processedData >= MAX_VL_INTEGER - desiredBufferSize + ? MAX_VL_INTEGER + : processedData + desiredBufferSize; long maxData = this.maxData; - if (newMaxData - maxData < (desiredBufferSize / 5)) { - return 0; - } - while (maxData < newMaxData) { + while (maxData < newMaxData) { // CAS-loop on `maxData` + if (newMaxData != MAX_VL_INTEGER && // Do not skip the final clamp-to-max bump + newMaxData - maxData < (desiredBufferSize / 5)) { // Skip small ordinary bumps + break; + } if (MAX_RCV_DATA.compareAndSet(this, maxData, newMaxData)) return newMaxData; maxData = this.maxData; @@ -1136,7 +1140,12 @@ private long bumpMaxData() { } public boolean needSendMaxData() { - return maxData - processedData < desiredBufferSize/2; + long maxData = this.maxData; + // `maxData` is the value that was already sent to the peer. + // `bumpMaxData()` clamps `maxData` at `MAX_VL_INTEGER`. + // Once `maxData == MAX_VL_INTEGER`, there is no larger legal value to advertise. + return maxData < MAX_VL_INTEGER && + maxData - processedData < (desiredBufferSize / 2); } String logTag() { return logTag.get(); } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java index 09d05f910661..338e690ce0e3 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/AckFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,7 +84,7 @@ public final class AckFrame extends QuicFrame { null, type, QuicTransportErrors.FRAME_ENCODING_ERROR); } - var ackRanges = new ArrayList(ackRangeCount + 1); + var ackRanges = new ArrayList(); AckRange first = AckRange.of(0, firstAckRange); ackRanges.addFirst(first); for (int i=1; i <= ackRangeCount; i++) { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java index b597e53c06c3..693eb059815c 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/frames/StreamFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,9 @@ import java.util.Objects; import jdk.internal.net.http.common.Utils; +import jdk.internal.net.http.quic.VariableLengthEncoder; import jdk.internal.net.http.quic.packets.QuicPacketEncoder; +import jdk.internal.net.quic.QuicTransportErrors; import jdk.internal.net.quic.QuicTransportException; /** @@ -69,6 +71,11 @@ public final class StreamFrame extends QuicFrame { } else { length = -1; } + if (length > VariableLengthEncoder.MAX_ENCODED_INTEGER - offset) { + throw new QuicTransportException( + "Maximum stream frame offset exceeded", + null, type, QuicTransportErrors.FRAME_ENCODING_ERROR); + } if (length == -1) { int remaining = buffer.remaining(); streamData = Utils.sliceOrCopy(buffer, buffer.position(), remaining); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java index 233a1a35778b..fc4984a30a2e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/packets/QuicPacketDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -888,26 +888,24 @@ private static final class IncomingInitialPacket final int size; final int length; - final int tokenLength; final long packetNumber; final byte[] token; final List frames; IncomingInitialPacket(QuicConnectionId sourceId, QuicConnectionId destinationId, int version, - int tokenLength, byte[] token, int length, + byte[] token, int length, long packetNumber, List frames, int size) { super(sourceId, destinationId, version); this.size = size; this.length = length; - this.tokenLength = tokenLength; this.token = token; this.packetNumber = packetNumber; this.frames = List.copyOf(frames); } @Override - public int tokenLength() { return tokenLength; } + public int tokenLength() { return token != null ? token.length : 0; } @Override public byte[] token() { return token; } @@ -968,7 +966,7 @@ static IncomingInitialPacket decode(PacketReader reader, CodingContext context) } // Get number of bytes needed to store the length of the token - var tokenLength = (int) reader.readTokenLength(); + var tokenLength = reader.readTokenLength(); if (debug.on()) { debug.log("IncomingInitialPacket.decode(token-length(%d), %s)", tokenLength, reader); @@ -1051,7 +1049,7 @@ static IncomingInitialPacket decode(PacketReader reader, CodingContext context) assert size == reader.bytesRead() : size - reader.bytesRead(); return new IncomingInitialPacket(sourceID, destinationID, - version, tokenLength, token, (int)packetLength, packetNumber, frames, size); + version, token, (int)packetLength, packetNumber, frames, size); } } @@ -1572,16 +1570,18 @@ public long readPacketLength() { return packetLength; } - public long readTokenLength() { + private long readTokenLength() { return readVariableLength(); } - public byte[] readToken(int tokenLength) { + public byte[] readToken(long tokenLength) { // Check to ensure that tokenLength is within valid range if (tokenLength < 0 || tokenLength > buffer.remaining()) { throw new BufferUnderflowException(); } - byte[] token = tokenLength > 0 ? new byte[tokenLength] : null; + // `int` conversion is safe due to the `> buffer.remaining()` check above. + int tokenLengthInt = Math.toIntExact(tokenLength); + byte[] token = tokenLength > 0 ? new byte[tokenLengthInt] : null; if (tokenLength > 0) { buffer.get(token); } diff --git a/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c b/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c index 4dc4137da2a7..542438ef3086 100644 --- a/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c +++ b/src/java.prefs/unix/native/libprefs/FileSystemPreferences.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,7 +67,6 @@ Java_java_util_prefs_FileSystemPreferences_lockFile0(JNIEnv *env, int fd, rc; int result[2] = {0, 0}; jintArray javaResult = NULL; - int old_umask; struct flock fl; if (!fname) @@ -86,10 +85,12 @@ Java_java_util_prefs_FileSystemPreferences_lockFile0(JNIEnv *env, fd = open(fname, O_RDONLY, 0); result[1] = errno; } else { - old_umask = umask(0); fd = open(fname, O_WRONLY|O_CREAT, permission); result[1] = errno; - umask(old_umask); + if (fd >= 0) { + /* lock file created; set correct permissions */ + fchmod(fd, permission); /* assume will succeed, ignore retval & errno */ + } } if (fd < 0) { diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java index 20bf6a147533..54f74353100c 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/CipherHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ import java.security.MessageDigest; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; + import sun.security.krb5.*; import sun.security.krb5.internal.crypto.Aes128Sha2; import sun.security.krb5.internal.crypto.Aes256Sha2; @@ -47,6 +48,7 @@ import sun.security.krb5.internal.crypto.Aes256; import sun.security.krb5.internal.crypto.ArcFourHmac; import sun.security.krb5.internal.crypto.EType; +import sun.security.util.ByteArrays; class CipherHelper { @@ -587,34 +589,43 @@ void decryptData(WrapToken token, byte[] ciphertext, int cStart, int cLen, // decrypt data in the new GSS tokens void decryptData(WrapToken_v2 token, byte[] ciphertext, int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { - - /* - Krb5Token.debug("decryptData : ciphertext = " + - Krb5Token.getHexBytes(ciphertext)); - */ + throws GSSException { + byte[] decrypted; switch (etype) { case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: - aes128Decrypt(token, ciphertext, cStart, cLen, - plaintext, pStart, key_usage); - break; + decrypted = aes128Decrypt(ciphertext, cStart, cLen, key_usage); + break; case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: - aes256Decrypt(token, ciphertext, cStart, cLen, - plaintext, pStart, key_usage); - break; + decrypted = aes256Decrypt(ciphertext, cStart, cLen, key_usage); + break; case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128: - aes128Sha2Decrypt(token, ciphertext, cStart, cLen, - plaintext, pStart, key_usage); - break; + decrypted = aes128Sha2Decrypt(ciphertext, cStart, cLen, key_usage); + break; case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192: - aes256Sha2Decrypt(token, ciphertext, cStart, cLen, - plaintext, pStart, key_usage); - break; + decrypted = aes256Sha2Decrypt(ciphertext, cStart, cLen, key_usage); + break; default: - throw new GSSException(GSSException.FAILURE, -1, + throw new GSSException(GSSException.FAILURE, -1, "Unsupported etype: " + etype); - } + } + byte[] outerHeader = token.getTokenHeader().clone(); + outerHeader[MessageToken_v2.TOKEN_RRC_POS] = 0; + outerHeader[MessageToken_v2.TOKEN_RRC_POS + 1] = 0; + int len = decrypted.length - WrapToken_v2.CONFOUNDER_SIZE - + WrapToken_v2.TOKEN_HEADER_SIZE - token.getEc(); + if (len < 0) { + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } + if (!ByteArrays.isEqual( + outerHeader, + 0, WrapToken_v2.TOKEN_HEADER_SIZE, + decrypted, + decrypted.length - WrapToken_v2.TOKEN_HEADER_SIZE, decrypted.length)) { + throw new GSSException(GSSException.DEFECTIVE_TOKEN); + } + System.arraycopy(decrypted, WrapToken_v2.CONFOUNDER_SIZE, + plaintext, pStart, len); } void decryptData(WrapToken token, InputStream cipherStream, int cLen, @@ -667,44 +678,6 @@ void decryptData(WrapToken token, InputStream cipherStream, int cLen, } } - void decryptData(WrapToken_v2 token, InputStream cipherStream, int cLen, - byte[] plaintext, int pStart, int key_usage) - throws GSSException, IOException { - - // Read encrypted data from stream - byte[] ciphertext = new byte[cLen]; - try { - Krb5Token.readFully(cipherStream, ciphertext, 0, cLen); - } catch (IOException e) { - GSSException ge = new GSSException( - GSSException.DEFECTIVE_TOKEN, -1, - "Cannot read complete token"); - ge.initCause(e); - throw ge; - } - switch (etype) { - case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: - aes128Decrypt(token, ciphertext, 0, cLen, - plaintext, pStart, key_usage); - break; - case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: - aes256Decrypt(token, ciphertext, 0, cLen, - plaintext, pStart, key_usage); - break; - case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128: - aes128Sha2Decrypt(token, ciphertext, 0, cLen, - plaintext, pStart, key_usage); - break; - case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192: - aes256Sha2Decrypt(token, ciphertext, 0, cLen, - plaintext, pStart, key_usage); - break; - default: - throw new GSSException(GSSException.FAILURE, -1, - "Unsupported etype: " + etype); - } - } - void encryptData(WrapToken token, byte[] confounder, byte[] plaintext, int start, int len, byte[] padding, OutputStream os) throws GSSException, IOException { @@ -824,45 +797,6 @@ void encryptData(WrapToken token, byte[] confounder, byte[] plaintext, } } - /* - * Encrypt data in the new GSS tokens - * - * Wrap Tokens (with confidentiality) - * { Encrypt(16-byte confounder | plaintext | 16-byte token_header) | - * 12-byte HMAC } - * where HMAC is on {16-byte confounder | plaintext | 16-byte token_header} - * HMAC is not encrypted; it is appended at the end. - */ - int encryptData(WrapToken_v2 token, byte[] confounder, byte[] tokenHeader, - byte[] plaintext, int pStart, int pLen, byte[] ciphertext, int cStart, - int key_usage) throws GSSException { - - byte[] ctext; - switch (etype) { - case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA1_96: - ctext = aes128Encrypt(confounder, tokenHeader, - plaintext, pStart, pLen, key_usage); - break; - case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA1_96: - ctext = aes256Encrypt(confounder, tokenHeader, - plaintext, pStart, pLen, key_usage); - break; - case EncryptedData.ETYPE_AES128_CTS_HMAC_SHA256_128: - ctext = aes128Sha2Encrypt(confounder, tokenHeader, - plaintext, pStart, pLen, key_usage); - break; - case EncryptedData.ETYPE_AES256_CTS_HMAC_SHA384_192: - ctext = aes256Sha2Encrypt(confounder, tokenHeader, - plaintext, pStart, pLen, key_usage); - break; - default: - throw new GSSException(GSSException.FAILURE, -1, - "Unsupported etype: " + etype); - } - System.arraycopy(ctext, 0, ciphertext, cStart, ctext.length); - return ctext.length; - } - // --------------------- DES methods /** @@ -1408,14 +1342,11 @@ private byte[] aes128Sha2Encrypt(byte[] confounder, byte[] tokenHeader, } } - private void aes128Decrypt(WrapToken_v2 token, byte[] ciphertext, - int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { - - byte[] ptext; + private byte[] aes128Decrypt(byte[] ciphertext, + int cStart, int cLen, int key_usage) throws GSSException { try { - ptext = Aes128.decryptRaw(keybytes, key_usage, + return Aes128.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, @@ -1423,36 +1354,13 @@ private void aes128Decrypt(WrapToken_v2 token, byte[] ciphertext, ge.initCause(e); throw ge; } - - /* - Krb5Token.debug("\naes128Decrypt in: " + - Krb5Token.getHexBytes(ciphertext, cStart, cLen)); - Krb5Token.debug("\naes128Decrypt plain: " + - Krb5Token.getHexBytes(ptext)); - Krb5Token.debug("\naes128Decrypt ptext: " + - Krb5Token.getHexBytes(ptext)); - */ - - // Strip out confounder and token header - int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - - WrapToken_v2.TOKEN_HEADER_SIZE; - System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, - plaintext, pStart, len); - - /* - Krb5Token.debug("\naes128Decrypt plaintext: " + - Krb5Token.getHexBytes(plaintext, pStart, len)); - */ } - private void aes128Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext, - int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { - - byte[] ptext; + private byte[] aes128Sha2Decrypt(byte[] ciphertext, + int cStart, int cLen, int key_usage) throws GSSException { try { - ptext = Aes128Sha2.decryptRaw(keybytes, key_usage, + return Aes128Sha2.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, @@ -1460,26 +1368,6 @@ private void aes128Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext, ge.initCause(e); throw ge; } - - /* - Krb5Token.debug("\naes128Sha2Decrypt in: " + - Krb5Token.getHexBytes(ciphertext, cStart, cLen)); - Krb5Token.debug("\naes128Sha2Decrypt plain: " + - Krb5Token.getHexBytes(ptext)); - Krb5Token.debug("\naes128Sha2Decrypt ptext: " + - Krb5Token.getHexBytes(ptext)); - */ - - // Strip out confounder and token header - int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - - WrapToken_v2.TOKEN_HEADER_SIZE; - System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, - plaintext, pStart, len); - - /* - Krb5Token.debug("\naes128Sha2Decrypt plaintext: " + - Krb5Token.getHexBytes(plaintext, pStart, len)); - */ } private byte[] aes256Encrypt(byte[] confounder, byte[] tokenHeader, @@ -1546,13 +1434,11 @@ private byte[] aes256Sha2Encrypt(byte[] confounder, byte[] tokenHeader, } } - private void aes256Decrypt(WrapToken_v2 token, byte[] ciphertext, - int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { + private byte[] aes256Decrypt(byte[] ciphertext, + int cStart, int cLen, int key_usage) throws GSSException { - byte[] ptext; try { - ptext = Aes256.decryptRaw(keybytes, key_usage, + return Aes256.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, @@ -1560,36 +1446,13 @@ private void aes256Decrypt(WrapToken_v2 token, byte[] ciphertext, ge.initCause(e); throw ge; } - - /* - Krb5Token.debug("\naes256Decrypt in: " + - Krb5Token.getHexBytes(ciphertext, cStart, cLen)); - Krb5Token.debug("\naes256Decrypt plain: " + - Krb5Token.getHexBytes(ptext)); - Krb5Token.debug("\naes256Decrypt ptext: " + - Krb5Token.getHexBytes(ptext)); - */ - - // Strip out confounder and token header - int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - - WrapToken_v2.TOKEN_HEADER_SIZE; - System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, - plaintext, pStart, len); - - /* - Krb5Token.debug("\naes128Decrypt plaintext: " + - Krb5Token.getHexBytes(plaintext, pStart, len)); - */ - } - private void aes256Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext, - int cStart, int cLen, byte[] plaintext, int pStart, int key_usage) - throws GSSException { + private byte[] aes256Sha2Decrypt(byte[] ciphertext, + int cStart, int cLen, int key_usage) throws GSSException { - byte[] ptext; try { - ptext = Aes256Sha2.decryptRaw(keybytes, key_usage, + return Aes256Sha2.decryptRaw(keybytes, key_usage, ZERO_IV_AES, ciphertext, cStart, cLen); } catch (GeneralSecurityException e) { GSSException ge = new GSSException(GSSException.FAILURE, -1, @@ -1597,27 +1460,6 @@ private void aes256Sha2Decrypt(WrapToken_v2 token, byte[] ciphertext, ge.initCause(e); throw ge; } - - /* - Krb5Token.debug("\naes256Sha2Decrypt in: " + - Krb5Token.getHexBytes(ciphertext, cStart, cLen)); - Krb5Token.debug("\naes256Sha2Decrypt plain: " + - Krb5Token.getHexBytes(ptext)); - Krb5Token.debug("\naes256Sha2Decrypt ptext: " + - Krb5Token.getHexBytes(ptext)); - */ - - // Strip out confounder and token header - int len = ptext.length - WrapToken_v2.CONFOUNDER_SIZE - - WrapToken_v2.TOKEN_HEADER_SIZE; - System.arraycopy(ptext, WrapToken_v2.CONFOUNDER_SIZE, - plaintext, pStart, len); - - /* - Krb5Token.debug("\naes256Sha2Decrypt plaintext: " + - Krb5Token.getHexBytes(plaintext, pStart, len)); - */ - } /** diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java index b118c9ee2157..d6a5de796721 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -904,39 +904,6 @@ public final byte[] wrap(byte[] inBuf, int offset, int len, } } - public final int wrap(byte[] inBuf, int inOffset, int len, - byte[] outBuf, int outOffset, - MessageProp msgProp) throws GSSException { - - if (state != STATE_DONE) - throw new GSSException(GSSException.NO_CONTEXT, -1, - "Wrap called in invalid state!"); - - int retVal = 0; - try { - if (cipherHelper.getProto() == 0) { - WrapToken token = - new WrapToken(this, msgProp, inBuf, inOffset, len); - retVal = token.encode(outBuf, outOffset); - } else if (cipherHelper.getProto() == 1) { - WrapToken_v2 token = - new WrapToken_v2(this, msgProp, inBuf, inOffset, len); - retVal = token.encode(outBuf, outOffset); - } - if (DEBUG != null) { - DEBUG.println("Krb5Context.wrap: token=[" - + getHexBytes(outBuf, outOffset, retVal) - + "]"); - } - return retVal; - } catch (IOException e) { - GSSException gssException = - new GSSException(GSSException.FAILURE, -1, e.getMessage()); - gssException.initCause(e); - throw gssException; - } - } - public final void wrap(byte[] inBuf, int offset, int len, OutputStream os, MessageProp msgProp) throws GSSException { @@ -994,79 +961,35 @@ public final void wrap(InputStream is, OutputStream os, public final byte[] unwrap(byte[] inBuf, int offset, int len, MessageProp msgProp) - throws GSSException { - - if (DEBUG != null) { - DEBUG.println("Krb5Context.unwrap: token=[" - + getHexBytes(inBuf, offset, len) - + "]"); - } - - if (state != STATE_DONE) { - throw new GSSException(GSSException.NO_CONTEXT, -1, - " Unwrap called in invalid state!"); - } - - byte[] data = null; - if (cipherHelper.getProto() == 0) { - WrapToken token = - new WrapToken(this, inBuf, offset, len, msgProp); - data = token.getData(); - setSequencingAndReplayProps(token, msgProp); - } else if (cipherHelper.getProto() == 1) { - WrapToken_v2 token = - new WrapToken_v2(this, inBuf, offset, len, msgProp); - data = token.getData(); - setSequencingAndReplayProps(token, msgProp); - } + throws GSSException { - return data; + if (DEBUG != null) { + DEBUG.println("Krb5Context.unwrap: token=[" + + getHexBytes(inBuf, offset, len) + + "]"); } - public final int unwrap(byte[] inBuf, int inOffset, int len, - byte[] outBuf, int outOffset, - MessageProp msgProp) throws GSSException { - - if (state != STATE_DONE) + if (state != STATE_DONE) { throw new GSSException(GSSException.NO_CONTEXT, -1, - "Unwrap called in invalid state!"); + " Unwrap called in invalid state!"); + } + byte[] data = null; if (cipherHelper.getProto() == 0) { WrapToken token = - new WrapToken(this, inBuf, inOffset, len, msgProp); - len = token.getData(outBuf, outOffset); + new WrapToken(this, inBuf, offset, len, msgProp); + data = token.getData(); setSequencingAndReplayProps(token, msgProp); } else if (cipherHelper.getProto() == 1) { WrapToken_v2 token = - new WrapToken_v2(this, inBuf, inOffset, len, msgProp); - len = token.getData(outBuf, outOffset); + new WrapToken_v2(this, inBuf, offset, len, msgProp); + data = token.getData(); setSequencingAndReplayProps(token, msgProp); } - return len; - } - public final int unwrap(InputStream is, - byte[] outBuf, int outOffset, - MessageProp msgProp) throws GSSException { - - if (state != STATE_DONE) - throw new GSSException(GSSException.NO_CONTEXT, -1, - "Unwrap called in invalid state!"); - - int len = 0; - if (cipherHelper.getProto() == 0) { - WrapToken token = new WrapToken(this, is, msgProp); - len = token.getData(outBuf, outOffset); - setSequencingAndReplayProps(token, msgProp); - } else if (cipherHelper.getProto() == 1) { - WrapToken_v2 token = new WrapToken_v2(this, is, msgProp); - len = token.getData(outBuf, outOffset); - setSequencingAndReplayProps(token, msgProp); - } - return len; + return data; } - public final void unwrap(InputStream is, OutputStream os, MessageProp msgProp) throws GSSException { @@ -1120,31 +1043,6 @@ public final byte[] getMIC(byte[] inMsg, int offset, int len, } } - private int getMIC(byte[] inMsg, int offset, int len, - byte[] outBuf, int outOffset, - MessageProp msgProp) - throws GSSException { - - int retVal = 0; - try { - if (cipherHelper.getProto() == 0) { - MicToken token = - new MicToken(this, msgProp, inMsg, offset, len); - retVal = token.encode(outBuf, outOffset); - } else if (cipherHelper.getProto() == 1) { - MicToken_v2 token = - new MicToken_v2(this, msgProp, inMsg, offset, len); - retVal = token.encode(outBuf, outOffset); - } - return retVal; - } catch (IOException e) { - GSSException gssException = - new GSSException(GSSException.FAILURE, -1, e.getMessage()); - gssException.initCause(e); - throw gssException; - } - } - /* * Checksum calculation requires a byte[]. Hence, might as well pass * a byte[] into the MicToken constructor. However, writing the diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java index 784e4803149b..5eb2dcd265b1 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/MessageToken_v2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -102,7 +102,9 @@ abstract class MessageToken_v2 extends Krb5Token { private static final int TOKEN_ID_POS = 0; private static final int TOKEN_FLAG_POS = 2; private static final int TOKEN_EC_POS = 4; - private static final int TOKEN_RRC_POS = 6; + + // Accessed by CipherHelper to normalize RRC + public static final int TOKEN_RRC_POS = 6; /** * The size of the random confounder used in a WrapToken. @@ -235,7 +237,14 @@ abstract class MessageToken_v2 extends Krb5Token { } if (tokenId == Krb5Token.WRAP_ID_v2) { - rotate(); + // According to RFC 4121, we should rotate by RRC. However, Windows + // rotates by RRC + EC in DCE-style tokens, and MIT krb5 follows it. + // (https://github.com/krb5/krb5/blob/07818f1/src/lib/gssapi/krb5/k5sealv3iov.c#L119) + // Non-DCE-style tokens observed from Windows shows EC = 0, and normal + // MIT krb5 build hardcoded EC = 0 for non-DCE-style tokens. + // (https://github.com/krb5/krb5/blob/07818f1/src/lib/gssapi/krb5/k5sealv3.c#L130), + // In this case, adding EC does not change the effective rotation. + rotate(prop.getPrivacy() ? (rrc + ec) : rrc); } if (tokenId == Krb5Token.MIC_ID_v2 || @@ -374,13 +383,13 @@ public final boolean verifySign(byte[] data, int offset, int len) * Our implementation does not do any rotates when sending, only * when receiving, we rotate left as per the RRC count, to revert it. */ - private void rotate() { - if (rrc % tokenDataLen != 0) { - rrc = rrc % tokenDataLen; + private void rotate(int rotateNum) { + rotateNum = rotateNum % tokenDataLen; + if (rotateNum != 0) { byte[] newBytes = new byte[tokenDataLen]; - System.arraycopy(tokenData, rrc, newBytes, 0, tokenDataLen-rrc); - System.arraycopy(tokenData, 0, newBytes, tokenDataLen-rrc, rrc); + System.arraycopy(tokenData, rotateNum, newBytes, 0, tokenDataLen-rotateNum); + System.arraycopy(tokenData, 0, newBytes, tokenDataLen-rotateNum, rotateNum); tokenData = newBytes; } @@ -490,6 +499,15 @@ protected final byte[] getTokenHeader() { return (tokenHeader.getBytes()); } + /** + * Returns the EC value for the message token. + * @return the EC value + * @see CipherHelper#decryptData(WrapToken_v2, byte[], int, int, byte[], int, int) + */ + public final int getEc() { + return ec; + } + // ******************************************* // // I N N E R C L A S S E S F O L L O W // ******************************************* // diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java index f25dfb464b1b..96f1c78f21ee 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/WrapToken_v2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -130,7 +130,7 @@ public int getData(byte[] dataBuf, int dataBufOffset) cipherHelper.decryptData(this, tokenData, 0, tokenDataLen, dataBuf, dataBufOffset, getKeyUsage()); - return tokenDataLen - CONFOUNDER_SIZE - + return tokenDataLen - CONFOUNDER_SIZE - getEc() - TOKEN_HEADER_SIZE - cipherHelper.getChecksumLength(); } else { diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java index 8682619096d9..7d32bf9a9f64 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbApReq.java @@ -510,7 +510,7 @@ private void createMessage(APOptions apOptions, // Check that key is one of the permitted types private static void checkPermittedEType(int target) throws KrbException { int[] etypes = EType.getDefaults("permitted_enctypes"); - if (!EType.isSupported(target, etypes)) { + if (!EType.isOneOf(target, etypes)) { throw new KrbException(EType.toString(target) + " encryption type not in permitted_enctypes list"); } diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java index 2b4bd4da2668..2393284ebf2c 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsRep.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,11 +53,14 @@ final class KrbAsRep extends KrbKdcRep { // message, created by initiator after calling // the decrypt() method - KrbAsRep(byte[] ibuf) throws + KrbAsRep(byte[] ibuf, int[] requestedETypes) throws KrbException, Asn1Exception, IOException { DerValue encoding = new DerValue(ibuf); try { rep = new ASRep(encoding); + if (!EType.isOneOf(rep.encPart.getEType(), requestedETypes)) { + throw new KrbException("AS-REP enc_part not using requested etype"); + } } catch (Asn1Exception e) { rep = null; KRBError err = new KRBError(encoding); diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java index 0d1375756874..5bf3bc503dd4 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/KrbAsReqBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -344,7 +344,7 @@ private KrbAsReqBuilder send() throws KrbException, IOException { } try { req = build(pakey, referralsState); - rep = new KrbAsRep(comm.send(req)); + rep = new KrbAsRep(comm.send(req), req.getMessage().reqBody.eType); return this; } catch (KrbException ke) { if (!preAuthFailedOnce && ( @@ -357,7 +357,7 @@ private KrbAsReqBuilder send() throws KrbException, IOException { preAuthFailedOnce = true; KRBError kerr = ke.getError(); int paEType = PAData.getPreferredEType(kerr.getPA(), - EType.getDefaults("default_tkt_enctypes")[0]); + req.getMessage().reqBody.eType); if (password == null) { EncryptionKey[] ks = Krb5Util.keysFromJavaxKeyTab(ktab, cname); pakey = EncryptionKey.findKey(paEType, ks); diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java index c1abbaa3f52f..c9a6d096743a 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/ETypeInfo2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2009, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -165,4 +165,7 @@ public byte[] getParams() { return ((s2kparams == null) ? null : s2kparams.clone()); } + public boolean hasParams() { + return s2kparams != null; + } } diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java index 8579dfdac412..d0354095a5be 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/KDCReqBody.java @@ -77,9 +77,9 @@ public class KDCReqBody { public KerberosTime till; public KerberosTime rtime; //optional public HostAddresses addresses; //optional + public int[] eType = null; //a sequence; not optional private int nonce; - private int[] eType = null; //a sequence; not optional private EncryptedData encAuthorizationData; //optional private Ticket[] additionalTickets; //optional diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java index bcbae770505d..4b95bfc6b09e 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/PAData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -188,13 +188,13 @@ public static PAData[] parseSequence(DerInputStream data, * * (This is useful when PA-DATAs from KRB-ERROR and AS-REP are combined). * - * @return the etype, or defaultEType if not enough info + * @return the etype, or requestedETypes[0] if not enough info * @throws Asn1Exception|IOException if there is an encoding error */ - public static int getPreferredEType(PAData[] pas, int defaultEType) + public static int getPreferredEType(PAData[] pas, int[] requestedETypes) throws IOException, Asn1Exception { - if (pas == null) return defaultEType; + if (pas == null) return requestedETypes[0]; DerValue d = null, d2 = null; for (PAData p: pas) { @@ -209,23 +209,34 @@ public static int getPreferredEType(PAData[] pas, int defaultEType) } } if (d2 != null) { + ArrayList offered = new ArrayList<>(); while (d2.data.available() > 0) { DerValue value = d2.data.getDerValue(); ETypeInfo2 tmp = new ETypeInfo2(value); - if (EType.isNewer(tmp.getEType()) || tmp.getParams() == null) { + if (EType.isNewer(tmp.getEType()) || !tmp.hasParams()) { // we don't support non-null s2kparams for old etypes - return tmp.getEType(); + offered.add(tmp.getEType()); + } + } + for (int etype : requestedETypes) { + if (offered.contains(etype)) { + return etype; } } } if (d != null) { + ArrayList offered = new ArrayList<>(); while (d.data.available() > 0) { DerValue value = d.data.getDerValue(); - ETypeInfo tmp = new ETypeInfo(value); - return tmp.getEType(); + offered.add(new ETypeInfo(value).getEType()); + } + for (int etype : requestedETypes) { + if (offered.contains(etype)) { + return etype; + } } } - return defaultEType; + return requestedETypes[0]; } /** @@ -280,7 +291,7 @@ public static SaltAndParams getSaltAndParams(int eType, PAData[] pas) DerValue value = d2.data.getDerValue(); ETypeInfo2 tmp = new ETypeInfo2(value); if (tmp.getEType() == eType && - (EType.isNewer(eType) || tmp.getParams() == null)) { + (EType.isNewer(eType) || !tmp.hasParams())) { // we don't support non-null s2kparams for old etypes return new SaltAndParams(tmp.getSalt(), tmp.getParams()); } diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java index ee385affb9e8..874a68fa6d9b 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/crypto/EType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -293,7 +293,7 @@ public static int[] getDefaults(String configName, EncryptionKey[] keys) } } - public static boolean isSupported(int eTypeConst, int[] config) { + public static boolean isOneOf(int eTypeConst, int[] config) { for (int i = 0; i < config.length; i++) { if (eTypeConst == config[i]) { return true; @@ -303,7 +303,7 @@ public static boolean isSupported(int eTypeConst, int[] config) { } public static boolean isSupported(int eTypeConst) { - return isSupported(eTypeConst, supportedETypes); + return isOneOf(eTypeConst, supportedETypes); } /** diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java index 567c71711165..547305f55fa2 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Base.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1000,7 +1000,7 @@ public byte[] unwrap(byte[] incoming, int start, int len) } /* First, compare MAC's before updating any of our state */ - if (!Arrays.equals(mac, expectedMac)) { + if (!MessageDigest.isEqual(mac, expectedMac)) { // Discard message and do not increment sequence number logger.log(Level.INFO, "DIGEST23:Unmatched MACs"); return EMPTY_BYTE_ARRAY; @@ -1450,7 +1450,7 @@ public byte[] unwrap(byte[] incoming, int start, int len) } // First, compare MACs before updating state - if (!Arrays.equals(mac, expectedMac)) { + if (!MessageDigest.isEqual(mac, expectedMac)) { // Discard message and do not increment sequence number logger.log(Level.INFO, "DIGEST41:Unmatched MACs"); return EMPTY_BYTE_ARRAY; diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java index c9938ec675d7..e48192cce9e6 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Client.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package com.sun.security.sasl.digest; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -32,7 +33,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Arrays; import java.util.logging.Level; import static java.nio.charset.StandardCharsets.UTF_8; @@ -657,7 +657,7 @@ private void validateResponseValue(byte[] fromServer) throws SaslException { byte[] expected = generateResponseValue("", digestUri, negotiatedQop, username, negotiatedRealm, passwd, nonce, cnonce, nonceCount, authzidBytes); - if (!Arrays.equals(expected, fromServer)) { + if (!MessageDigest.isEqual(expected, fromServer)) { /* Server's rspauth value does not match */ throw new SaslException( "Server's rspauth value does not match what client expects"); diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java index bc508e7397be..f0d43b352237 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/digest/DigestMD5Server.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.StringTokenizer; import java.util.ArrayList; @@ -619,7 +620,7 @@ private byte[] validateClientResponse(byte[][] responseVal) "DIGEST-MD5: problem duplicating client response", e); } - if (!Arrays.equals(responseFromClient, expectedResponse)) { + if (!MessageDigest.isEqual(responseFromClient, expectedResponse)) { throw new SaslException("DIGEST-MD5: digest response format " + "violation. Mismatched response."); } diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java index 6f3832d3b435..d79662535eef 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ import com.sun.security.ntlm.Client; import com.sun.security.ntlm.NTLMException; +import sun.security.jca.JCAUtil; + import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; @@ -69,8 +71,9 @@ * * com.sun.security.sasl.ntlm.random * java.util.Random, the nonce source to be used in NTLM v2 or NTLM v1 with - * Client Challenge. Default null, an internal java.util.Random object - * will be used + * Client Challenge. Users are encouraged to provide a + * java.security.SecureRandom object. If not provided, then an internal + * SecureRandom object is used. * * Negotiated Properties: * @@ -127,7 +130,7 @@ final class NTLMClient implements SaslClient { rtmp = (Random)props.get(NTLM_RANDOM); hostname = (String)props.get(NTLM_HOSTNAME); } - this.random = rtmp != null ? rtmp : new Random(); + this.random = rtmp != null ? rtmp : JCAUtil.getDefSecureRandom(); if (version == null) { version = System.getProperty("ntlm.version"); diff --git a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java index 0bd34e40fd15..94a15989028a 100644 --- a/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java +++ b/src/java.security.sasl/share/classes/com/sun/security/sasl/ntlm/NTLMServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ import com.sun.security.ntlm.NTLMException; import com.sun.security.ntlm.Server; +import sun.security.jca.JCAUtil; + import java.io.IOException; import java.util.Map; import java.util.Random; @@ -67,8 +69,9 @@ * String, the domain of the server, default is server name (fqdn parameter) * * com.sun.security.sasl.ntlm.random - * java.util.Random, the nonce source. Default null, an internal - * java.util.Random object will be used + * java.util.Random, the nonce source. Users are encouraged to provide + * a java.security.SecureRandom object. If not provided, then an internal + * SecureRandom object is used. * * Negotiated Properties: * @@ -123,7 +126,7 @@ final class NTLMServer implements SaslServer { version = (String)props.get(NTLM_VERSION); rtmp = (Random)props.get(NTLM_RANDOM); } - random = rtmp != null ? rtmp : new Random(); + random = rtmp != null ? rtmp : JCAUtil.getDefSecureRandom(); if (version == null) { version = System.getProperty("ntlm.version"); diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties index a3b810fb8f13..54ae42cf991a 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/RowSetResourceBundle.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -137,9 +137,7 @@ syncrsimpl.syncnotpos = Synchronization is not possible syncrsimpl.valtores = Value to be resolved can either be in the database or in cachedrowset #WebRowSetXmlReader exception -wrsxmlreader.invalidcp = End of RowSet reached. Invalid cursor position wrsxmlreader.readxml = readXML : {0} -wrsxmlreader.parseerr = ** Parsing Error : {0} , line : {1} , uri : {2} #WebRowSetXmlWriter exceptions wrsxmlwriter.ioex = IOException : {0} diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java b/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java index a72ed6cdf43d..e638e956c185 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/WebRowSetImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -179,27 +179,23 @@ public void readXml(java.io.Reader reader) throws SQLException { // This will change to a XmlReader, which over-rides the default // Xml that is used when a WRS is instantiated. //WebRowSetXmlReader xmlReader = getXmlReader(); - try { - if (reader != null) { - xmlReader.readXML(this, reader); - - // Position is before the first row - // The cursor position is to be stored while serializng - // and deserializing the WebRowSet Object. - if(curPosBfrWrite == 0) { - this.beforeFirst(); - } + if (reader != null) { + xmlReader.readXML(this, reader); - // Return the position back to place prior to callin writeXml - else { - this.absolute(curPosBfrWrite); - } + // Position is before the first row + // The cursor position is to be stored while serializing + // and deserializing the WebRowSet Object. + if(curPosBfrWrite == 0) { + this.beforeFirst(); + } - } else { - throw new SQLException(resBundle.handleGetObject("webrowsetimpl.invalidrd").toString()); + // Return the position back to place prior to callin writeXml + else { + this.absolute(curPosBfrWrite); } - } catch (Exception e) { - throw new SQLException(e.getMessage()); + + } else { + throw new SQLException(resBundle.handleGetObject("webrowsetimpl.invalidrd").toString()); } } diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java index 56f0d30c6df6..9f3f99d99034 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/CachedRowSetWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -336,6 +336,7 @@ public boolean writeData(RowSetInternal caller) throws SQLException { crs.beforeFirst(); int rows =1; + String[] primaryKeys = null; while (crs.next()) { if (crs.rowDeleted()) { // The row has been deleted. @@ -350,9 +351,18 @@ public boolean writeData(RowSetInternal caller) throws SQLException { } else if (crs.rowInserted()) { // The row has been inserted. - pstmtIns = con.prepareStatement(insertCmd); - if (insertNewRow(crs, pstmtIns, this.crsResolve)) { + if (primaryKeys == null) { + try (ResultSet rs = con.getMetaData().getPrimaryKeys(null, null, + crs.getTableName())) { + var pks = new ArrayList(); + while (rs.next()) { + pks.add(rs.getString("COLUMN_NAME")); + } + primaryKeys = pks.toArray(new String[0]); + } + } + if (insertNewRow(crs, pstmtIns, primaryKeys)) { status.add(rows, SyncResolver.INSERT_ROW_CONFLICT); conflicts++; } else { @@ -816,31 +826,24 @@ else if(rsval != null && (!rsval.equals(orig))) * and to whose underlying data source the row will be inserted * @param pstmt the {@code PreparedStatement} object that will be used * to execute the insertion + * @param primaryKeys the {@code String[]} of primary key columns from the underlying + * data source * @return {@code false} to indicate that the insertion was successful; * {@code true} otherwise * @throws SQLException if a database access error occurs */ private boolean insertNewRow(CachedRowSet crs, - PreparedStatement pstmt, CachedRowSetImpl crsRes) throws SQLException { + PreparedStatement pstmt, String[] primaryKeys) throws SQLException { boolean returnVal = false; try (PreparedStatement pstmtSel = con.prepareStatement(selectCmd, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY); - ResultSet rs = pstmtSel.executeQuery(); - ResultSet rs2 = con.getMetaData().getPrimaryKeys(null, null, - crs.getTableName()) - ) { + ResultSet rs = pstmtSel.executeQuery()) { ResultSetMetaData rsmd = crs.getMetaData(); int icolCount = rsmd.getColumnCount(); - String[] primaryKeys = new String[icolCount]; - int k = 0; - while (rs2.next()) { - primaryKeys[k] = rs2.getString("COLUMN_NAME"); - k++; - } if (rs.next()) { for (String pkName : primaryKeys) { diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java index a0d99e4fbcf4..67d0db98c7f1 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,18 +25,23 @@ package com.sun.rowset.internal; +import java.net.URL; import java.sql.*; import javax.sql.*; import java.io.*; import org.xml.sax.*; import org.xml.sax.helpers.*; + +import javax.xml.XMLConstants; import javax.xml.parsers.*; import com.sun.rowset.*; import java.text.MessageFormat; import javax.sql.rowset.*; import javax.sql.rowset.spi.*; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; /** * An implementation of the XmlReader interface, which @@ -45,6 +50,13 @@ * as its parser. */ public class WebRowSetXmlReader implements XmlReader, Serializable { + // standard schema + static final String WEBROWSET_XSD = "webrowset.xsd"; + private static final String VALIDATION_PROPERTY = "jdk.sql.rowset.webrowsetValidation"; + + // Error messages + private static final String STD_SCHEMA_MESSAGE = + "readXML : Cannot locate standard WebRowSet schema: %s.".formatted(WEBROWSET_XSD); private JdbcRowSetResourceBundle resBundle; @@ -81,56 +93,7 @@ public WebRowSetXmlReader(){ * @see XmlReaderContentHandler */ public void readXML(WebRowSet caller, java.io.Reader reader) throws SQLException { - try { - // Crimson Parser(as in J2SE 1.4.1 is NOT able to handle - // Reader(s)(FileReader). - // - // But getting the file as a Stream works fine. So we are going to take - // the reader but send it as a InputStream to the parser. Note that this - // functionality needs to work against any parser - // Crimson(J2SE 1.4.x) / Xerces(J2SE 1.5.x). - InputSource is = new InputSource(reader); - DefaultHandler dh = new XmlErrorHandler(); - XmlReaderContentHandler hndr = new XmlReaderContentHandler((RowSet)caller); - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setValidating(true); - SAXParser parser = factory.newSAXParser() ; - - parser.setProperty( - "http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); - - XMLReader reader1 = parser.getXMLReader() ; - reader1.setEntityResolver(new XmlResolver()); - reader1.setContentHandler(hndr); - - reader1.setErrorHandler(dh); - - reader1.parse(is); - - } catch (SAXParseException err) { - System.out.println (MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.parseerr").toString(), new Object[]{ err.getMessage (), err.getLineNumber(), err.getSystemId()})); - err.printStackTrace(); - throw new SQLException(err.getMessage()); - - } catch (SAXException e) { - Exception x = e; - if (e.getException () != null) - x = e.getException(); - x.printStackTrace (); - throw new SQLException(x.getMessage()); - - } - - // Will be here if trying to write beyond the RowSet limits - - catch (ArrayIndexOutOfBoundsException aie) { - throw new SQLException(resBundle.handleGetObject("wrsxmlreader.invalidcp").toString()); - } - catch (Throwable e) { - throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString() , e.getMessage())); - } - + readXml(caller, new InputSource(reader)); } @@ -160,52 +123,75 @@ public void readXML(WebRowSet caller, java.io.Reader reader) throws SQLException * @see XmlReaderContentHandler */ public void readXML(WebRowSet caller, java.io.InputStream iStream) throws SQLException { - try { - InputSource is = new InputSource(iStream); - DefaultHandler dh = new XmlErrorHandler(); - - XmlReaderContentHandler hndr = new XmlReaderContentHandler((RowSet)caller); - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setNamespaceAware(true); - factory.setValidating(true); - - SAXParser parser = factory.newSAXParser() ; - - parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", - "http://www.w3.org/2001/XMLSchema"); - - XMLReader reader1 = parser.getXMLReader() ; - reader1.setEntityResolver(new XmlResolver()); - reader1.setContentHandler(hndr); - - reader1.setErrorHandler(dh); - - reader1.parse(is); - - } catch (SAXParseException err) { - System.out.println (MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.parseerr").toString(), new Object[]{err.getLineNumber(), err.getSystemId() })); - System.out.println(" " + err.getMessage ()); - err.printStackTrace(); - throw new SQLException(err.getMessage()); - - } catch (SAXException e) { - Exception x = e; - if (e.getException () != null) - x = e.getException(); - x.printStackTrace (); - throw new SQLException(x.getMessage()); + readXml(caller, new InputSource(iStream)); + } + /** + * Parses the supplied XML input into the given {@code WebRowSet}. + * + * The parser validates the XML against the standard WebRowSet schema by default. + * Validation can be disabled by setting {@systemProperty jdk.sql.rowset.webrowsetValidation} + * to {@code false} so that the parser can read legacy WebRowSet XML. + * + * @param caller the rowset to populate + * @param is the SAX input source to parse + * @throws SQLException if parsing or rowset population fails + */ + private void readXml(WebRowSet caller, InputSource is) throws SQLException { + try { + boolean validating = validationEnabled(); + + SAXParserFactory spf = SAXParserFactory.newDefaultNSInstance(); + spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + if (validating) { + spf.setSchema(getSchema()); + } + + XMLReader reader = spf.newSAXParser().getXMLReader(); + reader.setEntityResolver(new XmlResolver()); + reader.setContentHandler(new XmlReaderContentHandler(caller, validating)); + reader.setErrorHandler(new XmlErrorHandler()); + reader.parse(is); + } catch (SAXException | ParserConfigurationException | IOException err) { + throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString(), + err.getMessage()), err); } + } - // Will be here if trying to write beyond the RowSet limits - - catch (ArrayIndexOutOfBoundsException aie) { - throw new SQLException(resBundle.handleGetObject("wrsxmlreader.invalidcp").toString()); + /** + * Returns the standard WebRowSet schema bundled in the + * {@code java.sql.rowset} module. + * + * A new schema is created for each call so parser instances do not share + * validator implementation state. This can be revisited once the bundled + * schema implementation is safe to share. + * + * @return the WebRowSet schema + * @throws SAXException if the schema cannot be located or parsed + */ + private Schema getSchema() throws SAXException { + URL stdSchema = WebRowSet.class.getResource(WEBROWSET_XSD); + if (stdSchema == null) { + throw new SAXException(STD_SCHEMA_MESSAGE); } - catch (Throwable e) { - throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlreader.readxml").toString() , e.getMessage())); - } + SchemaFactory sf = SchemaFactory.newDefaultInstance(); + sf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + return sf.newSchema(stdSchema); + } + + /** + * Returns whether WebRowSet XML validation is enabled. + * + * Validation is enabled by default. It is disabled only when the + * {@systemProperty jdk.sql.rowset.webrowsetValidation} is set to + * {@code false}, case-insensitive. + * + * @return {@code true} if XML should be schema-validated + */ + private static boolean validationEnabled() { + return !"false".equalsIgnoreCase( + System.getProperty(VALIDATION_PROPERTY, "true").trim()); } /** diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java index 3e419be005a0..426afbf551ac 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/WebRowSetXmlWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -148,6 +148,7 @@ private void writeRowSet(WebRowSet caller) throws SQLException { writeData(caller); endHeader(); + writer.flush(); } catch (java.io.IOException ex) { throw new SQLException(MessageFormat.format(resBundle.handleGetObject("wrsxmlwriter.ioex").toString(), ex.getMessage())); @@ -321,12 +322,19 @@ private void writeData(WebRowSet caller) throws java.io.IOException { caller.beforeFirst(); caller.setShowDeleted(true); while (caller.next()) { - if (caller.rowDeleted() && caller.rowInserted()) { + boolean rowDeleted = caller.rowDeleted(); + boolean rowInserted = caller.rowInserted(); + boolean rowUpdated = caller.rowUpdated(); + boolean writeUpdateValues = rowDeleted || rowUpdated; + + if (rowDeleted && rowInserted) { beginSection("modifyRow"); - } else if (caller.rowDeleted()) { + } else if (rowDeleted) { beginSection("deleteRow"); - } else if (caller.rowInserted()) { + } else if (rowInserted) { beginSection("insertRow"); + } else if (rowUpdated) { + beginSection("modifyRow"); } else { beginSection("currentRow"); } @@ -338,13 +346,17 @@ private void writeData(WebRowSet caller) throws java.io.IOException { beginTag("columnValue"); writeValue(i, (RowSet)rs); endTag("columnValue"); - beginTag("updateRow"); + beginTag("updateValue"); writeValue(i, caller); - endTag("updateRow"); + endTag("updateValue"); } else { beginTag("columnValue"); writeValue(i, caller); endTag("columnValue"); + if (writeUpdateValues) { + beginTag("updateValue"); + endTag("updateValue"); + } } } @@ -482,7 +494,6 @@ private void endSection(String tag) throws java.io.IOException { } else { ; } - writer.flush(); } private void endSection() throws java.io.IOException { @@ -492,7 +503,6 @@ private void endSection() throws java.io.IOException { String beginTag = getTag(); writer.write("\n"); - writer.flush(); } private void beginTag(String tag) throws java.io.IOException { @@ -513,7 +523,6 @@ private void endTag(String tag) throws java.io.IOException { } else { ; } - writer.flush(); } private void emptyTag(String tag) throws java.io.IOException { @@ -550,8 +559,6 @@ private void writeStringData(String s) throws java.io.IOException { private void writeString(String s) throws java.io.IOException { if (s != null) { writer.write(s); - } else { - writeNull(); } } @@ -595,9 +602,14 @@ private void writeIndent(int tabs) throws java.io.IOException { } private void propString(String tag, String s) throws java.io.IOException { - beginTag(tag); - writeString(s); - endTag(tag); + if (s == null) { + writeIndent(stack.size()); + writer.write("<" + tag + "/>\n"); + } else { + beginTag(tag); + writeString(s); + endTag(tag); + } } private void propInteger(String tag, int i) throws java.io.IOException { diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java index 099446c6865c..8bbafd0999c7 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlErrorHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,10 @@ package com.sun.rowset.internal; -import org.xml.sax.*; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; -import com.sun.rowset.*; -import javax.sql.rowset.*; - /** * An implementation of the DefaultHandler interface, which @@ -38,22 +36,16 @@ * This is the ErrorHandler which helps WebRowSetXmlReader * to handle any errors while reading the xml data. */ - - public class XmlErrorHandler extends DefaultHandler { - public int errorCounter = 0; - - public void error(SAXParseException e) throws SAXException { - errorCounter++; - - } - - public void fatalError(SAXParseException e) throws SAXException { - errorCounter++; - - } - - public void warning(SAXParseException exception) throws SAXException { - - } + @Override + public void error(SAXParseException e) throws SAXException { + throw e; + } + + @Override + public void warning(SAXParseException e) throws SAXException { + // to ensure consistent handling of all XML parsing and validation diagnostics + // warnings are not ignored, though unlikely to happen + throw e; + } } diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java index 73bb39eba985..8e1222a889ff 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlReaderContentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,7 +84,7 @@ public class XmlReaderContentHandler extends DefaultHandler { private String propertyValue; private String metaDataValue; - private int tag; + private int tag = -1; private int state; private WebRowSetImpl rs; @@ -370,16 +370,25 @@ public class XmlReaderContentHandler extends DefaultHandler { */ private static final int MetaNullTag = 19; - private String [] data = {"currentRow", "columnValue", "insertRow", "deleteRow", "insdel", "updateRow", "null" , "emptyString"}; + private String [] data = {"currentRow", "columnValue", "insertRow", "deleteRow", + "modifyRow", "updateValue", "null" , "emptyString"}; private static final int RowTag = 0; private static final int ColTag = 1; private static final int InsTag = 2; private static final int DelTag = 3; - private static final int InsDelTag = 4; + private static final int ModTag = 4; private static final int UpdTag = 5; private static final int NullTag = 6; private static final int EmptyStringTag = 7; + private static final int InsDelTag = 8; + private static final int UpdRowTag = 9; + + private static final String XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"; + + // Error messages + private static final String SCHEMA_LOC_MESSAGE = "readXML : Malformed xsi:schemaLocation: %s."; + private static final String SCHEMA_MESSAGE = "readXML : Non-standard schema specified for namespace %s : %s."; /** * A constant indicating the state of this XmlReaderContentHandler @@ -419,6 +428,9 @@ public class XmlReaderContentHandler extends DefaultHandler { private JdbcRowSetResourceBundle resBundle; + private boolean sawRoot; + private final boolean validating; + /** * Constructs a new XmlReaderContentHandler object that will * assist the SAX parser in reading a WebRowSet object in the @@ -431,6 +443,20 @@ public class XmlReaderContentHandler extends DefaultHandler { * @param r the RowSet object in XML format that will be read */ public XmlReaderContentHandler(RowSet r) { + this(r, true); + } + + /** + * Constructs a new {@code XmlReaderContentHandler} with a validation flag. + * When validation is disabled, the handler accepts legacy WebRowSet XML tags + * in addition to the standard tags. + * + * @param r the {@code RowSet} object in XML format that will be read + * @param validating the flag indicating whether schema validation is on + */ + XmlReaderContentHandler(RowSet r, boolean validating) { + this.validating = validating; + // keep the rowset we've been given rs = (WebRowSetImpl)r; @@ -481,21 +507,27 @@ private void initMaps() { items = properties.length; for (i=0;i(); items = colDef.length; for (i=0;i(); items = data.length; for (i=0;iThe XML parser is configured with the standard WebRowSet schema + * explicitly through the JAXP Validation API, so schema validation does + * not depend on xsi:schemaLocation being present in the XML instance + * document. This method therefore performs only an additional consistency + * check on the schema hint provided by the document.

        + * + * @param attributes the attributes of the root webRowSet element + * @throws SAXException if a non-standard schema location is specified + */ + private void checkSchemaLocation(Attributes attributes) throws SAXException { + String schemaLocation = attributes.getValue(XSI_NS, "schemaLocation"); + + // schemaLocation is optional when schema is supplied explicitly + if (schemaLocation == null || schemaLocation.isBlank()) { + return; + } + + String[] tokens = schemaLocation.trim().split("\\s+"); + if (tokens.length % 2 != 0) { + throw new SAXException(SCHEMA_LOC_MESSAGE.formatted(schemaLocation)); + } + + for (int i = 0; i < tokens.length; i += 2) { + String ns = tokens[i]; + String xsd = tokens[i + 1]; + + if (!isWebRowSetSchema(xsd)) { + throw new SAXException(SCHEMA_MESSAGE.formatted(ns, xsd)); + } + } + } + + private static boolean isWebRowSetSchema(String xsd) { + return xsd.equals(WebRowSetXmlReader.WEBROWSET_XSD) + || xsd.endsWith("/" + WebRowSetXmlReader.WEBROWSET_XSD); + } + /** * Sets the value for the given element if name is one of * the array elements in the fields properties, @@ -742,6 +821,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti } break; case RowTag: + case ModTag: + case InsDelTag: try { rs.insertRow(); rs.moveToCurrentRow(); @@ -751,6 +832,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti // rowInserted flagging rs.setOriginalRow(); + // Legacy currentRow elements can carry update tags when + // schema validation is disabled. applyUpdates(); } catch (SQLException ex) { throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errconstr").toString(), ex.getMessage())); @@ -762,6 +845,8 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti rs.moveToCurrentRow(); rs.next(); rs.setOriginalRow(); + // Legacy deleteRow elements may omit update tags when + // schema validation is disabled. applyUpdates(); rs.deleteRow(); } catch (SQLException ex) { @@ -779,33 +864,20 @@ public void endElement(String uri, String lName, String qName) throws SAXExcepti } break; - case InsDelTag: - try { - rs.insertRow(); - rs.moveToCurrentRow(); - rs.next(); - rs.setOriginalRow(); - applyUpdates(); - } catch (SQLException ex) { - throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errinsdel").toString() , ex.getMessage())); - } - break; - case UpdTag: - try { - if(getNullValue()) - { - insertValue(null); - setNullValue(false); - } else if(getEmptyStringValue()) { - insertValue(""); - setEmptyStringValue(false); - } else { - updates.add(upd); - } - } catch(SQLException ex) { - throw new SAXException(MessageFormat.format(resBundle.handleGetObject("xmlrch.errupdate").toString() , ex.getMessage())); - } + case UpdRowTag: + // Empty updateValue/updateRow placeholders are ignored when + // they carry no value. + if(getNullValue()) + { + addUpdate(null); + setNullValue(false); + } else if(getEmptyStringValue()) { + addUpdate(""); + setEmptyStringValue(false); + } else if(!tempUpdate.isEmpty()) { + addUpdate(tempUpdate); + } break; default: @@ -825,10 +897,7 @@ private void applyUpdates() throws SAXException { while (i.hasNext()) { upd = (Object [])i.next(); idx = ((Integer)upd[0]).intValue(); - - if(!(lastval.equals(upd[1]))){ - insertValue((String)(upd[1])); - } + insertValue((String)(upd[1])); } rs.updateRow(); @@ -841,6 +910,19 @@ private void applyUpdates() throws SAXException { } + /** + * Queues an update value for the current column to be applied when the + * row element is complete. + * + * @param value the update value, can be {@code null} + */ + private void addUpdate(String value) { + upd = new Object[2]; + upd[0] = Integer.valueOf(idx); + upd[1] = value; + updates.add(upd); + } + /** * Sets a property, metadata, or data value with the characters in * the given array of characters, starting with the array element @@ -861,6 +943,13 @@ public void characters(char[] ch, int start, int length) throws SAXException { try { switch (getState()) { case PROPERTIES: + + // If tag is -1, startElement has not registered a valid tag + // Thus, we are in whitespace between elements, and should exit + if (tag == -1) { + break; + } + propertyValue = new String(ch, start, length); /** @@ -1027,10 +1116,12 @@ private java.sql.Timestamp getTimestampValue(String s) { private void setPropertyValue(String s) throws SQLException { // find out if we are going to be dealing with a null boolean nullValue = getNullValue(); + // Empty nullable properties preserve the legacy behavior while + // remaining schema-valid. switch(getTag()) { case CommandTag: - if (nullValue) + if (nullValue || s == null || s.isEmpty()) ; //rs.setCommand(null); else rs.setCommand(s); @@ -1042,7 +1133,7 @@ private void setPropertyValue(String s) throws SQLException { rs.setConcurrency(getIntegerValue(s)); break; case DatasourceTag: - if (nullValue) + if (nullValue || s == null || s.isEmpty()) rs.setDataSourceName(null); else rs.setDataSourceName(s); @@ -1129,14 +1220,14 @@ private void setPropertyValue(String s) throws SQLException { rs.setShowDeleted(getBooleanValue(s)); break; case TableNameTag: - if (nullValue) + if (nullValue || s == null || s.isEmpty()) //rs.setTableName(null); ; else rs.setTableName(s); break; case UrlTag: - if (nullValue) + if (nullValue || s == null || s.isEmpty()) rs.setUrl(null); else rs.setUrl(s); @@ -1305,8 +1396,7 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException { tempStr = tempStr.concat(columnValue); break; case UpdTag: - upd = new Object[2]; - + case UpdRowTag: /** * This has been added for handling of special characters. When special * characters are encountered the characters function gets called for @@ -1316,12 +1406,6 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException { **/ tempUpdate = tempUpdate.concat(new String(ch,start,len)); - upd[0] = Integer.valueOf(idx); - upd[1] = tempUpdate; - //updates.add(upd); - - lastval = (String)upd[1]; - //insertValue(ch, start, len); break; case InsTag: @@ -1330,7 +1414,7 @@ private void setDataValue(char[] ch, int start, int len) throws SQLException { private void insertValue(String s) throws SQLException { - if (getNullValue()) { + if (s == null || getNullValue()) { rs.updateNull(idx); return; } diff --git a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java index a3fa6afc9a2b..96edbfa1f195 100644 --- a/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java +++ b/src/java.sql.rowset/share/classes/com/sun/rowset/internal/XmlResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,27 +30,35 @@ import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; +import javax.sql.rowset.WebRowSet; +import java.io.IOException; +import java.io.StringReader; + /** * An implementation of the EntityResolver interface, which * reads and parses an XML formatted WebRowSet object. * This is an implementation of org.xml.sax - * */ public class XmlResolver implements EntityResolver { - - public InputSource resolveEntity(String publicId, String systemId) { - String schemaName = systemId.substring(systemId.lastIndexOf('/')); - - if(systemId.startsWith("http://java.sun.com/xml/ns/jdbc")) { - return new InputSource(this.getClass().getResourceAsStream(schemaName)); - - } else { - // use the default behaviour - return null; - } + //The standard WebRowSet XML Schema as defined in WebRowSet + public static final String STD_SCHEMA_ID = "http://xmlns.jcp.org/xml/ns//jdbc/webrowset.xsd"; + // Error messages + private static final String ENTITY_MESSAGE = + "readXML : external entity %s other than the standard schema is not allowed."; + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + // accepts the standard schema without resolving it + // the schema is explicitly provided via the validation API + if (WebRowSet.SCHEMA_SYSTEM_ID.equals(systemId) || + STD_SCHEMA_ID.equals(systemId)) { + return new InputSource(new StringReader("")); + } - } + // reports error upon any external entity other than the standard schema + throw new SAXException(ENTITY_MESSAGE.formatted(systemId)); + } } diff --git a/src/java.sql.rowset/share/classes/javax/sql/rowset/spi/SyncProviderException.java b/src/java.sql.rowset/share/classes/javax/sql/rowset/spi/SyncProviderException.java index b1dd9f7e7611..be7cae9d796d 100644 --- a/src/java.sql.rowset/share/classes/javax/sql/rowset/spi/SyncProviderException.java +++ b/src/java.sql.rowset/share/classes/javax/sql/rowset/spi/SyncProviderException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -126,6 +126,8 @@ public SyncProviderException(SyncResolver syncResolver) { * implementation is also returned if the SyncResolver() or * SyncResolver(String) constructors are used to instantiate * the SyncResolver instance. + * @throws IllegalStateException if a {@code SyncResolver} is not set and the default + * implementation cannot be loaded. */ public SyncResolver getSyncResolver() { if (syncResolver != null) { diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java index a3875969bb7e..4c5f249326f8 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMessages.java @@ -1037,6 +1037,11 @@ public Object[][] getContents() "smaller templates." }, + {ErrorMsg.DESERIALIZE_TRANSLET_ERR, + "Support for deserializing TemplatesImpl is disabled. This can be " + + "overridden by setting the ''jdk.xml.enableTemplatesImplDeserialization'' " + + "property to ''true''."}, + {ErrorMsg.XPATH_GROUP_LIMIT, "JAXP0801001: the compiler encountered an XPath expression containing " + "''{0}'' groups that exceeds the ''{1}'' limit set by ''{2}''."}, diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java index 8ef4c9c86b60..ad63a44c57a7 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/compiler/util/ErrorMsg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -174,6 +174,7 @@ public final class ErrorMsg { public static final String XPATH_GROUP_LIMIT = "XPATH_GROUP_LIMIT"; public static final String XPATH_OPERATOR_LIMIT = "XPATH_OPERATOR_LIMIT"; public static final String XPATH_TOTAL_OPERATOR_LIMIT = "XPATH_TOTAL_OPERATOR_LIMIT"; + public static final String DESERIALIZE_TRANSLET_ERR = "DESERIALIZE_TEMPLATES_ERR"; // All error messages are localized and are stored in resource bundles. // This array and the following 4 strings are read from that bundle. diff --git a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java index 71aad15b09f6..cef94e450e90 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xalan/internal/xsltc/trax/TemplatesImpl.java @@ -58,6 +58,7 @@ import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.URIResolver; import jdk.xml.internal.JdkConstants; +import jdk.xml.internal.JdkXmlFeatures; import jdk.xml.internal.XMLSecurityManager; @@ -261,6 +262,14 @@ public TemplatesImpl() { } private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException { + TransformerFactoryImpl tfactory = new TransformerFactoryImpl(); + + if (!tfactory.getFeature( + JdkXmlFeatures.XmlFeature.ENABLE_TEMPLATESIMPL_DESERIALIZATION.apiProperty())) { + ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR); + throw new UnsupportedOperationException(err.toString()); + } + // We have to read serialized fields first. ObjectInputStream.GetField gf = is.readFields(); _name = (String)gf.get("_name", null); @@ -275,7 +284,7 @@ private void readObject(ObjectInputStream is) _uriResolver = (URIResolver) is.readObject(); } - _tfactory = new TransformerFactoryImpl(); + _tfactory = tfactory; } diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java index aca31d1e7704..79577c3ef509 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/impl/xs/SchemaNamespaceSupport.java @@ -1,6 +1,5 @@ /* - * reserved comment block - * DO NOT REMOVE OR ALTER! + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -32,6 +31,7 @@ * * @author Neil Graham, IBM * + * @LastModified: June 2026 */ public class SchemaNamespaceSupport extends NamespaceSupport { @@ -50,6 +50,7 @@ public SchemaNamespaceSupport(SchemaNamespaceSupport nSupport) { if (fContext.length <= fCurrentContext) fContext = new int[fCurrentContext+1]; System.arraycopy(nSupport.fContext, 0, fContext, 0, fCurrentContext+1); + rebuildNamespaceTable(); } // end constructor /** @@ -76,6 +77,7 @@ public void setEffectiveContext (String [] namespaceDecls) { System.arraycopy(namespaceDecls, 0, fNamespace, fNamespaceSize, namespaceDecls.length); fNamespaceSize = newSize; + rebuildNamespaceTable(); } // setEffectiveContext(String):void /** @@ -108,6 +110,7 @@ public void makeGlobal() { if (fCurrentContext >= 3) { fCurrentContext = 3; fNamespaceSize = fContext[3]; + rebuildNamespaceTable(); } } // makeGlobal } // class NamespaceSupport diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java index 05eba5b2560c..30cfb3d7db49 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/util/NamespaceSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -23,6 +23,7 @@ import com.sun.org.apache.xerces.internal.xni.NamespaceContext; import java.util.ArrayList; import java.util.Enumeration; +import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -35,10 +36,17 @@ * * @author Andy Clark, IBM * - * @LastModified: Oct 2017 + * @LastModified: June 2026 */ public class NamespaceSupport implements NamespaceContext { + // + // Constants + // + + /** No previous active namespace declaration exists for the prefix. */ + protected static final int NO_PREVIOUS_DECLARATION = -1; + // // Data // @@ -57,6 +65,17 @@ public class NamespaceSupport implements NamespaceContext { /** The top of the namespace information array. */ protected int fNamespaceSize; + /** + * Previous active declaration for the prefix at the same tuple index in + * {@link #fNamespace}, or {@link #NO_PREVIOUS_DECLARATION} if none exists. + * This allows {@link #popContext()} to restore {@link #fNamespaceTable} + * to the previous active binding without searching ancestor contexts. + */ + protected int[] fNamespacePrev = new int[16]; + + /** Active namespace declaration indexes, keyed by prefix identity. */ + protected IdentityHashMap fNamespaceTable = new IdentityHashMap<>(); + // NOTE: The constructor depends on the initial context size // being at least 1. -Ac @@ -111,14 +130,19 @@ public void reset() { // reset namespace and context info fNamespaceSize = 0; fCurrentContext = 0; + fNamespaceTable.clear(); // bind "xml" prefix to the XML uri + fNamespacePrev[0] = NO_PREVIOUS_DECLARATION; fNamespace[fNamespaceSize++] = XMLSymbols.PREFIX_XML; fNamespace[fNamespaceSize++] = NamespaceContext.XML_URI; + fNamespaceTable.put(XMLSymbols.PREFIX_XML, 0); // bind "xmlns" prefix to the XMLNS uri + fNamespacePrev[1] = NO_PREVIOUS_DECLARATION; fNamespace[fNamespaceSize++] = XMLSymbols.PREFIX_XMLNS; fNamespace[fNamespaceSize++] = NamespaceContext.XMLNS_URI; + fNamespaceTable.put(XMLSymbols.PREFIX_XMLNS, 2); fContext[fCurrentContext] = fNamespaceSize; //++fCurrentContext; @@ -148,8 +172,19 @@ public void pushContext() { * @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#popContext() */ public void popContext() { - fNamespaceSize = fContext[fCurrentContext--]; - //System.out.println("Calling popContext, fCurrentContext = " + fCurrentContext); + final int context = fContext[fCurrentContext]; + for (int prefixIndex = fNamespaceSize - 2; prefixIndex >= context; prefixIndex -= 2) { + final String prefix = fNamespace[prefixIndex]; + final int prev = fNamespacePrev[prefixIndex >> 1]; + if (prev == NO_PREVIOUS_DECLARATION) { + fNamespaceTable.remove(prefix); + } + else { + fNamespaceTable.put(prefix, prev); + } + } + fNamespaceSize = context; + fCurrentContext--; } // popContext() /** @@ -161,18 +196,14 @@ public boolean declarePrefix(String prefix, String uri) { return false; } + final int activeIndex = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION); + // see if prefix already exists in current context - for (int i = fNamespaceSize; i > fContext[fCurrentContext]; i -= 2) { - if (fNamespace[i - 2] == prefix) { - // REVISIT: [Q] Should the new binding override the - // previously declared binding or should it - // it be ignored? -Ac - // NOTE: The SAX2 "NamespaceSupport" helper allows - // re-bindings with the new binding overwriting - // the previous binding. -Ac - fNamespace[i - 1] = uri; - return true; - } + if (activeIndex >= fContext[fCurrentContext]) { + // A duplicate declaration in the current context overwrites the + // existing binding in place, preserving the active table entry. + fNamespace[activeIndex + 1] = uri; + return true; } // resize array, if needed @@ -180,9 +211,15 @@ public boolean declarePrefix(String prefix, String uri) { String[] namespacearray = new String[fNamespaceSize * 2]; System.arraycopy(fNamespace, 0, namespacearray, 0, fNamespaceSize); fNamespace = namespacearray; + + int[] namespacePrev = new int[namespacearray.length >> 1]; + System.arraycopy(fNamespacePrev, 0, namespacePrev, 0, fNamespacePrev.length); + fNamespacePrev = namespacePrev; } // bind prefix to uri in current context + fNamespacePrev[fNamespaceSize >> 1] = activeIndex; + fNamespaceTable.put(prefix, fNamespaceSize); fNamespace[fNamespaceSize++] = prefix; fNamespace[fNamespaceSize++] = uri; @@ -190,20 +227,30 @@ public boolean declarePrefix(String prefix, String uri) { } // declarePrefix(String,String):boolean + /** + * Rebuilds the active namespace table from {@link #fNamespace}. Subclasses + * that directly replace or truncate namespace tuples must call this method. + */ + protected void rebuildNamespaceTable() { + if (fNamespacePrev.length < (fNamespace.length >> 1)) { + fNamespacePrev = new int[fNamespace.length >> 1]; + } + fNamespaceTable.clear(); + for (int i = 0; i < fNamespaceSize; i += 2) { + final String prefix = fNamespace[i]; + fNamespacePrev[i >> 1] = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION); + fNamespaceTable.put(prefix, i); + } + } + /** * @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#getURI(String) */ public String getURI(String prefix) { // find prefix in current context - for (int i = fNamespaceSize; i > 0; i -= 2) { - if (fNamespace[i - 2] == prefix) { - return fNamespace[i - 1]; - } - } - - // prefix not found - return null; + final int active = fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION); + return active != NO_PREVIOUS_DECLARATION ? fNamespace[active + 1] : null; } // getURI(String):String @@ -241,71 +288,46 @@ public String getDeclaredPrefixAt(int index) { } // getDeclaredPrefixAt(int):String public Iterator getPrefixes(){ - int count = 0; - if (fPrefixes.length < (fNamespace.length/2)) { - // resize prefix array - String[] prefixes = new String[fNamespaceSize]; - fPrefixes = prefixes; - } - String prefix = null; - boolean unique = true; - for (int i = 2; i < (fNamespaceSize-2); i += 2) { - prefix = fNamespace[i + 2]; - for (int k=0;k getAllPrefixes() { - int count = 0; - if (fPrefixes.length < (fNamespace.length/2)) { - // resize prefix array - String[] prefixes = new String[fNamespaceSize]; - fPrefixes = prefixes; - } - String prefix = null; - boolean unique = true; - for (int i = 2; i < (fNamespaceSize-2); i += 2) { - prefix = fNamespace[i + 2]; - for (int k=0;k getPrefixes(String uri){ - int count = 0; - String prefix = null; - boolean unique = true; List prefixList = new ArrayList<>(); - for (int i = fNamespaceSize; i >0 ; i -= 2) { - if(fNamespace[i-1] == uri){ - if(!prefixList.contains(fNamespace[i-2])) - prefixList.add(fNamespace[i-2]); + for (int i = fNamespaceSize; i > 0; i -= 2) { + final int prefixIndex = i - 2; + if (fNamespace[prefixIndex + 1] == uri) { + final String prefix = fNamespace[prefixIndex]; + if (fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION) == prefixIndex) { + prefixList.add(prefix); + } } } return prefixList; } + protected int collectPrefixes(int start, int end) { + if (fPrefixes.length < (fNamespace.length / 2)) { + // resize prefix array + fPrefixes = new String[fNamespace.length / 2]; + } + int count = 0; + for (int i = start; i < end; i += 2) { + if (fNamespacePrev[i >> 1] < start) { + fPrefixes[count++] = fNamespace[i]; + } + } + return count; + } + /* * non-NamespaceContext methods */ @@ -321,14 +343,7 @@ public List getPrefixes(String uri){ public boolean containsPrefix(String prefix) { // find prefix in context - for (int i = fNamespaceSize; i > 0; i -= 2) { - if (fNamespace[i - 2] == prefix) { - return true; - } - } - - // prefix not found - return false; + return fNamespaceTable.containsKey(prefix); } /** @@ -340,16 +355,8 @@ public boolean containsPrefix(String prefix) { * @return true if the given prefix exists in the current context */ public boolean containsPrefixInCurrentContext(String prefix) { - // find prefix in current context - for (int i = fContext[fCurrentContext]; i < fNamespaceSize; i += 2) { - if (fNamespace[i] == prefix) { - return true; - } - } - - // prefix not found - return false; + return fNamespaceTable.getOrDefault(prefix, NO_PREVIOUS_DECLARATION) >= fContext[fCurrentContext]; } protected final class IteratorPrefixes implements Iterator { diff --git a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java index 7b506b72fa66..3d29a7466fc5 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xerces/internal/xinclude/MultipleScopeNamespaceSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -37,7 +37,7 @@ * * @author Peter McCracken, IBM * - * @LastModified: Oct 2017 + * @LastModified: June 2026 */ public class MultipleScopeNamespaceSupport extends NamespaceSupport { @@ -66,29 +66,7 @@ public MultipleScopeNamespaceSupport(NamespaceContext context) { * @see com.sun.org.apache.xerces.internal.xni.NamespaceContext#getAllPrefixes() */ public Enumeration getAllPrefixes() { - int count = 0; - if (fPrefixes.length < (fNamespace.length / 2)) { - // resize prefix array - String[] prefixes = new String[fNamespaceSize]; - fPrefixes = prefixes; - } - String prefix = null; - boolean unique = true; - for (int i = fContext[fScope[fCurrentScope]]; - i <= (fNamespaceSize - 2); - i += 2) { - prefix = fNamespace[i]; - for (int k = 0; k < count; k++) { - if (fPrefixes[k] == prefix) { - unique = false; - break; - } - } - if (unique) { - fPrefixes[count++] = prefix; - } - unique = true; - } + int count = collectPrefixes(fContext[fScope[fCurrentScope]], fNamespaceSize); return new Prefixes(fPrefixes, count); } @@ -131,10 +109,10 @@ public String getPrefix(String uri, int start, int end) { return XMLSymbols.PREFIX_XMLNS; } - // find uri in current context + // find uri in the requested context range within the current scope for (int i = start; i > end; i -= 2) { if (fNamespace[i - 1] == uri) { - if (getURI(fNamespace[i - 2]) == uri) + if (getURI(fNamespace[i - 2], start, end) == uri) return fNamespace[i - 2]; } } @@ -152,15 +130,18 @@ public String getURI(String prefix, int start, int end) { return NamespaceContext.XMLNS_URI; } - // find prefix in current context - for (int i = start; i > end; i -= 2) { - if (fNamespace[i - 2] == prefix) { - return fNamespace[i - 1]; - } - } + int prefixIndex = getPrefixIndex(prefix, start, end); + return prefixIndex != NO_PREVIOUS_DECLARATION + ? fNamespace[prefixIndex + 1] : null; + } - // prefix not found - return null; + private int getPrefixIndex(String prefix, int start, int end) { + final Integer active = fNamespaceTable.get(prefix); + int prefixIndex = active != null ? active : NO_PREVIOUS_DECLARATION; + while (prefixIndex >= start) { + prefixIndex = fNamespacePrev[prefixIndex >> 1]; + } + return prefixIndex >= end ? prefixIndex : NO_PREVIOUS_DECLARATION; } /** @@ -170,6 +151,7 @@ public String getURI(String prefix, int start, int end) { public void reset() { fCurrentContext = fScope[fCurrentScope]; fNamespaceSize = fContext[fCurrentContext]; + rebuildNamespaceTable(); } /** diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java index 1ab774f69f63..27669d89567b 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPathContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -49,6 +49,8 @@ import javax.xml.transform.ErrorListener; import javax.xml.transform.SourceLocator; import javax.xml.transform.URIResolver; +import jdk.xml.internal.JdkXmlConfig; +import jdk.xml.internal.JdkXmlFeatures; import org.xml.sax.XMLReader; /** @@ -56,7 +58,7 @@ * *

        This class extends DTMManager but does not directly implement it.

        * @xsl.usage advanced - * @LastModified: Jan 2019 + * @LastModified: June 2026 */ public class XPathContext extends DTMManager // implements ExpressionContext { @@ -94,6 +96,11 @@ public class XPathContext extends DTMManager // implements ExpressionContext private boolean m_overrideDefaultParser; + /** + * JDK XML feature settings + */ + private JdkXmlFeatures m_jdkXmlFeatures; + /** * Though XPathContext context extends * the DTMManager, it really is a proxy for this object, which @@ -128,6 +135,27 @@ public boolean isSecureProcessing() return m_isSecureProcessing; } + /** + * Sets the JDK XML feature settings for this XPath evaluation. + */ + public void setJdkXmlFeatures(JdkXmlFeatures features) + { + m_jdkXmlFeatures = features; + } + + /** + * {@return the JDK XML feature settings for this XPath evaluation} + * + * The settings are initialized from the global configuration if needed. + */ + public JdkXmlFeatures getJdkXmlFeatures() + { + if (m_jdkXmlFeatures == null) { + m_jdkXmlFeatures = JdkXmlConfig.getInstance(false).getXMLFeatures(true); + } + return m_jdkXmlFeatures; + } + /** * Get an instance of a DTM, loaded with the content from the * specified source. If the unique flag is true, a new instance will diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java index 0beccb5132bf..b6f8c87d61e2 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncSystemProperty.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -20,19 +20,22 @@ package com.sun.org.apache.xpath.internal.functions; +import com.sun.org.apache.xalan.internal.res.XSLMessages; import com.sun.org.apache.xpath.internal.XPathContext; import com.sun.org.apache.xpath.internal.objects.XObject; import com.sun.org.apache.xpath.internal.objects.XString; import com.sun.org.apache.xpath.internal.res.XPATHErrorResources; -import java.io.BufferedInputStream; import java.io.InputStream; import java.util.Properties; +import javax.xml.transform.TransformerException; +import jdk.xml.internal.JdkConstants; +import jdk.xml.internal.JdkXmlFeatures; import jdk.xml.internal.SecuritySupport; /** * Execute the SystemProperty() function. * @xsl.usage advanced - * @LastModified: Nov 2024 + * @LastModified: June 2026 */ public class FuncSystemProperty extends FunctionOneArg { @@ -96,6 +99,7 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme new Object[]{ namespace, fullName }); //"Don't currently do anything with namespace "+namespace+" in property: "+fullName); + checkSysPropAccess(xctxt, fullName); result = System.getProperty(propName); if (null == result) { @@ -106,6 +110,7 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme } else { + checkSysPropAccess(xctxt, fullName); result = System.getProperty(fullName); if (null == result) @@ -131,6 +136,22 @@ public XObject execute(XPathContext xctxt) throws javax.xml.transform.Transforme return new XString(result); } + /** + * Checks whether XPath may access non-XSLT-defined system properties. + */ + private void checkSysPropAccess(XPathContext xctxt, String name) + throws TransformerException + { + JdkXmlFeatures features = xctxt.getJdkXmlFeatures(); + + if (!features.getFeature(JdkXmlFeatures.XmlFeature.JDK_XPATH_SYSTEM_PROPERTY)) { + String fmsg = XSLMessages.createXPATHMessage( + XPATHErrorResources.ER_NON_XSLT_SYSTEM_PROPERTY, + new Object[] {name, JdkConstants.XPATH_ALLOW_NON_XSLT_SYS_PROPS}); + throw new TransformerException(fmsg); + } + } + /** * Retrieve a property bundle from XSLT_PROPERTIES * diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java index 2f4d2ade5453..56a09b645e4f 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathFactoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -35,7 +35,7 @@ * * @author Ramesh Mandava * - * @LastModified: June 2025 + * @LastModified: June 2026 */ public class XPathFactoryImpl extends XPathFactory { @@ -185,6 +185,8 @@ public void setFeature(String name, boolean value) if (value && _featureManager != null) { _featureManager.setFeature(JdkXmlFeatures.XmlFeature.ENABLE_EXTENSION_FUNCTION, JdkProperty.State.FSP, false); + _featureManager.setFeature(JdkXmlFeatures.XmlFeature.JDK_XPATH_SYSTEM_PROPERTY, + JdkProperty.State.FSP, false); _xmlSecMgr.setSecureProcessing(value); } diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java index 3de72f3f68bb..585822dcbd81 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImplUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,7 +56,7 @@ * This class contains several utility methods used by XPathImpl and * XPathExpressionImpl * - * @LastModified: June 2025 + * @LastModified: June 2026 */ class XPathImplUtil { XPathFunctionResolver functionResolver; @@ -94,6 +94,7 @@ XObject eval(Object contextItem, com.sun.org.apache.xpath.internal.XPath xpath) xpathSupport = new com.sun.org.apache.xpath.internal.XPathContext(); } + xpathSupport.setJdkXmlFeatures(featureManager); xpathSupport.setVarStack(new JAXPVariableStack(variableResolver)); XObject xobj; diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java index 716a4bcf63bd..55a106aa67ed 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -31,7 +31,7 @@ * Also you need to update the count of messages(MAX_CODE)or * the count of warnings(MAX_WARNING) [ Information purpose only] * @xsl.usage advanced - * @LastModified: Apr 2025 + * @LastModified: June 2026 */ public class XPATHErrorResources extends ListResourceBundle { @@ -327,6 +327,7 @@ public class XPATHErrorResources extends ListResourceBundle public static final String ER_GETTING_UNKNOWN_PROPERTY = "ER_GETTING_UNKNOWN_PROPERTY"; public static final String ER_XPATH_GROUP_LIMIT = "ER_XPATH_GROUP_LIMIT"; public static final String ER_XPATH_OPERATOR_LIMIT = "ER_XPATH_OPERATOR_LIMIT"; + public static final String ER_NON_XSLT_SYSTEM_PROPERTY = "ER_NON_XSLT_SYSTEM_PROPERTY"; //END: Keys needed for exception messages of JAXP 1.3 XPath API implementation @@ -862,6 +863,10 @@ public class XPATHErrorResources extends ListResourceBundle "JAXP0801002: the compiler encountered an XPath expression containing " + "''{0}'' operators that exceeds the ''{1}'' limit set by ''{2}''."}, + { ER_NON_XSLT_SYSTEM_PROPERTY, + "Access to non-XSLT system property ''{0}'' is not allowed. Set " + + "''{1}'' to ''true'' to allow it."}, + //END: Definitions of error keys used in exception messages of JAXP 1.3 XPath API implementation // Warnings... diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java index 927d19bc3b35..be0e7ec021a3 100644 --- a/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java +++ b/src/java.xml/share/classes/jdk/xml/internal/JdkConstants.java @@ -220,6 +220,13 @@ public final class JdkConstants { */ public static final String XPATH_TOTALOP_LIMIT = "jdk.xml.xpathTotalOpLimit"; + /** + * jdk.xml.allowNonXSLTSysProps: controls whether XPath's system-property() + * function is permitted to access non-XSLT-defined JVM system properties. + */ + public static final String XPATH_ALLOW_NON_XSLT_SYS_PROPS = + "jdk.xml.allowNonXSLTSysProps"; + /** * JDK TransformerFactory and Transformer attribute that specifies a class * loader that will be used for extension functions class loading @@ -428,4 +435,9 @@ public final class JdkConstants { public static final int CDATA_CHUNK_SIZE_DEFAULT = SecuritySupport.getJAXPSystemProperty(Integer.class, CDATA_CHUNK_SIZE, "0"); + /** + * Enable TemplatesImpl deserialization. + */ + public static final String SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION = + "jdk.xml.enableTemplatesImplDeserialization"; } diff --git a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java index 7016d2227ea3..13e69ac10c77 100644 --- a/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java +++ b/src/java.xml/share/classes/jdk/xml/internal/JdkXmlFeatures.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,10 @@ import javax.xml.XMLConstants; import jdk.xml.internal.JdkProperty.ImplPropMap; import jdk.xml.internal.JdkProperty.State; + +import static jdk.xml.internal.JdkConstants.XPATH_ALLOW_NON_XSLT_SYS_PROPS; import static jdk.xml.internal.JdkXmlUtils.SP_USE_CATALOG; +import static jdk.xml.internal.JdkConstants.SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION; /** * This class manages JDK's XML Features. Previously added features and properties @@ -72,7 +75,25 @@ public static enum XmlFeature { * FSP: not enforced by FSP. */ JDK_OVERRIDE_PARSER(ImplPropMap.OVERRIDEPARSER, null, null, false, - null, null, false, false, true, false); + null, null, false, false, true, false), + + /** + * Feature allowNonXSLTSysProps: controls whether XPath's system-property() + * function is permitted to access non-XSLT-defined JVM system properties. + * FSP: when FSP is on, accessing non-XSLT-defined JVM system properties is disabled + */ + JDK_XPATH_SYSTEM_PROPERTY(null, XPATH_ALLOW_NON_XSLT_SYS_PROPS, + XPATH_ALLOW_NON_XSLT_SYS_PROPS, false, + null, null, false, false, true, true), + + /* + * Feature TemplatesImplDeserialization + * FSP: TemplatesImpl deserialization is enforced by FSP. When FSP is on, + * TemplatesImpl deserialization is disabled. + */ + ENABLE_TEMPLATESIMPL_DESERIALIZATION(null, + SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION, SP_ENABLE_TEMPLATESIMPL_DESERIALIZATION, + false, null, null, false, false, true, true); private final ImplPropMap implMap; private final String name; @@ -92,16 +113,18 @@ public static enum XmlFeature { * parameters. * @param name the name of the feature * @param nameSP the name of the System Property + * @param differ * @param nameOld the name of the corresponding legacy property * @param nameOldSP the system property of the legacy property - * @param value the value of the feature - * @param hasSystem a flag to indicate whether the feature is supported + * @param valueDefault the default value of the feature + * @param valueEnforced the FSP-enforced value of the feature + * @param hasSystem a flag indicating whether a system property is supported for the feature * @param enforced a flag indicating whether the feature is * FSP (Feature_Secure_Processing) enforced * with a System property */ XmlFeature(ImplPropMap implMap, String name, String nameSP, boolean differ, - String nameOld, String nameOldSP, boolean value, boolean valueEnforced, + String nameOld, String nameOldSP, boolean valueDefault, boolean valueEnforced, boolean hasSystem, boolean enforced) { this.implMap = implMap; if (implMap != null) { @@ -116,7 +139,7 @@ public static enum XmlFeature { this.nameOldSP = nameOldSP; } this.differ = differ; - this.valueDefault = value; + this.valueDefault = valueDefault; this.valueEnforced = valueEnforced; this.hasSystem = hasSystem; this.enforced = enforced; diff --git a/src/java.xml/share/classes/module-info.java b/src/java.xml/share/classes/module-info.java index a27354ff6a60..fb2f2fe602d7 100644 --- a/src/java.xml/share/classes/module-info.java +++ b/src/java.xml/share/classes/module-info.java @@ -863,7 +863,7 @@ * 9 * * - * jdk.xml.extensionClassLoader + * {@systemProperty jdk.xml.extensionClassLoader} * Sets a non-null ClassLoader instance to be used for loading XSLTC java * extension functions. * @@ -877,7 +877,7 @@ * 9 * * - * jdk.xml.xpathExprGrpLimit + * {@systemProperty jdk.xml.xpathExprGrpLimit} * Limits the number of groups an XPath expression can contain. * * yes @@ -894,14 +894,14 @@ * 19 * * - * jdk.xml.xpathExprOpLimit + * {@systemProperty jdk.xml.xpathExprOpLimit} * Limits the number of operators an XPath expression can contain. * * 100 * 100 * * - * jdk.xml.xpathTotalOpLimit + * {@systemProperty jdk.xml.xpathTotalOpLimit} * Limits the total number of XPath operators in an XSL Stylesheet. * * 10000 @@ -911,6 +911,28 @@ * * * + * {@systemProperty jdk.xml.allowNonXSLTSysProps} + * controls whether XPath's {@code system-property()} function is permitted to + * access non-XSLT-defined JVM system properties in addition to the standard XSLT-defined + * properties supported by the JDK. Attempts to access non-XSLT-defined properties will + * fail with an XPathExpressionException during XPath expression evaluation. + * + * yes + * Boolean + * + * true or false. True allows access to non-XSLT JVM system properties; False + * restricts access to only XSLT-defined properties. + * + * false + * false + * Yes + * + * XPath + * + * Method 2 + * 27 + * + * * {@systemProperty jdk.xml.enableExtensionFunctions} * Determines whether extension functions in the Transform API are to be allowed. * The extension functions in the XPath API are not affected by this property. @@ -1039,6 +1061,24 @@ * 22 * * + * {@systemProperty jdk.xml.enableTemplatesImplDeserialization} + * Enables or disables deserialization of the JDK's implementation of the + * {@link javax.xml.transform.Templates} interface. + * + * yes + * boolean + * true or false. True indicates that deserialization is allowed. False indicates that + * deserialization is disallowed. + * false + * false + * Yes + * + * Transform + * + * [8] + * 7 + * + * * {@systemProperty jdk.xml.resource.access} * Defines allowed access to external resources by specifying a list * of URI patterns, each following the syntax:

        @@ -1130,7 +1170,7 @@ * * {@code *} * {@code *} - * No [8] + * No [9] * * DOM
        * SAX
        @@ -1185,7 +1225,11 @@ * they are set to deny or ignore, other properties that regulate a part or an * aspect of DTD shall have no effect. *

        - * [8] In the current release, the state of the {@code jdk.xml.resource.access} + * [8] This property can be set only via a system property or in the + * JAXP Configuration File. Setting this property + * on a transformer factory or on a {@code Transformer} instance has no effect. + *

        + * [9] In the current release, the state of the {@code jdk.xml.resource.access} * property does not change when {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING FEATURE_SECURE_PROCESSING} * (FSP) is enabled. In future releases, it will transition to a restrictive setting * when FSP is enabled. @@ -1263,7 +1307,7 @@ * {@code http://www.oracle.com/xml/jaxp/properties/xsltcIsStandalone} * * - * {@code jdk.xml.extensionClassLoader} + * {@systemProperty jdk.xml.extensionClassLoader} * {@code jdk.xml.transform.extensionClassLoader} * * diff --git a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c index df4fc54cc966..53d3f743a912 100644 --- a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c +++ b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c @@ -141,13 +141,14 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions memset(&sb, 0, sizeof(struct stat)); /* - * Check that the path is owned by the effective uid/gid of this - * process. Also check that group/other access is not allowed. + * Check that the path itself (not following symbolic links) is + * owned by the effective uid/gid of this process and that it is + * a socket. Also check that group/other access is not allowed. */ uid = geteuid(); gid = getegid(); - res = stat(p, &sb); + res = lstat(p, &sb); if (res != 0) { /* save errno */ res = errno; @@ -156,7 +157,11 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions if (res == 0) { char msg[100]; jboolean isError = JNI_FALSE; - if (sb.st_uid != uid && uid != ROOT_UID) { + if (!S_ISSOCK(sb.st_mode)) { + snprintf(msg, sizeof(msg), + "file is expected to be a socket: S_ISSOCK(sb.st_mode): %d", S_ISSOCK(sb.st_mode)); + isError = JNI_TRUE; + } else if (sb.st_uid != uid && uid != ROOT_UID) { snprintf(msg, sizeof(msg), "file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid); isError = JNI_TRUE; diff --git a/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c index 5b2579eba39c..e99d872ad1ac 100644 --- a/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c +++ b/src/jdk.attach/macosx/native/libattach/VirtualMachineImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -180,13 +180,14 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions memset(&sb, 0, sizeof(struct stat)); /* - * Check that the path is owned by the effective uid/gid of this - * process. Also check that group/other access is not allowed. + * Check that the path itself (not following symbolic links) is + * owned by the effective uid/gid of this process and that it is + * a socket. Also check that group/other access is not allowed. */ uid = geteuid(); gid = getegid(); - res = stat(p, &sb); + res = lstat(p, &sb); if (res != 0) { /* save errno */ res = errno; @@ -195,7 +196,11 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_checkPermissions if (res == 0) { char msg[100]; jboolean isError = JNI_FALSE; - if (sb.st_uid != uid && uid != ROOT_UID) { + if (!S_ISSOCK(sb.st_mode)) { + snprintf(msg, sizeof(msg), + "file is expected to be a socket: S_ISSOCK(sb.st_mode): %d", S_ISSOCK(sb.st_mode)); + isError = JNI_TRUE; + } else if (sb.st_uid != uid && uid != ROOT_UID) { snprintf(msg, sizeof(msg), "file should be owned by the current user (which is %d) but is owned by %d", uid, sb.st_uid); isError = JNI_TRUE; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java index 79773ac252f5..37fc5e7e9203 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java @@ -416,7 +416,7 @@ public boolean isStrictInstance() { } public boolean isInterface() { - return (flags_field & INTERFACE) != 0; + return (flags() & INTERFACE) != 0; } public boolean isAbstract() { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 19354a956c75..e4dfeb9fbde8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -290,7 +290,9 @@ boolean isAssignableAsBlankFinal(VarSymbol v, Env env) { && ((v.flags() & STATIC) != 0) == Resolve.isStatic(env)); boolean insideCompactConstructor = env.enclMethod != null && TreeInfo.isCompactConstructor(env.enclMethod); - return isAssignable & !insideCompactConstructor; + boolean assigningRecordField = (v.flags() & RECORD) != 0; + // Only record component fields are non-assignable in a compact constructor. + return isAssignable && (!insideCompactConstructor || !assigningRecordField); } /** Check that variable can be assigned to. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index c39868223dcf..ded2164c9e71 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -2150,7 +2150,7 @@ void checkOverride(Env env, JCMethodDecl tree, MethodSymbol m) { } if (allowValueClasses && origin.isValueClass() && names.finalize.equals(m.name)) { if (m.overrides(syms.objectFinalize, origin, types, false)) { - log.warning(tree.pos(), Warnings.ValueFinalize); + log.error(tree.pos(), Errors.ValueFinalize); } } if (allowRecords && origin.isRecord()) { @@ -2995,7 +2995,7 @@ void checkAccessFromSerializableElement(final JCTree tree, boolean isLambda) { private void checkAccessFromSerializableElementInner(final JCTree tree, boolean isLambda) { Symbol sym = TreeInfo.symbol(tree); - if (!sym.kind.matches(KindSelector.VAL_MTH)) { + if (!sym.kind.matches(KindSelector.VAL_MTH) || sym.kind == ERR) { return; } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java index 517dd9a89e35..960d204e4deb 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/LocalProxyVarsGen.java @@ -59,6 +59,8 @@ import com.sun.tools.javac.jvm.Target; import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.TreeInfo; import com.sun.tools.javac.tree.TreeScanner; @@ -315,7 +317,8 @@ public void visitIdent(JCTree.JCIdent tree) { @Override public void visitSelect(JCTree.JCFieldAccess tree) { super.visitSelect(tree); - if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null) { + if (ctorPrologue && fieldToLocalMap.get(tree.sym) != null && + TreeInfo.isThisOrSelectorDotThis(tree.selected)) { result = make.at(md).Ident(fieldToLocalMap.get(tree.sym)); rollback.put(result, tree); } else { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java index f338b1ab7264..c0852f26f902 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java @@ -3312,7 +3312,9 @@ JCExpression boxPrimitive(JCExpression tree, Type box) { box, List.nil() .prepend(tree.type)); - return make.App(make.QualIdent(valueOfSym), List.of(tree)); + JCMethodInvocation res = make.App(make.QualIdent(valueOfSym), List.of(tree)); + res.boxingKind = JCMethodInvocation.BoxingKind.BOX; + return res; } /** Unbox an object to a primitive value. */ @@ -3334,7 +3336,9 @@ JCExpression unbox(JCExpression tree, Type primitive) { unboxedType.tsym.name.append(names.Value), // x.intValue() tree.type, List.nil()); - return make.App(make.Select(tree, valueSym)); + JCMethodInvocation res = make.App(make.Select(tree, valueSym)); + res.boxingKind = JCMethodInvocation.BoxingKind.UNBOX; + return res; } /** Visitor method for parenthesized expressions. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java index e560a9db9f29..f27708342699 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java @@ -4024,9 +4024,17 @@ private boolean isSimpleEarlyFieldRefAllowed(DiagnosticPosition pos, // If unrelated this/super, ignore return field.owner != context.owner(); } - if (field.isStatic() || - !field.isMemberOf(context.owner(), types)) { - // If unqualified static field, or unrelated instance field, ignore + if (field.isStatic()) { + // If unqualified static field, ignore + return true; + } + boolean declaredInSuper = + types.closure(context.owner().type) + .stream() + .map(t -> t.tsym) + .anyMatch(ts -> field.owner == ts); + if (!declaredInSuper) { + // If unrelated instance field, ignore return true; } // We have now ruled out all cases where the check should not apply. Let's follow 6.5.6.1 diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java index 6d5d609a0777..3988c0f90d08 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java @@ -780,6 +780,61 @@ public CondItem genCond(JCTree _tree, boolean markBranches) { //expression: code.undefineVariablesInChain(result.falseJumps, limit); code.undefineVariablesInChain(result.trueJumps, limit); + return result; + } else if (inner_tree instanceof JCTypeCast cast) { + //as part of condition, the cast must always be (boolean), assert that holds: + Assert.check(cast.type.hasTag(BOOLEAN)); + CondItem result = genCond(cast.expr, markBranches); + setTypeAnnotationPositions(cast.pos); + return result; + } else if (inner_tree instanceof JCMethodInvocation mi && + mi.boxingKind == JCMethodInvocation.BoxingKind.UNBOX && + ((JCFieldAccess) mi.meth).selected instanceof JCTree unboxedValue && + findDeepestCast(unboxedValue) instanceof JCTypeCast deepestCast && + deepestCast.expr instanceof JCMethodInvocation boxing && + boxing.boxingKind == JCMethodInvocation.BoxingKind.BOX) { + //case like: + //(Boolean) (b && (result = 1) > 0) + //expanded to: + //((Boolean)Boolean.valueOf((b && (result = 1) > 0))).booleanValue() + //it is impossible to correctly track variable DA across the method calls, + //so call after evaluating the actual condition, + //keep the true and false branches separate + //note that in place of (Boolean), there may be multiple casts, + //to different types, including (Boolean) (Object) (String) (Object) (Boolean) + + //generate the actual condition: + JCExpression boxedValue = boxing.args.head; + CondItem value = genCond(boxedValue, markBranches); + + //now generate the cast cascade, once for true and once for false, + //ignoring the resulting value, but preserving the codepath: + ClassSymbol jlBoolean = types.boxedClass(syms.booleanType); + + Chain secondJumps = value.jumpFalse(); + code.resolve(value.trueJumps); + make.at(boxing.args.head); + VarSymbol _true = + rs.resolveInternalField(_tree, attrEnv, jlBoolean.type, names.TRUE); + deepestCast.expr = make.QualIdent(_true); + genExpr(unboxedValue, unboxedValue.type).drop(); + Chain trueJumps = code.branch(goto_); + + code.resolve(secondJumps); + make.at(boxing.args.head); + VarSymbol _false = + rs.resolveInternalField(_tree, attrEnv, jlBoolean.type, names.FALSE); + deepestCast.expr = make.QualIdent(_false); + genExpr(unboxedValue, unboxedValue.type).drop(); + Chain falseJumps = code.branch(goto_); + + CondItem result = items.makeCondItem(goto_, + trueJumps, + falseJumps); + if (markBranches) result.tree = _tree; + + deepestCast.expr = boxing; + return result; } else { CondItem result = genExpr(_tree, syms.booleanType).mkCond(); @@ -787,6 +842,17 @@ public CondItem genCond(JCTree _tree, boolean markBranches) { return result; } } + //where: + JCTree findDeepestCast(JCTree source) { + JCTree lastTypeCast = null; + + while (TreeInfo.skipParens(source) instanceof JCTypeCast tc) { + lastTypeCast = tc; + source = tc.expr; + } + + return lastTypeCast; + } public Code getCode() { return code; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index f676b7ae44d6..3669a2065a0b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -4398,5 +4398,5 @@ compiler.err.enclosing.class.type.non.denotable=\ enclosing class type: {0}\n\ is non-denotable, try casting to a denotable type -compiler.warn.value.finalize=\ - value classes should not have a finalizer method, as it is not invoked +compiler.err.value.finalize=\ + value classes cannot have finalize methods diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java index 717f95390fc7..27312f4ec817 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java @@ -1872,6 +1872,7 @@ public static class JCMethodInvocation extends JCPolyExpression implements Metho public JCExpression meth; public List args; public Type varargsElement; + public BoxingKind boxingKind; protected JCMethodInvocation(List typeargs, JCExpression meth, List args) @@ -1909,6 +1910,11 @@ public JCMethodInvocation setType(Type type) { public Tag getTag() { return(APPLY); } + public enum BoxingKind { + NONE, + BOX, + UNBOX; + } } /** diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java index 30374a40824d..bf2f5d1f372f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java @@ -171,6 +171,10 @@ public static Names instance(Context context) { public final Name Varargs; public final Name PermittedSubclasses; + // members of java.lang.Boolean + public final Name FALSE; + public final Name TRUE; + // members of java.lang.annotation.ElementType public final Name ANNOTATION_TYPE; public final Name CONSTRUCTOR; @@ -369,6 +373,10 @@ public Names(Context context) { Varargs = fromString("Varargs"); PermittedSubclasses = fromString("PermittedSubclasses"); + // members of java.lang.Boolean + FALSE = fromString("FALSE"); + TRUE = fromString("TRUE"); + // members of java.lang.annotation.ElementType ANNOTATION_TYPE = fromString("ANNOTATION_TYPE"); CONSTRUCTOR = fromString("CONSTRUCTOR"); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java index 39827af42e54..e349251cb10c 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Cipher.java @@ -71,9 +71,8 @@ private static interface Padding { int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen); // DEC: return the length of trailing padding bytes given the specified - // padded data - int unpad(byte[] paddedData, int len) - throws BadPaddingException, IllegalBlockSizeException; + // padded data, returns -1 if invalid + int unpad(byte[] paddedData, int len) throws IllegalBlockSizeException; } private static class PKCS5Padding implements Padding { @@ -95,23 +94,32 @@ public int setPaddingBytes(byte[] paddingBuffer, int startOff, int padLen) { } public int unpad(byte[] paddedData, int len) - throws BadPaddingException, IllegalBlockSizeException { + throws IllegalBlockSizeException { if ((len < 1) || (len % blockSize != 0)) { throw new IllegalBlockSizeException ("Input length must be multiples of " + blockSize); } - byte padValue = paddedData[len - 1]; - if (padValue < 1 || padValue > blockSize) { - throw new BadPaddingException("Invalid pad value!"); - } - // sanity check padding bytes - int padStartIndex = len - padValue; - for (int i = padStartIndex; i < len; i++) { - if (paddedData[i] != padValue) { - throw new BadPaddingException("Invalid pad bytes!"); - } + + int padValue = paddedData[len - 1] & 0x0ff; + // check padValue >= 1; negative if fail + int invalid = (padValue - 1); + // check padValue <= blockSize; negative if fail + invalid |= (blockSize - padValue); + + // check the last block for the padding bytes + for (int i = len - blockSize; i < len; i++) { + // mask = 0 for the first (blockSize - padValue) bytes in the + // block. Otherwise, use 0xffff to preserve the value + int mask = (len - padValue - i - 1) >> 8 ; + int valueCheck = ((paddedData[i] & 0x0ff) ^ padValue) & mask; + // again, assign negative value if fail + invalid |= -valueCheck; } - return padValue; + // convert 'invalid' to -1 when 'invalid' is not 0 + // right shifting by 8 should be sufficient since value range is + // small + invalid >>= 8; // 0 or -1 + return (invalid | padValue); } } @@ -970,6 +978,9 @@ private int implDoFinal(byte[] out, int outOfs, int outLen) padBuffer.length - k); int actualPadLen = paddingObj.unpad(padBuffer, k); + if (actualPadLen < 0) { + throw new BadPaddingException("Invalid pad value!"); + } k -= actualPadLen; System.arraycopy(padBuffer, 0, out, outOfs, k); } else { @@ -1084,6 +1095,9 @@ private int implDoFinal(ByteBuffer outBuffer) 0, padBuffer, k, padBuffer.length - k); int actualPadLen = paddingObj.unpad(padBuffer, k); + if (actualPadLen < 0) { + throw new BadPaddingException("Invalid pad value!"); + } k -= actualPadLen; outArray = padBuffer; outOfs = 0; diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Tls12ExtendedMasterSecretGenerator.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Tls12ExtendedMasterSecretGenerator.java new file mode 100644 index 000000000000..054baca520b7 --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Tls12ExtendedMasterSecretGenerator.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2026, IBM Corporation. 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 sun.security.pkcs11; + +import sun.security.internal.spec.TlsMasterSecretParameterSpec; +import sun.security.pkcs11.wrapper.*; + +import javax.crypto.KeyGeneratorSpi; +import javax.crypto.SecretKey; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; + +import static sun.security.pkcs11.TemplateManager.O_GENERATE; +import static sun.security.pkcs11.wrapper.PKCS11Constants.*; + +/** + * KeyGenerator for the TLS extended master secret. + * See RFC 7627 for further details. + * + * @since x.x + */ +public final class P11Tls12ExtendedMasterSecretGenerator extends KeyGeneratorSpi { + + // See RFC 5246, section 8.1 Computing the Master Secret + private static final int TLS_MASTER_SECRET_LEN = 48; + + private static final String MSG = "TlsExtendedMasterSecretGenerator must be " + + "initialized using a TlsMasterSecretParameterSpec"; + + private final Token token; + + CK_VERSION ckVersion; + private long mechanism; + private byte[] sessionHash; + @SuppressWarnings("deprecation") + private TlsMasterSecretParameterSpec spec; + private P11Key p11Key; + + P11Tls12ExtendedMasterSecretGenerator(Token token, String algorithm, long mechanism) + throws PKCS11Exception { + super(); + this.token = token; + this.mechanism = mechanism; + } + + protected void engineInit(SecureRandom random) { + throw new InvalidParameterException(MSG); + } + + @SuppressWarnings("deprecation") + protected void engineInit(AlgorithmParameterSpec params, SecureRandom random) + throws InvalidAlgorithmParameterException { + if (!(params instanceof TlsMasterSecretParameterSpec spec)) { + throw new InvalidAlgorithmParameterException(MSG); + } + + int tlsVersion = (spec.getMajorVersion() << 8) | spec.getMinorVersion(); + if (tlsVersion != 0x0303) { + // RFC 7627 defines EMS for earlier TLS versions as well, but + // this implementation only exposes PKCS#11 TLS 1.2 EMS mechanisms. + throw new InvalidAlgorithmParameterException( + "Extended Master Secret is only supported for TLS 1.2"); + } + + try { + p11Key = P11SecretKeyFactory.convertKey(token, + spec.getPremasterSecret(), null); + } catch (InvalidKeyException e) { + throw new InvalidAlgorithmParameterException("init() failed", e); + } + + this.spec = spec; + + String alg = p11Key.getAlgorithm(); + boolean isTlsRsaPremasterSecret = "TlsRsaPremasterSecret".equals(alg); + if (!isTlsRsaPremasterSecret && !"TlsPremasterSecret".equals(alg)) { + throw new InvalidAlgorithmParameterException( + "Unsupported premaster secret algorithm: " + alg); + } + + mechanism = isTlsRsaPremasterSecret + ? CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE + : CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH; + + if (isTlsRsaPremasterSecret) { + ckVersion = new CK_VERSION(0, 0); + } else { + // PKCS#11 defines separate EMS derivation mechanisms for RSA + // premaster secrets and all other premaster secret types. + // The non-RSA mechanism is used for DH, ECDH, ECDHE, Kerberos, + // and other key-exchange methods whose premaster secret does + // not contain the embedded protocol version present in RSA + // premaster secrets. + ckVersion = null; + } + + sessionHash = spec.getExtendedMasterSecretSessionHash(); + if (sessionHash.length == 0) { + throw new InvalidAlgorithmParameterException( + "Extended Master Secret session hash missing"); + } + } + + protected void engineInit(int keysize, SecureRandom random) { + throw new InvalidParameterException(MSG); + } + + protected SecretKey engineGenerateKey() { + if (spec == null) { + throw new IllegalStateException( + "TlsExtendedMasterSecretGenerator must be initialized"); + } + + CK_MECHANISM ckMechanism = new CK_MECHANISM( + mechanism, new CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS( + Functions.getHashMechId(spec.getPRFHashAlg()), + sessionHash, ckVersion)); + + Session session = null; + long p11KeyID = p11Key.getKeyID(); + + try { + session = token.getObjSession(); + + CK_ATTRIBUTE[] attributes = new CK_ATTRIBUTE[]{ + new CK_ATTRIBUTE(CKA_CLASS, CKO_SECRET_KEY), + new CK_ATTRIBUTE(CKA_KEY_TYPE, CKK_GENERIC_SECRET), + new CK_ATTRIBUTE(CKA_VALUE_LEN, TLS_MASTER_SECRET_LEN) + }; + + attributes = token.getAttributes( + O_GENERATE, + CKO_SECRET_KEY, + CKK_GENERIC_SECRET, + attributes); + + long keyID = token.p11.C_DeriveKey(session.id(), ckMechanism, + p11KeyID, attributes); + + int major = (ckVersion == null) ? -1 : ckVersion.major; + int minor = (ckVersion == null) ? -1 : ckVersion.minor; + + return P11Key.masterSecretKey(session, keyID, "TlsMasterSecret", + TLS_MASTER_SECRET_LEN << 3, attributes, major, minor); + } catch (PKCS11Exception e) { + throw new ProviderException("Could not generate extended master secret", e); + } finally { + p11Key.releaseKeyID(); + token.releaseSession(session); + } + } +} diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java index 00d8559e6966..276f9e14a814 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, IBM Corporation. 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 @@ -1031,6 +1032,10 @@ private static void register(Descriptor d) { d(KG, "SunTls12MasterSecret", "sun.security.pkcs11.P11TlsMasterSecretGenerator", m(CKM_TLS12_MASTER_KEY_DERIVE, CKM_TLS12_MASTER_KEY_DERIVE_DH)); + d(KG, "SunTls12ExtendedMasterSecret", + "sun.security.pkcs11.P11TlsExtendedMasterSecretGenerator", + m(CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE, + CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH)); d(KG, "SunTlsKeyMaterial", "sun.security.pkcs11.P11TlsKeyMaterialGenerator", m(CKM_SSL3_KEY_AND_MAC_DERIVE, CKM_TLS_KEY_AND_MAC_DERIVE)); @@ -1408,6 +1413,7 @@ public Object newInstance0(Object param) throws PKCS11Exception, NoSuchAlgorithmException { String algorithm = getAlgorithm(); String type = getType(); + if (type == MD) { return new P11Digest(token, algorithm, mechanism); } else if (type == CIP) { @@ -1455,6 +1461,9 @@ public Object newInstance0(Object param) throws || algorithm == "SunTls12MasterSecret") { return new P11TlsMasterSecretGenerator( token, algorithm, mechanism); + } else if (algorithm == "SunTls12ExtendedMasterSecret") { + return new P11Tls12ExtendedMasterSecretGenerator( + token, algorithm, mechanism); } else if (algorithm == "SunTlsKeyMaterial" || algorithm == "SunTls12KeyMaterial") { return new P11TlsKeyMaterialGenerator( diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java index 4d518b7a167a..4051f4cc07f7 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_MECHANISM.java @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -111,6 +110,10 @@ public CK_MECHANISM(long mechanism, CK_TLS12_MASTER_KEY_DERIVE_PARAMS params) { init(mechanism, params); } + public CK_MECHANISM(long mechanism, CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS params) { + init(mechanism, params); + } + public CK_MECHANISM(long mechanism, CK_SSL3_KEY_MAT_PARAMS params) { init(mechanism, params); } diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS.java new file mode 100644 index 000000000000..174aad5f6e8f --- /dev/null +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2026, IBM Corporation. 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.security.pkcs11.wrapper; + +/** + * CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS from PKCS#11 v3.20. + */ +public class CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS { + + /** + * PKCS#11: + * + *

        +     * CK_MECHANISM_TYPE prfHashMechanism;
        +     * 
        + */ + public long prfHashMechanism; + + /** + * PKCS#11: + * + *
        +     * CK_BYTE_PTR pSessionHash;
        +     * CK_ULONG ulSessionHashLen;
        +     * 
        + */ + public byte[] pSessionHash; + + /** + * PKCS#11: + * + *
        +     * CK_VERSION_PTR pVersion;
        +     * 
        + */ + public CK_VERSION pVersion; + + public CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS( + long prfHashMechanism, + byte[] sessionHash, + CK_VERSION version) { + this.prfHashMechanism = prfHashMechanism; + this.pSessionHash = sessionHash; + this.pVersion = version; + } +} diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java index 723235b7c36f..0b217b09492f 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/Functions.java @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -1162,6 +1161,10 @@ private static void addParamSources(long id, String name) { addMech(CKM_SP800_108_FEEDBACK_KDF, "CKM_SP800_108_FEEDBACK_KDF"); addMech(CKM_SP800_108_DOUBLE_PIPELINE_KDF, "CKM_SP800_108_DOUBLE_PIPELINE_KDF"); + addMech(CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE, + "CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE"); + addMech(CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH, + "CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH"); addMech(CKM_VENDOR_DEFINED, "CKM_VENDOR_DEFINED"); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java index 924bfbcc2267..2721664d8d3d 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/PKCS11Constants.java @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -989,6 +988,9 @@ public interface PKCS11Constants { public static final long CKM_SP800_108_FEEDBACK_KDF = 0x000003adL; public static final long CKM_SP800_108_DOUBLE_PIPELINE_KDF = 0x000003aeL; + public static final long CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE = 0x00000056L; + public static final long CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH = 0x00000057L; + public static final long CKM_VENDOR_DEFINED = 0x80000000L; // NSS private diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c index 37404db10248..b4ef9c33f374 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_convert.c @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -731,6 +730,79 @@ jTlsMacParamsToCKTlsMacParamPtr(JNIEnv *env, jobject jParam, CK_ULONG *pLength) return ckParamPtr; } +/* + * converts the Java CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS object to a + * CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS pointer + * + * @param env - used to call JNI functions to get the Java classes and objects + * @param jParam - the Java CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS object + * to convert + * @param pLength - length of the allocated memory of the returned pointer + * @return pointer to the new CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS + * structure + */ +CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS_PTR +jTls12ExtendedMasterKeyDeriveParamToCKTls12ExtendedMasterKeyDeriveParamPtr( + JNIEnv *env, jobject jParam, CK_ULONG *pLength) +{ + CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS_PTR ckParamPtr; + jclass clazz; + jfieldID fieldID; + + jlong prfHashMechanism; + jobject jVersion; + jobject jSessionHash; + + if (pLength != NULL) { + *pLength = 0L; + } + + clazz = (*env)->FindClass(env, CLASS_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS); + if (clazz == NULL) { return NULL; } + + fieldID = (*env)->GetFieldID(env, clazz, "prfHashMechanism", "J"); + if (fieldID == NULL) { return NULL;} + prfHashMechanism = (*env)->GetLongField(env, jParam, fieldID); + + fieldID = (*env)->GetFieldID(env, clazz, "pSessionHash", "[B"); + if (fieldID == NULL) { return NULL; } + jSessionHash = (*env)->GetObjectField(env, jParam, fieldID); + + fieldID = (*env)->GetFieldID(env, clazz, "pVersion", "Lsun/security/pkcs11/wrapper/CK_VERSION;"); + if (fieldID == NULL) { return NULL; } + jVersion = (*env)->GetObjectField(env, jParam, fieldID); + + ckParamPtr = calloc(1, sizeof(CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS)); + if (ckParamPtr == NULL) { + p11ThrowOutOfMemoryError(env, 0); + return NULL; + } + + ckParamPtr->prfHashMechanism = jLongToCKULong(prfHashMechanism); + + jByteArrayToCKByteArray(env, jSessionHash, &(ckParamPtr->pSessionHash), + &(ckParamPtr->ulSessionHashLen)); + if ((*env)->ExceptionCheck(env)) { + goto cleanup; + } + + ckParamPtr->pVersion = jVersionToCKVersionPtr(env, jVersion); + if ((*env)->ExceptionCheck(env)) { + goto cleanup; + } + + if (pLength != NULL) { + *pLength = sizeof(CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS); + } + + return ckParamPtr; +cleanup: + free(ckParamPtr->pSessionHash); + free(ckParamPtr->pVersion); + free(ckParamPtr); + return NULL; +} + /* * converts a Java CK_HKDF_PARAMS object to a CK_HKDF_PARAMS pointer * @@ -1631,6 +1703,12 @@ CK_VOID_PTR jMechParamToCKMechParamPtrSlow(JNIEnv *env, jobject jParam, ckpParamPtr = jTls12MasterKeyDeriveParamToCKTls12MasterKeyDeriveParamPtr(env, jParam, ckpLength); break; + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE: + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH: + ckpParamPtr = + jTls12ExtendedMasterKeyDeriveParamToCKTls12ExtendedMasterKeyDeriveParamPtr( + env, jParam, ckpLength); + break; case CKM_TLS_PRF: case CKM_NSS_TLS_PRF_GENERAL: ckpParamPtr = jTlsPrfParamsToCKTlsPrfParamPtr(env, jParam, diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_keymgmt.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_keymgmt.c index e540f3b5ed9f..3f06af317061 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_keymgmt.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_keymgmt.c @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -942,9 +941,22 @@ JNIEXPORT jlong JNICALL Java_sun_security_pkcs11_wrapper_PKCS11_C_1DeriveKey /* we must copy back the client version */ ssl3CopyBackClientVersion(env, ckpMechanism, jMechanism); break; - case CKM_TLS12_MASTER_KEY_DERIVE: - tls12CopyBackClientVersion(env, ckpMechanism, jMechanism); + case CKM_TLS12_MASTER_KEY_DERIVE: { + CK_TLS12_MASTER_KEY_DERIVE_PARAMS *p = + (CK_TLS12_MASTER_KEY_DERIVE_PARAMS *)ckpMechanism->pParameter; + + copyBackClientVersion(env, ckpMechanism, jMechanism, + p->pVersion, CLASS_TLS12_MASTER_KEY_DERIVE_PARAMS); break; + } + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE: { + CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS *p = + (CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS *)ckpMechanism->pParameter; + + copyBackClientVersion(env, ckpMechanism, jMechanism, + p->pVersion, CLASS_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS); + break; + } case CKM_SSL3_KEY_AND_MAC_DERIVE: case CKM_TLS_KEY_AND_MAC_DERIVE: /* we must copy back the unwrapped key info to the jMechanism object */ @@ -973,7 +985,7 @@ JNIEXPORT jlong JNICALL Java_sun_security_pkcs11_wrapper_PKCS11_C_1DeriveKey return jKeyHandle ; } -static void copyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, jobject jMechanism, +void copyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, jobject jMechanism, CK_VERSION *ckVersion, const char *class_master_key_derive_params) { jclass jMasterKeyDeriveParamsClass, jMechanismClass, jVersionClass; @@ -1046,25 +1058,6 @@ void ssl3CopyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, } } -/* - * Copy back the client version information from the native - * structure to the Java object. This is only used for - * CKM_TLS12_MASTER_KEY_DERIVE mechanism when used for deriving a key. - * - */ -void tls12CopyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, - jobject jMechanism) -{ - CK_TLS12_MASTER_KEY_DERIVE_PARAMS *ckTLS12MasterKeyDeriveParams; - ckTLS12MasterKeyDeriveParams = - (CK_TLS12_MASTER_KEY_DERIVE_PARAMS *)ckpMechanism->pParameter; - if (ckTLS12MasterKeyDeriveParams != NULL_PTR) { - copyBackClientVersion(env, ckpMechanism, jMechanism, - ckTLS12MasterKeyDeriveParams->pVersion, - CLASS_TLS12_MASTER_KEY_DERIVE_PARAMS); - } -} - static void copyBackKeyMatParams(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, jobject jMechanism, CK_SSL3_RANDOM_DATA *RandomInfo, CK_SSL3_KEY_MAT_OUT_PTR ckSSL3KeyMatOut, const char *class_key_mat_params) diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c index 463818626dd7..d222c4f139c8 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -318,6 +317,7 @@ void freeCKMechanismPtr(CK_MECHANISM_PTR mechPtr) { CK_SSL3_MASTER_KEY_DERIVE_PARAMS *sslMkdTmp; CK_SSL3_KEY_MAT_PARAMS* sslKmTmp; CK_TLS12_MASTER_KEY_DERIVE_PARAMS *tlsMkdTmp; + CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS *tlsExtMkdTmp; CK_TLS12_KEY_MAT_PARAMS* tlsKmTmp; if (mechPtr != NULL) { @@ -395,6 +395,13 @@ void freeCKMechanismPtr(CK_MECHANISM_PTR mechPtr) { free(tlsMkdTmp->RandomInfo.pServerRandom); free(tlsMkdTmp->pVersion); break; + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE: + case CKM_TLS12_EXTENDED_MASTER_KEY_DERIVE_DH: + tlsExtMkdTmp = tmp; + TRACE0("[ CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS ]\n"); + free(tlsExtMkdTmp->pSessionHash); + free(tlsExtMkdTmp->pVersion); + break; case CKM_TLS12_KEY_AND_MAC_DERIVE: tlsKmTmp = tmp; TRACE0("[ CK_TLS12_KEY_MAT_PARAMS ]\n"); diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h index 347ef00c93a1..1a2bab653289 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/pkcs11wrapper.h @@ -1,8 +1,7 @@ /* * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - */ - -/* Copyright (c) 2002 Graz University of Technology. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * Copyright (c) 2002 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -306,6 +305,7 @@ void printDebug(const char *format, ...); // CLASS_SSL3_KEY_MAT_OUT is used by CLASS_SSL3_KEY_MAT_PARAMS and CK_TLS12_KEY_MAT_PARAMS #define CLASS_SSL3_MASTER_KEY_DERIVE_PARAMS "sun/security/pkcs11/wrapper/CK_SSL3_MASTER_KEY_DERIVE_PARAMS" #define CLASS_TLS12_MASTER_KEY_DERIVE_PARAMS "sun/security/pkcs11/wrapper/CK_TLS12_MASTER_KEY_DERIVE_PARAMS" +#define CLASS_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS "sun/security/pkcs11/wrapper/CK_TLS12_EXTENDED_MASTER_KEY_DERIVE_PARAMS" #define CLASS_SSL3_KEY_MAT_PARAMS "sun/security/pkcs11/wrapper/CK_SSL3_KEY_MAT_PARAMS" #define CLASS_TLS12_KEY_MAT_PARAMS "sun/security/pkcs11/wrapper/CK_TLS12_KEY_MAT_PARAMS" #define CLASS_TLS_PRF_PARAMS "sun/security/pkcs11/wrapper/CK_TLS_PRF_PARAMS" @@ -443,7 +443,8 @@ typedef struct { void copyBackPBEInitializationVector(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); void copyBackSetUnwrappedKey(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); void ssl3CopyBackClientVersion(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); -void tls12CopyBackClientVersion(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); +void copyBackClientVersion(JNIEnv *env, CK_MECHANISM_PTR ckpMechanism, jobject jMechanism, + CK_VERSION_PTR pVersion, const char *mechanismClass); void ssl3CopyBackKeyMatParams(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); void tls12CopyBackKeyMatParams(JNIEnv *env, CK_MECHANISM *ckMechanism, jobject jMechanism); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/z/ZPageAllocator.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/z/ZPageAllocator.java index 46c830cc07c3..00317b6a6095 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/z/ZPageAllocator.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/gc/z/ZPageAllocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,7 +47,10 @@ public class ZPageAllocator extends VMObject { private static synchronized void initialize(TypeDataBase db) { Type type = db.lookupType("ZPageAllocator"); - maxCapacityField = type.getCIntegerField("_max_capacity"); + // This only represents the maximum possible capacity. Which may be different + // from what other servicability APIs (MXBeans, Runtime, etc.) report. + // See ZPageAllocator::static_max_capacity() vs ZPageAllocator::dynamic_max_capacity() + maxCapacityField = type.getCIntegerField("_static_max_capacity"); partitionsOffset = type.getAddressField("_partitions").getOffset(); numaCount = ZNUMA.count(); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java index add2f8b31184..f9d4016ac356 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Array.java @@ -61,6 +61,9 @@ private static void initialize(TypeDataBase db) throws WrongTypeException { // aligned 0 mod 8. The arrayOop itself must be aligned at least this // strongly. private static boolean elementTypeShouldBeAligned(BasicType type) { + if (type == BasicType.T_FLAT_ELEMENT) { + return true; + } if (VM.getVM().isLP64()) { if (type == BasicType.T_OBJECT || type == BasicType.T_ARRAY) { return !VM.getVM().isCompressedOopsEnabled(); @@ -117,6 +120,7 @@ public static long baseOffsetInBytes(BasicType type) { } public boolean isArray() { return true; } + public boolean isFlatArray() { return false; } public void iterateFields(OopVisitor visitor, boolean doVMFields) { super.iterateFields(visitor, doVMFields); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArray.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArray.java index 523378db5bff..d8614d040021 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArray.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArray.java @@ -34,7 +34,7 @@ // A FlatArray is an array containing flattened value objects. -public class FlatArray extends Array { +public class FlatArray extends ObjArray { static { VM.registerVMInitializedObserver(new Observer() { public void update(Observable o, Object data) { @@ -50,6 +50,7 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc super(handle, heap); } + @Override public boolean isFlatArray() { return true; } public void printValueOn(PrintStream tty) { @@ -57,14 +58,30 @@ public void printValueOn(PrintStream tty) { klass.printValueOn(tty); } - public void iterateFields(OopVisitor visitor, boolean doVMFields) { - super.iterateFields(visitor, doVMFields); - FlatArrayKlass klass = (FlatArrayKlass) getKlass(); - int length = (int) getLength(); - int type = klass.getElementType(); - //System.out.println("FlatArray.iterateFields: length:" + length + " type:" + type); + @Override + public OopHandle getOopHandleAt(long index) { + // FlatArray contains value object, it means array contents are payload. + // Thus we need to create oop handle from oop of value object. + return getObjAt(index).getHandle(); + } + + private OopField getOopField(int index) { + int shift = Klass.layoutHelperLog2ElementSize(getKlass().getLayoutHelper()); + long baseOffset = baseOffsetInBytes(BasicType.T_FLAT_ELEMENT); + long elementSize = 1 << shift; // from FlatArrayKlass::oop_oop_iterate_elements_specialized_bounded() (addr_incr) + long offset = baseOffset + (elementSize * (long)index); + return new OopField(new IndexableFieldIdentifier(index), offset, false); + } + + @Override + public Oop getObjAt(long index) { + return getHeap().newFlattenedOop(this, getOopField((int)index)); + } + + @Override + protected void iterateFieldsInternal(OopVisitor visitor, int length) { for (int index = 0; index < length; index++) { - // FIXME - call visitor.doXXX() for each component of each value object + visitor.doOop(getOopField(index), false); } } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java index 082777d27b5d..929154dbcce2 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/FlatArrayKlass.java @@ -36,6 +36,9 @@ // FlatArrayKlass is a proxy for FlatArrayKlass in the JVM public class FlatArrayKlass extends ObjArrayKlass { + + private static CIntegerField layoutKindField; + static { VM.registerVMInitializedObserver(new Observer() { public void update(Observable o, Object data) { @@ -46,13 +49,18 @@ public void update(Observable o, Object data) { private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { Type t = db.lookupType("FlatArrayKlass"); - // TODO: implement similar features as in ObjArrayKlass + + layoutKindField = t.getCIntegerField("_layout_kind"); } public FlatArrayKlass(Address addr) { super(addr); } + public LayoutKind getLayoutKind() { + return LayoutKind.valueOf((int)layoutKindField.getValue(addr)); + } + public void printValueOn(PrintStream tty) { tty.print("FlatArrayKlass for "); getElementKlass().printValueOn(tty); diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java index d4f59185b3ef..72db865ebdad 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/Klass.java @@ -123,6 +123,7 @@ public void update(Observable o, Object data) { // anon-enum constants for _layout_helper. public static int LH_INSTANCE_SLOW_PATH_BIT; public static int LH_LOG2_ELEMENT_SIZE_SHIFT; + public static int LH_LOG2_ELEMENT_SIZE_MASK; public static int LH_ELEMENT_TYPE_SHIFT; public static int LH_HEADER_SIZE_SHIFT; public static int LH_ARRAY_TAG_SHIFT; @@ -148,6 +149,7 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc LH_INSTANCE_SLOW_PATH_BIT = db.lookupIntConstant("Klass::_lh_instance_slow_path_bit").intValue(); LH_LOG2_ELEMENT_SIZE_SHIFT = db.lookupIntConstant("Klass::_lh_log2_element_size_shift").intValue(); + LH_LOG2_ELEMENT_SIZE_MASK = db.lookupIntConstant("Klass::_lh_log2_element_size_mask").intValue(); LH_ELEMENT_TYPE_SHIFT = db.lookupIntConstant("Klass::_lh_element_type_shift").intValue(); LH_HEADER_SIZE_SHIFT = db.lookupIntConstant("Klass::_lh_header_size_shift").intValue(); LH_ARRAY_TAG_SHIFT = db.lookupIntConstant("Klass::_lh_array_tag_shift").intValue(); @@ -279,4 +281,9 @@ public Klass arrayKlassImpl(boolean orNull) { // The subclasses override this to produce the correct form, eg // Ljava/lang/String; For ArrayKlasses getName itself is the signature. public String signature() { return getName().asString(); } + + public static int layoutHelperLog2ElementSize(int lh) { + int l2esz = (lh >>> LH_LOG2_ELEMENT_SIZE_SHIFT) & LH_LOG2_ELEMENT_SIZE_MASK; + return l2esz; + } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKind.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKind.java new file mode 100644 index 000000000000..f4b61aba7d41 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKind.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA + * 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 sun.jvm.hotspot.oops; + +import sun.jvm.hotspot.runtime.VM; +import sun.jvm.hotspot.runtime.VMObject; +import sun.jvm.hotspot.types.Type; +import sun.jvm.hotspot.types.TypeDataBase; +import sun.jvm.hotspot.types.WrongTypeException; + + +public final class LayoutKind { + + public static LayoutKind REFERENCE; + public static LayoutKind BUFFERED; + public static LayoutKind NULL_FREE_NON_ATOMIC_FLAT; + public static LayoutKind NULL_FREE_ATOMIC_FLAT; + public static LayoutKind NULLABLE_ATOMIC_FLAT; + public static LayoutKind NULLABLE_NON_ATOMIC_FLAT; + public static LayoutKind UNKNOWN; + + private final int value; + + static { + VM.registerVMInitializedObserver((_, _) -> initialize(VM.getVM().getTypeDataBase())); + } + + private static synchronized void initialize(TypeDataBase db) throws WrongTypeException { + Type type = db.lookupType("LayoutKind"); + + REFERENCE = new LayoutKind(db.lookupIntConstant("LayoutKind::REFERENCE").intValue()); + BUFFERED = new LayoutKind(db.lookupIntConstant("LayoutKind::BUFFERED").intValue()); + NULL_FREE_NON_ATOMIC_FLAT = new LayoutKind(db.lookupIntConstant("LayoutKind::NULL_FREE_NON_ATOMIC_FLAT").intValue()); + NULL_FREE_ATOMIC_FLAT = new LayoutKind(db.lookupIntConstant("LayoutKind::NULL_FREE_ATOMIC_FLAT").intValue()); + NULLABLE_ATOMIC_FLAT = new LayoutKind(db.lookupIntConstant("LayoutKind::NULLABLE_ATOMIC_FLAT").intValue()); + NULLABLE_NON_ATOMIC_FLAT = new LayoutKind(db.lookupIntConstant("LayoutKind::NULLABLE_NON_ATOMIC_FLAT").intValue()); + UNKNOWN = new LayoutKind(db.lookupIntConstant("LayoutKind::UNKNOWN").intValue()); + } + + private LayoutKind(int value) { + this.value = value; + } + + public static LayoutKind valueOf(int rawValue) { + if (rawValue == REFERENCE.value) { + return REFERENCE; + } else if (rawValue == BUFFERED.value) { + return BUFFERED; + } else if (rawValue == NULL_FREE_NON_ATOMIC_FLAT.value) { + return NULL_FREE_NON_ATOMIC_FLAT; + } else if (rawValue == NULL_FREE_ATOMIC_FLAT.value) { + return NULL_FREE_ATOMIC_FLAT; + } else if (rawValue == NULLABLE_ATOMIC_FLAT.value) { + return NULLABLE_ATOMIC_FLAT; + } else if (rawValue == NULLABLE_NON_ATOMIC_FLAT.value) { + return NULLABLE_NON_ATOMIC_FLAT; + } else if (rawValue == UNKNOWN.value) { + return UNKNOWN; + } else { + throw new IllegalArgumentException("Out of range"); + } + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof LayoutKind k) { + return k.value == this.value; + } + return false; + } +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKindHelper.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKindHelper.java new file mode 100644 index 000000000000..76b7be40cb55 --- /dev/null +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/LayoutKindHelper.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, NTT DATA + * 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 sun.jvm.hotspot.oops; + + +public class LayoutKindHelper { + + public static boolean isNullableFlat(LayoutKind layoutKind) { + return layoutKind.equals(LayoutKind.NULLABLE_ATOMIC_FLAT) || + layoutKind.equals(LayoutKind.NULLABLE_NON_ATOMIC_FLAT); + } + +} diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjArray.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjArray.java index bacf6011a266..a3da0b86135d 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjArray.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -73,9 +73,7 @@ public void printValueOn(PrintStream tty) { tty.print("ObjArray"); } - public void iterateFields(OopVisitor visitor, boolean doVMFields) { - super.iterateFields(visitor, doVMFields); - int length = (int) getLength(); + protected void iterateFieldsInternal(OopVisitor visitor, int length) { long baseOffset = baseOffsetInBytes(BasicType.T_OBJECT); for (int index = 0; index < length; index++) { long offset = baseOffset + (index * elementSize); @@ -88,4 +86,10 @@ public void iterateFields(OopVisitor visitor, boolean doVMFields) { visitor.doOop(field, false); } } + + public void iterateFields(OopVisitor visitor, boolean doVMFields) { + super.iterateFields(visitor, doVMFields); + int length = (int) getLength(); + iterateFieldsInternal(visitor, length); + } } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java index 2f1b662aaeb9..b503f39cf0c3 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ObjectHeap.java @@ -199,13 +199,25 @@ public Oop newOop(OopHandle handle) { // This method is used to instantiate a holder object that contains // the flattened field payload. - public Oop newOop(Address payload, ValueKlass klass, OopField field) { - if (Assert.ASSERTS_ENABLED) { - Assert.that(payload != null, "payload should not be null"); + public Oop newFlattenedOop(Oop obj, OopField field) { + ValueKlass vk; + boolean needNullCheck = false; + if (obj.isArray() && ((Array)obj).isFlatArray()) { + FlatArrayKlass k = (FlatArrayKlass)obj.getKlass(); + vk = (ValueKlass)k.getElementKlass(); + needNullCheck = LayoutKindHelper.isNullableFlat(k.getLayoutKind()); + } else { + var layout = ((InstanceKlass)obj.getKlass()).getValueFieldInfoArray().at(field.getFieldIndex()); + vk = layout.getKlass(); + needNullCheck = field.hasNullMarker(); } - return field.hasNullMarker() && klass.isPayloadMarkedAsNull(payload) + + // OopHandle does not allow to call addOffsetTo() due to prevent interior + // object pointers. So addOffsetToAsOopHandle() is required here. + Address payload = obj.getHandle().addOffsetToAsOopHandle(field.getOffset()); + return needNullCheck && vk.isPayloadMarkedAsNull(payload) ? null - : new FlattenedValue(payload, this, klass); + : new FlattenedValue(payload, this, vk); } // Print all objects in the object heap diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/OopField.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/OopField.java index 9eb1a62f7532..82f461276871 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/OopField.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/OopField.java @@ -48,14 +48,14 @@ public Oop getValue(Oop obj) { throw new InternalError(); } var heap = obj.getHeap(); - if (isFlat()) { - var layout = ((InstanceKlass)obj.getKlass()).getValueFieldInfoArray().at(getFieldIndex()); - ValueKlass vk = layout.getKlass(); - - // OopHandle does not allow to call addOffsetTo() due to prevent interior - // object pointers. So addOffsetToAsOopHandle() is required here. - Address payload = obj.getHandle().addOffsetToAsOopHandle(getOffset()); - return heap.newOop(payload, vk, this); + if (obj.isArray()) { + if (((Array)obj).isFlatArray()) { + return heap.newFlattenedOop(obj, this); + } else { + return heap.newOop(getValueAsOopHandle(obj)); + } + } else if (isFlat()) { + return heap.newFlattenedOop(obj, this); } else { return heap.newOop(getValueAsOopHandle(obj)); } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ValueFieldInfo.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ValueFieldInfo.java index 7e1a713fb281..433ce0c1695c 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ValueFieldInfo.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/oops/ValueFieldInfo.java @@ -35,6 +35,7 @@ public class ValueFieldInfo extends VMObject { private static MetadataField klassField; + private static CIntField kindField; static { VM.registerVMInitializedObserver((_, _) -> initialize(VM.getVM().getTypeDataBase())); @@ -44,6 +45,7 @@ private static synchronized void initialize(TypeDataBase db) throws WrongTypeExc Type type = db.lookupType("ValueFieldInfo"); klassField = new MetadataField(type.getAddressField("_klass"), 0); + kindField = new CIntField(type.getCIntegerField("_kind"), 0); } public ValueFieldInfo(Address addr) { @@ -54,4 +56,9 @@ public ValueKlass getKlass() { return (ValueKlass)klassField.getValue(this); } + public LayoutKind getKind() { + int rawVal = (int)kindField.getValue(this); + return LayoutKind.valueOf(rawVal); + } + } diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java index 36f71407cc92..6f6e091da93d 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/BasicType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,6 +42,7 @@ public class BasicType { public static final BasicType T_OBJECT = new BasicType(); public static final BasicType T_ARRAY = new BasicType(); public static final BasicType T_VOID = new BasicType(); + public static final BasicType T_FLAT_ELEMENT = new BasicType(); public static final BasicType T_ADDRESS = new BasicType(); public static final BasicType T_NARROWOOP = new BasicType(); public static final BasicType T_METADATA = new BasicType(); diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java index 0c003378d77c..057f15b93371 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ChunkedInputStream.java @@ -26,11 +26,11 @@ package sun.net.httpserver; import java.io.*; -import java.net.*; -import com.sun.net.httpserver.*; -import com.sun.net.httpserver.spi.*; class ChunkedInputStream extends LeftOverInputStream { + + private IOException readFailure = null; + ChunkedInputStream(ExchangeImpl t, InputStream src) { super (t, src); } @@ -50,6 +50,9 @@ class ChunkedInputStream extends LeftOverInputStream { private int numeric(char[] arr, int nchars) throws IOException { assert arr.length >= nchars; + if (nchars == 0) { + throw new IOException("missing chunk length"); + } int len = 0; for (int i=0; i Integer.MAX_VALUE) { + throw new IOException("chunk length too big: " + nextLen); + } + len = (int) nextLen; } return len; } @@ -111,14 +118,27 @@ private int readChunkHeader() throws IOException { } protected int readImpl(byte[] b, int off, int len) throws IOException { + // Chunk reader is stateful, don't retry after a failure + if (readFailure != null) { + throw readFailure; + } + try { + return readImpl0(b, off, len); + } catch (IOException ioe) { + readFailure = ioe; + throw ioe; + } + } + + private int readImpl0(byte[] b, int off, int len) throws IOException { if (eof) { return -1; } if (needToReadHeader) { remaining = readChunkHeader(); if (remaining == 0) { - eof = true; consumeCRLF(); + eof = true; t.getServerImpl().requestCompleted(t.getConnection()); return -1; } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java index 75eeb29f015b..c0e6131456b3 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,7 +37,6 @@ */ class Request { - static final int BUF_LEN = 2048; static final byte CR = 13; static final byte LF = 10; static final byte FIRST_CHAR = 32; @@ -60,11 +59,6 @@ class Request { } while ("".equals(startLine)); } - - char[] buf = new char[BUF_LEN]; - int pos; - StringBuffer lineBuf; - public InputStream inputStream() { return is; } @@ -79,8 +73,8 @@ public OutputStream outputStream() { */ public String readLine() throws IOException { - boolean gotCR = false, gotLF = false; - pos = 0; lineBuf = new StringBuffer(); + boolean gotLF = false; + StringBuilder lineBuf = new StringBuilder(); long lsize = 32; // For the first request that comes on a clear connection @@ -106,34 +100,19 @@ public String readLine() throws IOException { if (c == -1) { return null; } - if (gotCR) { - if (c == LF) { - gotLF = true; - } else { - gotCR = false; - consume(CR); - if (firstClearRequest && offset == 0) { - if (c < FIRST_CHAR) { - throw new ProtocolException("Unexpected start of request line"); - } - offset++; - } - consume(c); - lsize = lsize + 2; - } + if (c == CR || c == LF) { + checkCRLF(c); + gotLF = true; } else { - if (c == CR) { - gotCR = true; - } else { - if (firstClearRequest && offset == 0) { - if (c < FIRST_CHAR) { - throw new ProtocolException("Unexpected start of request line"); - } - offset++; + if (firstClearRequest && offset == 0) { + if (c < FIRST_CHAR) { + throw new ProtocolException("Unexpected start of request line"); } - consume(c); - lsize = lsize + 1; + offset++; } + checkCtl(c); + lineBuf.append((char)c); + lsize = lsize + 1; } if (maxReqHeaderSize > 0 && lsize > maxReqHeaderSize) { throw new IOException("Maximum header (" + @@ -141,16 +120,7 @@ public String readLine() throws IOException { ServerConfig.getMaxReqHeaderSize() + "."); } } - lineBuf.append(buf, 0, pos); - return new String(lineBuf); - } - - private void consume(int c) throws IOException { - if (pos == BUF_LEN) { - lineBuf.append(buf); - pos = 0; - } - buf[pos++] = (char)c; + return lineBuf.toString(); } /** @@ -161,7 +131,23 @@ public String requestLine() { } Headers hdrs = null; - @SuppressWarnings("fallthrough") + + void checkCRLF(int firstc) throws IOException { + if (firstc == CR) { + int c = is.read(); + if (c != LF) { + throw new ProtocolException("Invalid CR in request header"); + } + } + } + + void checkCtl(int c) throws IOException { + if (c == 0x7f || + c < ' ' && c != CR && c!= LF && c != '\t') { + throw new ProtocolException("Invalid character in request header"); + } + } + Headers headers() throws IOException { if (hdrs != null) { return hdrs; @@ -175,20 +161,16 @@ Headers headers() throws IOException { // check for empty headers if (firstc == CR || firstc == LF) { - int c = is.read(); - if (c == CR || c == LF) { - return hdrs; - } - s[0] = (char)firstc; - len = 1; - firstc = c; + checkCRLF(firstc); + return hdrs; } long hsize = startLine.length() + 32L; while (firstc != LF && firstc != CR && firstc >= 0) { int keyend = -1; int c; - boolean inKey = firstc > ' '; + boolean inKey = firstc != ' ' && firstc != '\t'; + checkCtl(firstc); s[len++] = (char) firstc; hsize = hsize + 1; parseloop:{ @@ -200,27 +182,26 @@ Headers headers() throws IOException { ? maxReqHeaderSize - hsize - 32 : Long.MAX_VALUE; while ((c = is.read()) >= 0) { + checkCtl(c); switch (c) { - /*fallthrough*/ case ':': - if (inKey && len > 0) + if (inKey && len > 0) { keyend = len; - inKey = false; + inKey = false; + } break; case '\t': c = ' '; - case ' ': - inKey = false; break; case CR: case LF: - firstc = is.read(); - if (c == CR && firstc == LF) { - firstc = is.read(); - if (firstc == CR) - firstc = is.read(); + checkCRLF(c); + if (inKey) { + throw new ProtocolException("Missing field separator"); } - if (firstc == LF || firstc == CR || firstc > ' ') + firstc = is.read(); + checkCRLF(firstc); + if (firstc != ' ' && firstc != '\t') break parseloop; /* continuation */ c = ' '; @@ -255,7 +236,7 @@ Headers headers() throws IOException { } String v; if (keyend >= len) - v = new String(); + v = ""; else v = String.copyValueOf(s, keyend, len - keyend); diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java index 3eda0eb1a484..3bc836186cda 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -71,6 +71,7 @@ class ServerConfig { // the value of the TCP_NODELAY socket-level option private static final boolean noDelay; + private static final boolean hostValidationDisabled; static { @@ -116,6 +117,7 @@ class ServerConfig { debug = Boolean.getBoolean("sun.net.httpserver.debug"); noDelay = Boolean.getBoolean("sun.net.httpserver.nodelay"); + hostValidationDisabled = Boolean.getBoolean("sun.net.httpserver.disableHostValidation"); } @@ -219,4 +221,8 @@ static long getReqRspTimerScheduleMillis() { static boolean noDelay() { return noDelay; } + + static boolean isHostValidationDisabled() { + return hostValidationDisabled; + } } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java index f0a8efe1a6b1..6339e41a3f45 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java @@ -110,6 +110,7 @@ class ServerImpl { static final long REQ_RSP_TIMER_SCHEDULE = ServerConfig.getReqRspTimerScheduleMillis(); static final long MAX_REQ_TIME = getTimeMillis(ServerConfig.getMaxReqTime()); static final long MAX_RSP_TIME = getTimeMillis(ServerConfig.getMaxRspTime()); + static final boolean HOST_VALIDATION_DISABLED = ServerConfig.isHostValidationDisabled();; static final boolean reqRspTimeoutEnabled = MAX_REQ_TIME != -1 || MAX_RSP_TIME != -1; // the maximum idle duration for a connection which is currently idle but has served // some request in the past @@ -790,7 +791,14 @@ public void run() { start = space+1; String version = requestLine.substring(start); - Headers headers = req.headers(); + Headers headers; + try { + headers = req.headers(); + } catch (ProtocolException pe) { + reject(Code.HTTP_BAD_REQUEST, requestLine, + "Failed to parse headers: " + pe.getMessage()); + return; + } /* check key for illegal characters */ for (var k : headers.keySet()) { if (!isValidName(k)) { @@ -840,6 +848,25 @@ public void run() { requestCompleted(connection); } } + /* check if there's a single Host header */ + if (!HOST_VALIDATION_DISABLED) { + List hostValueList = headers.get("Host"); + if (hostValueList == null || hostValueList.isEmpty()) { + if (!version.equalsIgnoreCase("http/1.0")) { + reject(400, requestLine, "Missing Host header"); + return; + } + } else if (hostValueList.size() > 1) { + reject(400, requestLine, "Multiple Host headers"); + return; + } else { + String host = hostValueList.get(0); + if (host.indexOf(',') != -1) { + reject(400, requestLine, "Multiple Host header values"); + return; + } + } + } ctx = contexts.findContext(protocol, uri.getPath()); if (ctx == null) { reject(Code.HTTP_NOT_FOUND, diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java index 08ea357b7f4b..1da06ed9397c 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java @@ -25,6 +25,7 @@ package sun.net.httpserver.simpleserver; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -221,7 +222,8 @@ private Path mapToPath(HttpExchange exchange, Path root) { // resolve each path segment against the root Path path = root; for (var segment : pathSegment) { - if (!URIPathSegment.isSupported(segment)) { + if (!URIPathSegment.isSupported(segment) || segment.contains(File.separator)) { + // checking for separator rules out anomalies like UNCs on Windows return null; // stop resolution, null results in 404 response } path = path.resolve(segment); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template index e00b8682811f..9dfc7ea45e19 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/script.js.template @@ -242,13 +242,13 @@ function isInput(elem) { function switchCopyLabel(button, span) { var copied = span.getAttribute("data-copied"); button.classList.add("visible"); - var initialLabel = span.innerHTML; - span.innerHTML = copied; + var initialLabel = span.textContent; + span.textContent = copied; setTimeout(function() { button.classList.remove("visible"); setTimeout(function() { if (initialLabel !== copied) { - span.innerHTML = initialLabel; + span.textContent = initialLabel; } }, 100); }, 1900); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template index 04b7ac7e3717..c00e4c0df39c 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search.js.template @@ -449,7 +449,7 @@ window.addEventListener("load", () => { if (redirect.checked) { href += "&r=1"; } - searchLink.innerHTML = href; + searchLink.textContent = href; copy.onmouseenter(); } function copyLink(e) { @@ -670,7 +670,7 @@ window.addEventListener("load", () => { const descSpan = document.createElement("span"); descSpan.className = "search-result-desc"; - descSpan.innerHTML = desc; + descSpan.textContent = desc; link.appendChild(labelSpan); link.appendChild(descSpan); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java index adf979697c5b..6f6b7e68f0c4 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/SimpleTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -60,21 +60,6 @@ public class SimpleTaglet extends BaseTaglet implements InheritableTaglet { private final boolean enabled; - /** - * Constructs a {@code SimpleTaglet}. - * - * @param tagName the name of this tag - * @param header the header to output - * @param locations the possible locations that this tag can appear in - * The string can contain 'p' for package, 't' for type, - * 'm' for method, 'c' for constructor and 'f' for field. - * See {@link #getLocations(String) getLocations} for the - * complete list. - */ - SimpleTaglet(HtmlConfiguration config, String tagName, String header, String locations) { - this(config, tagName, header, getLocations(locations), isEnabled(locations)); - } - /** * Constructs a {@code SimpleTaglet}. * @@ -125,6 +110,39 @@ protected SimpleTaglet(HtmlConfiguration config, DocTree.Kind tagKind, String he this.enabled = enabled; } + /** + * Constructs a {@code SimpleTaglet} to represent a custom tag. The returned taglet + * overrides block tag output to render multiple block tags as individual {@code
        } + * elements instead of as a comma-separated list. + * + * @param config the configuration + * @param tagName the name of this tag + * @param header the header to output + * @param locations the possible locations that this tag can appear in + * The string can contain 'p' for package, 't' for type, + * 'm' for method, 'c' for constructor and 'f' for field. + * See {@link #getLocations(String) getLocations} for the + * complete list. + */ + public static SimpleTaglet createCustomTaglet(HtmlConfiguration config, String tagName, + String header, String locations) { + return new SimpleTaglet(config, tagName, header, getLocations(locations), isEnabled(locations)) { + @Override + protected Content simpleBlockTagOutput(Element element, List simpleTags, String header) { + var ch = utils.getCommentHelper(element); + var context = tagletWriter.context; + var htmlWriter = tagletWriter.htmlWriter; + + ContentBuilder output = new ContentBuilder(HtmlTree.DT(RawHtml.of(header))); + for (DocTree simpleTag : simpleTags) { + output.add(HtmlTree.DD(htmlWriter.commentTagsToContent(element, ch.getBody(simpleTag), + context.within(simpleTag)))); + } + return output; + } + }; + } + /** * Constructs a {@code SimpleTaglet} that will look for tags on enclosing type elements * if there are no relevant tags on a nested (member) type element. @@ -134,7 +152,8 @@ protected SimpleTaglet(HtmlConfiguration config, DocTree.Kind tagKind, String he * @param locations the possible locations that this tag can appear in * @param enabled whether this tag is enabled */ - static SimpleTaglet createWithDefaultForNested(HtmlConfiguration config, DocTree.Kind tagKind, String header, Set locations, boolean enabled) { + static SimpleTaglet createWithDefaultForNested(HtmlConfiguration config, DocTree.Kind tagKind, String header, + Set locations, boolean enabled) { return new SimpleTaglet(config, tagKind, header, locations, enabled) { @Override protected List getDefaultBlockTags(Element e, Predicate accepts) { @@ -237,7 +256,7 @@ protected List getDefaultBlockTags(Element e, Predicate< * * @return the output */ - private Content simpleBlockTagOutput(Element element, + protected Content simpleBlockTagOutput(Element element, List simpleTags, String header) { var ch = utils.getCommentHelper(element); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java index 0e7b29590287..0237c08f5629 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/TagletManager.java @@ -303,7 +303,7 @@ public void addNewSimpleCustomTag(String tagName, String header, String location // remove + put in both branches below move the tag to the back of the map's ordering Taglet tag = allTaglets.remove(tagName); if (tag == null || header != null) { - allTaglets.put(tagName, new SimpleTaglet(config, tagName, header, locations)); + allTaglets.put(tagName, SimpleTaglet.createCustomTaglet(config, tagName, header, locations)); if (Utils.toLowerCase(locations).indexOf('x') == -1) { checkTagName(tagName); } diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/util.c b/src/jdk.jdwp.agent/share/native/libjdwp/util.c index 3146d0cfe16b..2c36c0d51ba5 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/util.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/util.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -717,6 +717,10 @@ methodSignature(jmethodID method, error = JVMTI_FUNC_PTR(gdata->jvmti,GetMethodName) (gdata->jvmti, method, &name, &signature, &generic_signature); + if (error != JVMTI_ERROR_NONE) { + EXIT_ERROR(error, "GetMethodName failed"); + } + if ( pname != NULL ) { *pname = name; } else if ( name != NULL ) { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SecurityPropertiesPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SecurityPropertiesPlugin.java new file mode 100644 index 000000000000..708410179aea --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SecurityPropertiesPlugin.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.tools.jlink.internal.plugins; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.HexFormat; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import jdk.tools.jlink.internal.ResourcePoolEntryFactory; +import jdk.tools.jlink.plugin.PluginException; +import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolBuilder; +import jdk.tools.jlink.plugin.ResourcePoolEntry; + +/** + * Security properties plugin. + * + * Creates the java.security configuration file in the output image and + * adds or overrides properties from the specified file. + */ +public class SecurityPropertiesPlugin extends AbstractPlugin { + + private static final String RES = "/java.base/conf/security/java.security"; + + // holds additional properties to be added or overridden + private Properties extraProps; + + public SecurityPropertiesPlugin() { + super("security-properties"); + } + + @Override + public boolean hasArguments() { + return true; + } + + @Override + public boolean hasRawArgument() { + return true; + } + + @Override + public void configure(Map config) { + String propsFile = config.get(getName()); + if (propsFile == null) { + throw new AssertionError(); + } + + extraProps = new Properties(); + try (FileInputStream fis = new FileInputStream(propsFile)) { + extraProps.load(fis); + } catch (IOException ioe) { + throw new IllegalArgumentException(ioe); + } + if (extraProps.isEmpty()) { + throw new IllegalArgumentException("No properties in " + propsFile); + } + } + + @Override + public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { + in.transformAndCopy(res -> { + if (res.type().equals(ResourcePoolEntry.Type.CONFIG) && + res.path().equals(RES)) { + byte[] props = processProperties(res.content()); + return ResourcePoolEntryFactory.create(res, props); + } + return res; + }, out); + return out.build(); + } + + private byte[] processProperties(InputStream content) { + + List lines = new ArrayList<>(); + Set keys = new HashSet<>(); + + // Read in contents of java.security file into separate list, + // replacing values of overridden properties as we go. + // Use ISO_8859_1 to be consistent with java.security file loading. + try (InputStreamReader isr = new InputStreamReader(content, + StandardCharsets.ISO_8859_1); + BufferedReader br = new BufferedReader(isr)) { + String line = br.readLine(); + while (line != null) { + // add comments and blank lines as-is + if (isBlankOrComment(line)) { + lines.add(line); + line = br.readLine(); + continue; + } + + StringBuilder sb = new StringBuilder(line); + while (lineContinues(line)) { + // multi-lined value, add lines until end of value + line = br.readLine(); + if (line == null) { + // end of stream + break; + } + sb.append("\n").append(line); + } + + // use Properties.load() to ensure property is parsed correctly + ByteArrayInputStream bais = new ByteArrayInputStream( + sb.toString().getBytes(StandardCharsets.ISO_8859_1)); + Properties props = new Properties(); + props.load(bais); + Set propNames = props.stringPropertyNames(); + // should only be one property + if (propNames.size() != 1) { + throw new PluginException("Parsing error: " + propNames); + } + + // check for duplicate properties and include statements + String propName = propNames.iterator().next(); + if (keys.contains(propName)) { + throw new PluginException("Parsing error, " + + "duplicate property: " + propName); + } + if (propName.equals("include")) { + throw new PluginException("Parsing error, " + + "include statement disallowed"); + } + keys.add(propName); + + // check if property should be overridden + String propValue = (String) extraProps.remove(propName); + if (propValue != null) { + // override value + lines.add(line(propName, propValue)); + } else { + lines.add(line(propName, props.getProperty(propName))); + } + line = br.readLine(); + } + } catch (Exception e) { + throw new PluginException(e); + } + + // extract and remove include property if it exists + String includeValue = (String) extraProps.remove("include"); + + // add user-defined properties at end + for (String propName : extraProps.stringPropertyNames()) { + lines.add(line(propName, extraProps.getProperty(propName))); + }; + + // add include property at end if it has been specified and use + // space character as delimiter + if (includeValue != null) { + lines.add("include " + convert(includeValue, false)); + } + + // Write contents of list to byte array. Use ISO_8859_1 to be + // consistent with java.security file loading. + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (OutputStreamWriter osw = new OutputStreamWriter(baos, + StandardCharsets.ISO_8859_1); + BufferedWriter bw = new BufferedWriter(osw)) { + for (CharSequence line: lines) { + bw.append(line); + bw.newLine(); + } + bw.flush(); + } catch (Exception e) { + throw new PluginException(e); + } + return baos.toByteArray(); + } + + /** + * Returns true if line is empty, only contains whitespace, or has an + * ASCII '#' or '!' as its first non-whitespace character. Whitespace + * characters are as specified by java.util.Properties. + */ + private static boolean isBlankOrComment(String line) { + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if (c == '#' || c == '!') { + return true; + } else if (c == ' ' || c == '\t' || c == '\f') { + continue; + } else if (c == '\n' || c == '\r') { + return true; + } else { + return false; + } + } + return true; + } + + /** + * Returns true if line ends in an odd number of contiguous backslashes. + */ + private static boolean lineContinues(String line) { + int backslashes = 0; + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if (c == '\\') { + backslashes++; + } else { + backslashes = 0; + } + } + return (backslashes % 2 == 0) ? false : true; + } + + /** + * Create line to add to properties file. + */ + private static String line(String name, String value) { + return convert(name, true) + "=" + convert(value, false); + } + + /** + * Converts unicodes to encoded \uxxxx and escapes + * special characters with a preceding slash. + * + * This method was copied from java.util.Properties.saveConvert(). + */ + private static String convert(String theString, + boolean escapeSpace) { + int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuilder outBuffer = new StringBuilder(bufLen); + HexFormat hex = HexFormat.of().withUpperCase(); + for(int x=0; x 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch(aChar) { + case ' ': + if (x == 0 || escapeSpace) + outBuffer.append('\\'); + outBuffer.append(' '); + break; + case '\t':outBuffer.append('\\'); outBuffer.append('t'); + break; + case '\n':outBuffer.append('\\'); outBuffer.append('n'); + break; + case '\r':outBuffer.append('\\'); outBuffer.append('r'); + break; + case '\f':outBuffer.append('\\'); outBuffer.append('f'); + break; + case '=': // Fall through + case ':': // Fall through + case '#': // Fall through + case '!': + outBuffer.append('\\'); outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e))) { + outBuffer.append("\\u"); + outBuffer.append(hex.toHexDigits(aChar)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties index 42a93e6c8170..8ac9227ae44a 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties @@ -163,6 +163,34 @@ generate-jli-classes.usage=\ \ correctness add ignore-version=true\n\ \ to override this. +security-properties.argument= + +security-properties.description=\ +Override the security properties in the\n\ +conf/security/java.security configuration file with the properties in the\n\ +specified filename. Security properties with the same name are overridden\n\ +and security properties not present are added to the end of the\n\ +conf/security/java.security configuration file. Duplicate properties\n\ +(properties with the same name) and include statements in the original\n\ +java.security file are disallowed and treated as an error. Comments in the\n\ +properties file are ignored and not copied. If the properties file contains\n\ +an include statement, it is always added as the last line of the\n\ +conf/security/java.security configuration file, after any new security\n\ +properties that may have been added. The include filename does not need to\n\ +exist at jlink time. If the properties file contains duplicate properties,\n\ +the value of the last property is used. + + +security-properties.usage=\ +\ --security-properties \n\ +\ Override the security properties in the\n\ +\ conf/security/java.security configuration file\n\ +\ with the properties in the specified filename.\n\ +\ Security properties with the same name are\n\ +\ overridden and security properties not present\n\ +\ are added to the end of the\n\ +\ conf/security/java.security configuration file. + system-modules.argument=batch-size= sets the batch size of module descriptors\n\ \ to avoid exceeding the method length limit. The default\n\ \ batch size is 75. diff --git a/src/jdk.jlink/share/classes/module-info.java b/src/jdk.jlink/share/classes/module-info.java index 51c5c3ab8c82..7d99c813710a 100644 --- a/src/jdk.jlink/share/classes/module-info.java +++ b/src/jdk.jlink/share/classes/module-info.java @@ -62,6 +62,8 @@ jdk.tools.jlink.internal.Main.JlinkToolProvider; provides jdk.tools.jlink.plugin.Plugin with + // SapMachine 2025-01-09: SapMachine tools plugin + jdk.tools.jlink.internal.plugins.AddSapMachineTools, jdk.tools.jlink.internal.plugins.DefaultStripDebugPlugin, jdk.tools.jlink.internal.plugins.StripJavaDebugAttributesPlugin, jdk.tools.jlink.internal.plugins.ExcludePlugin, @@ -83,6 +85,5 @@ jdk.tools.jlink.internal.plugins.CDSPlugin, jdk.tools.jlink.internal.plugins.SaveJlinkArgfilesPlugin, jdk.tools.jlink.internal.plugins.CACertsPlugin, - // SapMachine 2025-01-09: SapMachine tools plugin - jdk.tools.jlink.internal.plugins.AddSapMachineTools; + jdk.tools.jlink.internal.plugins.SecurityPropertiesPlugin; } diff --git a/src/jdk.jlink/share/man/jlink.md b/src/jdk.jlink/share/man/jlink.md index 865cdff999a9..256ef2d140b9 100644 --- a/src/jdk.jlink/share/man/jlink.md +++ b/src/jdk.jlink/share/man/jlink.md @@ -250,6 +250,26 @@ Options Description : Generate CDS archive if the runtime image supports the CDS feature. +### Plugin `security-properties` + +Options +: `--security-properties=`*filename* + +Description +: Override the security properties in the `conf/security/java.security` + configuration file with the properties in the specified *filename*. + Security properties with the same name are overridden and security + properties not present are added to the end of the + `conf/security/java.security` configuration file. Duplicate properties + (properties with the same name) and include statements in the + original `java.security` file are disallowed and treated as an error. + Comments in the properties file are ignored and not copied. If the + properties file contains an include statement, it is always added as the + last line of the `conf/security/java.security` configuration file, + after any new security properties that may have been added. The include + filename does not need to exist at jlink time. If the properties file + contains duplicate properties, the value of the last property is used. + ### Plugin `cacerts` Options diff --git a/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c new file mode 100644 index 000000000000..d75e1109081c --- /dev/null +++ b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.c @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ExecCommand.h" +#include "JvmLauncher.h" + + +static bool logCommandLine(const char* format, const char* const argv[]) { + char* formattedCommandLine = NULL; + int formattedCommandLineLength = 0; + const char* arg; + int i; + bool success = false; + + for (i = 0; (arg = argv[i]) != NULL; i++) { + /* Count trailing whitespace */ + formattedCommandLineLength += strlen(arg) + 1; + if (strchr(arg, ' ') != NULL) { + /* Enclose the argument into single quotes */ + formattedCommandLineLength += 2; + } + } + + if (!formattedCommandLineLength) { + /* Empty command line is an error */ + goto cleanup; + } + + formattedCommandLine = malloc(formattedCommandLineLength + 1 /* \0 */); + if (!formattedCommandLine) { + JP_LOG_ERRNO; + goto cleanup; + } + + formattedCommandLine[0] = '\0'; + for (i = 0; (arg = argv[i]) != NULL; i++) { + if (strchr(arg, ' ') != NULL) { + strcat(formattedCommandLine, "'"); + strcat(formattedCommandLine, arg); + strcat(formattedCommandLine, "'"); + } else { + strcat(formattedCommandLine, arg); + } + strcat(formattedCommandLine, " "); + } + + /* Trim trailing whitespace */ + formattedCommandLine[formattedCommandLineLength - 1] = '\0'; + + JP_LOG_TRACE(format, formattedCommandLine); + + success = true; + +cleanup: + free(formattedCommandLine); + + return success; +} + +static bool invokeCallback( + FILE* stream, + ExecCommandCallbackType callback, + void* callbackData) { + + char* strBufBegin = NULL; + char* strBufEnd = NULL; + char* strBufNextChar = NULL; + char* strNewBufBegin = NULL; + size_t strBufCapacity = 0; + int callbackMode = EXEC_CALLBACK_USE; + int c; + ptrdiff_t charOffset; + int success = false; + int tailCharCount = 0; + + for (;;) { + c = fgetc(stream); + if (EOF == c || '\n' == c) { + if (EXEC_CALLBACK_USE == callbackMode + && strBufBegin != strBufNextChar) { + *strBufNextChar = 0; + JP_LOG_TRACE("execCommand: [%s]", strBufBegin); + callbackMode = (*callback)(callbackData, strBufBegin); + strBufNextChar = strBufBegin; + } + + if (EOF == c) { + break; + } + + continue; + } + + if (EXEC_CALLBACK_USE != callbackMode) { + /* Fetch the stream */ + for (; EOF != c; c = fgetc(stream)) { + tailCharCount++; + } + if (tailCharCount) { + JP_LOG_TRACE( + "execCommand: fetched %d trailing chars", + tailCharCount); + } + break; + } + + if (strBufNextChar == strBufEnd) { + /* Double buffer size */ + if (strBufCapacity) { + strBufCapacity = strBufCapacity * 2; + } else { + strBufCapacity = 1; + } + + if (strBufNextChar && strBufBegin) { + charOffset = strBufNextChar - strBufBegin; + } else { + charOffset = 0; + } + + strNewBufBegin = realloc( strBufBegin, + /* Account for the terminal '\0' */ + strBufCapacity + 1); + if (!strNewBufBegin) { + JP_LOG_ERRNO; + goto cleanup; + } + + strBufNextChar = strNewBufBegin + charOffset; + strBufEnd = strNewBufBegin + strBufCapacity; + strBufBegin = strNewBufBegin; + } + + *strBufNextChar++ = (char)c; + } + + success = (EXEC_CALLBACK_ERROR != callbackMode); + +cleanup: + if (strBufBegin) { + free(strBufBegin); + } + + return success; +} + + +int execCommand( + const char* const argv[], + ExecCommandCallbackType callback, + void* callbackData) { + + int pipefd[] = { -1, -1 }; + pid_t cpid = -1; + FILE* stream = NULL; + int exitCode = -1; + int devNull = -1; + int savedStderr = -1; + int savedErrno = 0; + bool callbackSuccess = false; + int childExitCode = 1; + int waitpidStatus = -1; + + if (!logCommandLine("execCommand: cmdline=[%s]", argv)) { + return -1; + } + + if (pipe(pipefd) == -1) { + JP_LOG_ERRNO; + goto cleanup; + } + + cpid = fork(); + if (cpid == -1) { + JP_LOG_ERRNO; + } else if (cpid == 0) /* Child process */ { + /* Close unused read end */ + closePipeEnd(pipefd, 0); + + /* Save original stderr */ + if ((savedStderr = dup(STDERR_FILENO)) == -1) { + JP_LOG_ERRNO; + goto cleanupChild; + } + + /* Redirect stdout of the child process into the pipe's end */ + if (dup2(pipefd[1], STDOUT_FILENO) == -1) { + JP_LOG_ERRNO; + goto cleanupChild; + } + + devNull = open("/dev/null", O_WRONLY); + if (devNull == -1) { + JP_LOG_ERRNO; + goto cleanupChild; + } + + /* Silence stderr in the child process */ + if (dup2(devNull, STDERR_FILENO) == -1) { + JP_LOG_ERRNO; + goto cleanupChild; + } + + childExitCode = 0; + +cleanupChild: + if (devNull != -1) { + close(devNull); + } + + closePipeEnd(pipefd, 0); + + if (childExitCode == 0) { + execvp(argv[0], (char* const*)argv); + + /* + Normally, execvp() doesn't return. + If control flow reaches this point, execvp() failed. + Restore stderr to make JP_LOG_ERRNO macro work and report error. + */ + savedErrno = errno; + dup2(savedStderr, STDERR_FILENO); + errno = savedErrno; + JP_LOG_ERRNO; + + close(savedStderr); + + childExitCode = 127; /* Command not found */ + } + + _exit(childExitCode); + } + + /* Close unused write end */ + closePipeEnd(pipefd, 1); + + stream = fdopen(pipefd[0], "r"); + if (!stream) { + JP_LOG_ERRNO; + goto cleanup; + } + pipefd[0] = -1; + + callbackSuccess = invokeCallback(stream, callback, callbackData); + if (!callbackSuccess) { + JP_LOG_TRACE("Callback failed"); + goto cleanup; + } + +cleanup: + if (stream) { + fclose(stream); + } + + closePipeEnd(pipefd, 0); + closePipeEnd(pipefd, 1); + + if (cpid > 0) { + while (waitpid(cpid, &waitpidStatus, 0) == -1 && errno == EINTR) { + } + + if (WIFEXITED(waitpidStatus)) { + exitCode = WEXITSTATUS(waitpidStatus); + } + } + + JP_LOG_TRACE("execCommand: exit=%d", exitCode); + + if (exitCode == 0 && !callbackSuccess) { + exitCode = 1; + } + + return exitCode; +} + + +void closePipeEnd(int* pipefd, int idx) { + if (pipefd[idx] >= 0) { + close(pipefd[idx]); + pipefd[idx] = -1; + } +} diff --git a/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h new file mode 100644 index 000000000000..1bce66feeea4 --- /dev/null +++ b/src/jdk.jpackage/linux/native/applauncher/ExecCommand.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef ExecCommand_h +#define ExecCommand_h + +#ifdef __cplusplus +extern "C" { +#endif + +#define EXEC_CALLBACK_IGNORE 0 +#define EXEC_CALLBACK_USE 1 +#define EXEC_CALLBACK_ERROR 2 + +typedef int (*ExecCommandCallbackType)(void*, char*); + +int execCommand(const char* const argv[], + ExecCommandCallbackType callback, + void* callbackData); + +void closePipeEnd(int* pipefd, int idx); + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef ExecCommand_h */ diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c b/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c index 7db42bc2dbd6..96e6abe98c81 100644 --- a/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c +++ b/src/jdk.jpackage/linux/native/applauncher/LinuxLauncher.c @@ -35,31 +35,40 @@ #include #include "JvmLauncher.h" #include "LinuxPackage.h" +#include "ExecCommand.h" #define STATUS_FAILURE 1 -typedef JvmlLauncherHandle (*JvmlLauncherAPI_CreateFunType)(int argc, char *argv[]); +typedef JvmlLauncherHandle (*JvmlLauncherAPI_CreateFunType)( + const PackageDesc* pkg, + int argc, + char* argv[]); static int appArgc; static char **appArgv; static JvmlLauncherData* initJvmlLauncherData(int* size) { - char* launcherLibPath = 0; + JvmLauncherDesc* jvmLauncherDesc = 0; + PackageDesc pkg; void* jvmLauncherLibHandle = 0; JvmlLauncherAPI_GetAPIFunc getApi = 0; - JvmlLauncherAPI_CreateFunType createJvmlLauncher = 0; + JvmlLauncherAPI_CreateFunType createJvmLauncher = 0; JvmlLauncherAPI* api = 0; JvmlLauncherHandle jvmLauncherHandle = 0; JvmlLauncherData* result = 0; - launcherLibPath = getJvmLauncherLibPath(); - if (!launcherLibPath) { + jvmLauncherDesc = getJvmLauncherDesc(); + if (!jvmLauncherDesc) { goto cleanup; } - jvmLauncherLibHandle = dlopen(launcherLibPath, RTLD_NOW | RTLD_LOCAL); + pkg.name = jvmLauncherDesc->packageName; + pkg.type = jvmLauncherDesc->packageType; + + jvmLauncherLibHandle = dlopen( jvmLauncherDesc->jvmLauncherLibPath, + RTLD_NOW | RTLD_LOCAL); if (!jvmLauncherLibHandle) { JP_LOG_ERRMSG(dlerror()); goto cleanup; @@ -77,13 +86,13 @@ static JvmlLauncherData* initJvmlLauncherData(int* size) { goto cleanup; } - createJvmlLauncher = dlsym(jvmLauncherLibHandle, "jvmLauncherCreate"); - if (!createJvmlLauncher) { + createJvmLauncher = dlsym(jvmLauncherLibHandle, "jvmLauncherCreate"); + if (!createJvmLauncher) { JP_LOG_ERRMSG(dlerror()); goto cleanup; } - jvmLauncherHandle = (*createJvmlLauncher)(appArgc, appArgv); + jvmLauncherHandle = (*createJvmLauncher)(&pkg, appArgc, appArgv); if (!jvmLauncherHandle) { goto cleanup; } @@ -99,7 +108,7 @@ static JvmlLauncherData* initJvmlLauncherData(int* size) { if (jvmLauncherLibHandle) { dlclose(jvmLauncherLibHandle); } - free(launcherLibPath); + freeJvmLauncherDesc(jvmLauncherDesc); return result; } @@ -170,13 +179,6 @@ static ssize_t writeFully(const int fd, const void* buf, const size_t len) { return (ssize_t)nWritten; } -static void closePipeEnd(int* pipefd, int idx) { - if (pipefd[idx] >= 0) { - close(pipefd[idx]); - pipefd[idx] = -1; - } -} - static void initJvmlLauncherDataPointers(void* baseAddress, JvmlLauncherData* jvmLauncherData) { diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c index d346e241035a..31d2208d7a30 100644 --- a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c +++ b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.c @@ -23,16 +23,16 @@ * questions. */ -#include #include -#include #include #include #include #include #include +#include #include "JvmLauncher.h" #include "LinuxPackage.h" +#include "ExecCommand.h" static char* getModulePath(void) { @@ -56,19 +56,9 @@ static char* getModulePath(void) { } -# define PACKAGE_TYPE_UNKNOWN 0 -# define PACKAGE_TYPE_RPM 1 -# define PACKAGE_TYPE_DEB 2 - -typedef struct { - char* name; - int type; -} PackageDesc; - - static void freePackageDesc(PackageDesc* desc) { if (desc) { - free(desc->name); + free((void*)desc->name); free(desc); } } @@ -91,116 +81,73 @@ static PackageDesc* createPackageDesc(void) { } -static PackageDesc* initPackageDesc(PackageDesc* desc, const char* str, - int type) { +static bool initPackageDesc(PackageDesc* desc, const char* str, int type) { char *newStr = strdup(str); if (!newStr) { JP_LOG_ERRNO; - return 0; + return false; } - free(desc->name); + free((void*)desc->name); desc->name = newStr; desc->type = type; - return desc; + return true; } -#define POPEN_CALLBACK_USE 1 -#define POPEN_CALLBACK_IGNORE 0 - -typedef int (*popenCallbackType)(void*, char*); - -static int popenCommand(const char* cmdlineFormat, const char* arg, - popenCallbackType callback, void* callbackData) { - char* cmdline = 0; - FILE *stream = 0; - const size_t cmdlineLenth = strlen(cmdlineFormat) + strlen(arg); - char* strBufBegin = 0; - char* strBufEnd = 0; - char* strBufNextChar = 0; - char* strNewBufBegin = 0; - size_t strBufCapacity = 0; - int callbackMode = POPEN_CALLBACK_USE; - int exitCode = -1; - int c; - ptrdiff_t char_offset; - - cmdline = malloc(cmdlineLenth + 1 /* \0 */); - if (!cmdline) { - JP_LOG_ERRNO; - goto cleanup; - } +static JvmLauncherDesc* createJvmLauncherDesc(void) { + JvmLauncherDesc* result = 0; -#if defined(__GNUC__) && __GNUC__ >= 5 -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif - if (0 > snprintf(cmdline, cmdlineLenth, cmdlineFormat, arg)) { + result = malloc(sizeof(JvmLauncherDesc)); + if (!result) { JP_LOG_ERRNO; goto cleanup; } -#if defined(__GNUC__) && __GNUC__ >= 5 -#pragma GCC diagnostic pop -#endif - JP_LOG_TRACE("popen: (%s)", cmdline); + result->packageType = PACKAGE_TYPE_UNKNOWN; + result->packageName = NULL; + result->jvmLauncherLibPath = NULL; - stream = popen(cmdline, "r"); - if (!stream) { - JP_LOG_ERRNO; - goto cleanup; - } +cleanup: + return result; +} - for (;;) { - c = fgetc(stream); - if((EOF == c || '\n' == c)) { - if (POPEN_CALLBACK_USE == callbackMode - && strBufBegin != strBufNextChar) { - *strBufNextChar = 0; - JP_LOG_TRACE("popen: [%s]", strBufBegin); - callbackMode = (*callback)(callbackData, strBufBegin); - strBufNextChar = strBufBegin; - } - - if (EOF == c) { - break; - } - - continue; - } - if (strBufNextChar == strBufEnd) { - /* Double buffer size */ - strBufCapacity = strBufCapacity * 2 + 1; - char_offset = strBufNextChar - strBufBegin; - strNewBufBegin = realloc(strBufBegin, strBufCapacity); - if (!strNewBufBegin) { - JP_LOG_ERRNO; - goto cleanup; - } - - strBufNextChar = strNewBufBegin + char_offset; - strBufEnd = strNewBufBegin + strBufCapacity; - strBufBegin = strNewBufBegin; - } +static bool initJvmLauncherDesc( + JvmLauncherDesc* desc, + const PackageDesc* packageDesc, + const char* jvmLauncherLibPath) { - *strBufNextChar++ = (char)c; - } + char* newJvmLauncherLibPath = NULL; + char* newPackageName = NULL; -cleanup: - if (stream) { - exitCode = pclose(stream); + newJvmLauncherLibPath = strdup(jvmLauncherLibPath); + if (!newJvmLauncherLibPath) { + JP_LOG_ERRNO; + return false; } - if (strBufBegin) { - free(strBufBegin); + if (packageDesc && packageDesc->name) { + newPackageName = strdup(packageDesc->name); + if (!newPackageName) { + JP_LOG_ERRNO; + free(newJvmLauncherLibPath); + return false; + } } - free(cmdline); + free((void*)desc->packageName); + free((void*)desc->jvmLauncherLibPath); - JP_LOG_TRACE("popen: exit: %d", exitCode); - return exitCode; + desc->packageName = newPackageName; + if (packageDesc) { + desc->packageType = packageDesc->type; + } else { + desc->packageType = PACKAGE_TYPE_UNKNOWN; + } + desc->jvmLauncherLibPath = newJvmLauncherLibPath; + + return true; } @@ -221,8 +168,11 @@ static char* concat(const char *x, const char *y) { static int initRpmPackage(void* desc, char* str) { - initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_RPM); - return POPEN_CALLBACK_IGNORE; + if (initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_RPM)) { + return EXEC_CALLBACK_IGNORE; + } else { + return EXEC_CALLBACK_ERROR; + } } @@ -231,8 +181,12 @@ static int initDebPackage(void* desc, char* str) { if (colonChrPos) { *colonChrPos = 0; } - initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_DEB); - return POPEN_CALLBACK_IGNORE; + + if (initPackageDesc((PackageDesc*)desc, str, PACKAGE_TYPE_DEB)) { + return EXEC_CALLBACK_IGNORE; + } else { + return EXEC_CALLBACK_ERROR; + } } @@ -248,34 +202,38 @@ static int findLauncherLib(void* launcherLibPath, char* str) { buf = strdup(str); if (!buf) { JP_LOG_ERRNO; + return EXEC_CALLBACK_ERROR; } else { *(char**)launcherLibPath = buf; } - return POPEN_CALLBACK_IGNORE; + return EXEC_CALLBACK_IGNORE; } - return POPEN_CALLBACK_USE; + return EXEC_CALLBACK_USE; } static PackageDesc* findOwnerOfFile(const char* path) { - int popenStatus = -1; + int execStatus = -1; PackageDesc* pkg = 0; + const char* rpmOwner[] = { + "rpm", "--queryformat", "%{NAME}", "-qf", path, NULL + }; + const char* debOwner[] = { + "dpkg", "-S", path, NULL + }; pkg = createPackageDesc(); if (!pkg) { return 0; } - popenStatus = popenCommand( - "rpm --queryformat '%{NAME}' -qf '%s' 2>/dev/null", path, - initRpmPackage, pkg); - if (popenStatus) { + execStatus = execCommand(rpmOwner, initRpmPackage, pkg); + if (execStatus) { pkg->type = PACKAGE_TYPE_UNKNOWN; - popenStatus = popenCommand("dpkg -S '%s' 2>/dev/null", path, - initDebPackage, pkg); + execStatus = execCommand(debOwner, initDebPackage, pkg); } - if (popenStatus) { + if (execStatus) { pkg->type = PACKAGE_TYPE_UNKNOWN; } @@ -292,13 +250,25 @@ static PackageDesc* findOwnerOfFile(const char* path) { } -char* getJvmLauncherLibPath(void) { +void freeJvmLauncherDesc(JvmLauncherDesc* desc) { + if (desc) { + free((void*)desc->packageName); + free((void*)desc->jvmLauncherLibPath); + free(desc); + } +} + + +JvmLauncherDesc* getJvmLauncherDesc(void) { char* modulePath = 0; char* appImageDir = 0; char* launcherLibPath = 0; - const char* pkgQueryCmd = 0; - int popenStatus = -1; + const char* pkgQueryCmd[] = { + NULL, NULL, NULL, NULL + }; PackageDesc* pkg = 0; + JvmLauncherDesc* result = 0; + bool resultReady = false; modulePath = getModulePath(); if (!modulePath) { @@ -314,27 +284,45 @@ char* getJvmLauncherLibPath(void) { launcherLibPath = concat(appImageDir, "/lib" LAUNCHER_LIB_NAME); } else { if (PACKAGE_TYPE_RPM == pkg->type) { - pkgQueryCmd = "rpm -ql '%s' 2>/dev/null"; + pkgQueryCmd[0] = "rpm"; + pkgQueryCmd[1] = "-ql"; + pkgQueryCmd[2] = pkg->name; } else if (PACKAGE_TYPE_DEB == pkg->type) { - pkgQueryCmd = "dpkg -L '%s' 2>/dev/null"; + pkgQueryCmd[0] = "dpkg"; + pkgQueryCmd[1] = "-L"; + pkgQueryCmd[2] = pkg->name; } else { /* Should never happen */ JP_LOG_ERRMSG("Internal error"); goto cleanup; } - popenStatus = popenCommand(pkgQueryCmd, pkg->name, findLauncherLib, - &launcherLibPath); - if (popenStatus) { - free(launcherLibPath); - launcherLibPath = NULL; + if (execCommand(pkgQueryCmd, findLauncherLib, &launcherLibPath)) { goto cleanup; } } + if (!launcherLibPath) { + goto cleanup; + } + + result = createJvmLauncherDesc(); + if (result && initJvmLauncherDesc(result, pkg, launcherLibPath)) { + JP_LOG_TRACE("JvmLauncherDesc(%s|%d|%s)", + result->packageName ? result->packageName : "(null)", + result->packageType, + result->jvmLauncherLibPath); + resultReady = true; + } + cleanup: free(modulePath); freePackageDesc(pkg); + if (!resultReady) { + freeJvmLauncherDesc(result); + result = NULL; + } + free(launcherLibPath); - return launcherLibPath; + return result; } diff --git a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h index 24ec18910666..af862502a07b 100644 --- a/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h +++ b/src/jdk.jpackage/linux/native/applauncher/LinuxPackage.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,24 @@ extern "C" { #endif -char* getJvmLauncherLibPath(void); +#define PACKAGE_TYPE_UNKNOWN 0 +#define PACKAGE_TYPE_RPM 1 +#define PACKAGE_TYPE_DEB 2 + +typedef struct { + const char* name; + int type; +} PackageDesc; + +typedef struct { + const char* packageName; + int packageType; + const char* jvmLauncherLibPath; +} JvmLauncherDesc; + +void freeJvmLauncherDesc(JvmLauncherDesc* desc); + +JvmLauncherDesc* getJvmLauncherDesc(void); #ifdef __cplusplus } diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp b/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp index d8a1755ac88e..5caf01f193cf 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp +++ b/src/jdk.jpackage/linux/native/libapplauncher/Executor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,49 +23,36 @@ * questions. */ -#include -#include #include "Executor.h" -#include "Log.h" #include "ErrorHandling.h" +#include "ExecCommand.h" -int executeCommandLineAndReadStdout(const std::string& cmd, - CommandOutputConsumer& consumer) { - FILE * stream = popen(cmd.c_str(), "r"); - if (!stream) { - JP_THROW(tstrings::any() << "popen(" << cmd - << ") failed. Error: " << lastCRTError()); +namespace { + +int callbackAdapter(void* callback, char* str) { + JP_TRY; + if (reinterpret_cast(callback)->accept(str)) { + return EXEC_CALLBACK_IGNORE; + } else { + return EXEC_CALLBACK_USE; } + JP_CATCH_ALL; + return EXEC_CALLBACK_ERROR; +} - LOG_TRACE(tstrings::any() << "Reading output of [" << cmd << "] command"); +} // namespace - try { - bool useConsumer = true; - std::string buf; - for (;;) { - const int c = fgetc(stream); - if(c == EOF) { - if (useConsumer && !buf.empty()) { - LOG_TRACE(tstrings::any() << "Next line: [" << buf << "]"); - consumer.accept(buf); - } - break; - } +int executeCommandLineAndReadStdout(const tstring_array& cmd, + CommandOutputConsumer& consumer) { - if (c == '\n' && useConsumer) { - LOG_TRACE(tstrings::any() << "Next line: [" << buf << "]"); - useConsumer = !consumer.accept(buf); - buf.clear(); - } else { - buf.push_back(static_cast(c)); - } - } - return pclose(stream); - } catch (...) { - if (stream) { - pclose(stream); - } - throw; + std::vector argv; + tstring_array::const_iterator it = cmd.begin(); + tstring_array::const_iterator end = cmd.end(); + for (; it != end; ++it) { + argv.push_back(it->c_str()); } + argv.push_back(NULL); + + return execCommand(argv.data(), callbackAdapter, &consumer); } diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Executor.h b/src/jdk.jpackage/linux/native/libapplauncher/Executor.h index 44a5f0c7e26c..423289c7af03 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/Executor.h +++ b/src/jdk.jpackage/linux/native/libapplauncher/Executor.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ class CommandOutputConsumer { }; }; -int executeCommandLineAndReadStdout(const std::string& cmd, +int executeCommandLineAndReadStdout(const tstring_array& cmd, CommandOutputConsumer& consumer); #endif // #ifndef EXECUTOR_H diff --git a/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp b/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp index 561e2a942bb0..8430f3149bb8 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp +++ b/src/jdk.jpackage/linux/native/libapplauncher/LinuxLauncherLib.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,11 +45,16 @@ size_t hash(const std::string& str) { } Jvm* jvmLauncher; +PackageDesc pkgDesc = {}; void launchApp() { const tstring launcherPath = SysInfo::getProcessModulePath(); - const Package ownerPackage = Package::findOwnerOfFile(launcherPath); + LOG_TRACE(tstrings::any() << "PackageDesc(" + << (pkgDesc.name ? pkgDesc.name : "(null)") + << "|" << pkgDesc.type << ")"); + + const Package ownerPackage = Package(pkgDesc); AppLauncher appLauncher; appLauncher.addJvmLibName(_T("lib/libjli.so")); @@ -100,12 +105,12 @@ void launchApp() { tistringstream iss(launchInfo); iss.exceptions(std::ios::failbit | std::ios::badbit); - size_t hash = 0; - iss >> hash; + size_t launchInfoHash = 0; + iss >> launchInfoHash; launchInfo = ""; - if (thisHash != hash) { + if (thisHash != launchInfoHash) { // This launcher execution is the result of execve() call from // within JVM. // This means all JVM arguments are already configured in launcher @@ -133,10 +138,15 @@ void launchApp() { extern "C" { -JNIEXPORT JvmlLauncherHandle jvmLauncherCreate(int argc, char *argv[]) { +JNIEXPORT JvmlLauncherHandle jvmLauncherCreate( + const PackageDesc* pkg, + int argc, + char* argv[]) { + SysInfo::argc = argc; SysInfo::argv = argv; jvmLauncher = 0; + pkgDesc = *pkg; app::launch(std::nothrow, launchApp); JvmlLauncherHandle jlh = 0; diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp b/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp index 3665d7e14a00..fe99a723c4a5 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp +++ b/src/jdk.jpackage/linux/native/libapplauncher/Package.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,67 +33,9 @@ Package::Package(): type(Unknown) { } -namespace { -class FirstLineConsumer : public CommandOutputConsumer { -public: - FirstLineConsumer(): processed(false) { - } - - virtual bool accept(const std::string& line) { - if (!processed) { - value = line; - processed = true; - } - return processed; - }; - - std::string getValue() const { - if (!processed) { - JP_THROW("No output captured"); - } - return value; - } - -private: - bool processed; - std::string value; -}; - - -std::string findOwnerOfFile(const std::nothrow_t&, const std::string& cmdline, - const std::string& path) { - try { - FirstLineConsumer consumer; - int exitCode = executeCommandLineAndReadStdout( - cmdline + " \'" + path + "\' 2>/dev/null", consumer); - if (exitCode == 0) { - return consumer.getValue(); - } - } catch (...) { - } - return ""; -} - -} // namespace - -Package Package::findOwnerOfFile(const std::string& path) { - Package result; - result.theName = ::findOwnerOfFile(std::nothrow, - "rpm --queryformat '%{NAME}' -qf", path); - if (!result.theName.empty()) { - result.type = RPM; - } else { - tstring_array components = tstrings::split(::findOwnerOfFile( - std::nothrow, "dpkg -S", path), ":"); - if (!components.empty()) { - result.theName = components.front(); - if (!result.theName.empty()) { - result.type = DEB; - } - } - } - - return result; +Package::Package(const PackageDesc& desc): + type(static_cast(desc.type)), + theName(desc.name ? desc.name : "") { } @@ -119,7 +61,7 @@ class AppLauncherInitializer : public CommandOutputConsumer { return !appDir.empty() && !runtimeDir.empty(); }; - void apply(AppLauncher& launcher) { + void apply(AppLauncher& launcher) const { launcher.setDefaultRuntimePath(runtimeDir); launcher.setAppDir(appDir); } @@ -136,10 +78,10 @@ void Package::initAppLauncher(AppLauncher& appLauncher) const { int exitCode = -1; if (type == RPM) { exitCode = executeCommandLineAndReadStdout( - "rpm -ql \'" + theName + "\'", consumer); + { "rpm", "-ql", theName }, consumer); } else if (type == DEB) { exitCode = executeCommandLineAndReadStdout( - "dpkg -L \'" + theName + "\'", consumer); + { "dpkg", "-L", theName }, consumer); } if (exitCode == 0) { diff --git a/src/jdk.jpackage/linux/native/libapplauncher/Package.h b/src/jdk.jpackage/linux/native/libapplauncher/Package.h index 4c25d2c7bc1d..7cb7f26db734 100644 --- a/src/jdk.jpackage/linux/native/libapplauncher/Package.h +++ b/src/jdk.jpackage/linux/native/libapplauncher/Package.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,7 @@ #include "tstrings.h" +#include "LinuxPackage.h" class AppLauncher; @@ -37,16 +38,20 @@ class Package { public: Package(); - std::string name() const { + explicit Package(const PackageDesc& desc); + + const std::string& name() const { return theName; } void initAppLauncher(AppLauncher& appLauncher) const; - static Package findOwnerOfFile(const std::string& path); - private: - enum Type { Unknown, RPM, DEB }; + enum Type { + Unknown = PACKAGE_TYPE_UNKNOWN, + RPM = PACKAGE_TYPE_RPM, + DEB = PACKAGE_TYPE_DEB + }; Type type; std::string theName; diff --git a/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h b/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h index ab8a9d792a42..4bcaa7f08465 100644 --- a/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h +++ b/src/jdk.jpackage/share/native/applauncher/JvmLauncher.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -86,11 +86,11 @@ JvmlLauncherData* jvmLauncherCreateJvmlLauncherData(JvmlLauncherAPI* api, JvmlLauncherHandle h, int* size); int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch); -void jvmLauncherLog(const char* format, ...); +void jvmLauncherLog(const char* file, int line, const char* format, ...); -#define JP_LOG_ERRMSG(msg) do { jvmLauncherLog((msg)); } while (0) +#define JP_LOG_ERRMSG(msg) do { jvmLauncherLog(__FILE__, __LINE__, (msg)); } while (0) #define JP_LOG_ERRNO JP_LOG_ERRMSG(strerror(errno)) -#define JP_LOG_TRACE jvmLauncherLog +#define JP_LOG_TRACE(format, ...) jvmLauncherLog(__FILE__, __LINE__, format, ##__VA_ARGS__) #ifdef __cplusplus diff --git a/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c b/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c index 6d3c9f3dd546..fb765ca99025 100644 --- a/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c +++ b/src/jdk.jpackage/share/native/applauncher/JvmLauncherLib.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,6 +98,27 @@ static void dumpJvmlLauncherData(const JvmlLauncherData* jvmArgs) { } +static const char* filename(const char* path) { + const char* slash = strrchr(path, '/'); +#ifdef _WIN32 + const char* backslash = strrchr(path, '\\'); +#else + const char* backslash = NULL; +#endif + const char* sep; + + if (slash == NULL) { + sep = backslash; + } else if (backslash == NULL) { + sep = slash; + } else { + sep = slash > backslash ? slash : backslash; + } + + return sep ? sep + 1 : path; +} + + int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch) { int i; int exitCode; @@ -135,7 +156,7 @@ int jvmLauncherStartJvm(JvmlLauncherData* jvmArgs, void* JLI_Launch) { } -void jvmLauncherLog(const char* format, ...) { +void jvmLauncherLog(const char* file, int line, const char* format, ...) { const char *withLog = getenv("JPACKAGE_DEBUG"); if (!withLog || strcmp(withLog, "true")) { return; @@ -151,6 +172,7 @@ void jvmLauncherLog(const char* format, ...) { #ifdef LINUX fprintf(stderr, "[%d]: ", getpid()); #endif + fprintf(stderr, "%s:%d: ", filename(file), line); vfprintf(stderr, format, args); fprintf(stderr, "\n"); #if defined(__GNUC__) && __GNUC__ >= 5 diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java index 1df6dc5191b3..49521748b3b8 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiDefaultExecutionControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Consumer; import com.sun.jdi.BooleanValue; import com.sun.jdi.ClassNotLoadedException; import com.sun.jdi.Field; @@ -48,8 +47,12 @@ import com.sun.jdi.VMDisconnectedException; import com.sun.jdi.VirtualMachine; import java.io.PrintStream; +import java.security.SecureRandom; import java.time.Duration; +import java.util.Arrays; +import java.util.Base64; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import jdk.jshell.JShellConsole; import jdk.jshell.execution.JdiDefaultExecutionControl.JdiStarter.TargetDescription; import jdk.jshell.spi.ExecutionControl; @@ -103,6 +106,7 @@ public class JdiDefaultExecutionControl extends JdiExecutionControl { * @throws IOException if there are errors in set-up */ static ExecutionControl create(ExecutionEnv env, Map parameters, String remoteAgent, int timeout, JdiStarter starter) throws IOException { + Process processToStop = null; try (final ServerSocket listener = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) { // timeout on I/O-socket listener.setSoTimeout(timeout); @@ -146,15 +150,30 @@ public void closeDown() { }; // Set-up the JDI connection - TargetDescription target = starter.start(augmentedEnv, parameters, port); + TargetDescription target = starter.start(augmentedEnv, parameters, -1); VirtualMachine vm = target.vm(); - Process process = target.process(); + Process process = processToStop = target.process(); + byte[] secret; + + OutputStream stdout = process.getOutputStream(); + + //send the port number: + stdout.write(("" + port + "\n").getBytes()); + stdout.flush(); + + //generate a secret that will help us validate the correct remote is attached: + secret = new byte[64]; + new SecureRandom().nextBytes(secret); + stdout.write((Base64.getEncoder().encodeToString(secret) + "\n").getBytes()); + stdout.flush(); + + CompletableFuture agentStopped = new CompletableFuture<>(); - List> deathListeners = new ArrayList<>(); Util.detectJdiExitEvent(vm, s -> { - for (Consumer h : deathListeners) { - h.accept(s); - } + agentStopped.complete(s); + }); + process.onExit().thenRun(() -> { + agentStopped.complete("Remote process died."); }); // Set-up the commands/reslts on the socket. Piggy-back snippet @@ -162,6 +181,19 @@ public void closeDown() { Socket socket = listener.accept(); // out before in -- match remote creation so we don't hang OutputStream out = socket.getOutputStream(); + InputStream in = socket.getInputStream(); + + socket.setSoTimeout(timeout); + + //verify the secret: + byte[] receivedSecret = in.readNBytes(secret.length); + + if (!Arrays.equals(secret, receivedSecret)) { + throw new IOException("Incorrect secret received."); + } + + socket.setSoTimeout(0); + Map outputs = new HashMap<>(); outputs.put("out", env.userOut()); outputs.put("err", env.userErr()); @@ -176,10 +208,17 @@ public void closeDown() { outputs.put("consoleInput", consoleOutput); input.put("consoleOutput", consoleOutput.sinkInput); } - return remoteInputOutput(socket.getInputStream(), out, outputs, input, + ExecutionControl result = remoteInputOutput(in, out, outputs, input, (objIn, objOut) -> new JdiDefaultExecutionControl(env, - objOut, objIn, vm, process, remoteAgent, deathListeners)); - } + objOut, objIn, vm, process, remoteAgent, + agentStopped)); + processToStop = null; + return result; + } finally { + if (processToStop != null) { + processToStop.destroy(); + } +} } /** @@ -191,7 +230,7 @@ public void closeDown() { private JdiDefaultExecutionControl(ExecutionEnv env, ObjectOutput cmdout, ObjectInput cmdin, VirtualMachine vm, Process process, String remoteAgent, - List> deathListeners) { + CompletableFuture agentStopped) { super(cmdout, cmdin); this.vm = vm; this.process = process; @@ -199,8 +238,10 @@ private JdiDefaultExecutionControl(ExecutionEnv env, // We have now succeeded in establishing the connection. // If there is an exit now it propagates all the way up // and the VM should be disposed of. - deathListeners.add(s -> env.closeDown()); - deathListeners.add(s -> disposeVM()); + agentStopped.thenAccept(s -> { + env.closeDown(); + disposeVM(); + }); } /** @@ -356,7 +397,7 @@ public interface JdiStarter { * * @param env the execution context * @param parameters additional execution parameters - * @param port the port to which the remote process should connect + * @param port the value is always {@code -1} * @return a description of the started external process * @throws RuntimeException if the process cannot be started * @throws Error if the process cannot be started diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java index fd908745d81d..13497f91edce 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/JdiInitiator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -216,6 +216,7 @@ protected void runListenProcess(String jdiAddress, ProcessBuilder pb = new ProcessBuilder(args); pb.redirectError(crashErrorFile); pb.redirectOutput(crashOutputFile); + pb.redirectInput(ProcessBuilder.Redirect.PIPE); process = pb.start(); setupVM.processStarted(process); diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java index 3add8dd7a2b3..75c6e17846cd 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/RemoteExecutionControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,11 +24,15 @@ */ package jdk.jshell.execution; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; +import java.io.Reader; import java.lang.reflect.Method; import java.net.Socket; +import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -45,6 +49,12 @@ * {@link DirectExecutionControl} which adds stop support controlled by * an external process. Designed to work with {@link JdiDefaultExecutionControl}. * + *

        The initial handshake with {@link JdiDefaultExecutionControl} is as follows: + * the first command line argument is {@code -1}. The port number to which the + * agent should connect is passed on the first line on stdin. On the second line + * of stdin, an Base64-encoded byte array is sent, which should be sent to + * the socket immediately after the connection is established. + * * @author Jan Lahoda * @author Robert Field * @since 9 @@ -61,9 +71,24 @@ public class RemoteExecutionControl extends DirectExecutionControl implements Ex */ public static void main(String[] args) throws Exception { String loopBack = null; - Socket socket = new Socket(loopBack, Integer.parseInt(args[0])); + int commandLinePort = Integer.parseInt(args[0]); + int port; + byte[] secret; + if (commandLinePort == (-1)) { + Reader r = new InputStreamReader(System.in); + port = Integer.parseInt(readLine(r)); + secret = Base64.getDecoder().decode(readLine(r)); + } else { + port = commandLinePort; + secret = null; + } + Socket socket = new Socket(loopBack, port); InputStream inStream = socket.getInputStream(); OutputStream outStream = socket.getOutputStream(); + if (secret != null) { + outStream.write(secret); + outStream.flush(); + } Map> outputs = new HashMap<>(); outputs.put("out", st -> System.setOut(new PrintStream(st, true, System.out.charset()))); outputs.put("err", st -> System.setErr(new PrintStream(st, true, System.err.charset()))); @@ -74,6 +99,17 @@ public static void main(String[] args) throws Exception { forwardExecutionControlAndIO(new RemoteExecutionControl(), inStream, outStream, outputs, input); } + private static String readLine(Reader r) throws IOException { + StringBuilder line = new StringBuilder(); + int read; + + while ((read = r.read()) != (-1) && read != '\n') { + line.append((char)read); + } + + return line.toString(); + } + // These three variables are used by the main JShell process in interrupting // the running process. Access is via JDI, so the reference is not visible // to code inspection. diff --git a/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java b/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java index d4df40dac9a7..5f5372b780fc 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/execution/StreamingExecutionControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ import java.io.EOFException; import java.io.IOException; import java.io.ObjectInput; +import java.io.ObjectInputFilter; +import java.io.ObjectInputStream; import java.io.ObjectOutput; import jdk.jshell.JShellException; import jdk.jshell.spi.ExecutionControl; @@ -45,16 +47,23 @@ public class StreamingExecutionControl implements ExecutionControl { private final ObjectOutput out; private final ObjectInput in; + private final ObjectInputFilterImpl filter = new ObjectInputFilterImpl(); /** * Creates an instance. * * @param out the output for commands - * @param in the input for command responses + * @param in the input for command responses; must be an {@link ObjectInputStream} + * @throws IllegalArgumentException if {@code in} is not an {@linkplain ObjectInputStream} */ public StreamingExecutionControl(ObjectOutput out, ObjectInput in) { this.out = out; this.in = in; + if (in instanceof ObjectInputStream ois) { + ois.setObjectInputFilter(filter); + } else { + throw new IllegalArgumentException("The 'in' parameter must be an ObjectInputStream."); + } } @Override @@ -162,27 +171,16 @@ public void stop() } /** - * @throws ExecutionControl.UserException {@inheritDoc} - * @throws ExecutionControl.ResolutionException {@inheritDoc} - * @throws ExecutionControl.StoppedException {@inheritDoc} - * @throws ExecutionControl.EngineTerminationException {@inheritDoc} - * @throws ExecutionControl.NotImplementedException {@inheritDoc} + * {@inheritDoc} + * + * @apiNote This method always throws {@link NotImplementedException}. + * + * @throws ExecutionControl.NotImplementedException always thrown. */ @Override public Object extensionCommand(String command, Object arg) - throws RunException, EngineTerminationException, InternalException { - try { - writeCommand(command); - out.writeObject(arg); - out.flush(); - // Retrieve and report results from the remote agent. - readAndReportExecutionResult(); - Object result = in.readObject(); - return result; - } catch (IOException | ClassNotFoundException ex) { - throw new EngineTerminationException("Exception transmitting remote extensionCommand: " - + command + " -- " + ex); - } + throws NotImplementedException { + throw new NotImplementedException("extensionCommand not implemented."); } /** @@ -379,4 +377,21 @@ private ResolutionException readResolutionException() throws IOException, ClassN StackTraceElement[] elems = (StackTraceElement[]) in.readObject(); return new ResolutionException(id, elems); } + + private static final class ObjectInputFilterImpl implements ObjectInputFilter { + + @Override + public ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo) { + if (filterInfo.serialClass() instanceof Class clazz) { + while (clazz.isArray()) { + clazz = clazz.componentType(); + } + return clazz == StackTraceElement.class || + clazz == boolean.class ? Status.ALLOWED + : Status.REJECTED; + } + + return Status.ALLOWED; + } + } } diff --git a/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java b/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java index cfd1212a67ab..3d6d4ff12752 100644 --- a/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java +++ b/src/jdk.jstatd/share/classes/sun/tools/jstatd/Jstatd.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -149,10 +149,9 @@ public static void main(String[] args) { name.append("/").append(rminame); try { - remoteHost = new RemoteHostImpl(rmiPort); ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(rmiFilterPattern); - RemoteHost stub = (RemoteHost) UnicastRemoteObject.exportObject( - remoteHost, rmiPort, filter); + remoteHost = new RemoteHostImpl(rmiPort, filter); + RemoteHost stub = (RemoteHost) UnicastRemoteObject.exportObject(remoteHost, rmiPort, filter); bind(name.toString(), stub); System.out.println("jstatd started (bound to " + name.toString() + ")"); System.out.flush(); diff --git a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java index 5db7834e8f1e..9f050fbbb99c 100644 --- a/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java +++ b/src/jdk.jstatd/share/classes/sun/tools/jstatd/RemoteHostImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package sun.tools.jstatd; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.nio.*; import java.io.*; import java.net.*; @@ -52,13 +53,20 @@ public class RemoteHostImpl implements RemoteHost, HostListener { private Set activeVms; private static RemoteVm rvm; private final int rmiPort; + private final ObjectInputFilter filter; + private final Map exportedVMs = new ConcurrentHashMap<>(); public RemoteHostImpl() throws MonitorException { this(0); } public RemoteHostImpl(int rmiPort) throws MonitorException { + this(rmiPort, null); + } + + public RemoteHostImpl(int rmiPort, ObjectInputFilter filter) throws MonitorException { this.rmiPort = rmiPort; + this.filter = filter; try { monitoredHost = MonitoredHost.getMonitoredHost("localhost"); } catch (URISyntaxException e) { } @@ -78,7 +86,8 @@ public RemoteVm attachVm(int lvmid) throws RemoteException, MonitorException { VmIdentifier vmid = new VmIdentifier(vmidStr); MonitoredVm mvm = monitoredHost.getMonitoredVm(vmid); rvm = new RemoteVmImpl((BufferedMonitoredVm)mvm); - stub = (RemoteVm) UnicastRemoteObject.exportObject(rvm, rmiPort); + stub = (RemoteVm) UnicastRemoteObject.exportObject(rvm, rmiPort, filter); + exportedVMs.put(stub, (RemoteVmImpl) rvm); } catch (URISyntaxException e) { throw new RuntimeException("Malformed VmIdentifier URI: " @@ -88,7 +97,11 @@ public RemoteVm attachVm(int lvmid) throws RemoteException, MonitorException { } public void detachVm(RemoteVm rvm) throws RemoteException { - rvm.detach(); + // Do not invoke on caller-supplied Object. + RemoteVm v = exportedVMs.remove(rvm); + if (v != null) { + v.detach(); + } } public int[] activeVms() throws MonitorException { diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java index 4694134e3880..12a687f83d16 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/JndiLoginModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,23 @@ package com.sun.security.auth.module; -import javax.security.auth.*; -import javax.security.auth.callback.*; -import javax.security.auth.login.*; -import javax.security.auth.spi.*; -import javax.naming.*; -import javax.naming.directory.*; +import javax.naming.InitialContext; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.Attribute; +import javax.naming.directory.Attributes; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchResult; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.FailedLoginException; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; import java.util.Map; import java.util.LinkedList; @@ -200,9 +211,8 @@ public class JndiLoginModule implements LoginModule { private static final String CRYPT = "{crypt}"; private static final String USER_PWD = "userPassword"; - private static final String USER_UID = "uidNumber"; - private static final String USER_GID = "gidNumber"; - private static final String GROUP_ID = "gidNumber"; + private static final String UID_NUMBER = "uidNumber"; + private static final String GID_NUMBER = "gidNumber"; private static final String NAME = "javax.security.auth.login.name"; private static final String PWD = "javax.security.auth.login.password"; @@ -381,7 +391,7 @@ public boolean login() throws LoginException { */ public boolean commit() throws LoginException { - if (succeeded == false) { + if (!succeeded) { return false; } else { if (subject.isReadOnly()) { @@ -389,11 +399,11 @@ public boolean commit() throws LoginException { throw new LoginException ("Subject is Readonly"); } // add Principals to the Subject - if (!subject.getPrincipals().contains(userPrincipal)) + if (userPrincipal != null && !subject.getPrincipals().contains(userPrincipal)) subject.getPrincipals().add(userPrincipal); - if (!subject.getPrincipals().contains(UIDPrincipal)) + if (UIDPrincipal != null && !subject.getPrincipals().contains(UIDPrincipal)) subject.getPrincipals().add(UIDPrincipal); - if (!subject.getPrincipals().contains(GIDPrincipal)) + if (GIDPrincipal != null && !subject.getPrincipals().contains(GIDPrincipal)) subject.getPrincipals().add(GIDPrincipal); for (int i = 0; i < supplementaryGroups.size(); i++) { if (!subject.getPrincipals().contains @@ -436,9 +446,9 @@ public boolean abort() throws LoginException { System.out.println("\t\t[JndiLoginModule]: " + "aborted authentication failed"); - if (succeeded == false) { + if (!succeeded) { return false; - } else if (succeeded == true && commitSucceeded == false) { + } else if (!commitSucceeded) { // Clean out state succeeded = false; @@ -510,34 +520,25 @@ public boolean logout() throws LoginException { * @param getPasswdFromSharedState boolean that tells this method whether * to retrieve the password from the sharedState. */ - private void attemptAuthentication(boolean getPasswdFromSharedState) - throws LoginException { - + private void attemptAuthentication(boolean getPasswdFromSharedState) throws LoginException { String encryptedPassword = null; - // first get the username and password getUsernamePassword(getPasswdFromSharedState); - + if (username == null) { + throw new FailedLoginException("No username provided"); + } try { - // get the user's passwd entry from the user provider URL InitialContext iCtx = new InitialContext(); ctx = (DirContext)iCtx.lookup(userProvider); - - /* - SearchControls controls = new SearchControls - (SearchControls.ONELEVEL_SCOPE, - 0, - 5000, - new String[] { USER_PWD }, - false, - false); - */ - - SearchControls controls = new SearchControls(); - NamingEnumeration ne = ctx.search("", - "(uid=" + username + ")", - controls); + // Search for the user with the given "uid" and obtain all their attributes. + // Then verify the user's password attribute matches with the one set on + // this LoginModule + NamingEnumeration ne = ctx.search("", new BasicAttributes("uid", username)); + // Create an exception instance beforehand, to use it in the case of a failed login due + // to a missing user or due to an incorrect credential. It's OK for it to go unused + // in the case of a successful authentication. + final FailedLoginException failedLogin = new FailedLoginException("Login failed"); if (ne.hasMore()) { SearchResult result = ne.next(); Attributes attributes = result.getAttributes(); @@ -559,14 +560,17 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) // authentication for LDAP is recommended only for secure // channels. For nonsecure channels, SSL is recommended. + final boolean passwordMatches; Attribute pwd = attributes.get(USER_PWD); - String encryptedPwd = new String((byte[])pwd.get(), UTF_8); - encryptedPassword = encryptedPwd.substring(CRYPT.length()); - - // check the password - if (verifyPassword - (encryptedPassword, new String(password)) == true) { - + if (pwd == null) { + passwordMatches = false; + } else { + String encryptedPwd = new String((byte[]) pwd.get(), UTF_8); + encryptedPassword = encryptedPwd.substring(CRYPT.length()); + // check the password + passwordMatches = verifyPassword(encryptedPassword, new String(password)); + } + if (passwordMatches) { // authentication succeeded if (debug) System.out.println("\t\t[JndiLoginModule] " + @@ -577,7 +581,7 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) if (debug) System.out.println("\t\t[JndiLoginModule] " + "attemptAuthentication() failed"); - throw new FailedLoginException("Login incorrect"); + throw failedLogin; } // save input as shared state only if @@ -592,27 +596,36 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) // create the user principal userPrincipal = new UnixPrincipal(username); - // get the UID - Attribute uid = attributes.get(USER_UID); - String uidNumber = (String)uid.get(); - UIDPrincipal = new UnixNumericUserPrincipal(uidNumber); - if (debug && uidNumber != null) { - System.out.println("\t\t[JndiLoginModule] " + - "user: '" + username + "' has UID: " + + // get the uidNumber + Attribute uid = attributes.get(UID_NUMBER); + if (uid == null) { + UIDPrincipal = null; + } else { + String uidNumber = (String) uid.get(); + UIDPrincipal = uidNumber == null + ? null + : new UnixNumericUserPrincipal(uidNumber); + if (debug && UIDPrincipal != null) { + System.out.println("\t\t[JndiLoginModule] " + + "user: '" + username + "' has uidNumber: " + uidNumber); + } } - // get the GID - Attribute gid = attributes.get(USER_GID); - String gidNumber = (String)gid.get(); - GIDPrincipal = new UnixNumericGroupPrincipal - (gidNumber, true); - if (debug && gidNumber != null) { - System.out.println("\t\t[JndiLoginModule] " + - "user: '" + username + "' has GID: " + + Attribute gid = attributes.get(GID_NUMBER); + if (gid == null) { + GIDPrincipal = null; + } else { + String gidNumber = (String) gid.get(); + GIDPrincipal = gidNumber == null + ? null + : new UnixNumericGroupPrincipal(gidNumber, true); + if (debug && GIDPrincipal != null) { + System.out.println("\t\t[JndiLoginModule] " + + "user: '" + username + "' has gidNumber: " + gidNumber); + } } - // get the supplementary groups from the group provider URL ctx = (DirContext)iCtx.lookup(groupProvider); ne = ctx.search("", new BasicAttributes("memberUid", username)); @@ -621,13 +634,19 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) result = ne.next(); attributes = result.getAttributes(); - gid = attributes.get(GROUP_ID); - String suppGid = (String)gid.get(); - if (!gidNumber.equals(suppGid)) { + gid = attributes.get(GID_NUMBER); + if (gid == null) { + continue; + } + String suppGid = (String) gid.get(); + if (suppGid == null) { + continue; + } + if (GIDPrincipal == null || !GIDPrincipal.getName().equals(suppGid)) { UnixNumericGroupPrincipal suppPrincipal = new UnixNumericGroupPrincipal(suppGid, false); supplementaryGroups.add(suppPrincipal); - if (debug && suppGid != null) { + if (debug) { System.out.println("\t\t[JndiLoginModule] " + "user: '" + username + "' has Supplementary Group: " + @@ -641,17 +660,15 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) if (debug) { System.out.println("\t\t[JndiLoginModule]: User not found"); } - throw new FailedLoginException("User not found"); + throw failedLogin; } } catch (NamingException ne) { - // bad username if (debug) { - System.out.println("\t\t[JndiLoginModule]: User not found"); + System.out.println("\t\t[JndiLoginModule]: exception when finding user"); ne.printStackTrace(); } - throw new FailedLoginException("User not found"); + throw new FailedLoginException("Login failed"); } - // authentication succeeded } diff --git a/test/hotspot/gtest/gc/g1/test_g1CardSet.cpp b/test/hotspot/gtest/gc/g1/test_g1CardSet.cpp index 0cb663e1ade2..1ea81bdd1d2b 100644 --- a/test/hotspot/gtest/gc/g1/test_g1CardSet.cpp +++ b/test/hotspot/gtest/gc/g1/test_g1CardSet.cpp @@ -92,6 +92,8 @@ class G1CardSetTest : public ::testing::Test { static void card_set_basic_test(); static void card_set_mt_test(); + static void card_set_howl_bounds_test(); + static void add_cards(G1CardSet* card_set, uint cards_per_region, uint* cards, uint num_cards, G1AddCardResult* results); static void contains_cards(G1CardSet* card_set, uint cards_per_region, uint* cards, uint num_cards); @@ -492,6 +494,29 @@ void G1CardSetTest::card_set_mt_test() { ASSERT_TRUE(count_cards._num_cards <= cl.added()); } +void G1CardSetTest::card_set_howl_bounds_test() { + const uint CardsPerRegion = 65536; + const double CoarsenThreshold = 0.9; + + G1CardSetConfiguration config(1, + CoarsenThreshold, + 8, + CoarsenThreshold, + CardsPerRegion, + 0); + + G1CardSetFreePool free_pool(config.num_mem_object_types()); + G1CardSetMemoryManager mm(&config, &free_pool); + + G1CardSet card_set(&config, &mm); + + // Add a few cards so that we coarsen to howl in this configuration. + card_set.add_card(0, 1); + card_set.add_card(0, 2); + card_set.add_card(0, 3); + card_set.add_card(0, 4); +} + TEST_VM(G1CardSetTest, basic_card_set_test) { G1CardSetTest::card_set_basic_test(); } @@ -499,3 +524,7 @@ TEST_VM(G1CardSetTest, basic_card_set_test) { TEST_VM(G1CardSetTest, mt_card_set_test) { G1CardSetTest::card_set_mt_test(); } + +TEST_VM(G1CardSetTest, howl_container_bucket_bounds) { + G1CardSetTest::card_set_howl_bounds_test(); +} diff --git a/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp b/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp index 6f2ccc6c14e7..0fbc8c0f8dc0 100644 --- a/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp +++ b/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp @@ -23,50 +23,27 @@ */ #include "gc/shared/partialArrayTaskStepper.inline.hpp" -#include "memory/allStatic.hpp" -#include "runtime/atomic.hpp" #include "unittest.hpp" -using Step = PartialArrayTaskStepper::Step; -using Stepper = PartialArrayTaskStepper; - -class PartialArrayTaskStepper::TestSupport : AllStatic { -public: - static Step next(const Stepper* stepper, - size_t length, - size_t chunk_size, - Atomic* to_length_addr) { - return stepper->next_impl(length, chunk_size, to_length_addr); - } -}; - -using StepperSupport = PartialArrayTaskStepper::TestSupport; - -static uint simulate(const Stepper* stepper, - size_t length, - size_t chunk_size, - Atomic* to_length_addr) { - Step init = stepper->start(length, chunk_size); - to_length_addr->store_relaxed(init._index); - uint queue_count = init._ncreate; - uint task = 0; - for ( ; queue_count > 0; ++task) { - --queue_count; - Step step = StepperSupport::next(stepper, length, chunk_size, to_length_addr); - queue_count += step._ncreate; - } - return task; -} - static void run_test(size_t length, size_t chunk_size, uint n_workers) { const PartialArrayTaskStepper stepper(n_workers); - Atomic to_length; - uint tasks = simulate(&stepper, length, chunk_size, &to_length); - ASSERT_EQ(length, to_length.load_relaxed()); + size_t index = length % chunk_size; + uint pending = index < length ? 1u : 0u; + uint tasks = 0; + while (pending > 0) { + ASSERT_LT(index, length); + --pending; + pending += stepper.continuation_tasks(index, length, chunk_size); + index += chunk_size; + ++tasks; + ASSERT_LE(pending, n_workers); + ASSERT_LE(pending, (length - index) / chunk_size); + } + ASSERT_EQ(length, index); ASSERT_EQ(tasks, length / chunk_size); } -TEST(PartialArrayTaskStepperTest, doit) { +TEST(PartialArrayTaskStepperTest, coverage_and_task_limit) { for (size_t chunk_size = 50; chunk_size <= 500; chunk_size += 50) { for (uint n_workers = 1; n_workers <= 256; n_workers = (n_workers * 3 / 2 + 1)) { for (size_t length = 0; length <= 1000000; length = (length * 2 + 1)) { @@ -85,13 +62,9 @@ TEST(PartialArrayTaskStepperTest, overflow_beyond_array) { const size_t Length = INT32_MAX; const size_t ChunkSize = 1; const size_t Index = 1431655765; - const size_t NumWorkers = 16; // Fanout is 4. - - const Stepper stepper(NumWorkers); + const uint NumWorkers = 16; // Fanout is 4. - Atomic to_length; - to_length.store_relaxed(Index); + const PartialArrayTaskStepper stepper(NumWorkers); - Step step = StepperSupport::next(&stepper, Length, ChunkSize, &to_length); - ASSERT_EQ(1u, step._ncreate); + ASSERT_EQ(1u, stepper.continuation_tasks(Index, Length, ChunkSize)); } diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index 9189e5af456e..394e7037e9ad 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -41,8 +41,7 @@ class ShenandoahAgeCensusTest : public ::testing::Test { } static void add_population(ShenandoahAgeCensus& census, const uint age, const size_t population_words) { - CENSUS_NOISE(census.add(age, 0, 0, population_words, 0)); - NO_CENSUS_NOISE(census.add(age, 0, population_words, 0)); + census.add(age, 0, population_words, 0); } void update(ShenandoahAgeCensus& census, size_t cohorts) const { diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahCollectorPolicy.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahCollectorPolicy.cpp index 70d458ec5e50..8b9b379d88a9 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahCollectorPolicy.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahCollectorPolicy.cpp @@ -22,51 +22,27 @@ * */ +#include "gc/shenandoah/mode/shenandoahGenerationalMode.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" +#include "gc/shenandoah/shenandoahController.hpp" #include "unittest.hpp" +#include "utilities/ostream.hpp" -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_sanity) { - ShenandoahCollectorPolicy policy; - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 0UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); -} - -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_no_upgrade) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, true); - policy.record_degenerated(true, true, true); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 2UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); -} - -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_upgrade) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, false); - policy.record_degenerated(true, true, false); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 2UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), true); -} - -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_reset_progress) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, false); - policy.record_degenerated(true, true, true); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 2UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); -} - -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_full_reset) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, false); - policy.record_success_full(); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 0UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); -} +using ::testing::HasSubstr; -TEST(ShenandoahCollectorPolicyTest, track_degen_cycles_reset) { - ShenandoahCollectorPolicy policy; - policy.record_degenerated(true, true, false); - policy.record_success_concurrent(true, true); - EXPECT_EQ(policy.consecutive_degenerated_gc_count(), 0UL); - EXPECT_EQ(policy.should_upgrade_degenerated_gc(), false); +TEST_VM(ShenandoahCollectorPolicyTest, track_allocation_stalls) { + ShenandoahGenerationalMode mode; + ShenandoahCollectorPolicy policy(&mode); + for (int i = 0; i < ShenandoahController::PHASE_LIMIT; ++i) { + policy.record_allocation_stall(static_cast(i)); + } + stringStream ss; + policy.print_gc_stats(&ss); + ASSERT_THAT(ss.base(), HasSubstr("6 Stalls")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Outside of Cycle")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Initializing")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Roots")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Mark")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Evacuation")); + ASSERT_THAT(ss.base(), HasSubstr("1 happened at Update References")); } diff --git a/test/hotspot/gtest/gc/z/test_zAdaptiveHeap.cpp b/test/hotspot/gtest/gc/z/test_zAdaptiveHeap.cpp new file mode 100644 index 000000000000..5c51b9658545 --- /dev/null +++ b/test/hotspot/gtest/gc/z/test_zAdaptiveHeap.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "gc/z/zAdaptiveHeap.hpp" +#include "gc/z/zGlobals.hpp" +#include "unittest.hpp" + +class ZAdaptiveHeapTest : public ::testing::Test { +protected: + static constexpr size_t K = 1024; + static constexpr size_t M = 1024 * K; + static constexpr size_t G = 1024 * M; + + static constexpr size_t container_capacity = 16 * G; + static constexpr size_t machine_capacity = 32 * G; + + static ZHeapResizeMetrics heap_resize_metrics() { + return { + container_capacity - 1 * G, + machine_capacity - 1 * G, + 4 * G, + 2 * M, + 5 * G, + 2 * G, + double(128 * M) + }; + } + + static ZSystemMemoryPressureMetrics sys_mem_pressure_metrics(double usage, physical_memory_size_type max) { + return { + physical_memory_size_type(usage * max), + max, + ZMemoryConcerningThreshold, + ZMemoryHighThreshold, + ZMemoryCriticalThreshold + }; + } + + static ZMemoryPressureMetrics mem_pressure_metrics(double container_usage, physical_memory_size_type container_max, + double machine_usage, physical_memory_size_type machine_max) { + return { + 5.0, + true, + sys_mem_pressure_metrics(machine_usage, machine_max), + sys_mem_pressure_metrics(container_usage, container_max) + }; + } + + static int concern_level(double availability) { + if (availability < ZMemoryCriticalThreshold) { + return 3; + } + + if (availability < ZMemoryHighThreshold) { + return 2; + } + + if (availability < ZMemoryConcerningThreshold) { + return 1; + } + + return 0; + } + + static double memory_pressure(const ZMemoryPressureMetrics& metrics) { + return ZAdaptiveHeap::compute_memory_pressure(metrics); + } +}; + +TEST_F(ZAdaptiveHeapTest, test_mem_pressure) { + double mem_pressures[101 * 101]; + + for (int i = 0; i <= 100; ++i) { + for (int j = 0; j <= 100; ++j) { + double container_availability = double(100 - j) / 100.0; + double machine_availability = double(100 - i) / 100.0; + + int container_concern_level = concern_level(container_availability); + int machine_concern_level = concern_level(machine_availability); + int highest_concern_level = MAX2(container_concern_level, machine_concern_level); + + ZMemoryPressureMetrics metrics = mem_pressure_metrics(1.0 - container_availability, 16 * G, + 1.0 - machine_availability, 32 * G); + + double mem_pressure = memory_pressure(metrics); + + switch (highest_concern_level) { + // Normal + case 0: { + EXPECT_EQ(mem_pressure, 1.0); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_concerning(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_high(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_critical(metrics)); + break; + } + // Concerning + case 1: { + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_concerning(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_high(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_critical(metrics)); + EXPECT_GT(mem_pressure, 1.0); + EXPECT_LE(mem_pressure, 5.0); + break; + } + + // High + case 2: { + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_concerning(metrics)); + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_high(metrics)); + EXPECT_FALSE(ZAdaptiveHeap::is_memory_pressure_critical(metrics)); + EXPECT_GT(mem_pressure, 5.0); + EXPECT_LE(mem_pressure, 25.0); + break; + } + + // Critical + case 3: { + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_concerning(metrics)); + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_high(metrics)); + EXPECT_TRUE(ZAdaptiveHeap::is_memory_pressure_critical(metrics)); + EXPECT_GT(mem_pressure, 25.0); + } + } + + mem_pressures[i * 101 + j] = mem_pressure; + + if (j > 0) { + // Pressure should not go down when only increasing container usage + EXPECT_GE(mem_pressure, mem_pressures[i * 101 + j - 1]); + } + + if (i > 0) { + // Pressure should not go down when only increasing machine usage + EXPECT_GE(mem_pressure, mem_pressures[(i - 1) * 101 + j]); + } + + double epsilon = 0.001; + + if (j > 1) { + double prev = mem_pressures[i * 101 + j - 1]; + double prev_prev = mem_pressures[i * 101 + j - 2]; + + double diff = mem_pressure - prev; + double prev_diff = prev - prev_prev; + + // Rate of increase should never go down when increasing container usage + EXPECT_GE(diff - prev_diff, -epsilon); + } + + if (i > 1) { + double prev = mem_pressures[(i - 1) * 101 + j]; + double prev_prev = mem_pressures[(i - 2) * 101 + j]; + + double diff = mem_pressure - prev; + double prev_diff = prev - prev_prev; + + // Rate of increase should never go down when increasing machine usage + EXPECT_GE(diff - prev_diff, -epsilon); + } + } + } +} diff --git a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt index 775479953383..7edf6ce88f71 100644 --- a/test/hotspot/jtreg/ProblemList-Virtual.txt +++ b/test/hotspot/jtreg/ProblemList-Virtual.txt @@ -27,6 +27,7 @@ runtime/jni/critical/SuspendInCritical.java 8384369 generic-all serviceability/AsyncGetCallTrace/MyPackage/ASGCTBaseTest.java 8308026 generic-all serviceability/jvmti/Heap/IterateHeapWithEscapeAnalysisEnabled.java 8264699 generic-all +serviceability/jvmti/FollowReferences/KindSystemClass/KindSystemClass.java 8383422 generic-all vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2manyDiff_a/TestDescription.java 8308367 generic-all vmTestbase/nsk/jvmti/unit/functions/Dispose/JvmtiTest/TestDescription.java 8387429 generic-all diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 9fee7e62e0b5..5825040f56cb 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -69,8 +69,6 @@ compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64 -compiler/vectorapi/VectorExpressionFuzzer.java 8337281 linux-aarch64,macosx-aarch64 - compiler/igvn/TestIncorrectDeadPathTypeNode.java 8392354 generic-all ############################################################################# diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 298f9590db91..7588726a1f05 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -595,6 +595,7 @@ tier1_serviceability = \ -serviceability/jvmti/RedefineClasses/RedefineRunningMethods.java \ -serviceability/jvmti/RedefineClasses/RedefineRunningMethodsWithBacktrace.java \ -serviceability/jvmti/RedefineClasses/TestRedefineObject.java \ + -serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java \ -serviceability/sa/ClhsdbScanOops.java \ -serviceability/sa/ClhsdbJstackXcompStress.java \ -serviceability/sa/TestJmapCore.java \ diff --git a/test/hotspot/jtreg/compiler/c1/CanonicalizeArrayLength.java b/test/hotspot/jtreg/compiler/c1/CanonicalizeArrayLength.java index 30f1aab00474..ba903044ae9f 100644 --- a/test/hotspot/jtreg/compiler/c1/CanonicalizeArrayLength.java +++ b/test/hotspot/jtreg/compiler/c1/CanonicalizeArrayLength.java @@ -27,21 +27,25 @@ * @summary C1 crashes in Canonicalizer::do_ArrayLength() after fix for JDK-8150102 * * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:CompileThreshold=100 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:CompileThreshold=100 -XX:CompileThresholdScaling=1.0 + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 * -XX:-BackgroundCompilation * compiler.c1.CanonicalizeArrayLength * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:CompileThreshold=100 -XX:+TieredCompilation -XX:TieredStopAtLevel=3 + * -XX:CompileThreshold=100 -XX:CompileThresholdScaling=1.0 + * -XX:+TieredCompilation -XX:TieredStopAtLevel=3 * -XX:-BackgroundCompilation * -XX:+PatchALot * compiler.c1.CanonicalizeArrayLength * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:CompileThreshold=100 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:CompileThreshold=100 -XX:CompileThresholdScaling=1.0 + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 * -XX:-BackgroundCompilation * -XX:ScavengeRootsInCode=0 * compiler.c1.CanonicalizeArrayLength * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:CompileThreshold=100 -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + * -XX:CompileThreshold=100 -XX:CompileThresholdScaling=1.0 + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 * -XX:-BackgroundCompilation -XX:ScavengeRootsInCode=1 * compiler.c1.CanonicalizeArrayLength */ diff --git a/test/hotspot/jtreg/compiler/c2/TestImplicitNullCheckWithScalarizedDebugInfo.java b/test/hotspot/jtreg/compiler/c2/TestImplicitNullCheckWithScalarizedDebugInfo.java new file mode 100644 index 000000000000..a2299365ae96 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestImplicitNullCheckWithScalarizedDebugInfo.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @key stress + * @bug 8389578 + * @summary Test an implicit null check whose result is used by scalarized + * debug info on the null path. + * @enablePreview + * @run main ${test.main.class} + * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM -XX:StressSeed=371 + * -XX:CompileCommand=compileonly,${test.main.class}::testImplicitNullCheck + * ${test.main.class} + */ + +public class TestImplicitNullCheckWithScalarizedDebugInfo { + static char c1 = 'A'; + static double d; + static Character c2 = '('; + static short s = -2; + static int i4; + + static int testImplicitNullCheck() { + if (--s != 0) { + c2.charValue(); + } else { + Byte.valueOf((byte) 1).shortValue(); + d++; + } + + char c3 = c1; + int res = 0; + int i1 = i4; + int i2 = i1 + 1; + int i3 = i1 + i2; + i4 = i3; + + char debugInfoPayload = c2; + boolean cond = ++c3 <= 1; + char c4 = cond ? Character.valueOf('r') : c2; + --s; + + res -= c1 > 1 ? 2 : 1; + for (byte b = 9; b >= 0; b--) { + long l = 2L << c3; + res |= l; + } + return res + i1 + i2 + i3; + } + + public static void main(String[] args) { + testImplicitNullCheck(); + } +} diff --git a/test/hotspot/jtreg/compiler/c2/TestPrintFrameConverterAssembly.java b/test/hotspot/jtreg/compiler/c2/TestPrintFrameConverterAssembly.java new file mode 100644 index 000000000000..d10cdb239467 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestPrintFrameConverterAssembly.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8394831 + * @requires vm.debug == true & vm.compiler2.enabled + * @summary Run with -XX:+PrintFrameConverterAssembly in debug builds. + * + * @run main/othervm -XX:-DisplayVMOutput -XX:+PrintFrameConverterAssembly + * -XX:+PrintExceptionHandlers ${test.main.class} + */ +package compiler.c2; + +public class TestPrintFrameConverterAssembly { + public static void main(String[] args) { + } +} diff --git a/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java b/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java new file mode 100644 index 000000000000..bc64df850c48 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/gvn/InvolutionIdentityTests.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.c2.gvn; + +import compiler.lib.generators.Generator; +import compiler.lib.generators.Generators; +import compiler.lib.generators.RestrictableGenerator; +import compiler.lib.ir_framework.DontCompile; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8350988 8364407 + * @summary Test that Identity simplifications of Involution nodes are being performed as expected. + * @key randomness + * @library /test/lib / + * @compile ReverseBytesConstantsHelper.jasm + * @run driver ${test.main.class} + */ +public class InvolutionIdentityTests { + + public static final RestrictableGenerator GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE); + public static final RestrictableGenerator GEN_SHORT = Generators.G.safeRestrict(Generators.G.ints(), Short.MIN_VALUE, Short.MAX_VALUE); + public static final RestrictableGenerator GEN_LONG = Generators.G.longs(); + public static final RestrictableGenerator GEN_INT = Generators.G.ints(); + public static final Generator GEN_FLOAT = Generators.G.floats(); + public static final Generator GEN_DOUBLE = Generators.G.doubles(); + + public static void main(String[] args) { + TestFramework.runWithFlags("-XX:CompileCommand=inline,compiler.c2.gvn.ReverseBytesConstantsHelper::*"); + } + + @Run(test = { + "testI1", "testI2", + "testL1", "testL2", + "testS1", "testS2", + "testUS1", "testUS2", + "testF1", + "testD1" + }) + public void runMethod() { + int ai = GEN_INT.next(); + + int mini = Integer.MIN_VALUE; + int maxi = Integer.MAX_VALUE; + + assertResultI(0); + assertResultI(ai); + assertResultI(mini); + assertResultI(maxi); + + long al = GEN_LONG.next(); + + long minl = Long.MIN_VALUE; + long maxl = Long.MAX_VALUE; + + assertResultL(0); + assertResultL(al); + assertResultL(minl); + assertResultL(maxl); + + short as = GEN_SHORT.next().shortValue(); + + short mins = Short.MIN_VALUE; + short maxs = Short.MAX_VALUE; + + assertResultS((short) 0); + assertResultS(as); + assertResultS(mins); + assertResultS(maxs); + + char ac = (char) GEN_CHAR.next().intValue(); + + char minc = Character.MIN_VALUE; + char maxc = Character.MAX_VALUE; + + assertResultUS((char) 0); + assertResultUS(ac); + assertResultUS(minc); + assertResultUS(maxc); + + // short and char variants but passing an int + assertResultTruncating(0); + assertResultTruncating(ai); + assertResultTruncating(mini); + assertResultTruncating(maxi); + + float af = GEN_FLOAT.next(); + float inf = Float.POSITIVE_INFINITY; + float nanf = Float.NaN; + + assertResultF(0f); + assertResultF(-0f); + assertResultF(af); + assertResultF(inf); + assertResultF(nanf); + + double ad = GEN_DOUBLE.next(); + double ind = Double.POSITIVE_INFINITY; + double nand = Double.NaN; + + assertResultD(0d); + assertResultD(-0d); + assertResultD(ad); + assertResultD(ind); + assertResultD(nand); + } + + @DontCompile + public void assertResultI(int a) { + Asserts.assertEQ(Integer.reverseBytes(Integer.reverseBytes(a)), testI1(a)); + Asserts.assertEQ(Integer.reverse(Integer.reverse(a)) , testI2(a)); + } + + @DontCompile + public void assertResultL(long a) { + Asserts.assertEQ(Long.reverseBytes(Long.reverseBytes(a)), testL1(a)); + Asserts.assertEQ(Long.reverse(Long.reverse(a)) , testL2(a)); + } + + @DontCompile + public void assertResultS(short a) { + Asserts.assertEQ(Short.reverseBytes(Short.reverseBytes(a)), testS1(a)); + } + + @DontCompile + public void assertResultUS(char a) { + Asserts.assertEQ(Character.reverseBytes(Character.reverseBytes(a)), testUS1(a)); + } + + @DontCompile + public void assertResultF(float a) { + Asserts.assertEQ(Float.floatToRawIntBits(-(-a)), Float.floatToRawIntBits(testF1(a))); + } + + @DontCompile + public void assertResultD(double a) { + Asserts.assertEQ(Double.doubleToRawLongBits(-(-a)), Double.doubleToRawLongBits(testD1(a))); + } + + @DontCompile + public void assertResultTruncating(int a) { + Asserts.assertEQ((int) Short.reverseBytes( ReverseBytesConstantsHelper.reverseBytesShort(a)), testS2( a)); + Asserts.assertEQ((int) Character.reverseBytes(ReverseBytesConstantsHelper.reverseBytesChar( a)), testUS2(a)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_BYTES_I}) + public int testI1(int x) { + return Integer.reverseBytes(Integer.reverseBytes(x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_I}) + public int testI2(int x) { + return Integer.reverse(Integer.reverse(x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_BYTES_L}) + public long testL1(long x) { + return Long.reverseBytes(Long.reverseBytes(x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_L}) + public long testL2(long x) { + return Long.reverse(Long.reverse(x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_BYTES_S}) + public short testS1(short x) { + // explicit cast to restrict the input to TypeInt::SHORT + return Short.reverseBytes(Short.reverseBytes((short) (int) x)); + } + + @Test + @IR(failOn = {IRNode.REVERSE_BYTES_US}) + public char testUS1(char x) { + // explicit cast to restrict the input to TypeInt::CHAR + return Character.reverseBytes(Character.reverseBytes((char) (int) x)); + } + + @Test + @IR(counts = {IRNode.REVERSE_BYTES_S, "2"}, + applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = { "zbb", "true" }) + @IR(counts = {IRNode.REVERSE_BYTES_S, "2"}, + applyIfPlatform = {"riscv64", "false"}) + public int testS2(int x) { + return Short.reverseBytes(ReverseBytesConstantsHelper.reverseBytesShort(x)); + } + + @Test + @IR(counts = {IRNode.REVERSE_BYTES_US, "2"}, + applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = { "zbb", "true" }) + @IR(counts = {IRNode.REVERSE_BYTES_US, "2"}, + applyIfPlatform = {"riscv64", "false"}) + public int testUS2(int x) { + return Character.reverseBytes(ReverseBytesConstantsHelper.reverseBytesChar(x)); + } + + @Test + @IR(failOn = {IRNode.NEG_F}) + public float testF1(float x) { + return -(-x); + } + + @Test + @IR(failOn = {IRNode.NEG_D}) + public double testD1(double x) { + return -(-x); + } +} diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java new file mode 100644 index 000000000000..02c7805ac3ec --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingAndOrIdealization.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.igvn; + +/* + * @test + * @bug 8393645 8393641 8393586 + * @summary C2 IGVN should idealize: (x | c1) & c2 -> (x & c2) | (c1 & c2) + * @library /test/lib / + * @run main/othervm -XX:-TieredCompilation -Xcomp + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileOnly=${test.main.class}::test* + * -XX:+StressIGVN + * ${test.main.class} + * @run main ${test.main.class} + */ + +public class TestMissingAndOrIdealization { + public static void main(String[] args) { + testI(0, 0.0, false); + testL(0, 0.0, false); + } + + // Failed to optimize AndI(OrI(x, c1), c2). + // Optimization from MulNode::Ideal + static int testI(int x, double i, boolean b) { + while (i < 4) { + x |= b ? -126 : -30; + x &= 32; + i++; + } + return x; + } + + // Failed to optimize AndL(OrL(x, c1), c2). + // Optimization from MulNode::Ideal + static long testL(long x, double i, boolean b) { + while (i < 4) { + x |= b ? -126 : -30; + x &= 32; + i++; + } + return x; + } +} diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java new file mode 100644 index 000000000000..77aa48e4ed65 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingNestedMulIdealization.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.igvn; + +/* + * @test + * @bug 8393645 8393608 8393644 + * @summary C2 IGVN should idealize: (x * c1) * c2 -> x * (c1 * c2) + * @library /test/lib / + * @run main/othervm -XX:-TieredCompilation -Xcomp + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileOnly=${test.main.class}::test* + * -XX:+StressIGVN + * ${test.main.class} + * @run main ${test.main.class} + */ + +// The test is somewhat intermittent, more stable: -XX:RepeatCompilation=50 +public class TestMissingNestedMulIdealization { + + public static void main(String[] args) { + testI(0); + testL(0); + } + + // Failed to optimize MulI(MulI(x, c1), c2). + // Optimization from MulNode::Ideal + static int testI(int x) { + for (int i = 0; i < 17; i++) { + x *= ~i; + } + return x; + } + + // Failed to optimize MulL(MulL(x, c1), c2). + // Optimization from MulNode::Ideal + static long testL(long x) { + for (long i = 0; i < 17; i++) { + x *= ~i; + } + return x; + } +} diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java new file mode 100644 index 000000000000..f0f0f6b41f88 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/igvn/TestMissingVariableTimesConIdealization.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.igvn; + +/* + * @test + * @bug 8393645 8393610 8393640 + * @summary Missing notification pattern for AddNode::Ideal_collapse_variable_times_con: + * ((x << c1) + (x << c2)) + x -> c * x + * @library /test/lib / + * @run main/othervm -XX:-TieredCompilation -Xbatch + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileOnly=${test.main.class}::test* + * -XX:+StressIGVN + * ${test.main.class} + * @run main/othervm --enable-preview + * -XX:-TieredCompilation -Xcomp + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileOnly=${test.main.class}::test* + * -XX:+StressIGVN + * ${test.main.class} + * @run main ${test.main.class} + */ + +// Test was intermittent, reproduces better with -XX:RepeatCompilation=50 +public class TestMissingVariableTimesConIdealization { + static short sFld; + static int iFld; + static long lFld; + + public static void main(String[] args) { + for (int i = 0; i < 10000; i++) { + test1I(); + test1L(); + } + test2I(); + } + + // ((x << 2) + (x << 1)) + x -> 7 * x + static void test1I() { + iFld = iFld - Byte.valueOf((byte) -6) * iFld; + } + + // ((x << 2) + (x << 1)) + x -> 7 * x + static void test1L() { + lFld = lFld - Byte.valueOf((byte) -6) * lFld; + } + + // ((x << 14) + (x << 2)) + x -> 16389 * x + // Only reproduced with --enable-preview. + static int test2I() { + return sFld + Short.valueOf((short) 16388) * sFld; + } +} diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java b/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java index 9a06d12b15f7..3e350e83091a 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java @@ -89,9 +89,14 @@ private void testStridePosScalePosInIntLoop1_runner() { } @Test - @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) - @IR(failOn = { IRNode.COUNTED_LOOP}) + // This is a negative test: the range check cannot be currently optimized + // because the index expression scale * i + offset contains short scaling + // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long + // range check transformations. The two rules below verify that range check + // optimization does NOT take place. + @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP) + @IR(counts = { IRNode.COUNTED_LOOP, "1"}) public static void testStridePosScalePosInIntLoop2(int start, int stop, long length, long offset) { final int scale = 2; final int stride = 1; @@ -143,9 +148,14 @@ private void testStrideNegScaleNegInIntLoop1_runner() { } @Test - @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) - @IR(failOn = { IRNode.COUNTED_LOOP}) + // This is a negative test: the range check cannot be currently optimized + // because the index expression scale * i + offset contains short scaling + // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long + // range check transformations. The two rules below verify that range check + // optimization does NOT take place. + @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP) + @IR(counts = { IRNode.COUNTED_LOOP, "1"}) public static void testStrideNegScaleNegInIntLoop2(int start, int stop, long length, long offset) { final int scale = -2; final int stride = 1; @@ -195,9 +205,14 @@ private void testStrideNegScalePosInIntLoop1_runner() { } @Test - @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) - @IR(failOn = { IRNode.COUNTED_LOOP}) + // This is a negative test: the range check cannot be currently optimized + // because the index expression scale * i + offset contains short scaling + // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long + // range check transformations. The two rules below verify that range check + // optimization does NOT take place. + @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP) + @IR(counts = { IRNode.COUNTED_LOOP, "1"}) public static void testStrideNegScalePosInIntLoop2(int start, int stop, long length, long offset) { final int scale = 2; final int stride = 1; @@ -246,9 +261,14 @@ private void testStridePosScaleNegInIntLoop1_runner() { } @Test - @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) - @IR(failOn = { IRNode.COUNTED_LOOP}) + // This is a negative test: the range check cannot be currently optimized + // because the index expression scale * i + offset contains short scaling + // (see PhaseIdealLoop::is_scaled_iv) and is thus not a candidate for long + // range check transformations. The two rules below verify that range check + // optimization does NOT take place. + @IR(applyIf = { "ShortRunningLongLoop", "false" }, failOn = IRNode.LOOP) + @IR(counts = { IRNode.COUNTED_LOOP, "1"}) public static void testStridePosScaleNegInIntLoop2(int start, int stop, long length, long offset) { final int scale = -2; final int stride = 1; diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java index eb4b611e8ebe..4239dd469566 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMultiInvar.java @@ -64,7 +64,7 @@ public static void main(String[] args) { @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) public static void testByteLong1(byte[] dest, long[] src) { for (int i = 0; i < src.length; i++) { - long j = Objects.checkIndex(i * 8, (long)(src.length * 8)); + long j = Objects.checkIndex(i * 8L, (long)(src.length * 8)); UNSAFE.putLongUnaligned(dest, baseOffset + j, src[i]); } } @@ -75,6 +75,26 @@ public static void testByteLong1_runner() { testByteLong1(byteArray, longArray); } + // This is a negative version of the test above where C2 currently fails to + // perform range check elimination, which inhibits vectorization. + // The reason is that the i * 8 index expression is not accepted as a valid + // index for long range check transformations, see + // PhaseIdealLoop::is_scaled_iv_plus_offset. + @Test + @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR }) + public static void testNegativeByteLong1(byte[] dest, long[] src) { + for (int i = 0; i < src.length; i++) { + long j = Objects.checkIndex(i * 8, (long)(src.length * 8)); + UNSAFE.putLongUnaligned(dest, baseOffset + j, src[i]); + } + } + + @Run(test = "testNegativeByteLong1") + public static void testNegativeByteLong1_runner() { + baseOffset = UNSAFE.ARRAY_BYTE_BASE_OFFSET; + testNegativeByteLong1(byteArray, longArray); + } + @Test @IR(counts = { IRNode.LOAD_VECTOR_B, ">=1", IRNode.STORE_VECTOR, ">=1" }) public static void testLoopNest1(byte[] dest, byte[] src, diff --git a/test/hotspot/jtreg/compiler/c2/riscv64/TestCRCSignExtension.java b/test/hotspot/jtreg/compiler/c2/riscv64/TestCRCSignExtension.java new file mode 100644 index 000000000000..6457bcdacf86 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/riscv64/TestCRCSignExtension.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2026, Institute of Software, Chinese Academy of Sciences. + * All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=c1 + * @bug 8394822 + * @summary Verify sign extension of RISC-V CRC32 and CRC32C intrinsic results + * @requires os.arch == "riscv64" & vm.compiler1.enabled + * @modules java.base/java.util.zip:+open + * @run main/othervm -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+UseCRC32Intrinsics -XX:+UseCRC32CIntrinsics + * -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -XX:CompileThreshold=1000 + * -XX:CompileCommand=compileonly,compiler.c2.riscv64.TestCRCSignExtension::test* + * compiler.c2.riscv64.TestCRCSignExtension + */ + +/* + * @test id=c2 + * @bug 8394822 + * @summary Verify sign extension of RISC-V CRC32 and CRC32C intrinsic results + * @requires os.arch == "riscv64" & vm.compiler2.enabled + * @modules java.base/java.util.zip:+open + * @run main/othervm -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+UseCRC32Intrinsics -XX:+UseCRC32CIntrinsics + * -XX:-TieredCompilation -XX:CompileThreshold=1000 + * -XX:CompileCommand=compileonly,compiler.c2.riscv64.TestCRCSignExtension::test* + * compiler.c2.riscv64.TestCRCSignExtension + */ + +package compiler.c2.riscv64; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.charset.StandardCharsets; +import java.util.zip.CRC32; +import java.util.zip.CRC32C; + +public class TestCRCSignExtension { + private static final MethodHandle CRC32_UPDATE; + private static final MethodHandle CRC32_UPDATE_BYTES; + private static final MethodHandle CRC32C_UPDATE_BYTES; + + static { + try { + var lookup32 = MethodHandles.privateLookupIn(CRC32.class, MethodHandles.lookup()); + var lookup32c = MethodHandles.privateLookupIn(CRC32C.class, MethodHandles.lookup()); + var byteType = MethodType.methodType(int.class, int.class, int.class); + var arrayType = MethodType.methodType(int.class, int.class, byte[].class, int.class, int.class); + CRC32_UPDATE = lookup32.findStatic(CRC32.class, "update", byteType); + CRC32_UPDATE_BYTES = lookup32.findStatic(CRC32.class, "updateBytes0", arrayType); + CRC32C_UPDATE_BYTES = lookup32c.findStatic(CRC32C.class, "updateBytes", arrayType); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + // Compare before a field store or an interpreted return can normalize the result. + static boolean testUpdateByte(int b, int expected) throws Throwable { + // Fresh CRC32 state passed to the private native. + return (int) CRC32_UPDATE.invokeExact(0, b) == expected; + } + + static boolean testUpdateBytesCRC32(byte[] data, int expected) throws Throwable { + return (int) CRC32_UPDATE_BYTES.invokeExact(0, data, 0, data.length) == expected; + } + + static boolean testUpdateBytesCRC32C(byte[] data, int expected) throws Throwable { + // A fresh CRC32C starts from the raw state 0xffffffff. + return (int) CRC32C_UPDATE_BYTES.invokeExact(0xffffffff, data, 0, data.length) == expected; + } + + static void check(boolean ok, int i, String what) { + if (!ok) { + throw new RuntimeException(what + " mismatch at iteration " + i); + } + } + + public static void main(String[] args) throws Throwable { + // One input per polarity, each chosen so CRC32 and the raw CRC32C state agree, so a single + // pair of arrays covers both stubs. + byte[] clear = "b".getBytes(StandardCharsets.US_ASCII); // bit 31 clear for both + byte[] set = "e".getBytes(StandardCharsets.US_ASCII); // bit 31 set for both + + // Enough iterations to reach -XX:CompileThreshold and compile the test methods with C1 or C2. + for (int i = 0; i < 2_000; i++) { + // CRC32.update(int): C1's inline updateCRC32. Its single byte is the array's only byte, + // so the expected value equals the CRC32 array one below. + // python3 -c "import zlib; print(hex(zlib.crc32(bytes([ord('b')]), 0)))" # 0x71beeff9 + check(testUpdateByte('b', 0x71beeff9), i, "CRC32.update(int) bit31 clear"); + // python3 -c "import zlib; print(hex(zlib.crc32(bytes([ord('e')]), 0)))" # 0xefda7a5a + check(testUpdateByte('e', 0xefda7a5a), i, "CRC32.update(int) bit31 set"); + + // updateBytesCRC32 stub. + // python3 -c "import zlib; print(hex(zlib.crc32(b'b')))" # 0x71beeff9 + check(testUpdateBytesCRC32(clear, 0x71beeff9), i, "CRC32.update(byte[]) bit31 clear"); + // python3 -c "import zlib; print(hex(zlib.crc32(b'e')))" # 0xefda7a5a + check(testUpdateBytesCRC32(set, 0xefda7a5a), i, "CRC32.update(byte[]) bit31 set"); + + // updateBytesCRC32C stub; the raw state is the complement of getValue(). + // python3 -c "from crc32c import crc32c; print(hex((~crc32c(b'b')) & 0xffffffff))" # 0x2d7f4f3b + check(testUpdateBytesCRC32C(clear, 0x2d7f4f3b), i, "CRC32C.update(byte[]) bit31 clear"); + // python3 -c "from crc32c import crc32c; print(hex((~crc32c(b'e')) & 0xffffffff))" # 0xf9b52bd0 + check(testUpdateBytesCRC32C(set, 0xf9b52bd0), i, "CRC32C.update(byte[]) bit31 set"); + } + } +} diff --git a/test/hotspot/jtreg/compiler/c2/riscv64/TestVectorTestCMove.java b/test/hotspot/jtreg/compiler/c2/riscv64/TestVectorTestCMove.java new file mode 100644 index 000000000000..028f93e20185 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/riscv64/TestVectorTestCMove.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2026, Alibaba Group Holding Limited. 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 8395341 + * @summary Conditional moves whose condition is a VectorTest are matched. + * + * A diamond controlled by VectorMask.anyTrue()/allTrue() is collapsed into a + * CMove by PhaseIdealLoop::conditional_move(), which only consults the type of + * the merged value and never looks at the comparison feeding the If. RISC-V has + * no flags register, so every cmove rule has to spell the comparison out in its + * own match rule, and a missing one makes the matcher give up with + * "bad AD file" -- an assert in a debug build, a silent and endlessly retried + * bailout in a product build. Each test therefore requests CompLevel.C2 so that + * a bailout fails the test either way. + * + * @library /test/lib / + * @requires os.arch == "riscv64" & vm.compiler2.enabled + * @requires vm.cpu.features ~= ".*rvv.*" + * @modules jdk.incubator.vector + * @run driver ${test.main.class} + */ + +package compiler.c2.riscv64; + +import compiler.lib.ir_framework.CompLevel; +import compiler.lib.ir_framework.IR; +import compiler.lib.ir_framework.IRNode; +import compiler.lib.ir_framework.Run; +import compiler.lib.ir_framework.Test; +import compiler.lib.ir_framework.TestFramework; + +import jdk.incubator.vector.ByteVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorSpecies; + +import jdk.test.lib.Asserts; + +public class TestVectorTestCMove { + private static final VectorSpecies SPECIES = ByteVector.SPECIES_128; + + // Mask bit patterns, interpreted over the 16 lanes of SPECIES. + private static final long ALL = -1L; // every lane set -> anyTrue, allTrue + private static final long SOME = 1L; // only lane 0 set -> anyTrue + private static final long NONE = 0L; // no lane set -> neither + + public static void main(String[] args) { + TestFramework framework = new TestFramework(); + framework.addFlags("--add-modules=jdk.incubator.vector"); + framework.start(); + } + + // ---------------- int: CMoveI ---------------- + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ANYTRUE, ">0"}) + static int anyTrueI(long bits, int a, int b) { + return VectorMask.fromLong(SPECIES, bits).anyTrue() ? a : b; + } + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ALLTRUE, ">0"}) + static int allTrueI(long bits, int a, int b) { + return VectorMask.fromLong(SPECIES, bits).allTrue() ? a : b; + } + + // ---------------- long: CMoveL ---------------- + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ANYTRUE, ">0"}) + static long anyTrueL(long bits, long a, long b) { + return VectorMask.fromLong(SPECIES, bits).anyTrue() ? a : b; + } + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ALLTRUE, ">0"}) + static long allTrueL(long bits, long a, long b) { + return VectorMask.fromLong(SPECIES, bits).allTrue() ? a : b; + } + + // ---------------- float: CMoveF ---------------- + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ANYTRUE, ">0"}) + static float anyTrueF(long bits, float a, float b) { + return VectorMask.fromLong(SPECIES, bits).anyTrue() ? a : b; + } + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ALLTRUE, ">0"}) + static float allTrueF(long bits, float a, float b) { + return VectorMask.fromLong(SPECIES, bits).allTrue() ? a : b; + } + + // ---------------- double: CMoveD ---------------- + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ANYTRUE, ">0"}) + static double anyTrueD(long bits, double a, double b) { + return VectorMask.fromLong(SPECIES, bits).anyTrue() ? a : b; + } + + @Test(compLevel = CompLevel.C2) + @IR(counts = {IRNode.RISCV_CMOVE_VTEST_ALLTRUE, ">0"}) + static double allTrueD(long bits, double a, double b) { + return VectorMask.fromLong(SPECIES, bits).allTrue() ? a : b; + } + + // ---------------- driver ---------------- + // + // Every test sees all three mask patterns on each invocation. Keeping the + // branch unpredictable matters: conditional_move() refuses to convert a + // diamond whose If is almost always, or almost never, taken. + + @Run(test = "anyTrueI") + public void runAnyTrueI() { + Asserts.assertEQ(anyTrueI(ALL, 7, 9), 7); + Asserts.assertEQ(anyTrueI(SOME, 7, 9), 7); + Asserts.assertEQ(anyTrueI(NONE, 7, 9), 9); + } + + @Run(test = "allTrueI") + public void runAllTrueI() { + Asserts.assertEQ(allTrueI(ALL, 7, 9), 7); + Asserts.assertEQ(allTrueI(SOME, 7, 9), 9); + Asserts.assertEQ(allTrueI(NONE, 7, 9), 9); + } + + @Run(test = "anyTrueL") + public void runAnyTrueL() { + Asserts.assertEQ(anyTrueL(ALL, 7L, 9L), 7L); + Asserts.assertEQ(anyTrueL(SOME, 7L, 9L), 7L); + Asserts.assertEQ(anyTrueL(NONE, 7L, 9L), 9L); + } + + @Run(test = "allTrueL") + public void runAllTrueL() { + Asserts.assertEQ(allTrueL(ALL, 7L, 9L), 7L); + Asserts.assertEQ(allTrueL(SOME, 7L, 9L), 9L); + Asserts.assertEQ(allTrueL(NONE, 7L, 9L), 9L); + } + + @Run(test = "anyTrueF") + public void runAnyTrueF() { + Asserts.assertEQ(anyTrueF(ALL, 7f, 9f), 7f); + Asserts.assertEQ(anyTrueF(SOME, 7f, 9f), 7f); + Asserts.assertEQ(anyTrueF(NONE, 7f, 9f), 9f); + } + + @Run(test = "allTrueF") + public void runAllTrueF() { + Asserts.assertEQ(allTrueF(ALL, 7f, 9f), 7f); + Asserts.assertEQ(allTrueF(SOME, 7f, 9f), 9f); + Asserts.assertEQ(allTrueF(NONE, 7f, 9f), 9f); + } + + @Run(test = "anyTrueD") + public void runAnyTrueD() { + Asserts.assertEQ(anyTrueD(ALL, 7d, 9d), 7d); + Asserts.assertEQ(anyTrueD(SOME, 7d, 9d), 7d); + Asserts.assertEQ(anyTrueD(NONE, 7d, 9d), 9d); + } + + @Run(test = "allTrueD") + public void runAllTrueD() { + Asserts.assertEQ(allTrueD(ALL, 7d, 9d), 7d); + Asserts.assertEQ(allTrueD(SOME, 7d, 9d), 9d); + Asserts.assertEQ(allTrueD(NONE, 7d, 9d), 9d); + } +} diff --git a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java index 1f4562defa53..4baf003c9221 100644 --- a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java +++ b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java @@ -30,6 +30,8 @@ * @requires vm.compMode != "Xint" & vm.flavor == "server" * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * & vm.opt.CompileThresholdScaling == null + * & vm.opt.CompileThreshold == null * @library /test/lib * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox diff --git a/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java b/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java index 15d4e150d906..c3ad5e903057 100644 --- a/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java +++ b/test/hotspot/jtreg/compiler/exceptions/OptimizeImplicitExceptions.java @@ -28,6 +28,7 @@ * * @requires vm.compiler2.enabled & vm.compMode != "Xcomp" * @requires vm.opt.DeoptimizeALot != true + * @requires vm.opt.CompileThresholdScaling == null & vm.opt.CompileThreshold == null * * @library /test/lib * @build jdk.test.whitebox.WhiteBox diff --git a/test/hotspot/jtreg/compiler/intrinsics/TestCheckIndex.java b/test/hotspot/jtreg/compiler/intrinsics/TestCheckIndex.java index d0c67fc7f151..5f0347ca4454 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/TestCheckIndex.java +++ b/test/hotspot/jtreg/compiler/intrinsics/TestCheckIndex.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Red Hat, Inc. All rights reserved. + * Copyright (c) 2020, 2026, 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 @@ -85,16 +85,16 @@ public static void main(String[] args) throws NoSuchMethodException { throw new RuntimeException("exception not thrown"); } catch (IndexOutOfBoundsException ioobe) { } - if (WHITE_BOX.isMethodCompiled(m1)) { - throw new RuntimeException("should have deoptimized"); + if (!WHITE_BOX.isMethodCompiled(m1)) { + throw new RuntimeException("should still be compiled"); } try { test2(0, -10); throw new RuntimeException("exception not thrown"); } catch (IndexOutOfBoundsException ioobe) { } - if (WHITE_BOX.isMethodCompiled(m2)) { - throw new RuntimeException("should have deoptimized"); + if (!WHITE_BOX.isMethodCompiled(m2)) { + throw new RuntimeException("should still be compiled"); } try { @@ -102,16 +102,16 @@ public static void main(String[] args) throws NoSuchMethodException { throw new RuntimeException("exception not thrown"); } catch (IndexOutOfBoundsException ioobe) { } - if (WHITE_BOX.isMethodCompiled(m3)) { - throw new RuntimeException("should have deoptimized"); + if (!WHITE_BOX.isMethodCompiled(m3)) { + throw new RuntimeException("should still be compiled"); } try { test4(42, 10); throw new RuntimeException("exception not thrown"); } catch (IndexOutOfBoundsException ioobe) { } - if (WHITE_BOX.isMethodCompiled(m4)) { - throw new RuntimeException("should have deoptimized"); + if (!WHITE_BOX.isMethodCompiled(m4)) { + throw new RuntimeException("should still be compiled"); } } diff --git a/test/hotspot/jtreg/compiler/intrinsics/TestPollutedCheckIndex.java b/test/hotspot/jtreg/compiler/intrinsics/TestPollutedCheckIndex.java new file mode 100644 index 000000000000..baa9fe9000f3 --- /dev/null +++ b/test/hotspot/jtreg/compiler/intrinsics/TestPollutedCheckIndex.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.intrinsics; + +import compiler.lib.ir_framework.*; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Objects; + +/* + * @test + * @bug 8394736 + * @summary When a call to Objects::checkIndex fails and hits the uncommon trap, the compiler + * should still intrinsify Preconditions::checkIndex at all other calls to + * Objects::checkIndex. + * @library /test/lib / + * @run driver ${test.main.class} + */ +public class TestPollutedCheckIndex { + private static final MemorySegment SEGMENT = Arena.global().allocate(Integer.BYTES); + + public static void main(String[] args) { + var framework = new TestFramework(); + framework.setDefaultWarmup(1); + // Ensure the stability of the tests + framework.addFlags("-XX:-CompactStrings", + "-XX:CompileCommand=dontinline,*::outOfBoundsCheckIndex", + "-XX:CompileCommand=inline,java.lang.String::*", + "-XX:CompileCommand=inline,java.lang.StringUTF16::*"); + framework.addScenarios(new Scenario(0)); + framework.addScenarios(new Scenario(1, "-XX:PerMethodTrapLimit=0")); + framework.start(); + } + + private static void polluteInt() { + try { + Objects.checkIndex(1, 0); + throw new RuntimeException(); + } catch (IndexOutOfBoundsException _) {} + } + + private static void polluteLong() { + try { + Objects.checkIndex(1L, 0L); + throw new RuntimeException(); + } catch (IndexOutOfBoundsException _) {} + } + + private static void polluteString() { + try { + // This trap pollutes String::checkIndex, preventing Preconditions::checkIndex from + // intrinsifying from there + int _ = "".codePointAt(1); + throw new RuntimeException(); + } catch (IndexOutOfBoundsException _) {} + } + + private static void polluteMemorySegment() { + try { + SEGMENT.set(ValueLayout.JAVA_INT_UNALIGNED, 4, 0); + throw new RuntimeException(); + } catch (IndexOutOfBoundsException _) {} + } + + @Test + @IR(counts = {IRNode.RANGE_CHECK_TRAP, "1"}) + private static int testInt(int idx, int limit) { + return Objects.checkIndex(idx, limit); + } + + @Test + @IR(counts = {IRNode.RANGE_CHECK_TRAP, "1"}) + private static long testLong(long idx, long limit) { + return Objects.checkIndex(idx, limit); + } + + @Test + @IR(counts = {IRNode.RANGE_CHECK_TRAP, "1"}) + private static char testString(String str, int idx) { + return str.charAt(idx); + } + + @Test + @IR(counts = {IRNode.RANGE_CHECK_TRAP, "1"}) + private static int testMemorySegment(MemorySegment ms, long offset) { + return ms.get(ValueLayout.JAVA_INT_UNALIGNED, offset); + } + + @Run(test = {"testInt", "testLong", "testString", "testMemorySegment"}) + public void run() { + for (int i = 0; i < 20000; i++) { + polluteInt(); + polluteLong(); + polluteString(); + polluteMemorySegment(); + } + + testInt(0, 1); + testLong(0, 1); + testString("a", 0); + testMemorySegment(SEGMENT, 0); + } +} diff --git a/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16ToFloatUnnormalized.java b/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16ToFloatUnnormalized.java index 075dd13fc583..7df6981ffd10 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16ToFloatUnnormalized.java +++ b/test/hotspot/jtreg/compiler/intrinsics/float16/TestFloat16ToFloatUnnormalized.java @@ -24,7 +24,7 @@ /* * @test - * @bug 8393488 + * @bug 8393488 8392738 * @summary Verify float16ToFloat ignores bits above the low 16 bits of its argument * @requires vm.compiler1.enabled & vm.compiler2.enabled * @run main/othervm -Xverify:all -Xint -XX:+UnlockDiagnosticVMOptions diff --git a/test/hotspot/jtreg/compiler/intrinsics/string/TestAntiDependency.java b/test/hotspot/jtreg/compiler/intrinsics/string/TestAntiDependency.java index 341396e56dd7..5fa8f448a461 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/string/TestAntiDependency.java +++ b/test/hotspot/jtreg/compiler/intrinsics/string/TestAntiDependency.java @@ -31,14 +31,14 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.Arrays; import jdk.test.lib.Asserts; /* * @test - * @bug 8373591 - * @summary Verify that StringLatin1::inflate, StringUTF16::compress, and - * StringCoding::implEncodeAsciiArray are scheduled properly + * @bug 8373591 8393492 + * @summary Verify that several memory access intrinsics are scheduled properly * @library /test/lib / * @modules java.base/java.lang:+open * @run driver ${test.main.class} @@ -70,7 +70,7 @@ public class TestAntiDependency { public static void main(String[] args) { var testFramework = new TestFramework(); testFramework.setDefaultWarmup(1); - testFramework.addFlags("--add-opens=java.base/java.lang=ALL-UNNAMED"); + testFramework.addFlags("--add-opens=java.base/java.lang=ALL-UNNAMED", "-XX:CompileCommand=inline,*::hashCode"); testFramework.start(); } @@ -92,6 +92,11 @@ static int testStringCompress() throws Throwable { return dst[0]; } + @Run(test = "testStringCompress") + public void runStringCompress() throws Throwable { + Asserts.assertEQ(0, testStringCompress()); + } + @Test static int testStringInflate() throws Throwable { char[] dst = new char[4]; @@ -107,6 +112,11 @@ static int testStringInflate() throws Throwable { return dst[0]; } + @Run(test = "testStringInflate") + public void runStringInflate() throws Throwable { + Asserts.assertEQ(0, testStringInflate()); + } + @Test static int testEncodeISO() throws Throwable { byte[] dst = new byte[4]; @@ -122,10 +132,44 @@ static int testEncodeISO() throws Throwable { return dst[0]; } - @Run(test = {"testStringCompress", "testStringInflate", "testEncodeISO"}) - public void run() throws Throwable { - Asserts.assertEQ(0, testStringCompress()); - Asserts.assertEQ(0, testStringInflate()); + @Run(test = "testEncodeISO") + public void runEncodeISO() throws Throwable { Asserts.assertEQ(0, testEncodeISO()); } + + @Test + static boolean testAryEq(byte[] a1, byte[] a2) { + // The second store must introduce no trap + a1[0] = 0; + + // The backend must have enough information to calculate the anti-dependencies of this node + boolean result = Arrays.equals(a1, a2); + a1[0] = 1; + return result; + } + + @Run(test = "testAryEq") + public void runAryEq() { + byte[] a1 = new byte[100]; + byte[] a2 = new byte[100]; + Asserts.assertEQ(true, testAryEq(a1, a2)); + } + + @Test + static int testVectorizedHashCode() { + int[] a = new int[100]; + consume(a, null); + + // The backend must have enough information to calculate the anti-dependencies of this node + int result = Arrays.hashCode(a); + a[0] = 1; + return result; + } + + @Run(test = "testVectorizedHashCode") + public void runVectorizedHashCode() { + int[] a = new int[100]; + int expected = Arrays.hashCode(a); + Asserts.assertEQ(expected, testVectorizedHashCode()); + } } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 87ad9f029f7c..cd22824605e9 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -490,6 +490,12 @@ public static void allocateArrayOfNodes(String irNodePlaceholder, String allocat superWordNodes(AND_REDUCTION_V, "AndReductionV"); } + public static final String ANY_LOOP = PREFIX + "ANY_LOOP" + POSTFIX; + static { + String regex = START + "(Counted|LongCounted|OuterStripMined)?Loop\\b" + MID + END; + fromBeforeCountedLoops(ANY_LOOP, regex); + } + public static final String CALL = PREFIX + "CALL" + POSTFIX; static { beforeMatchingNameRegex(CALL, "Call.*Java"); @@ -1705,6 +1711,16 @@ public static void anyLoadOfNodes(String irNodePlaceholder, String fieldHolder) superWordNodes(UMAX_REDUCTION_V, "UMaxReductionV"); } + public static final String NEG_F = PREFIX + "NEG_F" + POSTFIX; + static { + beforeMatchingNameRegex(NEG_F, "NegF"); + } + + public static final String NEG_D = PREFIX + "NEG_D" + POSTFIX; + static { + beforeMatchingNameRegex(NEG_D, "NegD"); + } + public static final String NEG_VF = VECTOR_PREFIX + "NEG_VF" + POSTFIX; static { vectorNode(NEG_VF, "NegVF", TYPE_FLOAT); @@ -2487,6 +2503,18 @@ public static void anyStoreOfNodes(String irNodePlaceholder, String fieldHolder) machOnlyNameRegex(RISCV_LOAD_N2P_UNSCALED, "loadN2P_unscaled"); } + // Conditional move whose condition is a VectorTest. One rule per value type. + // Exclude the separate _negate rules that materialize boolean results. + public static final String RISCV_CMOVE_VTEST_ANYTRUE = PREFIX + "RISCV_CMOVE_VTEST_ANYTRUE" + POSTFIX; + static { + machOnlyNameRegex(RISCV_CMOVE_VTEST_ANYTRUE, "cmov[ILFD]_vtest_anytrue\\b"); + } + + public static final String RISCV_CMOVE_VTEST_ALLTRUE = PREFIX + "RISCV_CMOVE_VTEST_ALLTRUE" + POSTFIX; + static { + machOnlyNameRegex(RISCV_CMOVE_VTEST_ALLTRUE, "cmov[ILFD]_vtest_alltrue\\b"); + } + public static final String RISCV_VAND_NOTI_VX_MASKED = PREFIX + "RISCV_VAND_NOTI_VX_MASKED" + POSTFIX; static { machOnlyNameRegex(RISCV_VAND_NOTI_VX_MASKED, "vand_notI_vx_masked"); diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md index 5560869d80f1..77b11277b3c1 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md @@ -60,6 +60,11 @@ A custom run test gives full control over the invocation of the `@Test` annotate More information on checked tests with a precise definition can be found in the Javadocs of [Run](./Run.java). Concrete examples on how to specify a custom run test can be found in [CustomRunTestsExample](../../../testlibrary_tests/ir_framework/examples/CustomRunTestExample.java). +#### Skipping a Test Completely When Encountering Problems +When a test execution is failing due to a VM problem (crash, wrong execution etc.) without having an immediate fix, it is preferred to specify [@Skip](./Skip.java) at the `@Test`-method to disable the test over commenting the test out. + +Specifying `@Skip` allows the IR Framework to still execute the skipped test on command by using the property flag `-DIgnoreSkip=true`. This can be useful when trying to quickly verify if a disabled test is still failing or not. + ### 2.2 IR Verification The main feature of this framework is to perform a simple but yet powerful regex-based C2 IR matching on the output of `-XX:+PrintIdeal`, `-XX:+PrintOptoAssembly` and/or on specific compile phases emitted by the compile command `-XX:CompileCommand=PrintIdealPhase` which supports the same set of compile phases as the Ideal Graph Visualizer (IGV). @@ -187,6 +192,7 @@ The framework provides various stress and debug flags. They should mainly be use - `-DWaitForCompilationTimeout=20`: Change the default waiting time (default: 10s) for a compilation of a `@Test` annotated method with compilation level [WAIT\_FOR\_COMPILATION](./CompLevel.java). - `-DIgnoreCompilerControls=true`: Ignore all compiler controls applied in the framework. This includes any compiler control annotations (`@DontCompile`, `@DontInline`, `@ForceCompile`, `@ForceInline`, `@ForceCompileStaticInitializer`), the exclusion of `@Run` and `@Check` methods from compilation, and the directive to not inline `@Test` annotated methods. - `-DPreferCommandLineFlags=true`: Prefer flags set via the command line over flags specified by the tests. +- `-DIgnoreSkip=true`: Additionally run all skipped tests (i.e. tests with a `@Skip` annotation). ## 3. Test Framework Execution This section gives an overview of how the framework is executing a JTreg test that calls the framework from within its `main()` method. diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java b/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java new file mode 100644 index 000000000000..fefd2ab2f214 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/Skip.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.ir_framework; + +import compiler.lib.ir_framework.shared.TestFormatException; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This annotation skips the execution of a {@link Test @Test}-annotated method completely. This is useful when the + * {@link Test @Test}-method causes the VM to misbehave (crash, wrong execution etc.) with no immediately available fix. + * + *

        + * It is preferable to use {@link Skip @Skip} over commenting a test out because one can still execute the skipped + * test by passing the property flag {@code -DIgnoreSkip=true} (see {@link ir_framework.examples.SkipTestExample}). + * This can be useful when trying to quickly verify if a disabled test is still failing or not. + * + *

        + * The {@link Skip @Skip} annotation can only be used at {@link Test @Test}-annotated methods. If the + * {@link Test @Test}-method is part of a custom run test, then the {@link Run @Run}-method is also not executed. + * A special case is a {@link Run @Run}-annotated method with multiple associated {@link Test @Test}-methods. In this + * case, you must either skip all or none to limit the impact. Partially skipping tests results in a + * {@link TestFormatException}. When only needing to disable one of the multiple associated tests, consider moving it + * to a separate custom run test with a 1-to-1 {@link Run @Run}-{@link Test @Test}-relation for minimum impact. + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Skip { +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java index 2db7975b3e2e..94d1b039f212 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java @@ -122,6 +122,7 @@ public class ApplicableIRRulesPrinter { "asimdhp", // RISCV64 "rvv", + "zbb", "zbkb", "zfh", "zvbb", diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java index 81143b6c2540..f72a3c3f9265 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/DeclaredTest.java @@ -37,16 +37,18 @@ public class DeclaredTest { private final CompLevel compLevel; private final int warmupIterations; private final boolean allowNotCompilable; + private final boolean hasSkipAnno; private Method attachedMethod; public DeclaredTest(Method testMethod, ArgumentsProvider argumentsProvider, CompLevel compLevel, int warmupIterations, - boolean allowNotCompilable) { + boolean hasSkipAnno, boolean allowNotCompilable) { // Make sure we can also call non-public or public methods in package private classes testMethod.setAccessible(true); this.testMethod = testMethod; this.argumentsProvider = argumentsProvider; this.compLevel = compLevel; this.warmupIterations = warmupIterations; + this.hasSkipAnno = hasSkipAnno; this.allowNotCompilable = allowNotCompilable; this.attachedMethod = null; } @@ -71,6 +73,10 @@ public Object[] getArguments(Object invocationTarget, int invocationCounter) { return argumentsProvider.getArguments(invocationTarget, invocationCounter); } + public boolean hasSkipAnno() { + return hasSkipAnno; + } + public void setAttachedMethod(Method m) { attachedMethod = m; } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java index be189cd6f245..32e6ae5faa32 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java @@ -97,6 +97,8 @@ assertions from main() of your test! static final boolean EXCLUDE_RANDOM = Boolean.getBoolean("ExcludeRandom"); private static final String TEST_LIST = SystemProperty.getTestList(); private static final String EXCLUDE_LIST = SystemProperty.getExcludeList(); + // We ignore @Skip when running with -DIgnoreSkip=true + private static final boolean IGNORE_SKIP = Boolean.getBoolean("IgnoreSkip"); private static final boolean DUMP_REPLAY = Boolean.getBoolean("DumpReplay"); private static final boolean GC_AFTER = Boolean.getBoolean("GCAfter"); private static final boolean SHUFFLE_TESTS = Boolean.parseBoolean(System.getProperty("ShuffleTests", "true")); @@ -312,11 +314,13 @@ private void addBaseTests() { * A test is excluded from execution if: * - -DTest does not list the method * - -DExclude lists the method + * - The method specifies a (temporary) @Skip annotation. */ private boolean shouldExcludeTest(Method testMethod) { String testName = testMethod.getName(); return isNotOnTestList(testName) || - isOnExcludeList(testName); + isOnExcludeList(testName) || + shouldSkip(testMethod); } private boolean isNotOnTestList(String testName) { @@ -327,6 +331,14 @@ private boolean isOnExcludeList(String testName) { return excludeList.contains(testName); } + private boolean shouldSkip(Method method) { + return !IGNORE_SKIP && hasSkipAnnotation(method); + } + + private boolean hasSkipAnnotation(Method method) { + return getAnnotation(method, Skip.class) != null; + } + /** * Generate replay compilation files. */ @@ -560,6 +572,8 @@ private void setupDeclaredTests() { "Found @IR annotation on non-@Test method " + m); TestFormat.checkNoThrow(!m.isAnnotationPresent(Warmup.class) || getAnnotation(m, Run.class) != null, "Found @Warmup annotation on non-@Test or non-@Run method " + m); + TestFormat.checkNoThrow(!m.isAnnotationPresent(Skip.class) , + "Found @Skip annotation on non-@Test method " + m); } } catch (TestFormatException e) { // Failure logged. Continue and report later. @@ -582,16 +596,22 @@ private void addDeclaredTest(Method m) { // Don't inline test methods by default. Do not apply this when -DIgnoreCompilerControls=true is set. WHITE_BOX.testSetDontInlineMethod(m, true); } + CompLevel compLevel = restrictCompLevel(testAnno.compLevel()); if (FLIP_C1_C2) { compLevel = restrictCompLevel(compLevel.flipCompLevel()); } + if (EXCLUDE_RANDOM) { compLevel = compLevel.excludeCompilationRandomly(m); } + boolean allowNotCompilable = testAnno.allowNotCompilable() || ALLOW_METHOD_NOT_COMPILABLE; ArgumentsProvider argumentsProvider = ArgumentsProviderBuilder.build(m, setupMethodMap); - DeclaredTest test = new DeclaredTest(m, argumentsProvider, compLevel, warmupIterations, allowNotCompilable); + Skip skip = getAnnotation(m, Skip.class); + boolean hasSkip = skip != null; + DeclaredTest test = new DeclaredTest(m, argumentsProvider, compLevel, warmupIterations, + hasSkip, allowNotCompilable); declaredTests.put(m, test); testMethodMap.put(m.getName(), m); } @@ -762,9 +782,13 @@ private void addCustomRunTest(Method m, Run runAnno) { // Logged, continue. } } - if (tests.isEmpty()) { - return; // There was a format violation. Return. + + if (tests.size() != runAnno.test().length) { + // At least one test is invalid. Return and report the already recorded format violation(s) later. + return; } + + checkNoneOrAllAssociatedTestsSkipped(m, tests); dontCompileAndDontInlineMethod(m); CustomRunTest customRunTest = new CustomRunTest(m, getAnnotation(m, Warmup.class), runAnno, tests, shouldExcludeTest); allTests.add(customRunTest); @@ -809,6 +833,32 @@ private void checkRunMethod(Method m, Run runAnno) { "Cannot set @Warmup at @Run method " + m + " when used with RunMode.STANDALONE. The @Run method is only invoked once."); } + private void checkNoneOrAllAssociatedTestsSkipped(Method runMethod, List tests) { + List testMethodsWithoutSkip = testMethodsWithoutSkipAnnotation(tests); + if (testMethodsWithoutSkip.isEmpty() || + testMethodsWithoutSkip.size() == tests.size()) { + // All with @Skip or none. + return; + } + + // Skipping some tests but not all is a format violation. + reportPartiallySkippingTests(runMethod, testMethodsWithoutSkip); + } + + private List testMethodsWithoutSkipAnnotation(List tests) { + return tests.stream().filter(test -> !test.hasSkipAnno()).map(DeclaredTest::getTestMethod).toList(); + } + + private void reportPartiallySkippingTests(Method runMethod, List testMethodsWithoutSkip) { + StringBuilder builder = new StringBuilder(); + builder.append("@Run-methods with multiple tests can only either skip none or all methods with @Skip:") + .append(System.lineSeparator()) + .append(" - @Run-method: ").append(runMethod); + testMethodsWithoutSkip.forEach(method -> builder.append(System.lineSeparator()) + .append(" - @Test without @Skip: ").append(method)); + TestFormat.fail(builder.toString()); + } + private static T getAnnotation(AnnotatedElement element, Class c) { T[] annos = element.getAnnotationsByType(c); TestFormat.check(annos.length < 2, element + " has duplicated annotations"); diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestLoopNestTooManyTraps.java b/test/hotspot/jtreg/compiler/longcountedloops/TestLoopNestTooManyTraps.java index c2008a2cccb0..8a1f11d213b8 100644 --- a/test/hotspot/jtreg/compiler/longcountedloops/TestLoopNestTooManyTraps.java +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestLoopNestTooManyTraps.java @@ -60,7 +60,7 @@ public static void main(String[] args) throws NoSuchMethodException { test1(10000, 1000); } catch (IndexOutOfBoundsException ioobe) { } - if (j <= 1) { + if (j == 0) { if (WHITE_BOX.isMethodCompiled(methodTest1)) { throw new RuntimeException("test1 should have deoptimized at iteration " + j); } diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java index 599e95bd6aba..d7b4278ede90 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java @@ -118,8 +118,7 @@ public static void main(String[] args) { switch (args[0]) { case "P0" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=0"); } case "P1" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=1"); } - // Note: increasing the node count limit also helps in some cases. - case "P2" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=2", "-XX:LoopUnrollLimit=1000"); } + case "P2" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=2"); } default -> { throw new RuntimeException("Test argument not recognized: " + args[0]); } }; framework.start(); @@ -2116,15 +2115,7 @@ private static long longAddBig() { IRNode.MUL_REDUCTION_VL, "> 0", IRNode.MUL_VL, "> 0"}, applyIfCPUFeatureOr = {"avx512dq", "true", "sve", "true"}, - applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0", - "LoopUnrollLimit", ">= 1000"}) - @IR(failOn = IRNode.LOAD_VECTOR_L, - applyIfCPUFeature = {"avx512dq", "true"}, - applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0", - "LoopUnrollLimit", "< 1000"}) - // Increasing the body limit seems to help. Filed for investigation: JDK-8370685 - // If you can eliminate this exception for LoopUnrollLimit, please remove - // the flag completely from the test, also the "addFlags" at the top. + applyIf = {"AutoVectorizationOverrideProfitability", "> 0"}) @IR(failOn = IRNode.LOAD_VECTOR_L, applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) // MulVL is not implemented on NEON, so we also not have the reduction. diff --git a/test/hotspot/jtreg/compiler/print/CompileCommandPrintIdealNodeCount.java b/test/hotspot/jtreg/compiler/print/CompileCommandPrintIdealNodeCount.java new file mode 100644 index 000000000000..3d0e550f2bcf --- /dev/null +++ b/test/hotspot/jtreg/compiler/print/CompileCommandPrintIdealNodeCount.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8393716 + * @summary Checks that -XX:CompileCommand=PrintIdealNodeCount,... works + * @requires vm.debug & vm.compiler2.enabled & vm.flagless + * @library /test/lib + * @run driver ${test.main.class} + */ + +package compiler.print; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class CompileCommandPrintIdealNodeCount { + private static final String PARSE_NODE_COUNTS = "phase name='parse' nodes='[0-9]+' live='[0-9]+' live_graph_walk='[0-9]+'"; + + public static void main(String[] args) throws Exception { + // No PrintIdealNodeCount prints nothing + List testOptions = new ArrayList<>(); + test(testOptions) + .shouldHaveExitValue(0) + .shouldNotMatch(PARSE_NODE_COUNTS); + + testOptions = new ArrayList<>(); + testOptions.add("-XX:CompileCommand=PrintIdealNodeCount," + TestMain.class.getName() + "::method1"); + OutputAnalyzer result = test(testOptions); + result.shouldHaveExitValue(0); + int matches = result.matchersForAllMatchingLinesStdout(Pattern.compile(PARSE_NODE_COUNTS)).size(); + int expected_matches = 1; + if (matches != expected_matches) { + throw new RuntimeException("Unexpected number of matching lines: " + matches + ", expected " + expected_matches); + } + + testOptions = new ArrayList<>(); + testOptions.add("-XX:CompileCommand=PrintIdealNodeCount," + TestMain.class.getName() + "::*"); + result = test(testOptions); + result.shouldHaveExitValue(0); + matches = result.matchersForAllMatchingLinesStdout(Pattern.compile(PARSE_NODE_COUNTS)).size(); + expected_matches = 2; + if (matches != expected_matches) { + throw new RuntimeException("Unexpected number of matching lines: " + matches + ", expected " + expected_matches); + } + } + + private static OutputAnalyzer test(List testOptions) throws Exception { + List options = new ArrayList<>(); + options.add("-Xcomp"); + options.add("-Xbatch"); + options.add("-XX:-TieredCompilation"); + options.add("-XX:CompileCommand=compileonly," + TestMain.class.getName() + "::method*"); + options.addAll(testOptions); + options.add(TestMain.class.getName()); + return ProcessTools.executeLimitedTestJava(options); + } + + public static class TestMain { + public static void main(String[] args) { + method1(); + method2(); + } + + public static int method1() { + return 1; + } + + public static int method2() { + return 2; + } + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java b/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java new file mode 100644 index 000000000000..72b4cc7e5e97 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestIndexMatching.java @@ -0,0 +1,779 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.rangechecks; + +import compiler.lib.ir_framework.*; +import java.util.Objects; + +/** + * @test + * @bug 8382378 + * @summary Tests that valid index expressions in range checks are matched for + * range check optimizations, and that invalid expressions are + * rejected. These tests should be kept updated with the grammar-based + * specification provided as a comment on + * PhaseIdealLoop::is_scaled_iv_plus_offset(). + * @library /test/lib / + * @run driver ${test.main.class} + */ + +public class TestIndexMatching { + + static int intVar0 = 0; + static int intVar3 = 3; + static int intVar100 = 100; + static int intVar1000 = 1000; + static int intVar10000 = 10000; + + static long longVar0 = 0L; + static long longVar3 = 3L; + static long longVar100 = 100L; + static long longVar1000 = 1000L; + static long longVar10000 = 10000L; + + public static void main(String[] args) { + TestFramework.run(); + } + + // Positive tests: expect the index expressions to be matched and range + // check optimizations to trigger. Use C2-opaque range check limits to + // increase the likelihood that loop elimination is indeed caused by range + // check optimizations and not accidentally by other transformations. + + // Int indices using int induction variables + + // iv + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIV() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv, intVar10000); + } + } + + // AddI(iv, e) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithAddedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv + intVar0, intVar10000); + } + } + + // SubI(iv, e) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithSubtractedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv - intVar0, intVar10000); + } + } + + // SubI(e, iv) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVSubtractedFromVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(intVar1000 - iv, intVar10000); + } + } + + // AddI(iv, ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithAddedConstOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv + 7, intVar10000); + } + } + + // AddI((AddI(iv, e)), ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithAddedNestedOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv + intVar0) + 7, intVar10000); + } + } + + // MulI(iv, ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv * 11, intVar10000); + } + } + + // AddI(MulI(iv, ConI), e) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithMulScalingAndAddedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv * 11) + intVar0, intVar10000); + } + } + + // AddI(AddI(MulI(iv, ConI), e), ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithMulScalingAndAddedNestedOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((iv * 11) + intVar0) + 7, intVar10000); + } + } + + // LShiftI(iv, ConI) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv << 2, intVar10000); + } + } + + // SubI(0, MulI(iv, ConI)) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithNegatedMulScaling() { + for (int iv = -intVar100; iv <= 0; iv++) { + Objects.checkIndex(0 - (iv * 11), intVar10000); + } + } + + // AddI(MulI(iv, ConI), MulI(iv, ConI)) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithAddOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv * 3) + (iv * 7), intVar10000); + } + } + + // SubI(LShiftI(iv, ConI), MulI(iv, ConI)) + @Test + @IR(failOn = {IRNode.ANY_LOOP}) + static void testIntIndexIntIVWithSubOfShiftAndMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv << 4) - (iv * 13), intVar10000); + } + } + + // Long indices using long induction variables + + // iv + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIV() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv, longVar10000); + } + } + + // AddL(iv, e) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithAddedVarOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv + longVar0, longVar10000); + } + } + + // SubL(iv, e) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithSubtractedVarOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv - longVar0, longVar10000); + } + } + + // SubL(e, iv) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVSubtractedFromVarOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(longVar1000 - iv, longVar10000); + } + } + + // AddL(iv, ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithAddedConstOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv + 1L, longVar10000); + } + } + + // AddL((AddL(iv, e)), ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithAddedNestedOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex((iv + longVar0) + 7L, longVar10000); + } + } + + // MulL(iv, ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithMulScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv * 11L, longVar10000); + } + } + + // AddL(MulL(iv, ConL), e) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithMulScalingAndAddedVarOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex((iv * 11L) + longVar0, longVar10000); + } + } + + // AddL(AddL(MulL(iv, ConL), e), ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithMulScalingAndAddedNestedOffset() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(((iv * 11L) + longVar0) + 7L, longVar10000); + } + } + + // LShiftL(iv, ConI) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithShiftScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv << 2, longVar10000); + } + } + + // SubL(0L, MulL(iv, ConL)) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexLongIVWithNegatedMulScaling() { + for (long iv = -longVar100; iv <= 0; iv++) { + Objects.checkIndex(0L - (iv * 11L), longVar10000); + } + } + + // Long indices using int induction variables + + // ConvI2L(iv) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIV() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv, longVar10000); + } + } + + // AddL(ConvI2L(iv), e) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithAddedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv + longVar0, longVar10000); + } + } + + // AddL(ConvI2L(iv), ConL) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithAddedConstOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv + 1L, longVar10000); + } + } + + // MulL(ConvI2L(iv), ConL) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv * 11L, longVar10000); + } + } + + // AddL(MulL(ConvI2L(iv), ConL), e) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithMulScalingAndAddedVarOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)iv * 11L) + longVar0, longVar10000); + } + } + + // LShiftL(ConvI2L(iv), ConI) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv << 2, longVar10000); + } + } + + // SubL(0L, MulL(ConvI2L(iv), ConL)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithNegatedMulScaling() { + for (int iv = -intVar100; iv <= 0; iv++) { + Objects.checkIndex(0L - ((long)iv * 11L), longVar10000); + } + } + + // ConvI2L(MulI(iv, ConI)) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv * 1), longVar10000); + } + } + + // ConvI2L(LShiftI(iv, ConI)) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv << 0), longVar10000); + } + } + + // ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI))) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortAddOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * 3) + (iv * -2)), longVar10000); + } + } + + // ConvI2L(SubI(0, MulI(iv, ConI))) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortNegatedSubScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(0 - (iv * -1)), longVar10000); + } + } + + // ConvI2L(SubI(MulI(iv, ConI), MulI(iv, ConI))) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortSubOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * 3) - (iv * 2)), longVar10000); + } + } + + // AddL(MulL(ConvI2L(iv), ConL), MulL(ConvI2L(iv), ConL)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithAddOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)iv * 3L) + ((long)iv * 7L), longVar10000); + } + } + + // SubL(LShiftL(ConvI2L(iv), ConI), ConvI2L(iv)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithSubOfShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)iv << 3) - iv, longVar10000); + } + } + + // ConvI2L(AddI(MulI(iv, ConI), LShiftI(iv, ConI))) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortAddOfMulAndShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * -15) + (iv << 4)), longVar10000); + } + } + + // AddL(AddL(ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI))), e), ConL) + // Range check optimizations still trigger because the short scaling is + // optimized away during IGVN. + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(failOn = {IRNode.ANY_LOOP}) + static void testLongIndexIntIVWithShortAddOfMulsScalingAndAddedNestedOffset() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)((iv * 3) + (iv * -2)) + longVar0) + 7L, longVar10000); + } + } + + // Negative tests: expect the index expressions to be rejected and the loop + // to not be eliminated. + + // Int indices using int induction variables + + // MulI(iv, LoadI(..)) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithVarMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv * intVar3, 10000); + } + } + + // LShiftI(iv, LoadI(..)) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithVarShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv << intVar0, 10000); + } + } + + // MulI(AddI(iv, LoadI(..)), ConI) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithNonTrivialMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv + intVar0) * 11, 10000); + } + } + + // LShiftI(AddI(iv, LoadI(..)), LoadI(..)) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithNonTrivialShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((iv + intVar0) << intVar0, 10000); + } + } + + // RShiftI(iv, ConI) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithUnsupportedRightShift() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv >> 1, 10000); + } + } + + // OrI(iv, ConI) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithUnsupportedOr() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv | 1024, 10000); + } + } + + // XorI(iv, ConI) + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithUnsupportedXor() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(iv ^ 1024, 10000); + } + } + + // SubI(0, LShiftI(iv, ConI)) where scale(LShiftI(..)) == min_jint + @Test + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testIntIndexIntIVWithNegatedMinJIntShiftScaling() { + for (int iv = 0; iv <= intVar100; iv += 2) { + Objects.checkIndex(0 - (iv << 31), 10000); + } + } + + // Long indices using long induction variables + + // MulL(iv, LoadL(..)) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithVarMulScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv * longVar3, 10000L); + } + } + + // LShiftL(iv, LoadI(..)) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithVarShiftScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv << intVar0, 10000L); + } + } + + // MulL(AddL(iv, LoadL(..)), ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithNonTrivialMulScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex((iv + longVar0) * 11L, 10000L); + } + } + + // LShiftL(AddL(iv, LoadL(..)), LoadI(..)) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithNonTrivialShiftScaling() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex((iv + longVar0) << intVar0, 10000L); + } + } + + // RShiftL(iv, ConI) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithUnsupportedRightShift() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv >> 1, 10000L); + } + } + + // OrL(iv, ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithUnsupportedOr() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv | 1024L, 10000L); + } + } + + // XorL(iv, ConL) + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithUnsupportedXor() { + for (long iv = 0L; iv <= longVar100; iv++) { + Objects.checkIndex(iv ^ 1024L, 10000L); + } + } + + // SubL(0L, LShiftL(iv, ConI)) where scale(LShiftL(..)) == min_jlong + @Test + @IR(failOn = {IRNode.CONV_I2L}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexLongIVWithNegatedMinJLongShiftScaling() { + for (long iv = 0L; iv <= longVar100; iv += 2L) { + Objects.checkIndex(0L - (iv << 63), 10000L); + } + } + + // Long indices using int induction variables + + // ConvI2L(MulI(iv, ConI)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv * 11), 10000L); + } + } + + // ConvI2L(LShiftI(iv, ConI)) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv << 2), 10000L); + } + } + + // ConvI2L(SubI(0, MulI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortNegatedSubScaling() { + for (int iv = -100; iv <= 0; iv++) { + Objects.checkIndex((long)(0 - (iv * 3)), + // Using a variable range check length is + // required here for the index expression not to + // be optimized into something else that matches. + longVar10000); + } + } + + // ConvI2L(SubI(0, LShiftI(iv, ConI))) where scale(LShiftI(..)) == min_jint + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithShortNegatedSubMinJIntShiftScaling() { + for (int iv = 0; iv <= intVar100; iv += 2) { + Objects.checkIndex((long)(0 - (iv << 31)), 10000L); + } + } + + // ConvI2L(MulI(iv, LoadI(..))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithShortVarMulScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv * intVar3), 10000L); + } + } + + // ConvI2L(LShiftI(iv, LoadI(..))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithShortVarShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)(iv << intVar3), 10000L); + } + } + + // MulL(AddL(ConvI2L(iv), LoadL(..)), ConL) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithMulOfAddScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex(((long)iv + longVar0) * 11L, 10000L); + } + } + + // RShiftL(ConvI2L(iv), ConI) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithUnsupportedRightShift() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)iv >> 1, 10000L); + } + } + + // ConvI2L(AddI(MulI(iv, ConI), MulI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortAddOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * 3) + (iv * 5)), 10000L); + } + } + + // ConvI2L(AddI(MulI(iv, ConI), LShiftI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortAddOfMulAndShiftScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * -15) + (iv << 5)), 10000L); + } + } + + // ConvI2L(SubI(MulI(iv, ConI), MulI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortSubOfMulsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv * 7) - (iv * 2)), 10000L); + } + } + + // ConvI2L(SubI(LShiftI(iv, ConI), LShiftI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithShortSubOfShiftsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv << 33) - (iv << 0)), longVar10000); + } + } + + // ConvI2L(SubI(LShiftI(iv, ConI), LShiftI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortSubOfShiftsScaling() { + for (int iv = 0; iv <= intVar100; iv++) { + Objects.checkIndex((long)((iv << 5) - (iv << 1)), 10000L); + } + } + + // ConvI2L(SubI(0, LShiftI(iv, ConI))) + @Test + @IR(counts = {IRNode.CONV_I2L, ">0"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.COUNTED_LOOP, ">0"}) + static void testLongIndexIntIVWithNonUnitShortNegatedSubShiftScaling() { + for (int iv = -100; iv <= 0; iv++) { + Objects.checkIndex((long)(0 - (iv << 2)), + // Using a variable range check length is + // required here for the index expression not to + // be optimized into something else that matches. + longVar10000); + } + } +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java index 1c4fbb8cc9c0..6f8233bc70aa 100644 --- a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java +++ b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java @@ -95,7 +95,6 @@ private static void test(String method, long start, long stop, long length, long throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class); m.invoke(null, start, stop, length, offset); // run once so all classes are loaded @@ -112,7 +111,6 @@ private static void test(String method, long start, long stop, long length, long throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class); m.invoke(null, start, stop, length, offset); // run once so all classes are loaded @@ -128,7 +126,6 @@ private static void test(String method, long start, long stop, long length, long throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } private static void testOverflow(String method, long start, long stop, long length, long offset0, long offset1) throws Exception { @@ -146,7 +143,6 @@ private static void testOverflow(String method, long start, long stop, long leng throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } private static void testConditional(String method, long start, long stop, long length, long offset0, long offset1, long start1, long stop1) throws Exception { @@ -167,7 +163,6 @@ private static void testConditional(String method, long start, long stop, long l throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } if (stop1 != stop) { @@ -185,7 +180,6 @@ private static void testConditional(String method, long start, long stop, long l throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } m = newClassLoader().loadClass("TestLongRangeCheck").getDeclaredMethod(method, long.class, long.class, long.class, long.class, long.class, long.class); @@ -328,7 +322,6 @@ public static void main(String[] args) throws Exception { throw new RuntimeException("unexpected exception"); } } - assertIsNotCompiled(m); } } diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java new file mode 100644 index 000000000000..f170929a2e01 --- /dev/null +++ b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheckWithZeroScaling.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.rangechecks; + +import java.util.Objects; + +/** + * @test + * @bug 8382378 + * @summary Test that C2 can handle long range check indices scaled by zero. + * @run main ${test.main.class} + * @run main/othervm -Xcomp -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} + */ + +public class TestLongRangeCheckWithZeroScaling { + + static void testCheckCancellingSubtractionOfScaledIndices() { + for (int i = 0; i < 100; i++) { + long index = ((long)(i << 10)) - (((long)i) << 10); + Objects.checkIndex(index, 100); + } + } + + public static void main(String[] args) { + Objects.checkIndex(0, 1); // Ensure java.util.Objects is loaded. + for (int i = 0; i < 10_000; i++) { + testCheckCancellingSubtractionOfScaledIndices(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/types/TestTryExactAbstractKlass.java b/test/hotspot/jtreg/compiler/types/TestTryExactAbstractKlass.java new file mode 100644 index 000000000000..a9b7bbbe94f1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/types/TestTryExactAbstractKlass.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.types; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8394225 + * @summary Multiple cases where a TypeInstPtr is constructed with an abstract instance_klass and + * klass_is_exact. + * @library /test/lib / + * @enablePreview + * @run driver ${test.main.class} + */ +public class TestTryExactAbstractKlass { + private static abstract value class A {} + + public static void main(String[] args) { + var framework = new TestFramework(); + framework.setDefaultWarmup(1); + framework.addFlags("--enable-preview"); + framework.start(); + } + + @Test + @IR(counts = {IRNode.IF, "2"}) + private static A testMethodSignature(A[] array) { + // Should know that A[] must not be flat + return array[0]; + } + + @Run(test = "testMethodSignature") + public void runMethodSignature() { + Asserts.assertNull(testMethodSignature(new A[1])); + } + + @Test + private static A testFromKlass(Object o) { + return (A) o; + } + + @Run(test = "testFromKlass") + public void runFromKlass() { + Asserts.assertNull(testFromKlass(null)); + } + + @Test + private static A testNewInstance(A obj) throws ReflectiveOperationException { + if (obj == null) { + return null; + } + + return obj.getClass().getConstructor().newInstance(); + } + + @Run(test = "testNewInstance") + public void runNewInstance() throws ReflectiveOperationException { + Asserts.assertNull(testNewInstance(null)); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java index 0c8c449eaa07..17d7eb0fa985 100644 --- a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestBasicFunctionality.java @@ -377,7 +377,7 @@ public void test11_verifier() { } // Test loop with uncommon trap referencing a value object - @Test + @Test(allowNotCompilable = true) @IR(applyIf = {"UseArrayFlattening", "true"}, failOn = LOAD_OF_ANY_KLASS, counts = {SCOPE_OBJECT, ">= 1"}) @@ -405,10 +405,13 @@ public long test12(boolean b) { @Run(test = "test12") public void test12_verifier(RunInfo info) { - // Disable OSR compilation prevents the method from getting recompiled because the IR rules - // expect all loads moved into the uncommon trap, which is not the case when the method get - // recompiled and the path that was unreached before is now compiled + // IR rules expect all loads moved into the uncommon trap, which is not the case when the method get + // recompiled and the path that was unreached before is now compiled. To avoid this, we need to block + // stray OSR compilations and recompiles after warmup. WhiteBox.getWhiteBox().makeMethodNotCompilable(info.getTest(), CompLevel.C2.getValue(), true); + if (info.isTestC2Compiled()) { + WhiteBox.getWhiteBox().makeMethodNotCompilable(info.getTest(), CompLevel.C2.getValue(), false); + } long result = test12(info.isWarmUp()); Asserts.assertEQ(result, info.isWarmUp() ? rL + (1000 * rI) : ((Math.abs(rI) % 10) + 1) * hash()); } diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyValueFieldNullChecks.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyValueFieldNullChecks.java new file mode 100644 index 000000000000..cb19a26ee1ff --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyValueFieldNullChecks.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8392533 8393322 + * @summary Test null checks for field accesses with empty value classes. + * @enablePreview + * @requires vm.compiler1.enabled + * @requires (vm.opt.PreloadClasses == null | vm.opt.PreloadClasses == "true") + * @library /test/lib + * @modules java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} + * @run main/othervm -XX:TieredStopAtLevel=1 ${test.main.class} + * @run main/othervm -XX:TieredStopAtLevel=1 + * -XX:+IgnoreUnrecognizedVMOptions -XX:-EliminateNullChecks + * ${test.main.class} + */ + +package compiler.valhalla.valuetypes; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.test.lib.Asserts; + +public class TestEmptyValueFieldNullChecks { + static boolean initialized = false; + + static value class Empty { } + + static value class UninitializedEmpty { + static { + initialized = true; + } + } + + static class UninitializedHolder { + @NullRestricted + UninitializedEmpty empty; + int value; + + UninitializedHolder() { + empty = new UninitializedEmpty(); + super(); + } + } + + // Recursively empty value class (only contains null marker) + static value class Holder1 { + Empty empty; + + Holder1() { + empty = new Empty(); + super(); + } + } + + // Same as Holder1 but with null-restricted empty field + static value class Holder2 { + @NullRestricted + Empty empty; + + Holder2() { + empty = new Empty(); + super(); + } + } + + Empty empty; + + @NullRestricted + Empty emptyNullFree; + + Holder1 holder1; + + Holder2 holder2; + + @NullRestricted + Holder1 holder1NullFree; + + @NullRestricted + Holder2 holder2NullFree; + + TestEmptyValueFieldNullChecks() { + empty = new Empty(); + emptyNullFree = new Empty(); + holder1 = new Holder1(); + holder2 = new Holder2(); + holder1NullFree = new Holder1(); + holder2NullFree = new Holder2(); + super(); + } + + // A null holder must throw before buffering initializes its empty field class. + static Object testUninitializedEmptyLoad(UninitializedHolder outerHolder) { + Object empty = outerHolder.empty; + // This later access must not absorb the explicit null check across the buffer allocation. + return outerHolder.value == 0 ? empty : null; + } + + // An empty nullable store only updates its null marker with Unsafe and must null-check its holder. + static void testEmptyStore(TestEmptyValueFieldNullChecks outerHolder, Empty value) { + outerHolder.empty = value; + } + + // An empty null-free store emits no access but must null-check both operands. + static void testEmptyNullFreeStore(TestEmptyValueFieldNullChecks outerHolder, Empty value) { + outerHolder.emptyNullFree = value; + } + + // A marker-only atomic store must null-check its holder. + static void testRecursivelyEmptyStore(TestEmptyValueFieldNullChecks outerHolder, Holder1 value) { + outerHolder.holder1 = value; + } + + // A recursively empty nullable store only updates its null marker with Unsafe and must null-check its holder. + static void testRecursivelyEmptyStore2(TestEmptyValueFieldNullChecks outerHolder, Holder2 value) { + outerHolder.holder2 = value; + } + + // A marker-only null-free store only emits Unsafe accesses and must null-check both operands. + static void testRecursivelyEmptyNullFreeStore(TestEmptyValueFieldNullChecks outerHolder, Holder1 value) { + outerHolder.holder1NullFree = value; + } + + // A recursively empty null-free store emits no access but must null-check both operands. + static void testRecursivelyEmptyNullFreeStore2(TestEmptyValueFieldNullChecks outerHolder, Holder2 value) { + outerHolder.holder2NullFree = value; + } + + // An empty nullable load only reads its null marker with Unsafe and must null-check its holder. + static Empty testEmptyLoad(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.empty; + } + + // An empty null-free load emits no access but must null-check its holder. + static Empty testEmptyNullFreeLoad(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.emptyNullFree; + } + + // A marker-only atomic load must null-check its holder. + static Holder1 testRecursivelyEmptyLoad(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.holder1; + } + + // A recursively empty nullable load only reads its null marker with Unsafe and must null-check its holder. + static Holder2 testRecursivelyEmptyLoad2(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.holder2; + } + + // A marker-only null-free load only emits Unsafe accesses and must null-check its holder. + static Holder1 testRecursivelyEmptyNullFreeLoad(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.holder1NullFree; + } + + // A recursively empty null-free load emits no access but must null-check its holder. + static Holder2 testRecursivelyEmptyNullFreeLoad2(TestEmptyValueFieldNullChecks outerHolder) { + return outerHolder.holder2NullFree; + } + + public static void main(String[] args) { + Empty empty = new Empty(); + TestEmptyValueFieldNullChecks outerHolder = new TestEmptyValueFieldNullChecks(); + Holder1 innerHolder = new Holder1(); + Holder2 innerHolder2 = new Holder2(); + for (int i = 0; i < 20_000; i++) { + Asserts.assertThrows(NullPointerException.class, () -> testUninitializedEmptyLoad(null)); + testEmptyStore(outerHolder, null); + testEmptyNullFreeStore(outerHolder, empty); + testRecursivelyEmptyStore(outerHolder, innerHolder); + testRecursivelyEmptyStore2(outerHolder, innerHolder2); + testRecursivelyEmptyNullFreeStore(outerHolder, innerHolder); + testRecursivelyEmptyNullFreeStore2(outerHolder, innerHolder2); + Asserts.assertEquals(testEmptyLoad(outerHolder), outerHolder.empty); + Asserts.assertEquals(testEmptyNullFreeLoad(outerHolder), outerHolder.emptyNullFree); + Asserts.assertEquals(testRecursivelyEmptyLoad(outerHolder), outerHolder.holder1); + Asserts.assertEquals(testRecursivelyEmptyLoad2(outerHolder), outerHolder.holder2); + Asserts.assertEquals(testRecursivelyEmptyNullFreeLoad(outerHolder), outerHolder.holder1NullFree); + Asserts.assertEquals(testRecursivelyEmptyNullFreeLoad2(outerHolder), outerHolder.holder2NullFree); + } + Asserts.assertFalse(initialized, "Should not have been initialized!"); + + Asserts.assertThrows(NullPointerException.class, () -> testEmptyStore(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyStore(null, empty)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyNullFreeStore(null, empty)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyNullFreeStore(outerHolder, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyStore(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyStore(null, innerHolder)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyStore2(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyStore2(null, innerHolder2)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore(null, innerHolder)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore(outerHolder, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore2(null, null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore2(null, innerHolder2)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeStore2(outerHolder, null)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyLoad(null)); + Asserts.assertThrows(NullPointerException.class, () -> testEmptyNullFreeLoad(null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyLoad(null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyLoad2(null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeLoad(null)); + Asserts.assertThrows(NullPointerException.class, () -> testRecursivelyEmptyNullFreeLoad2(null)); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayCheckExpansion.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayCheckExpansion.java index f1a2fa8c4146..5eeb7435cb66 100644 --- a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayCheckExpansion.java +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestFlatArrayCheckExpansion.java @@ -28,8 +28,10 @@ /** * @test - * @bug 8391280 + * @bug 8391280 8393019 * @summary Test macro expansion of merged flat-array checks with array and klass inputs + * @key stress randomness + * @requires vm.compiler2.enabled * @enablePreview * @library /test/lib * @modules java.base/jdk.internal.value @@ -37,6 +39,18 @@ * @run main/othervm -Xbatch * -XX:CompileCommand=compileonly,${test.main.class}::test* * ${test.main.class} + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:+UnlockDiagnosticVMOptions + * -XX:ConditionalMoveLimit=4 + * -XX:+StressIGVN -XX:StressSeed=4 + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:+UnlockDiagnosticVMOptions + * -XX:ConditionalMoveLimit=4 + * -XX:+StressIGVN + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} */ public class TestFlatArrayCheckExpansion { @@ -103,6 +117,35 @@ static int test4(Object[] array1, Object[] array2) { return res; } + // Forcing two outputs to the FlatArrayCheck: + // Naively, notFlat would be + // CMove(Bool(FlatArrayCheck, !=), false=0, true=1) + // But CMove doesn't like having 0 on the false-case (with true-case being constant), + // so it is tranformed into + // CMove(Bool(FlatArrayCheck, ==), false=1, true=0) + // On the other hand, (notFlat ? 1 : 2) + // CMove(Bool(FlatArrayCheck, !=), false=2, true=1) + // So we end up with a FlatArrayCheck with two Bool output: + // one with condition "==", and one "!=". This test makes sure we support FlatArrayCheck + // with multiple outputs. + static int test5(Object[] array, int i) { + boolean notFlat = !ValueClass.isFlatArray(array); + Object value = array[i]; + return (notFlat ? 1 : 2) + (value == null ? 4 : 8); + } + + // Pretty much the same as above, but in the situation we want to move the flatness check + // above the loop. + static int test6(Object[] array) { + int res = 0; + for (int i = 0; i < 4; i++) { + boolean notFlat = !ValueClass.isFlatArray(array); + Object value = array[i]; + res += (notFlat ? 1 : 2) + (value == null ? 4 : 8); + } + return res; + } + public static void main(String[] args) { Object[] refArray = {1, 2, 3, 4}; Integer[] flatArray = {1, 2, 3, 4}; @@ -124,6 +167,12 @@ public static void main(String[] args) { Asserts.assertEQ(test4(refArray, flatArray), 18); Asserts.assertEQ(test4(flatArray, refArray), isFlat ? 14 : 18); Asserts.assertEQ(test4(flatArray, flatArray), isFlat ? 14 : 18); + + Asserts.assertEQ(test5(refArray, 0), 9); + Asserts.assertEQ(test5(flatArray, 0), isFlat ? 10 : 9); + + Asserts.assertEQ(test6(refArray), 36); + Asserts.assertEQ(test6(flatArray), isFlat ? 40 : 36); } } } diff --git a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyStoreToNullHolder.java b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInfinitePushValueTypesDown.java similarity index 60% rename from test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyStoreToNullHolder.java rename to test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInfinitePushValueTypesDown.java index b0995d722b3f..563c88231b25 100644 --- a/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestEmptyStoreToNullHolder.java +++ b/test/hotspot/jtreg/compiler/valhalla/valuetypes/TestInfinitePushValueTypesDown.java @@ -21,37 +21,35 @@ * questions. */ -/* +/** * @test - * @bug 8392533 - * @summary Test that a holder null check is performed when storing to an empty flat field. + * @bug 8392784 * @enablePreview - * @requires vm.compiler1.enabled + * @run main/othervm -Xbatch -XX:CompileCommand=compileonly,${test.main.class}::test ${test.main.class} * @run main ${test.main.class} - * @run main/othervm -Xbatch -XX:TieredStopAtLevel=1 ${test.main.class} */ -package compiler.valhalla.valuetypes; +public class TestInfinitePushValueTypesDown { -public class TestEmptyStoreToNullHolder { - static value class Empty { } + static value class MyValue { + Object other; - Empty field = new Empty(); + MyValue(Object other) { + this.other = other; + } + } - static void test(TestEmptyStoreToNullHolder holder) { - holder.field = null; + static Object test() { + Object other = null; + for (int i = 0; i < 1; i++) { + other = new MyValue(other); + } + return other; } public static void main(String[] args) { - TestEmptyStoreToNullHolder holder = new TestEmptyStoreToNullHolder(); - for (int i = 0; i < 20_000; i++) { - test(holder); - } - try { - test(null); - throw new RuntimeException("No NullPointerException thrown!"); - } catch (NullPointerException expected) { - // Expected + for (int i = 0; i < 50_000; i++) { + test(); } } } diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java b/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java new file mode 100644 index 000000000000..18ed88dd4c7a --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestFloat16RearrangeInlining.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8337821 8393397 + * @summary Test that C2's register allocator gracefully handles pathological + * input resulting from extreme inlining. See 8393397 for the + * inlining issue (observed on aarch64). + * @modules jdk.incubator.vector + * @library /test/lib / + * @run main/othervm -Xbatch + * -esa + * -XX:CompileCommand=CompileOnly,jdk.incubator.vector.Float16Vector::lambda$rearrangeTemplate$0 + * compiler.vectorapi.TestFloat16RearrangeInlining + * @run main compiler.vectorapi.TestFloat16RearrangeInlining + */ + +package compiler.vectorapi; + +import jdk.incubator.vector.*; + +public class TestFloat16RearrangeInlining { + + public static void main(String[] args) { + Float16Vector vector512 = Float16Vector.zero(Float16Vector.SPECIES_512); + VectorShuffle shuffle512 = + VectorShuffle.iota(Float16Vector.SPECIES_512, 0, 1, false); + for (int i = 0; i < 1_500; i++) { + vector512.rearrange(shuffle512); + } + Float16Vector vector256 = Float16Vector.zero(Float16Vector.SPECIES_256); + VectorShuffle shuffle256 = + VectorShuffle.iota(Float16Vector.SPECIES_256, 0, 1, false); + for (int i = 0; i < 20_000; i++) { + vector256.rearrange(shuffle256); + } + } +} diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorSliceOutOfBounds.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorSliceOutOfBounds.java new file mode 100644 index 000000000000..c202d5a9bcf0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorSliceOutOfBounds.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.vectorapi; + +import compiler.lib.verify.Verify; +import jdk.incubator.vector.ShortVector; +import jdk.internal.vm.vector.VectorSupport; + +/* + * @test id=Test1 + * @bug 8393338 + * @summary Out-of-range constant vector slice index must throw, not crash C2. + * @modules java.base/jdk.internal.vm.vector + * jdk.incubator.vector + * @library /test/lib / + * @run main/othervm -Xcomp -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,${test.main.class}$Test1::test + * -XX:CompileCommand=dontinline,java.util.Objects::checkIndex + * -XX:CompileCommand=dontinline,jdk.internal.util.Preconditions::checkIndex + * ${test.main.class} Test1 + * @run main ${test.main.class} Test1 + */ + +/* + * @test id=Test2 + * @bug 8393338 + * @summary Out-of-range constant vector slice index must throw, not crash C2. + * @modules java.base/jdk.internal.vm.vector + * jdk.incubator.vector + * @library /test/lib / + * @run main/othervm -Xbatch -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,${test.main.class}$Test2::test + * ${test.main.class} Test2 + * @run main ${test.main.class} Test2 + */ + +public class TestVectorSliceOutOfBounds { + public static class Test1 { + static void test(short[] a, short[] b) { + try { + ShortVector.fromArray(ShortVector.SPECIES_512, a, 0) + .slice(-271, ShortVector.fromArray(ShortVector.SPECIES_512, b, 0)) + .intoArray(a, 0); + throw new RuntimeException("missing IndexOutOfBoundsException"); + } catch (IndexOutOfBoundsException e) { + } + } + + public static void main(String[] args) { + short[] a = new short[32]; + ShortVector.fromArray(ShortVector.SPECIES_512, a, 0) + .slice(0, ShortVector.fromArray(ShortVector.SPECIES_512, a, 0)); + test(a, a); + } + } + + public static class Test2 { + static ShortVector test() { + ShortVector sv = ShortVector.broadcast(ShortVector.SPECIES_128, (short) -5); + return (ShortVector) VectorSupport.sliceOp(128, sv.getClass(), VectorSupport.LT_SHORT, + 8, sv, sv, (v1, v2, v3) -> v2); + } + + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + test(); + } + Verify.checkEQ(test().lane(0), (short) -5); + } + } + + public static void main(String[] args) { + switch (args[0]) { + case "Test1" -> Test1.main(args); + case "Test2" -> Test2.main(args); + default -> throw new IllegalArgumentException(args[0]); + } + } +} diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorRearrangeTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorRearrangeTest.java index f2d172b88881..eb900fe0056c 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorRearrangeTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorRearrangeTest.java @@ -23,8 +23,8 @@ /** * @test - * @bug 8350463 - * @summary AArch64: Add vector rearrange support for small lane count vectors + * @bug 8350463 8394902 + * @summary Test vector rearrange support for small lane count vectors * @modules jdk.incubator.vector * @library /test/lib / * @@ -103,7 +103,7 @@ public class VectorRearrangeTest { } @Test - @IR(counts = {IRNode.REARRANGE_VB, IRNode.VECTOR_SIZE_8, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VB, IRNode.VECTOR_SIZE_8, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_byte64() { VectorShuffle shuffle = VectorShuffle.fromArray(bspec64, indexes[2], 0); for (int i = 0; i < LENGTH; i += bspec64.length()) { @@ -123,7 +123,7 @@ public void rearrange_byte64_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VB, IRNode.VECTOR_SIZE_16, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VB, IRNode.VECTOR_SIZE_16, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_byte128() { VectorShuffle shuffle = VectorShuffle.fromArray(bspec128, indexes[3], 0); for (int i = 0; i < LENGTH; i += bspec128.length()) { @@ -143,7 +143,7 @@ public void rearrange_byte128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VS, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VS, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_short64() { VectorShuffle shuffle = VectorShuffle.fromArray(sspec64, indexes[1], 0); for (int i = 0; i < LENGTH; i += sspec64.length()) { @@ -163,7 +163,7 @@ public void rearrange_short64_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VS, IRNode.VECTOR_SIZE_8, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VS, IRNode.VECTOR_SIZE_8, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_short128() { VectorShuffle shuffle = VectorShuffle.fromArray(sspec128, indexes[2], 0); for (int i = 0; i < LENGTH; i += sspec128.length()) { @@ -183,7 +183,7 @@ public void rearrange_short128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VI, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeature = {"asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VI, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"}) public void rearrange_int64() { VectorShuffle shuffle = VectorShuffle.fromArray(ispec64, indexes[0], 0); for (int i = 0; i < LENGTH; i += ispec64.length()) { @@ -203,7 +203,7 @@ public void rearrange_int64_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VI, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VI, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_int128() { VectorShuffle shuffle = VectorShuffle.fromArray(ispec128, indexes[1], 0); for (int i = 0; i < LENGTH; i += ispec128.length()) { @@ -223,7 +223,7 @@ public void rearrange_int128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VL, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeature = {"asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VL, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"}) public void rearrange_long128() { VectorShuffle shuffle = VectorShuffle.fromArray(lspec128, indexes[0], 0); for (int i = 0; i < LENGTH; i += lspec128.length()) { @@ -243,7 +243,7 @@ public void rearrange_long128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VF, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeature = {"asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VF, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"}) public void rearrange_float64() { VectorShuffle shuffle = VectorShuffle.fromArray(fspec64, indexes[0], 0); for (int i = 0; i < LENGTH; i += fspec64.length()) { @@ -263,7 +263,7 @@ public void rearrange_float64_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VF, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VF, IRNode.VECTOR_SIZE_4, " >0 "}, applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) public void rearrange_float128() { VectorShuffle shuffle = VectorShuffle.fromArray(fspec128, indexes[1], 0); for (int i = 0; i < LENGTH; i += fspec128.length()) { @@ -283,7 +283,7 @@ public void rearrange_float128_verify() { } @Test - @IR(counts = {IRNode.REARRANGE_VD, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeature = {"asimd", "true"}) + @IR(counts = {IRNode.REARRANGE_VD, IRNode.VECTOR_SIZE_2, " >0 "}, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"}) public void rearrange_double128() { VectorShuffle shuffle = VectorShuffle.fromArray(dspec128, indexes[0], 0); for (int i = 0; i < LENGTH; i += dspec128.length()) { diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java index f9cb7d163d02..c288fc2a4d6d 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorReplicateLongSpecialImmTest.java @@ -39,7 +39,6 @@ * @run testng/othervm -XX:UseSVE=0 -XX:-TieredCompilation * -XX:CompileThreshold=100 * -XX:+IgnoreUnrecognizedVMOptions - * -XX:CompileCommand=MemLimit,*.*,0 * -XX:CompileTaskTimeout=0 * compiler.vectorapi.VectorReplicateLongSpecialImmTest */ diff --git a/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java b/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java index 0a80a59efd43..9f5a90f2e03b 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestSubwordTruncation.java @@ -29,7 +29,7 @@ /* * @test - * @bug 8350177 8362171 8369881 8342095 8380988 + * @bug 8350177 8362171 8369881 8342095 8380988 8394230 * @summary Ensure that truncation of subword vectors produces correct results * @library /test/lib / * @run driver compiler.vectorization.TestSubwordTruncation @@ -70,6 +70,16 @@ static Object[] setupCharArray() { return new Object[] { arr }; } + @Setup + static Object[] setupLongArray() { + long[] arr = new long[SIZE]; + for (int i = 0; i < SIZE; i++) { + arr[i] = G.next().longValue(); + } + + return new Object[] { arr }; + } + // Shorts @Test @@ -557,6 +567,45 @@ public Object[] testCastII() { return new Object[] { bytes }; } + @Test + @IR(counts = { IRNode.POPCOUNT_L, ">0" }) + @Arguments(setup = "setupLongArray") + public Object[] testPopCountL(long[] in) { + byte[] res = new byte[SIZE]; + + for (int i = 0; i < SIZE; i++) { + res[i] = (byte) Long.bitCount(in[i]); + } + + return new Object[] { res }; + } + + @Test + @IR(counts = { IRNode.COUNT_LEADING_ZEROS_L, ">0" }) + @Arguments(setup = "setupLongArray") + public Object[] testCountLeadingZerosL(long[] in) { + byte[] res = new byte[SIZE]; + + for (int i = 0; i < SIZE; i++) { + res[i] = (byte) Long.numberOfLeadingZeros(in[i]); + } + + return new Object[] { res }; + } + + @Test + @IR(counts = { IRNode.COUNT_TRAILING_ZEROS_L, ">0" }) + @Arguments(setup = "setupLongArray") + public Object[] testCountTrailingZerosL(long[] in) { + byte[] res = new byte[SIZE]; + + for (int i = 0; i < SIZE; i++) { + res[i] = (byte) Long.numberOfTrailingZeros(in[i]); + } + + return new Object[] { res }; + } + public static void main(String[] args) { TestFramework.run(); } diff --git a/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java b/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java index efcc9f03fb91..b3f04b4af4fc 100644 --- a/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java +++ b/test/hotspot/jtreg/compiler/whitebox/StressNMethodRelocation.java @@ -180,6 +180,11 @@ public void invoke(byte[] num1, byte[] num2, byte[] result) throws Exception { } public void compileWithC2() throws Exception { + // The enqueue below is dropped while a compile the policy queued + // on its own is still in the queue, so wait for that one first + while (WHITE_BOX.isMethodQueuedForCompilation(method)) { + Thread.onSpinWait(); + } WHITE_BOX.enqueueMethodForCompilation(method, C2_LEVEL); while (WHITE_BOX.isMethodQueuedForCompilation(method)) { Thread.onSpinWait(); diff --git a/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java b/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java index bcd6e33c81e8..3c327978d90c 100644 --- a/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java +++ b/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2018, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -33,22 +33,11 @@ * * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocHumongousFragment - * - * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocHumongousFragment - * - * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestAllocHumongousFragment * * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC * TestAllocHumongousFragment */ diff --git a/test/hotspot/jtreg/gc/arguments/TestSoftMaxHeapSizeFlag.java b/test/hotspot/jtreg/gc/arguments/TestSoftMaxHeapSizeFlag.java index 8f0f17c23a73..e6eac6aad9b6 100644 --- a/test/hotspot/jtreg/gc/arguments/TestSoftMaxHeapSizeFlag.java +++ b/test/hotspot/jtreg/gc/arguments/TestSoftMaxHeapSizeFlag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,10 +41,10 @@ public class TestSoftMaxHeapSizeFlag { private static final long betweenXmsAndXmx = (Xms + Xmx) / 2; public static void main(String args[]) throws Exception { - // Test default value + // Test default value; expect 0 or Xmx ProcessTools.executeTestJava("-Xms" + Xms, "-Xmx" + Xmx, "-XX:+PrintFlagsFinal", "-version") - .shouldMatch("SoftMaxHeapSize[ ]+=[ ]+" + Xmx) + .shouldMatch("(SoftMaxHeapSize[ ]+=[ ]+" + 0 + ")|(SoftMaxHeapSize[ ]+=[ ]+" + Xmx + ")") .shouldHaveExitValue(0); // Test setting small value diff --git a/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousMXBeanCollectionUsage.java b/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousMXBeanCollectionUsage.java new file mode 100644 index 000000000000..09d7a77d9507 --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestEagerReclaimHumongousMXBeanCollectionUsage.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package gc.g1; + +/* + * @test TestEagerReclaimHumongousMXBeanCollectionUsage + * @bug 8386253 + * @summary Test that eager reclaim updates the old generation collection usage. + * @requires vm.gc.G1 + * @library /test/lib / + * @build jdk.test.whitebox.WhiteBox + * @modules java.base/jdk.internal.misc + * java.management + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -XX:+UseG1GC -Xms128m -Xmx128m -XX:G1HeapRegionSize=1m + * -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * gc.g1.TestEagerReclaimHumongousMXBeanCollectionUsage + */ + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +public class TestEagerReclaimHumongousMXBeanCollectionUsage { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + private static final int M = 1024 * 1024; + private static Object humongous; + + private static MemoryPoolMXBean findOldGenPool() { + for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) { + if (pool.getName().equals("G1 Old Gen")) { + return pool; + } + } + throw new RuntimeException("G1 Old Gen pool not found"); + } + + public static void main(String[] args) throws Exception { + MemoryPoolMXBean oldGen = findOldGenPool(); + + WB.fullGC(); + long baseline = oldGen.getCollectionUsage().getUsed(); + + humongous = new byte[4 * M]; + WB.concurrentGCAcquireControl(); + long withHumongous; + try { + WB.concurrentGCRunTo(WB.G1_AFTER_REBUILD_STARTED); + withHumongous = oldGen.getCollectionUsage().getUsed(); + WB.concurrentGCRunToIdle(); + } finally { + WB.concurrentGCReleaseControl(); + } + + // Keep the humongous object live across several young collections. + for (int i = 0; i < 5; i++) { + WB.youngGC(); + } + + humongous = null; + WB.youngGC(); + + long current = oldGen.getUsage().getUsed(); + long collection = oldGen.getCollectionUsage().getUsed(); + System.out.println("baseline=" + baseline + + " withHumongous=" + withHumongous + + " current=" + current + + " collection=" + collection); + + Asserts.assertGT(withHumongous, baseline, + "The humongous object should be reflected in collection usage"); + Asserts.assertLT(current, withHumongous, + "The eagerly reclaimed object should no longer be in current usage"); + Asserts.assertLT(collection, withHumongous, + "Collection usage should be updated after eager reclaim"); + } +} diff --git a/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java b/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java index b1d4a4ed5fc0..7c3ad69f831c 100644 --- a/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java +++ b/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,7 @@ /* * @test TestLogging + * @bug 8393007 * @summary Check that a mixed GC is reflected in the gc logs * @requires vm.gc.G1 * @requires vm.opt.MaxGCPauseMillis == "null" @@ -68,6 +69,7 @@ public static void main(String args[]) throws Exception { test("-Xlog:gc,gc+heap=debug"); // Test turns logging on by giving -Xlog:gc=debug flag test("-Xlog:gc=debug,gc+heap=debug"); + testWasteAllowance(); } private static void test(String vmFlag) throws Exception { @@ -78,6 +80,16 @@ private static void test(String vmFlag) throws Exception { output.shouldContain("Pause Young (Mixed)"); } + private static void testWasteAllowance() throws Exception { + // Multiplying a 64 MiB heap size by 100 overflows on 32-bit VMs. + OutputAnalyzer output = spawnMixedGCProvoker("-Xms64m", "-Xmx64m", + "-XX:G1HeapWastePercent=100", "-Xlog:gc,gc+ergo+cset=debug"); + System.out.println(output.getStdout()); + output.shouldHaveExitValue(0); + output.shouldContain("Pause Young (Mixed)"); + output.shouldMatch("Pruned .*\\(allowed 67108864\\)"); + } + /** * Method spawns MixedGCProvoker with addition flags set * diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java index 8488b8f4a8d0..c8d073483a1f 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java @@ -32,22 +32,11 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocIntArrays - * - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocIntArrays - * - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestAllocIntArrays * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestAllocIntArrays */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java index bc8c451450cc..ea9648749e9b 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java @@ -32,23 +32,13 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocObjectArrays - * - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify + * -XX:+ShenandoahVerify * TestAllocObjectArrays * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC * TestAllocObjectArrays * - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC - * TestAllocObjectArrays */ /* diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java index a1d06945b790..7e070727c609 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java @@ -30,22 +30,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestAllocObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestAllocObjects * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestAllocObjects */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestDynamicSoftMaxHeapSize.java b/test/hotspot/jtreg/gc/shenandoah/TestDynamicSoftMaxHeapSize.java index 7ca9c7f46d60..0a09bde65181 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestDynamicSoftMaxHeapSize.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestDynamicSoftMaxHeapSize.java @@ -31,7 +31,6 @@ * @run main/othervm -Xms100m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info -Dtarget=10000 * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=adaptive * TestDynamicSoftMaxHeapSize * @@ -45,7 +44,6 @@ * @run main/othervm -Xms100m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info -Dtarget=10000 * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=aggressive * TestDynamicSoftMaxHeapSize * @@ -59,7 +57,6 @@ * @run main/othervm -Xms100m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info -Dtarget=10000 * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=compact * TestDynamicSoftMaxHeapSize * @@ -73,7 +70,6 @@ * @run main/othervm -Xms100m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info -Dtarget=10000 * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=static * TestDynamicSoftMaxHeapSize * @@ -86,13 +82,6 @@ * * @run main/othervm -Xms16m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * -Dtarget=10000 - * TestDynamicSoftMaxHeapSize - * - * @run main/othervm -Xms16m -Xmx512m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * -Dtarget=10000 * TestDynamicSoftMaxHeapSize */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestHeapUncommit.java b/test/hotspot/jtreg/gc/shenandoah/TestHeapUncommit.java index ffa7027a3c80..a0ce91c2ac97 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestHeapUncommit.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestHeapUncommit.java @@ -32,22 +32,11 @@ * * @run main/othervm -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahUncommit -XX:ShenandoahUncommitDelay=0 * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestHeapUncommit - * - * @run main/othervm -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahUncommit -XX:ShenandoahUncommitDelay=0 - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestHeapUncommit - * - * @run main/othervm -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahUncommit -XX:ShenandoahUncommitDelay=0 - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestHeapUncommit * * @run main/othervm -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahUncommit -XX:ShenandoahUncommitDelay=0 * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestHeapUncommit */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java b/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java index e790851e2e84..ce48e7b2d62f 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java @@ -31,12 +31,6 @@ * * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * TestJcmdHeapDump - * - * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestJcmdHeapDump */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java b/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java index fbf3cd5c34b8..71ae891b424f 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java @@ -29,13 +29,6 @@ * * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * -Dtarget=10000 - * TestLotsOfCycles - * - * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * -Dtarget=10000 * TestLotsOfCycles */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestObjItrWithHeapDump.java b/test/hotspot/jtreg/gc/shenandoah/TestObjItrWithHeapDump.java index 0aad385d4e36..a540fd1e0796 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestObjItrWithHeapDump.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestObjItrWithHeapDump.java @@ -68,7 +68,6 @@ public static void main(String[] args) throws Exception { testWith("-XX:+UnlockDiagnosticVMOptions", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseShenandoahGC", - "-XX:-ShenandoahDegeneratedGC", "-XX:ShenandoahGCMode=" + mode, "-XX:ShenandoahGCHeuristics=" + h, "-Xlog:gc+classhisto=trace", diff --git a/test/hotspot/jtreg/gc/shenandoah/TestRegionSampling.java b/test/hotspot/jtreg/gc/shenandoah/TestRegionSampling.java index 442f0ab8b2d1..3191c2342c05 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestRegionSampling.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestRegionSampling.java @@ -29,12 +29,6 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahRegionSampling * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * TestRegionSampling - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ShenandoahRegionSampling - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestRegionSampling */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestResizeTLAB.java b/test/hotspot/jtreg/gc/shenandoah/TestResizeTLAB.java index f42b14c0d365..8b8383b91e43 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestResizeTLAB.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestResizeTLAB.java @@ -32,28 +32,12 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify * -XX:+ShenandoahVerify * -XX:+ResizeTLAB * TestResizeTLAB * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * -XX:+ShenandoahVerify - * -XX:-ResizeTLAB - * TestResizeTLAB - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * -XX:+ShenandoahVerify - * -XX:+ResizeTLAB - * TestResizeTLAB - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify * -XX:+ShenandoahVerify * -XX:-ResizeTLAB * TestResizeTLAB diff --git a/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java index 010bdb5e4f1d..0af2f25fbb8e 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java @@ -30,22 +30,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestRetainObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestRetainObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestRetainObjects * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestRetainObjects */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java index fa140d62a662..6247163dfb84 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java @@ -32,22 +32,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestSieveObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestSieveObjects - * - * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestSieveObjects * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestSieveObjects */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java b/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java index 100666fe859d..0b656255b708 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestSoftMaxHeapSizeAvailableCalc.java @@ -33,7 +33,6 @@ * @run main/othervm -XX:SoftMaxHeapSize=512m -Xmx2g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=adaptive * -XX:ShenandoahLearningSteps=0 * TestSoftMaxHeapSizeAvailableCalc @@ -47,7 +46,6 @@ * @run main/othervm -XX:SoftMaxHeapSize=512m -Xmx2g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -Xlog:gc=info * -XX:ShenandoahGCMode=satb - * -XX:+ShenandoahDegeneratedGC * -XX:ShenandoahGCHeuristics=static * TestSoftMaxHeapSizeAvailableCalc */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestStringDedup.java b/test/hotspot/jtreg/gc/shenandoah/TestStringDedup.java index 760e396beb1a..9c88b3df3407 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestStringDedup.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestStringDedup.java @@ -35,12 +35,7 @@ * * @run main/othervm -Xmx256m -Xlog:stringdedup -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -DGCCount=1 - * TestStringDedup - * - * @run main/othervm -Xmx256m -Xlog:stringdedup -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -DGCCount=1 + * -DGCCount=1 * TestStringDedup */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestStringDedupStress.java b/test/hotspot/jtreg/gc/shenandoah/TestStringDedupStress.java index fa8c67e095df..218ca16b846e 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestStringDedupStress.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestStringDedupStress.java @@ -34,12 +34,6 @@ * * @run main/othervm -Xmx1g -Xlog:stringdedup -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * TestStringDedupStress - * - * @run main/othervm -Xmx1g -Xlog:stringdedup -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestStringDedupStress */ @@ -54,7 +48,6 @@ * * @run main/othervm -Xmx1g -Xlog:stringdedup -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational - * -XX:+ShenandoahDegeneratedGC * -DtargetStrings=3000000 * TestStringDedupStress */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestStringInternCleanup.java b/test/hotspot/jtreg/gc/shenandoah/TestStringInternCleanup.java index 0aeafc09bc04..0c5a26289040 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestStringInternCleanup.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestStringInternCleanup.java @@ -30,22 +30,11 @@ * * @run main/othervm -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloadingWithConcurrentMark * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestStringInternCleanup - * - * @run main/othervm -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloadingWithConcurrentMark - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * TestStringInternCleanup - * - * @run main/othervm -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloadingWithConcurrentMark - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestStringInternCleanup * * @run main/othervm -Xmx64m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:+ClassUnloadingWithConcurrentMark * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestStringInternCleanup */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestVerifyJCStress.java b/test/hotspot/jtreg/gc/shenandoah/TestVerifyJCStress.java index 8af77a07d0b2..4cb2176f0d93 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestVerifyJCStress.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestVerifyJCStress.java @@ -32,12 +32,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify + * -XX:+ShenandoahVerify * TestVerifyJCStress * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify * TestVerifyJCStress */ diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestHelpers.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestHelpers.java new file mode 100644 index 000000000000..e7006b4788f0 --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestHelpers.java @@ -0,0 +1,133 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +package gc.shenandoah.generational; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.management.Notification; +import javax.management.NotificationEmitter; +import javax.management.NotificationListener; +import javax.management.openmbean.CompositeData; + +import com.sun.management.GarbageCollectionNotificationInfo; +import jdk.test.whitebox.WhiteBox; + +class TestHelpers { + interface GcNotificationListener { + void handleNotification(GarbageCollectionNotificationInfo info); + } + + public static void logEvent(GarbageCollectionNotificationInfo event) { + System.out.println(event.getGcAction() + ", " + event.getGcCause() + ", " + event.getGcName()); + } + + static class ScopedListener implements Closeable { + private final NotificationListener handle; + private final CountDownLatch sawSystemGC; + + ScopedListener() { + this(TestHelpers::logEvent); + } + + ScopedListener(final GcNotificationListener gcListener) { + sawSystemGC = new CountDownLatch(1); + handle = (notification, handback) -> { + if (isCollectorNotification(notification)) { + GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData()); + if (isSystemGC(info)) { + sawSystemGC.countDown(); + } + + gcListener.handleNotification(info); + } + }; + + TestHelpers.subscribeToCollectorNotifications(handle); + } + + @Override + public void close() throws IOException { + try { + System.gc(); + + if (!sawSystemGC.await(30, TimeUnit.SECONDS)) { + throw new RuntimeException("Timed out waiting for end of gc event stream"); + } + + TestHelpers.unsubscribeToCollectorNotifications(handle); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + static void promote(List objects) { + final int maxTries = 5; + int tries = 0; + while (!allAreInOld(objects) && tries < maxTries) { + WB.fullGC(); + ++tries; + } + + assert allAreInOld(objects) : "Not all objects were promoted"; + } + + static boolean allAreInOld(List objects) { + for (Object obj : objects) { + if (!WB.isObjectInOldGen(obj)) { + return false; + } + } + return true; + } + + static boolean isCollectorNotification(Notification n) { + return n.getType().equals(GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION); + } + + static void subscribeToCollectorNotifications(NotificationListener listener) { + for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) { + ((NotificationEmitter) b).addNotificationListener(listener, null, null); + } + } + + static void unsubscribeToCollectorNotifications(NotificationListener listener) throws Exception { + for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) { + ((NotificationEmitter) b).removeNotificationListener(listener, null, null); + } + } + + static boolean isSystemGC(GarbageCollectionNotificationInfo info) { + return info.getGcCause().contains("System.gc"); + } +} diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureDegeneration.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureRecovery.java similarity index 65% rename from test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureDegeneration.java rename to test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureRecovery.java index 29a0aedb3335..b625c19f412b 100644 --- a/test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureDegeneration.java +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestHumongousAllocFailureRecovery.java @@ -25,7 +25,7 @@ /* * @test id=generational * @bug 8391591 - * @summary Humongous allocation failure triggers a degenerated cycle. + * @summary Humongous allocation failure should recover without full GC. * @requires vm.gc.Shenandoah * @library /test/lib * @run main/othervm @@ -33,22 +33,26 @@ * -XX:ShenandoahGCMode=generational -Xmx128m -Xms128m * -XX:ShenandoahRegionSize=1m -XX:+AlwaysPreTouch * -XX:ConcGCThreads=1 -XX:ParallelGCThreads=1 - * TestHumongousAllocFailureDegeneration + * TestHumongousAllocFailureRecovery */ import com.sun.management.GarbageCollectionNotificationInfo; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; import javax.management.openmbean.CompositeData; -public class TestHumongousAllocFailureDegeneration { +public class TestHumongousAllocFailureRecovery { private static final int MB = 1024 * 1024; private static final int HUMONGOUS_SIZE_MB = 32; private static final int ITERATIONS = 16; @@ -70,20 +74,33 @@ private static void unsubscribeToCollectorNotifications(NotificationListener lis } } - private static boolean isHumongousAllocFailureDegen(GarbageCollectionNotificationInfo info) { + private static boolean isFullGC(GarbageCollectionNotificationInfo info) { return info.getGcName().equals("Shenandoah Pauses") - && info.getGcAction().contains("Degenerated") - && info.getGcCause().equals("Humongous Allocation Failure"); + && info.getGcAction().contains("Full"); + } + + private static boolean isSystemGC(GarbageCollectionNotificationInfo info) { + return info.getGcCause().contains("System.gc"); + } + + private static boolean isHumongousAllocationFailure(GarbageCollectionNotificationInfo info) { + return info.getGcCause().contains("Humongous Allocation Failure"); } public static void main(String[] args) throws Exception { - final AtomicBoolean sawDegenForHumongous = new AtomicBoolean(false); + final List unexpectedFullGCs = Collections.synchronizedList(new ArrayList<>()); + final AtomicLong humongousAllocFailures = new AtomicLong(); + final CountDownLatch sawSystemGC = new CountDownLatch(1); NotificationListener listener = (Notification n, Object o) -> { if (isCollectorNotification(n)) { GarbageCollectionNotificationInfo info = GarbageCollectionNotificationInfo.from((CompositeData) n.getUserData()); - if (isHumongousAllocFailureDegen(info)) { - sawDegenForHumongous.set(true); + if (isSystemGC(info)) { + sawSystemGC.countDown(); + } else if (isFullGC(info)) { + unexpectedFullGCs.add(info.getGcCause()); + } else if (isHumongousAllocationFailure(info)) { + humongousAllocFailures.incrementAndGet(); } } }; @@ -96,17 +113,21 @@ public static void main(String[] args) throws Exception { sink = new byte[MB * HUMONGOUS_SIZE_MB]; } - // Wait for gc notifications until we've encountered a degenerated cycle caused by - // humongous allocation failure. Fail if none arrives before the deadline. - long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(60); - while (!sawDegenForHumongous.get()) { - if (System.nanoTime() > deadlineNanos) { - throw new RuntimeException("Timed out waiting for a degenerated cycle caused by " - + "humongous allocation failure"); - } - Thread.sleep(10); + // Invoke an explicit GC. When our stream listener hears this, we know the test is complete. + System.gc(); + + if (!sawSystemGC.await(30, TimeUnit.SECONDS)) { + throw new RuntimeException("Timed out waiting for System.gc"); } unsubscribeToCollectorNotifications(listener); + + if (humongousAllocFailures.get() == 0) { + throw new RuntimeException("Test conditions not met, no humongous allocation failures"); + } + + if (!unexpectedFullGCs.isEmpty()) { + throw new RuntimeException("Unexpected full GCs: " + unexpectedFullGCs); + } } } diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestPromoteInPlaceDuringAbbreviatedCycle.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestPromoteInPlaceDuringAbbreviatedCycle.java index e9be9f39fbd1..577aedbe068e 100644 --- a/test/hotspot/jtreg/gc/shenandoah/generational/TestPromoteInPlaceDuringAbbreviatedCycle.java +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestPromoteInPlaceDuringAbbreviatedCycle.java @@ -118,9 +118,7 @@ private static boolean isCollectorNotification(Notification n) { private static boolean isIllegalPause(GarbageCollectionNotificationInfo info) { return info.getGcName().equals("Shenandoah Pauses") - && (info.getGcAction().contains("Update Refs") - || info.getGcAction().contains("Degen") - || info.getGcAction().contains("Full")); + && (info.getGcAction().contains("Update Refs") || info.getGcAction().contains("Full")); } private static void subscribeToCollectorNotifications(NotificationListener listener) { diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxOldMarkGC.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxOldMarkGC.java new file mode 100644 index 000000000000..114aa4169b9d --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxOldMarkGC.java @@ -0,0 +1,219 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=old + * @bug 8394388 + * @summary Test that WhiteBox::shenandoahOldMarkGC is robust + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxOldMarkGC old + */ + +/* + * @test id=mixed + * @bug 8394388 + * @summary Test that WhiteBox::shenandoahOldMarkGC will expedite mixed + * evacuations to finish an extent OldGC. + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxOldMarkGC mixed + */ + +/* + * @test id=cancel + * @bug 8394388 + * @summary Test that WhiteBox::shenandoahOldMarkGC will timeout and fail when + * the old mark is canceled by a full GC. + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxOldMarkGC cancel + */ +package gc.shenandoah.generational; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; + +import com.sun.management.GarbageCollectionNotificationInfo; + +import static gc.shenandoah.generational.TestHelpers.promote; + +public class TestWhiteBoxOldMarkGC { + public static final int OBJECT_COUNT = 2048; + + public static void main(String[] argv) throws Exception { + switch (argv[0]) { + case "old" -> Old.runTest(); + case "mixed" -> Mixed.runTest(); + case "cancel" -> Cancelled.runTest(); + default -> throw new IllegalArgumentException("Argument should be: old|mixed|cancel"); + } + } + + public static class Old { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments(Old.class.getName()) + ); + + output.shouldContain("WhiteBox Old Mark"); + output.shouldNotContain("WhiteBox Old Expedite Mixed"); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) { + testConsecutiveOld(); + } + + private static void testConsecutiveOld() { + final WhiteBox WB = WhiteBox.getWhiteBox(); + long one = WB.shenandoahOldMarkGC(); + long two = WB.shenandoahOldMarkGC(); + if (one == two) { + throw new RuntimeException("Old GC did not advance GC ID"); + } + } + } + + public static class Mixed { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments(Mixed.class.getName()) + ); + + output.shouldContain("WhiteBox Old Mark"); + output.shouldContain("WhiteBox Old Expedite Mixed"); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) { + testMixed(); + } + + private static void testMixed() { + final WhiteBox WB = WhiteBox.getWhiteBox(); + + var objects = new ArrayList<>(OBJECT_COUNT); + for (int i = 0; i < OBJECT_COUNT; ++i) { + objects.add(new Object()); + } + + promote(objects); + + // Release half the objects in a way that avoids 100% free regions + for (int i = 0; i < OBJECT_COUNT; ++i) { + if (i % 2 == 0) { + objects.set(i, null); + } + } + + long one = WB.shenandoahOldMarkGC(); + long two = WB.shenandoahOldMarkGC(); + if (one == two) { + throw new RuntimeException("Old GC did not advance GC ID"); + } + } + } + + // TODO: Old mark cancellation is broken. Fixed by JDK-8387790. + // The test makes no assertions because we cannot readily test the old + // mark cancellation case. + public static class Cancelled { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments( + TestWhiteBoxOldMarkGC.Cancelled.class.getName() + ) + ); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) throws Exception { + testCancelledOldGc(); + } + + private static void testCancelledOldGc() throws InterruptedException { + final WhiteBox WB = WhiteBox.getWhiteBox(); + + // Using a linked list here to intentionally slow down old marking + var objects = new LinkedList<>(); + for (int i = 0; i < OBJECT_COUNT * 100; ++i) { + objects.add(new Object()); + } + + promote(objects); + + int i = 0; + for (var iter = objects.listIterator(); iter.hasNext(); iter.next()) { + if (++i % 2 == 0) { + iter.remove(); + } + } + + final var fullGC = new Thread(WB::fullGC); + try (var _ = new TestHelpers.ScopedListener()) { + fullGC.start(); + WB.shenandoahOldMarkGC(5); + + fullGC.join(10000); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private static String[] getCommandLineArguments(String... additional) { + var defaultArguments = new String[] { + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseShenandoahGC", "-XX:ShenandoahGCMode=generational", + "-Xlog:gc*", "-Xmx128M", "-ea", + "-XX:ShenandoahGenerationalMinTenuringAge=1", + "-XX:ShenandoahGenerationalMaxTenuringAge=1", + }; + var arguments = new String[defaultArguments.length + additional.length]; + System.arraycopy(defaultArguments, 0, arguments, 0, defaultArguments.length); + System.arraycopy(additional, 0, arguments, defaultArguments.length, additional.length); + return arguments; + } +} diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxYoungGC.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxYoungGC.java new file mode 100644 index 000000000000..52e656bb102b --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestWhiteBoxYoungGC.java @@ -0,0 +1,120 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=young + * @bug 8394388 + * @summary Test that WhiteBox::youngGC is robust + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxYoungGC young + */ + +/* + * @test id=not-concurrent + * @bug 8394388 + * @summary Test that WhiteBox::youngGC falls back to global with -XX:-ExplicitGCInvokesConcurrent + * @requires vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib / + * @build jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver gc.shenandoah.generational.TestWhiteBoxYoungGC not-concurrent + */ +package gc.shenandoah.generational; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedList; + +import static gc.shenandoah.generational.TestHelpers.promote; + +public class TestWhiteBoxYoungGC { + public static void main(String[] argv) throws Exception { + switch (argv[0]) { + case "young" -> Young.runTest(); + case "not-concurrent" -> NotConcurrent.runTest(); + default -> throw new IllegalArgumentException("Argument should be: old|mixed|cancel"); + } + } + + public static class Young { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments(Young.class.getName()) + ); + + output.shouldContain("WhiteBox Initiated Young GC"); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) { + final WhiteBox WB = WhiteBox.getWhiteBox(); + WB.youngGC(); + WB.youngGC(); + } + } + + public static class NotConcurrent { + private static void runTest() throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + getCommandLineArguments( + "-XX:-ExplicitGCInvokesConcurrent", + NotConcurrent.class.getName()) + ); + + output.shouldNotContain("WhiteBox Initiated Global GC"); + output.shouldHaveExitValue(0); + } + + public static void main(String[] argv) { + final WhiteBox WB = WhiteBox.getWhiteBox(); + WB.youngGC(); + WB.youngGC(); + } + } + + private static String[] getCommandLineArguments(String... additional) { + var defaultArguments = new String[] { + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", + "-XX:+UnlockExperimentalVMOptions", + "-XX:+UseShenandoahGC", "-XX:ShenandoahGCMode=generational", + "-Xlog:gc*", "-Xmx128M", "-ea", + "-XX:ShenandoahGenerationalMinTenuringAge=1", + "-XX:ShenandoahGenerationalMaxTenuringAge=1", + }; + var arguments = new String[defaultArguments.length + additional.length]; + System.arraycopy(defaultArguments, 0, arguments, 0, defaultArguments.length); + System.arraycopy(additional, 0, arguments, defaultArguments.length, additional.length); + return arguments; + } +} diff --git a/test/hotspot/jtreg/gc/shenandoah/jni/TestPinnedGarbage.java b/test/hotspot/jtreg/gc/shenandoah/jni/TestPinnedGarbage.java index eabe244a9d13..dc74451bf5f9 100644 --- a/test/hotspot/jtreg/gc/shenandoah/jni/TestPinnedGarbage.java +++ b/test/hotspot/jtreg/gc/shenandoah/jni/TestPinnedGarbage.java @@ -31,12 +31,7 @@ * * @run main/othervm/native -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx128m * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahVerify -XX:+ShenandoahDegeneratedGC - * TestPinnedGarbage - * - * @run main/othervm/native -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx128m - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahVerify -XX:-ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * TestPinnedGarbage */ diff --git a/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestChurnNotifications.java b/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestChurnNotifications.java index 6473c310ec79..caeb0464feb2 100644 --- a/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestChurnNotifications.java +++ b/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestChurnNotifications.java @@ -31,12 +31,7 @@ * * @run main/othervm -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -Dprecise=true - * TestChurnNotifications - * - * @run main/othervm -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -Dprecise=true + * -Dprecise=true * TestChurnNotifications */ diff --git a/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestPauseNotifications.java b/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestPauseNotifications.java index e5c3acaccac6..01e56db31f6d 100644 --- a/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestPauseNotifications.java +++ b/test/hotspot/jtreg/gc/shenandoah/mxbeans/TestPauseNotifications.java @@ -31,12 +31,6 @@ * * @run main/othervm -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC - * TestPauseNotifications - * - * @run main/othervm -Xmx128m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * TestPauseNotifications */ @@ -116,8 +110,7 @@ public class TestPauseNotifications { private static boolean isExpectedPauseAction(String action) { return "Init Mark".equals(action) || "Final Mark".equals(action) || "Full GC".equals(action) - || "Degenerated GC".equals(action) || "Init Update Refs".equals(action) - || "Final Update Refs".equals(action) || "Final Roots".equals(action); + || "Init Update Refs".equals(action) || "Final Update Refs".equals(action) || "Final Roots".equals(action); } public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/gc/shenandoah/options/TestHumongousMoves.java b/test/hotspot/jtreg/gc/shenandoah/options/TestHumongousMoves.java index e2254fdc0ab9..a553f95f1bec 100644 --- a/test/hotspot/jtreg/gc/shenandoah/options/TestHumongousMoves.java +++ b/test/hotspot/jtreg/gc/shenandoah/options/TestHumongousMoves.java @@ -31,14 +31,12 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahHumongousMoves - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify + * -XX:-ShenandoahHumongousMoves -XX:+ShenandoahVerify * TestHumongousMoves * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahHumongousMoves - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify + * -XX:+ShenandoahHumongousMoves -XX:+ShenandoahVerify * TestHumongousMoves */ diff --git a/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java b/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java index 858efacd7586..c56723e84a3d 100644 --- a/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java +++ b/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java @@ -37,12 +37,7 @@ * * @run main/othervm/timeout=200 -Xlog:gc*=info -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahVerify -XX:+ShenandoahDegeneratedGC - * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 - * - * @run main/othervm/timeout=200 -Xlog:gc*=info -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahVerify -XX:-ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 */ @@ -116,13 +111,7 @@ * @run main/othervm/timeout=200 -Xlog:gc*=info,nmethod+barrier=trace -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive * -XX:+DeoptimizeNMethodBarriersALot -XX:-Inline - * -XX:+ShenandoahVerify -XX:+ShenandoahDegeneratedGC - * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 - * - * @run main/othervm/timeout=200 -Xlog:gc*=info,nmethod+barrier=trace -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+DeoptimizeNMethodBarriersALot -XX:-Inline - * -XX:+ShenandoahVerify -XX:-ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 */ diff --git a/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java b/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java index 9b2eb530b2ae..2dc806545fa9 100644 --- a/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java +++ b/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java @@ -1,26 +1,26 @@ /* -* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. -* Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. -* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -* -* This code is free software; you can redistribute it and/or modify it -* under the terms of the GNU General Public License version 2 only, as -* published by the Free Software Foundation. -* -* This code is distributed in the hope that it will be useful, but WITHOUT -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -* version 2 for more details (a copy is included in the LICENSE file that -* accompanied this code). -* -* You should have received a copy of the GNU General Public License version -* 2 along with this work; if not, write to the Free Software Foundation, -* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -* -* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -* or visit www.oracle.com if you need additional information or have any -* questions. -*/ + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ package gc.stress.gcold; @@ -33,22 +33,11 @@ * * @run main/othervm/timeout=600 -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * gc.stress.gcold.TestGCOld 50 1 20 10 10000 - * - * @run main/othervm/timeout=600 -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC -XX:+ShenandoahVerify - * gc.stress.gcold.TestGCOld 50 1 20 10 10000 - * - * @run main/othervm/timeout=600 -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions - * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:+ShenandoahDegeneratedGC + * -XX:+ShenandoahVerify * gc.stress.gcold.TestGCOld 50 1 20 10 10000 * * @run main/othervm/timeout=600 -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=passive - * -XX:-ShenandoahDegeneratedGC * gc.stress.gcold.TestGCOld 50 1 20 10 10000 */ diff --git a/test/hotspot/jtreg/gc/z/TestAdaptiveHeapSizingMode.java b/test/hotspot/jtreg/gc/z/TestAdaptiveHeapSizingMode.java new file mode 100644 index 000000000000..01c1570abca6 --- /dev/null +++ b/test/hotspot/jtreg/gc/z/TestAdaptiveHeapSizingMode.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package gc.z; + +/** + * @test TestAdaptiveHeapSizingMode + * @requires vm.gc.Z & vm.flagless + * @summary Test that heap size flags affect ZGC Adaptive Heap Sizing mode. + * @library / /test/lib + * @run driver gc.z.TestAdaptiveHeapSizingMode + */ + +import jdk.test.lib.JDKToolFinder; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TestAdaptiveHeapSizingMode { + private static List createTestSpecificArgs(String... heapSizeArgs) { + List args = new ArrayList<>(); + args.add("-XX:+UseZGC"); + args.add("-XX:+ZAdaptiveHeapSizing"); + args.add("-Xlog:gc+init"); + args.addAll(Arrays.asList(heapSizeArgs)); + args.add("--version"); + return args; + } + + private static void assertExpectedMode(OutputAnalyzer out, List args, String expectedMode) { + String output = out.getOutput(); + String needle = "Heap Sizing: " + expectedMode; + if (!output.contains(needle)) { + throw new AssertionError("Expected to find: \"" + needle + "\"\n" + + "Arguments: " + String.join(" ", args) + "\n" + + "Output:\n" + output); + } + } + + private static void assertHeapSizingMode(String expectedMode, String... heapSizeArgs) throws Exception { + List args = createTestSpecificArgs(heapSizeArgs); + + // Launch child JVM + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder(args); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + out.shouldHaveExitValue(0); + + assertExpectedMode(out, args, expectedMode); + } + + private static void assertHeapSizingModeClean(String expectedMode, String... heapSizeArgs) throws Exception { + String java = JDKToolFinder.getJDKTool("java"); + List args = createTestSpecificArgs(heapSizeArgs); + + List cmd = new ArrayList<>(args.size() + 1); + cmd.add(java); // executable + cmd.addAll(args); // arguments + + // Launch child JVM + ProcessBuilder pb = new ProcessBuilder(cmd); + OutputAnalyzer out = new OutputAnalyzer(pb.start()); + out.shouldHaveExitValue(0); + + assertExpectedMode(out, args, expectedMode); + } + + private static final String FIXED_MODE = "Fixed"; + private static final String IMPLICIT_MODE = "Adaptive (Implicit Boundaries)"; + private static final String EXPLICIT_MODE = "Adaptive (Explicit Boundaries)"; + + public static void main(String[] args) throws Exception { + // No flags + assertHeapSizingModeClean(IMPLICIT_MODE); + + // Only lower-bound + assertHeapSizingModeClean(EXPLICIT_MODE, + "-XX:MinHeapSize=100m"); + assertHeapSizingModeClean(EXPLICIT_MODE, + "-Xms100m"); + + // Only upper-bound + assertHeapSizingModeClean(EXPLICIT_MODE, + "-XX:MaxRAMPercentage=10"); + assertHeapSizingModeClean(EXPLICIT_MODE, + "-Xmx100m"); + + // Xms == Xmx + assertHeapSizingMode(FIXED_MODE, + "-Xms100m", + "-Xmx100m"); + + // Xms != Xmx + assertHeapSizingMode(EXPLICIT_MODE, + "-Xms50m", + "-Xmx100m"); + + // MinHeapSize == Xmx + assertHeapSizingMode(FIXED_MODE, + "-XX:MinHeapSize=100m", + "-Xmx100m"); + + // MinHeapSize < Xmx + assertHeapSizingMode(EXPLICIT_MODE, + "-XX:MinHeapSize=50m", + "-Xmx100m"); + } +} diff --git a/test/hotspot/jtreg/gc/z/TestUncommit.java b/test/hotspot/jtreg/gc/z/TestUncommit.java index 0e674358be54..1a8f7f0ee829 100644 --- a/test/hotspot/jtreg/gc/z/TestUncommit.java +++ b/test/hotspot/jtreg/gc/z/TestUncommit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,8 +26,9 @@ /* * @test TestUncommit * @requires vm.gc.Z + * @requires vm.opt.ZAdaptiveHeapSizing == null | !vm.opt.ZAdaptiveHeapSizing * @summary Test ZGC uncommit unused memory - * @run main/othervm/timeout=480 -XX:+UseZGC -Xlog:gc*,gc+heap=debug,gc+stats=off -Xms128M -Xmx512M -XX:ZUncommitDelay=5 gc.z.TestUncommit + * @run main/othervm/timeout=480 -XX:+UseZGC -Xlog:gc*,gc+heap=debug,gc+stats=off -XX:-ZAdaptiveHeapSizing -Xms128M -Xmx512M -XX:ZUncommitDelay=5 gc.z.TestUncommit */ import java.util.ArrayList; diff --git a/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRangesDynamic.java b/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRangesDynamic.java index 480e8929ff8f..709a5d656259 100644 --- a/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRangesDynamic.java +++ b/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRangesDynamic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,6 +61,10 @@ public static void main(String[] args) throws Exception { * Exclude SoftMaxHeapSize as its valid range is only known at runtime. */ excludeTestRange("SoftMaxHeapSize"); + /* + * Exclude ZGCIntensity as its valid range depends on its initial value. + */ + excludeTestRange("ZGCIntensity"); Asserts.assertGT(allWriteableOptions.size(), 0, "Options with ranges not found!"); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java index a48051bb6725..ba325df2e7ab 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCacheConsistency.java @@ -59,7 +59,7 @@ public static void main(String args[]) throws Exception { // Modify each of the region individually. The production should fail to run // with these args; - String extraVMArgs[] = {"-XX:+VerifySharedSpaces", "-XX:AOTMode=on"}; + String extraVMArgs[] = {"-XX:+VerifySharedSpaces", "-XX:AOTMode=required"}; tester.setCheckExitValue(false); for (int i = 0; i < regions.length; i++) { @@ -73,4 +73,4 @@ public static void main(String args[]) throws Exception { } } } -} \ No newline at end of file +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java index 0aaf2f5fb778..864f7557d8c0 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTCompatibleOopCompression.java @@ -63,7 +63,7 @@ public String[] vmArgs(RunMode runMode) { if (runMode == RunMode.ASSEMBLY) { return new String[] {"-XX:+UnlockDiagnosticVMOptions", "-XX:+AOTCompatibleOopCompression"}; } else if (runMode == RunMode.PRODUCTION) { - return new String[] {"-Xlog:aot", "-XX:AOTMode=on", "-XX:HeapBaseMinAddress=0x800000000"}; + return new String[] {"-Xlog:aot", "-XX:AOTMode=required", "-XX:HeapBaseMinAddress=0x800000000"}; } else { return new String[0]; } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java index 864ce88cd118..86c2b420f758 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTLoggingTag.java @@ -87,7 +87,7 @@ public static void main(String[] args) throws Exception { printTestCase("Production Run: errors should be printed with [aot] decoration"); pb = ProcessTools.createLimitedTestJavaProcessBuilder( "-XX:AOTCache=nosuchfile.aot", - "-XX:AOTMode=on", + "-XX:AOTMode=required", "-cp", appJar, helloClass); out = CDSTestUtils.executeAndLog(pb, "prod"); out.shouldMatch("\\[aot *\\] An error has occurred while processing the AOT cache\\. Run with -Xlog:aot for details\\."); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/AOTCacheOtherDirectoryTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/AOTCacheOtherDirectoryTest.java index 622d99cc0679..5a03431ef012 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/AOTCacheOtherDirectoryTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/aotClassLinking/AOTCacheOtherDirectoryTest.java @@ -74,7 +74,7 @@ public static void main(String[] args) throws Exception { .runAOTTrainingAndAssemblyWorkflow(); // Sanity test, run from same directory - String[] cmdLine = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=on", + String[] cmdLine = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=required", "-XX:AOTCache=" + targetDirName + File.separator + aotCacheName + ".aot", "-Xlog:cds,aot=trace,aot+map+oops=trace:file=production1.map:none:filesize=0", "-cp", destPath.toString(), mainClass}; @@ -84,7 +84,7 @@ public static void main(String[] args) throws Exception { output.shouldContain("CodeSource = " + realPath); // At runtime, run from /target so the classpath is different but use the same JAR - String[] cmdLine2 = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=on", + String[] cmdLine2 = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=required", "-XX:AOTCache=" + aotCacheName + ".aot", "-Xlog:cds,aot=trace,aot+map+oops=trace:file=production2.map:none:filesize=0", "-cp", jarName, mainClass}; @@ -95,7 +95,7 @@ public static void main(String[] args) throws Exception { output.shouldContain("CodeSource = " + realPath); // At runtime, run from /target2 so the classpath is different but use the same JAR - String[] cmdLine3 = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=on", + String[] cmdLine3 = new String[] { "-XX:-AOTClassLinking", "-XX:AOTMode=required", "-XX:AOTCache=" + targetDirName + File.separator + aotCacheName + ".aot", "-Xlog:cds,aot=trace,aot+map+oops=trace:file=production3.map:none:filesize=0", "-cp", destPath2.toString(), mainClass}; diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java index 6ee6b8e41688..d0437d76ef94 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTestUnknownExternAddress.java @@ -94,7 +94,7 @@ public static void main(String... args) throws Exception { "-XX:CompileCommand=compileonly,TestUnknownAddress::test*", "-Xcomp", "-XX:-TieredCompilation", "-Xlog:aot+codecache+init=debug", - "-XX:AOTMode=on", + "-XX:AOTMode=required", "-XX:AOTCache=" + aotCacheFile, "-cp", appJar, appClass); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/VerifyArchivedFields.java b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/VerifyArchivedFields.java index 80eebf60811c..df5f834fbdcf 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/VerifyArchivedFields.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/cacheObject/VerifyArchivedFields.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,7 +37,7 @@ public class VerifyArchivedFields { // Note: -XX:VerifyArchivedFields=2 will force a GC every time when - // HeapShared::initialize_from_archived_subgraph(Klass* k, ...) is called. This ensures + // AOTHeap::initialize_from_archived_subgraph(Klass* k, ...) is called. This ensures // that it's safe to do a GC even when the oop->klass() of some archived heap objects // are not yet loaded into the system dictionary. public static void main(String[] args) throws Exception { diff --git a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java index 08c9191a33c8..4fe5a9c2c6c1 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/resolvedConstants/AOTLinkedLambdas.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,7 @@ * @requires vm.cds * @requires vm.cds.supports.aot.class.linking * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes/ + * @modules java.base/jdk.internal.misc * @build AOTLinkedLambdas * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar * AOTLinkedLambdasApp InitTracker @@ -41,6 +42,7 @@ import java.util.function.Supplier; import static java.util.stream.Collectors.*; +import jdk.internal.misc.Unsafe; import jdk.test.lib.cds.CDSOptions; import jdk.test.lib.cds.CDSTestUtils; import jdk.test.lib.cds.SimpleCDSAppTester; @@ -56,6 +58,7 @@ public static void main(String[] args) throws Exception { .classpath(appJar) .addVmArgs("-esa", // see JDK-8340836 "-XX:+AOTClassLinking", + "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED", "-Xlog:aot+resolve=trace", "-Xlog:aot+class=debug", "-Xlog:cds+class=debug") @@ -206,6 +209,13 @@ static void testClinitOrder() { // ID2 - has , but no default method ID2 id2 = () -> {}; id2.doit(); + // ID1 must be initialized even though the intervening ID2 is not. + if (Unsafe.getUnsafe().shouldBeInitialized(ID1.class)) { + throw new RuntimeException("ID1 should have been initialized"); + } + if (!Unsafe.getUnsafe().shouldBeInitialized(ID2.class)) { + throw new RuntimeException("ID2 should not have been initialized"); + } InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC"); System.out.println(ID2._dummy); InitTracker.assertOrder("InitTracker, FooB, BarB, FooA, BarA, IA, IC, ID2"); diff --git a/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp b/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp index 0c5e95de6114..6daf8e7b0268 100644 --- a/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp +++ b/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp @@ -66,7 +66,7 @@ Java_SuspendInCritical_getNativeCounter(JNIEnv* jni, jclass cls) { JNIEXPORT void JNICALL Java_SuspendInCritical_doCritical(JNIEnv* jni, jclass cls, jbyteArray bytes, jstring str) { jboolean is_copy = JNI_FALSE; - jbyte* b; + jbyte* b = nullptr; if (bytes != nullptr) { LOG("CriticalThread doing GetPrimitiveArrayCritical\n"); b = (jbyte*) jni->GetPrimitiveArrayCritical(bytes, &is_copy); @@ -75,7 +75,7 @@ JNIEXPORT void JNICALL } } - const jchar* c; + const jchar* c = nullptr; if (str != nullptr) { LOG("CriticalThread doing GetStringCritical\n"); c = jni->GetStringCritical(str, &is_copy); diff --git a/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyAbstractVal.jasm b/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyAbstractVal.jasm new file mode 100644 index 000000000000..f2611b5fcc9f --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyAbstractVal.jasm @@ -0,0 +1,39 @@ +/* Javac reports an error when a value class declares a method overriding + * Object::finalize. The VM must still handle such class files correctly, so + * this class is supplied as jasm. It corresponds to the following source: + * + * abstract value class MyAbstractVal { + * static volatile boolean finalizerWasCalled = false; + * int i = 0; + * + * @SuppressWarnings("deprecation") + * protected void finalize() { + * finalizerWasCalled = true; + * } + * } + */ +abstract class MyAbstractVal version 72:65535 { + static volatile Field finalizerWasCalled:Z; + final strict Field i:I = 0; + + Method "":"()V" stack 2 locals 1 { + aload_0; + iconst_0; + putfield Field i:I; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + protected Method finalize:"()V" stack 1 locals 1 { + iconst_1; + putstatic Field finalizerWasCalled:Z; + return; + } + + static Method "":"()V" stack 1 locals 0 { + iconst_0; + putstatic Field finalizerWasCalled:Z; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyVal.jasm b/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyVal.jasm new file mode 100644 index 000000000000..fa1bf55c162b --- /dev/null +++ b/test/hotspot/jtreg/runtime/valhalla/valuetypes/MyVal.jasm @@ -0,0 +1,39 @@ +/* Javac reports an error when a value class declares a method overriding + * Object::finalize. The VM must still handle such class files correctly, so + * this class is supplied as jasm. It corresponds to the following source: + * + * value class MyVal { + * static volatile boolean finalizerWasCalled = false; + * int i = 0; + * + * @SuppressWarnings("deprecation") + * protected void finalize() { + * finalizerWasCalled = true; + * } + * } + */ +final class MyVal version 72:65535 { + static volatile Field finalizerWasCalled:Z; + final strict Field i:I = 0; + + Method "":"()V" stack 2 locals 1 { + aload_0; + iconst_0; + putfield Field i:I; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + protected Method finalize:"()V" stack 1 locals 1 { + iconst_1; + putstatic Field finalizerWasCalled:Z; + return; + } + + static Method "":"()V" stack 1 locals 0 { + iconst_0; + putstatic Field finalizerWasCalled:Z; + return; + } +} diff --git a/test/hotspot/jtreg/runtime/valhalla/valuetypes/TestFinalizableValues.java b/test/hotspot/jtreg/runtime/valhalla/valuetypes/TestFinalizableValues.java index 95a958b8a4e5..5d9928767a41 100644 --- a/test/hotspot/jtreg/runtime/valhalla/valuetypes/TestFinalizableValues.java +++ b/test/hotspot/jtreg/runtime/valhalla/valuetypes/TestFinalizableValues.java @@ -25,7 +25,7 @@ * @test TestFinalizableValues * @library /test/lib * @enablePreview - * @compile TestFinalizableValues.java + * @compile MyVal.jasm MyAbstractVal.jasm TestFinalizableValues.java * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI TestFinalizableValues */ @@ -43,39 +43,24 @@ public class TestFinalizableValues { static WhiteBox WB = WhiteBox.getWhiteBox(); + /* + * MyVal and MyAbstractVal are supplied as MyVal.jasm and MyAbstractVal.jasm: both are + * classes that javac can no longer produce from source since JDK-8339188 forbids a + * value class from declaring a method that overrides Object::finalize. See the + * jasm files for the corresponding source. + */ public static class TestHelper { - static boolean finalizer1WasCalled = false; - static boolean finalizer2WasCalled = false; - - static value class MyVal { - static volatile boolean finalizerWasCalled = false; - int i = 0; - - @SuppressWarnings("deprecation") - protected void finalize() { - finalizerWasCalled = true; - } - } - - static abstract value class MyAbstractVal { - int i = 0; - - @SuppressWarnings("deprecation") - protected void finalize() { - finalizer1WasCalled = true; - } - } - static value class MyVal2 extends MyAbstractVal {} static class MyId extends MyAbstractVal {} static class MyId2 extends MyAbstractVal { + static boolean finalizerWasCalled = false; int i = 0; @SuppressWarnings("deprecation") protected void finalize() { - finalizer2WasCalled = true; + finalizerWasCalled = true; } } @@ -101,14 +86,14 @@ public static void main(String[] args) throws ClassNotFoundException { Thread.currentThread().interrupt(); } } - if (finalizer1WasCalled != expectFinalizer1) { + if (MyAbstractVal.finalizerWasCalled != expectFinalizer1) { throw new RuntimeException("Finalizer1 was " - + (finalizer1WasCalled ? "" : "not ") + + (MyAbstractVal.finalizerWasCalled ? "" : "not ") + "executed"); } - if (finalizer2WasCalled != expectFinalizer2) { + if (MyId2.finalizerWasCalled != expectFinalizer2) { throw new RuntimeException("Finalizer2 was " - + (finalizer2WasCalled ? "" : "not ") + + (MyId2.finalizerWasCalled ? "" : "not ") + "executed"); } } @@ -122,7 +107,7 @@ static void test(String classname, boolean expectRegistration, String... args) t Collections.addAll(argsList, "-XX:+IgnoreUnrecognizedVMOptions"); Collections.addAll(argsList, "-XX:+TraceFinalizerRegistration"); Collections.addAll(argsList, "TestFinalizableValues$TestHelper"); - Collections.addAll(argsList, "TestFinalizableValues$TestHelper$" + classname); + Collections.addAll(argsList, classname); Collections.addAll(argsList, args); ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(argsList); OutputAnalyzer out = new OutputAnalyzer(pb.start()); @@ -137,8 +122,8 @@ static void test(String classname, boolean expectRegistration, String... args) t } public static void main(String[] args) throws IOException { test("MyVal", false, "false", "false"); - test("MyVal2", false, "false", "false"); - test("MyId", false, "false", "false"); - test("MyId2", true, "false", "true"); + test("TestFinalizableValues$TestHelper$MyVal2", false, "false", "false"); + test("TestFinalizableValues$TestHelper$MyId", false, "false", "false"); + test("TestFinalizableValues$TestHelper$MyId2", true, "false", "true"); } } diff --git a/test/hotspot/jtreg/serviceability/HeapDump/ClassLoaderDefinedClassesTest.java b/test/hotspot/jtreg/serviceability/HeapDump/ClassLoaderDefinedClassesTest.java new file mode 100644 index 000000000000..c320298bc8d6 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/HeapDump/ClassLoaderDefinedClassesTest.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.File; +import java.lang.ref.Reference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import jdk.test.lib.Asserts; +import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.hprof.model.JavaClass; +import jdk.test.lib.hprof.model.Snapshot; +import jdk.test.lib.hprof.parser.Reader; + +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; + +/* + * @test + * @bug 8391308 + * @summary Verifies heap dump contains a reference to a java.lang.Class instance from its ClassLoader + * @library /test/lib + * @run driver ClassLoaderDefinedClassesTest + */ +class ClassLoaderDefinedClassesTarg extends LingeredApp { + + static class MyLoader extends ClassLoader { + byte[] bytes = ClassFile.of().build(ClassDesc.of("Test"), clb -> + clb.withFlags(ACC_PUBLIC) + .withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> + cob.aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_()) + ); + + @Override + public Class findClass(String name) throws ClassNotFoundException { + if (name.equals("Test")) { + return defineClass(name, bytes, 0, bytes.length); + } + throw new ClassNotFoundException(name); + } + } + + public static void main(String[] args) { + MyLoader ldr = new MyLoader(); + Class test; + try { + test = ldr.loadClass("Test"); + } catch (ClassNotFoundException cnfe) { + throw new RuntimeException("Test not loaded"); + } + LingeredApp.main(args); + Reference.reachabilityFence(ldr); + } +} + +public class ClassLoaderDefinedClassesTest { + + public static void main(String[] args) throws Exception { + File dumpFile = new File("Myheapdump.hprof"); + createDump(dumpFile, args); + verifyDump(dumpFile); + } + + private static void createDump(File dumpFile, String[] extraOptions) throws Exception { + LingeredApp theApp = null; + try { + theApp = new ClassLoaderDefinedClassesTarg(); + + List extraVMArgs = new ArrayList<>(); + extraVMArgs.addAll(Arrays.asList(extraOptions)); + LingeredApp.startApp(theApp, extraVMArgs.toArray(new String[0])); + + //jcmd GC.heap_dump + JDKToolLauncher launcher = JDKToolLauncher + .createUsingTestJDK("jcmd") + .addToolArg(Long.toString(theApp.getPid())) + .addToolArg("GC.heap_dump") + .addToolArg("-overwrite") + .addToolArg(dumpFile.getAbsolutePath()); + Process p = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand())); + // If something goes wrong with heap dumping most likely we'll get crash of the target VM. + while (!p.waitFor(5, TimeUnit.SECONDS)) { + if (!theApp.getProcess().isAlive()) { + log("ERROR: target VM died, killing jcmd..."); + p.destroyForcibly(); + throw new Exception("Target VM died"); + } + } + + if (p.exitValue() != 0) { + throw new Exception("Jcmd exited with code " + p.exitValue()); + } + } finally { + LingeredApp.stopApp(theApp); + } + } + + private static void verifyDump(File dumpFile) throws Exception { + Asserts.assertTrue(dumpFile.exists(), "Heap dump file not found."); + + log("Reading " + dumpFile + "..."); + try (Snapshot snapshot = Reader.readFile(dumpFile.getPath(), true, 0)) { + log("Resolving snapshot..."); + snapshot.resolve(true); + log("Snapshot resolved."); + + JavaClass loaderClass = + snapshot.findClass("ClassLoaderDefinedClassesTarg$MyLoader"); + JavaClass testClass = snapshot.findClass("Test"); + + Asserts.assertNotNull(loaderClass, "MyLoader class missing from dump"); + Asserts.assertNotNull(testClass, "Test class missing from dump"); + // Assert that the Class has a field for the class loader. + Asserts.assertSame(testClass.getLoader(), + loaderClass.getInstances(false).nextElement()); + } + } + + private static void log(Object s) { + System.out.println(s); + } + +} diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramFlattenedArrayTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramFlattenedArrayTest.java new file mode 100644 index 000000000000..4d901521970a --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramFlattenedArrayTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.util.regex.Pattern; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; + +import jdk.internal.value.ValueClass; + + +/* + * @test + * @summary Test of diagnostic command GC.class_histogram for arrays with different layouts + * @enablePreview + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.compiler + * java.management + * java.base/jdk.internal.vm.annotation + * java.base/jdk.internal.value + * jdk.internal.jvmstat/sun.jvmstat.monitor + * @run main ${test.main.class} + */ +public class ClassHistogramFlattenedArrayTest { + + public static value record TestClassNA(short s1, short s2) {} + + public static value record TestClassNRA(short s1, short s2) {} + + @jdk.internal.vm.annotation.LooselyConsistentValue + public static value record TestClassNRNA(short s1, short s2) {} + + + public static Object[] arrays = { + ValueClass.newNullableAtomicArray(TestClassNA.class, 1024), + ValueClass.newNullRestrictedAtomicArray(TestClassNRA.class, 1024, new TestClassNRA((short)1, (short)1)), + ValueClass.newNullRestrictedNonAtomicArray(TestClassNRNA.class, 1024, new TestClassNRNA((short)1, (short)1)), + }; + + public static void main(String[] args) { + CommandExecutor executor = new JMXExecutor(); + OutputAnalyzer output = executor.execute("GC.class_histogram "); + + assertType(output, TestClassNA[].class, "flat, nullable, atomic"); + assertType(output, TestClassNRA[].class, "flat, null-restricted, atomic"); + assertType(output, TestClassNRNA[].class, "flat, null-restricted, non-atomic"); + } + + private static void assertType(OutputAnalyzer output, Class arrayClass, String type) { + output.shouldMatch("^\\s+\\d+:\\s+\\d+\\s+\\d+\\s+" + + Pattern.quote(arrayClass.getName()) + + "\\s+\\[" + Pattern.quote(type) + "\\](?:\\s+\\(.*\\))?\\s*$"); + } + +} diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramParallelTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramParallelTest.java new file mode 100644 index 000000000000..9d21e3cf5ee3 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/ClassHistogramParallelTest.java @@ -0,0 +1,198 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import jdk.test.lib.Asserts; +import jdk.test.lib.apps.LingeredApp; +import jdk.test.lib.dcmd.PidJcmdExecutor; +import jdk.test.lib.process.OutputAnalyzer; + +/* + * @test id=Serial + * @bug 8392050 + * @requires vm.gc.Serial + * @summary Verify serial heap inspection doesn't create and use the diagnostic worker pool (Serial GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseSerialGC + */ + +/* + * @test id=Parallel + * @bug 8392050 + * @requires vm.gc.Parallel + * @summary Verify parallel heap inspection uses the diagnostic worker pool (Parallel GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseParallelGC + */ + +/* + * @test id=G1 + * @bug 8392050 + * @requires vm.gc.G1 + * @summary Verify parallel heap inspection uses the diagnostic worker pool (G1 GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseG1GC + */ + +/* + * @test id=Shenandoah + * @bug 8392050 + * @requires vm.gc.Shenandoah + * @summary Verify parallel heap inspection uses the diagnostic worker pool (Shenandoah GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseShenandoahGC + */ + +/* + * @test id=ShenandoahGenerational + * @bug 8392050 + * @requires vm.gc.Shenandoah + * @summary Verify parallel heap inspection uses the diagnostic worker pool (Generational Shenandoah GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational + */ + +/* + * @test id=Z + * @bug 8392050 + * @requires vm.gc.Z + * @summary Verify parallel heap inspection uses the diagnostic worker pool (Z GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UseZGC + */ + +/* + * @test id=Epsilon + * @bug 8392050 + * @requires vm.gc.Epsilon + * @summary Verify serial heap inspection doesn't create and use the diagnostic worker pool (Epsilon GC) + * @library /test/lib + * @run driver ClassHistogramParallelTest -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC + */ + +public class ClassHistogramParallelTest { + private static final int ACTIVE_PROCESSOR_COUNT = 8; + private static final int DEFAULT_WORKERS = Math.max(1, ACTIVE_PROCESSOR_COUNT * 3 / 8); + private static final String POOL_CREATION_MSG = + "Created diagnostic worker pool (max " + ACTIVE_PROCESSOR_COUNT + " workers)"; + + private static void checkAndVerify(OutputAnalyzer threadPrintOut, LingeredApp app, + int expectedDiagWorkers, String[] gcArgs) throws Exception { + threadPrintOut.shouldHaveExitValue(0); + OutputAnalyzer appOut = new OutputAnalyzer(app.getProcessStdout()); + + List gcFlags = Arrays.asList(gcArgs); + if (gcFlags.contains("-XX:+UseSerialGC") || gcFlags.contains("-XX:+UseEpsilonGC")) { + System.out.println("Serial or Epsilon GC detected."); + expectedDiagWorkers = 0; + } + + if (expectedDiagWorkers > 1) { + appOut.shouldContain(POOL_CREATION_MSG); + + // Verify that the worker pool was only created once. + int count = (int)appOut.stdoutAsLines().stream() + .filter(line -> line.contains(POOL_CREATION_MSG)) + .count(); + Asserts.assertEquals(1, count); + + threadPrintOut.shouldContain("\"DiagWorker#" + (expectedDiagWorkers - 1) + "\""); + threadPrintOut.shouldNotContain("\"DiagWorker#" + expectedDiagWorkers + "\""); + } else { + appOut.shouldNotContain("Created diagnostic worker pool"); + threadPrintOut.shouldNotContain("DiagWorker"); + } + } + + private static LingeredApp launchApp(String[] gcArgs) throws IOException { + LingeredApp theApp = new LingeredApp(); + List jvmArgs = new ArrayList<>(List.of("-Xlog:gc+task", + "-Xmx512m", + "-XX:ActiveProcessorCount=" + ACTIVE_PROCESSOR_COUNT)); + jvmArgs.addAll(Arrays.asList(gcArgs)); + LingeredApp.startApp(theApp, jvmArgs.toArray(new String[0])); + return theApp; + } + + public static void main(String[] args) throws Exception { + LingeredApp theApp = launchApp(args); + try { + // Expect error message + OutputAnalyzer out = attachJcmd(theApp.getPid(), "GC.class_histogram -parallel=-1"); + out.shouldContain("Parallel thread number out of range (>=0): -1"); + + // Expect serial inspection. + test("-parallel=1", 0, args, 1); + + // If no parallel threads are passed in or -parallel=0, the default number of parallel + // threads to use is based on CPUs: max(1, ACTIVE_PROCESSOR_COUNT * 3 / 8). + test("", DEFAULT_WORKERS, args, 1); + test("-parallel=0", DEFAULT_WORKERS, args, 1); + + // Expect parallel heap inspection. + test("-parallel=2", 2, args, 1); + test("-parallel=" + ACTIVE_PROCESSOR_COUNT, ACTIVE_PROCESSOR_COUNT, args, 1); + test("-parallel=" + Integer.MAX_VALUE, ACTIVE_PROCESSOR_COUNT, args, 1); + + // Expect that the diagnostic worker pool is created once for multiple requests. + test("-parallel=2", 2, args, 5); + } finally { + theApp.stopApp(); + } + } + + private static void test(String parallelThreads, int expectedDiagWorkers, String[] gcArgs, int attempts) throws Exception { + LingeredApp theApp = launchApp(gcArgs); + try { + OutputAnalyzer classHistogramOut = attachJcmd(theApp.getPid(), "GC.class_histogram " + parallelThreads); + verifyHistogram(classHistogramOut); + + for (int i = 1; i < attempts; i++) { + classHistogramOut = attachJcmd(theApp.getPid(), "GC.class_histogram " + parallelThreads); + verifyHistogram(classHistogramOut); + } + + OutputAnalyzer threadPrintOut = attachJcmd(theApp.getPid(), "Thread.print"); + theApp.stopApp(); + checkAndVerify(threadPrintOut, theApp, expectedDiagWorkers, gcArgs); + } finally { + theApp.stopApp(); + } + } + + private static OutputAnalyzer attachJcmd(long lingeredAppPid, String cmd) throws Exception { + System.out.println("Testing pid " + lingeredAppPid); + PidJcmdExecutor executor = new PidJcmdExecutor("" + lingeredAppPid); + return executor.execute(cmd, true); + } + + private static void verifyHistogram(OutputAnalyzer out) throws Exception { + out.shouldHaveExitValue(0); + // Verify that the histogram was printed. + out.shouldMatch("^Total\\s+\\d+\\s+\\d+"); + } +} diff --git a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java index 71507fa6e705..57361fdab05c 100644 --- a/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java +++ b/test/hotspot/jtreg/serviceability/dcmd/gc/HeapDumpParallelTest.java @@ -24,33 +24,89 @@ import java.io.File; import java.io.IOException; -import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import jdk.test.lib.Asserts; -import jdk.test.lib.JDKToolLauncher; import jdk.test.lib.apps.LingeredApp; import jdk.test.lib.dcmd.PidJcmdExecutor; import jdk.test.lib.process.OutputAnalyzer; -import jdk.test.lib.process.ProcessTools; import jdk.test.lib.hprof.HprofParser; -/** - * @test - * @bug 8306441 8319053 8384612 - * @summary Verify the integrity of generated heap dump and capability of parallel dump +/* + * @test id=Serial + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Serial + * @summary Verify the integrity of generated heap dump and that parallel dump requests run serially (Serial GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseSerialGC + */ + +/* + * @test id=Parallel + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Parallel + * @summary Verify the integrity of generated heap dump and capability of parallel dump (Parallel GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseParallelGC + */ + +/* + * @test id=G1 + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.G1 + * @summary Verify the integrity of generated heap dump and capability of parallel dump (G1 GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseG1GC + */ + +/* + * @test id=Shenandoah + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Shenandoah + * @summary Verify the integrity of generated heap dump and capability of parallel dump (Shenandoah GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseShenandoahGC + */ + +/* + * @test id=ShenandoahGenerational + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Shenandoah + * @summary Verify the integrity of generated heap dump and capability of parallel dump (Generational Shenandoah GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational + */ + +/* + * @test id=Z + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Z + * @summary Verify the integrity of generated heap dump and capability of parallel dump (Z GC) * @library /test/lib - * @run main HeapDumpParallelTest + * @run driver HeapDumpParallelTest -XX:+UseZGC + */ + +/* + * @test id=Epsilon + * @bug 8306441 8319053 8384612 8392050 + * @requires vm.gc.Epsilon + * @summary Verify the integrity of generated heap dump and that parallel dump requests run serially (Epsilon GC) + * @library /test/lib + * @run driver HeapDumpParallelTest -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC */ public class HeapDumpParallelTest { private static final String heapDumpFileName = "parallelHeapDump.bin"; + private static final int ACTIVE_PROCESSOR_COUNT = 8; + private static final int DEFAULT_WORKERS = Math.max(1, ACTIVE_PROCESSOR_COUNT * 3 / 8); - private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File heapDumpFile, boolean expectSerial) throws Exception { + private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File heapDumpFile, + int expectedDiagWorkers) throws Exception { dcmdOut.shouldHaveExitValue(0); dcmdOut.shouldContain("Heap dump file created"); OutputAnalyzer appOut = new OutputAnalyzer(app.getProcessStdout()); @@ -59,13 +115,17 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File appOut.shouldMatch("\\[gc *\\] Using "); if (appOut.getStdout().contains("Using Serial") || appOut.getStdout().contains("Using Epsilon")) { System.out.println("Serial or Epsilon GC detected."); - expectSerial = true; + expectedDiagWorkers = 0; } - if (!expectSerial) { + if (expectedDiagWorkers > 1) { + appOut.shouldContain("Created diagnostic worker pool (max " + ACTIVE_PROCESSOR_COUNT + " workers)"); appOut.shouldContain("Dump heap objects in parallel"); appOut.shouldContain("Merge heap files complete"); + appOut.shouldContain("active dump threads " + expectedDiagWorkers + ","); } else { + appOut.shouldNotContain("Created diagnostic worker pool"); appOut.shouldNotContain("Dump heap objects in parallel"); + appOut.shouldContain("active dump threads 0"); } HprofParser.parseAndVerify(heapDumpFile); @@ -83,11 +143,12 @@ private static void checkAndVerify(OutputAnalyzer dcmdOut, LingeredApp app, File } } - private static LingeredApp launchApp() throws IOException { + private static LingeredApp launchApp(String[] gcArgs) throws IOException { LingeredApp theApp = new LingeredApp(); - LingeredApp.startApp(theApp, "-Xlog:gc,heapdump", "-Xmx512m", - "-XX:-UseDynamicNumberOfGCThreads", - "-XX:ParallelGCThreads=2"); + List jvmArgs = new ArrayList<>(List.of("-Xlog:gc,heapdump,gc+task", "-Xmx512m", + "-XX:ActiveProcessorCount=" + ACTIVE_PROCESSOR_COUNT)); + jvmArgs.addAll(Arrays.asList(gcArgs)); + LingeredApp.startApp(theApp, jvmArgs.toArray(new String[0])); return theApp; } @@ -97,7 +158,7 @@ public static void main(String[] args) throws Exception { heapDumpFile.delete(); } - LingeredApp theApp = launchApp(); + LingeredApp theApp = launchApp(args); try { // Expect error message OutputAnalyzer out = attachJcmdHeapDump(heapDumpFile, theApp.getPid(), "-parallel=" + -1); @@ -108,27 +169,34 @@ public static void main(String[] args) throws Exception { out.shouldContain("Invalid number of parallel dump threads."); // Expect serial dump because 0 implies to disable parallel dump - test(heapDumpFile, "-parallel=" + 0, true); + test(heapDumpFile, "-parallel=" + 0, 0, args); // Expect serial dump - test(heapDumpFile, "-parallel=" + 1, true); + test(heapDumpFile, "-parallel=" + 1, 0, args); // Expect parallel dump - test(heapDumpFile, "-parallel=" + Integer.MAX_VALUE, false); + test(heapDumpFile, "-parallel=" + 2, 2, args); // Expect parallel dump - test(heapDumpFile, "-gz=9 -overwrite -parallel=" + Math.max(Runtime.getRuntime().availableProcessors(), 2), false); + test(heapDumpFile, "-parallel=" + Integer.MAX_VALUE, ACTIVE_PROCESSOR_COUNT, args); + + // Expect parallel dump + test(heapDumpFile, "-gz=9 -overwrite -parallel=" + ACTIVE_PROCESSOR_COUNT, ACTIVE_PROCESSOR_COUNT, args); + + // If no parallel threads are passed in, the default number of parallel threads + // to use is based on CPUs: max(1, ACTIVE_PROCESSOR_COUNT * 3 / 8). + test(heapDumpFile, "", DEFAULT_WORKERS, args); } finally { theApp.stopApp(); } } - private static void test(File heapDumpFile, String arg, boolean expectSerial) throws Exception { - LingeredApp theApp = launchApp(); + private static void test(File heapDumpFile, String arg, int expectedDiagWorkers, String[] gcArgs) throws Exception { + LingeredApp theApp = launchApp(gcArgs); try { OutputAnalyzer dcmdOut = attachJcmdHeapDump(heapDumpFile, theApp.getPid(), arg); theApp.stopApp(); - checkAndVerify(dcmdOut, theApp, heapDumpFile, expectSerial); + checkAndVerify(dcmdOut, theApp, heapDumpFile, expectedDiagWorkers); } finally { theApp.stopApp(); } diff --git a/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/ClassLoaderTest.java b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/ClassLoaderTest.java new file mode 100644 index 000000000000..89ced9952f12 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/ClassLoaderTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8391308 + * @library /test/lib + * @requires vm.jvmti + * @summary The test verifies heap walking API (FollowReferences) reports classes + * when starting from the class loader. + * @run main/othervm/native -agentlib:ClassLoaderTest ClassLoaderTest + */ + +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.ref.Reference; +import jdk.test.lib.Asserts; + +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; + +public class ClassLoaderTest { + + static class MyLoader extends ClassLoader { + byte[] bytes = ClassFile.of().build(ClassDesc.of("Test"), clb -> + clb.withFlags(ACC_PUBLIC) + .withMethodBody(INIT_NAME, MTD_void, ACC_PUBLIC, cob -> + cob.aload(0) + .invokespecial(CD_Object, INIT_NAME, MTD_void) + .return_()) + ); + + @Override + public Class findClass(String name) throws ClassNotFoundException { + if (name.equals("Test")) { + return defineClass(name, bytes, 0, bytes.length); + } + throw new ClassNotFoundException(name); + } + } + + static { + System.loadLibrary("ClassLoaderTest"); + } + + private static native boolean targetReachedFrom(ClassLoader loader, Class target); + private static native boolean targetKindIsOther(); + + public static void main(String[] args) { + MyLoader ldr = new MyLoader(); + Class test; + try { + test = ldr.loadClass("Test"); + } catch (ClassNotFoundException cnfe) { + throw new RuntimeException("Test not loaded"); + } + + Asserts.assertTrue(targetReachedFrom(ldr, test), + "FollowReferences starting at MyLoader should have reached Test.class"); + + Asserts.assertTrue(targetKindIsOther(), + "FollowReferences should have reported Test.class as target kind OTHER"); + + Reference.reachabilityFence(ldr); + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/libClassLoaderTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/libClassLoaderTest.cpp new file mode 100644 index 000000000000..2f9fd36ddb8c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/FollowReferences/ClassLoader/libClassLoaderTest.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include "jvmti.h" +#include "jvmti_common.hpp" + +extern "C" { + +static jvmtiEnv* jvmti = nullptr; + +JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + jint res = jvm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_1); + if (res != JNI_OK || jvmti == nullptr) { + LOG("jvm->GetEnv failed\n"); + return JNI_ERR; + } + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_tag_objects = 1; + jvmtiError err = jvmti->AddCapabilities(&caps); + check_jvmti_error(err, "AddCapabilities"); + return JNI_OK; +} + +static constexpr jlong TARGET_TAG = 0x1234; +static long target_kind; +static bool target_seen; + +static jint JNICALL reference_callback( + jvmtiHeapReferenceKind kind, + const jvmtiHeapReferenceInfo* info, + jlong class_tag, + jlong referrer_class_tag, + jlong size, + jlong* tag_ptr, + jlong* referrer_tag_ptr, + jint length, + void* user_data) { + if (*tag_ptr == TARGET_TAG) { + target_seen = true; + target_kind = kind; + LOG("Reached tagged Test.class, reference kind: %d\n", kind); + } + return JVMTI_VISIT_OBJECTS; +} + +JNIEXPORT jboolean JNICALL +Java_ClassLoaderTest_targetReachedFrom( + JNIEnv* env, jclass, jobject loader, jclass target) { + target_seen = false; + + check_jvmti_error(jvmti->SetTag(target, TARGET_TAG), "SetTag target"); + + jvmtiHeapCallbacks callbacks = {}; + callbacks.heap_reference_callback = reference_callback; + + check_jvmti_error( + jvmti->FollowReferences(0, // do not filter tagged objects + nullptr, // no class filter + loader, // initial object + &callbacks, + nullptr), + "FollowReferences"); + + check_jvmti_error(jvmti->SetTag(target, 0), "clear target tag"); + return target_seen; +} + +JNIEXPORT jboolean JNICALL +Java_ClassLoaderTest_targetKindIsOther(JNIEnv* env, jclass) { + return target_kind == JVMTI_HEAP_REFERENCE_OTHER; +} + +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java b/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java new file mode 100644 index 000000000000..e4160491ad8c --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/BreakpointMetaspaceOOM.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2026, Justus Garbe. 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 8352077 + * @summary A breakpoint set while metaspace is exhausted must not be left half-installed. + * @requires vm.jvmti & vm.compiler1.enabled & vm.flagless + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/native/timeout=240 + * -agentlib:BreakpointMetaspaceOOM + * -Xbootclasspath/a:. + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -Xbatch + * -XX:MaxMetaspaceSize=32m + * BreakpointMetaspaceOOM + */ + +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassFile; +import java.lang.classfile.Label; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.reflect.Method; +import java.util.function.IntUnaryOperator; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import static java.lang.constant.ConstantDescs.*; + +public class BreakpointMetaspaceOOM { + + static final int ERR_NONE = 0; // JVMTI_ERROR_NONE + static final int ERR_OUT_OF_MEMORY = 110; // JVMTI_ERROR_OUT_OF_MEMORY + + static final int TIER_C1_PROFILED = 3; // CompLevel_full_profile (C1 with full profiling) + + static final int CALLS = 20_000; + static final int BRANCHES = 1_000; + + static native void prepare(Method m); + static native int setBreakpoint(); + static native int breakpointHits(); + + static final MethodTypeDesc APPLY_AS_INT = MethodTypeDesc.of(CD_int, CD_int); + static final ClassDesc INT_UNARY_OPERATOR = ClassDesc.of("java.util.function.IntUnaryOperator"); + + static final class GenLoader extends ClassLoader { + GenLoader() { super(BreakpointMetaspaceOOM.class.getClassLoader()); } + Class define(String name, byte[] bytes) { return defineClass(name, bytes, 0, bytes.length); } + } + + private static void noArgConstructor(ClassBuilder cb) { + cb.withMethodBody(INIT_NAME, MTD_void, ClassFile.ACC_PUBLIC, + b -> b.aload(0).invokespecial(CD_Object, INIT_NAME, MTD_void).return_()); + } + + private static byte[] incrementerBytes() { + return ClassFile.of().build(ClassDesc.of("Target"), cb -> { + cb.withSuperclass(CD_Object).withInterfaceSymbols(INT_UNARY_OPERATOR); + noArgConstructor(cb); + cb.withMethodBody("applyAsInt", APPLY_AS_INT, ClassFile.ACC_PUBLIC, + b -> b.iload(1).iconst_1().iadd().ireturn()); + }); + } + + private static byte[] branchHeavyBytes() { + return ClassFile.of().build(ClassDesc.of("Big"), cb -> { + cb.withSuperclass(CD_Object).withInterfaceSymbols(ClassDesc.of("java.lang.Runnable")); + noArgConstructor(cb); + cb.withMethodBody("run", MTD_void, ClassFile.ACC_PUBLIC, b -> { + for (int i = 0; i < BRANCHES; i++) { + Label fallThrough = b.newLabel(); + b.iconst_0().ifne(fallThrough).labelBinding(fallThrough); + } + b.return_(); + }); + }); + } + + private static byte[] fillerBytes(String name, int methodCount) { + return ClassFile.of().build(ClassDesc.of(name), cb -> { + cb.withSuperclass(CD_Object); + for (int i = 0; i < methodCount; i++) { + cb.withMethodBody("f".concat(Integer.toString(i)), MTD_void, + ClassFile.ACC_PUBLIC | ClassFile.ACC_STATIC, b -> b.return_()); + } + }); + } + + private static void exhaustMetaspace(GenLoader loader) { + int index = 0; + for (int methodCount : new int[] {1000, 100, 10, 1, 0}) { + try { + for (;;) { + String name = "Filler".concat(Integer.toString(index++)); + loader.define(name, fillerBytes(name, methodCount)); + } + } catch (OutOfMemoryError expected) { + // Arena full at this size; shrink the class and keep going. + } + } + System.out.println("Metaspace exhausted"); + } + + public static void main(String[] args) throws Exception { + WhiteBox wb = WhiteBox.getWhiteBox(); + + GenLoader loader = new GenLoader(); + IntUnaryOperator incrementer = + (IntUnaryOperator) loader.define("Target", incrementerBytes()).getDeclaredConstructor().newInstance(); + Method targetMethod = incrementer.getClass().getDeclaredMethod("applyAsInt", int.class); + Runnable branchy = + (Runnable) loader.define("Big", branchHeavyBytes()).getDeclaredConstructor().newInstance(); + + // Resolve and warm everything needed after the fill + prepare(targetMethod); + branchy.run(); + wb.isMethodCompiled(targetMethod); + fillerBytes("Warmup", 1); + Asserts.assertTrue(true); + breakpointHits(); + + // Fill a throwaway loader, kept apart from the target's, so it can be collected later + // to lift the OOM state again. + GenLoader fillers = new GenLoader(); + exhaustMetaspace(fillers); + + // Make the JIT attempt branchy's profiling allocation under the full arena, tripping + // the sticky out-of-memory state that causes the breakpoint count to be dropped. + for (int i = 0; i < CALLS; i++) { + branchy.run(); + } + + int rc = setBreakpoint(); + System.out.print("SetBreakpoint returned "); + System.out.println(rc); + + // Drop the fillers and collect them so the metaspace OOM state clears: MethodCounters + // can now be built again (with a breakpoint count of zero -- the count was lost), the + // method runs, and C1 compiles it at tier 3 over the stale 0xCA. + fillers = null; + wb.fullGC(); + System.out.println("Fillers unloaded"); + + for (int i = 0; i < CALLS; i++) { + incrementer.applyAsInt(i); + } + wb.enqueueMethodForCompilation(targetMethod, TIER_C1_PROFILED); + + // On the unfixed VM a compilation above aborts with the 0xCA crash (the call loop can + // trigger it, or the explicit request) and we never get here, so jtreg records the + // crash as a failure. Reaching this point means no crash: + if (rc == ERR_OUT_OF_MEMORY) { + // The VM reported the allocation failure and installed no breakpoint, so none may + // have fired. + Asserts.assertEquals(0, breakpointHits(), "breakpoint fired after SetBreakpoint reported OUT_OF_MEMORY"); + } else if (rc == ERR_NONE) { + // The breakpoint was installed in full. A VM that tracks it correctly refuses to + // compile the method, and every interpreted call must have hit it. + Asserts.assertFalse(wb.isMethodCompiled(targetMethod), "a method with a live breakpoint was compiled"); + Asserts.assertEquals(CALLS, breakpointHits(), "breakpoint hit count did not match the number of calls"); + } else { + Asserts.fail("SetBreakpoint returned an unexpected JVMTI error code. rc=" + rc); + } + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/libBreakpointMetaspaceOOM.cpp b/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/libBreakpointMetaspaceOOM.cpp new file mode 100644 index 000000000000..8a361ddb7d52 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/SetBreakpoint/libBreakpointMetaspaceOOM.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026, Justus Garbe. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jvmti.h" +#include "jvmti_common.hpp" + +extern "C" { + +static jvmtiEnv* jvmti = nullptr; +static jmethodID target = nullptr; +static std::atomic hits{0}; + +static void JNICALL +Breakpoint(jvmtiEnv* env, JNIEnv* jni, jthread thread, jmethodID method, jlocation location) { + hits++; +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { + if (vm->GetEnv((void**)&jvmti, JVMTI_VERSION_1_0) != JNI_OK) { + LOG("Agent_OnLoad: GetEnv failed"); + return JNI_ERR; + } + + jvmtiCapabilities caps; + memset(&caps, 0, sizeof(caps)); + caps.can_generate_breakpoint_events = 1; + check_jvmti_error(jvmti->AddCapabilities(&caps), "AddCapabilities"); + + jvmtiEventCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.Breakpoint = Breakpoint; + check_jvmti_error(jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)), + "SetEventCallbacks"); + + check_jvmti_error(jvmti->SetEventNotificationMode(JVMTI_ENABLE, + JVMTI_EVENT_BREAKPOINT, + nullptr), + "SetEventNotificationMode"); + + return JNI_OK; +} + +// Resolve the jmethodID while metaspace still has room: FromReflectedMethod can allocate, +// and the whole point of the test is to call setBreakpoint() once metaspace is exhausted. +JNIEXPORT void JNICALL +Java_BreakpointMetaspaceOOM_prepare(JNIEnv* env, jclass cls, jobject method) { + target = env->FromReflectedMethod(method); +} + +// Returns the raw JVMTI error code so the Java side can see exactly what SetBreakpoint +// reported under the out-of-memory condition. +JNIEXPORT jint JNICALL +Java_BreakpointMetaspaceOOM_setBreakpoint(JNIEnv* env, jclass cls) { + return jvmti->SetBreakpoint(target, 0); +} + +JNIEXPORT jint JNICALL +Java_BreakpointMetaspaceOOM_breakpointHits(JNIEnv* env, jclass cls) { + return hits; +} + +} diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java index e6a6e55c7f74..eb5d59792c4c 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java @@ -27,6 +27,7 @@ * @summary Test the clhsdb commands 'printall', 'jstack' on a CDS enabled corefile. * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @requires vm.cds * @requires vm.flavor == "server" diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java index 821ce1356e5d..8222ca8b9539 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java @@ -51,6 +51,7 @@ * @summary Test the clhsdb 'findpc' command with Xcomp on core file * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @requires vm.compMode != "Xcomp" * @requires vm.compiler1.enabled @@ -77,6 +78,7 @@ * @summary Test the clhsdb 'findpc' command w/o Xcomp on core file * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @requires vm.compiler1.enabled * @library /test/lib diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbInspectWithValueObject.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbInspectWithValueObject.java index 9f2e1da67e03..9331f36d6fc5 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbInspectWithValueObject.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbInspectWithValueObject.java @@ -34,7 +34,7 @@ /** * @test - * @bug 8390106 + * @bug 8390106 8381336 * @requires vm.hasSA * @requires vm.gc != "Z" * @requires (os.arch != "riscv64" | !(vm.cpu.features ~= ".*qemu.*")) @@ -77,7 +77,9 @@ public static void main(String[] args) throws Exception { var cmds = List.of( "inspect " + addresses.getProperty("valObj"), "inspect " + addresses.getProperty("nonNullValObj"), - "inspect " + addresses.getProperty("nonFlattenedValObj") + "inspect " + addresses.getProperty("nonFlattenedValObj"), + "inspect " + addresses.getProperty("valObjArray"), + "inspect " + addresses.getProperty("nonFlattenedValObjArray") ); var expStrMap = Map.of( @@ -97,6 +99,30 @@ public static void main(String[] args) throws Exception { cmds.get(2) /* nonFlattenedValObj */ , List.of( "a: 100", "obj: Oop for java/lang/Object" + ), + cmds.get(3) /* valObjArray */ , List.of( + "0:", + "a: 1", + "rec:", + "recA: 10", + "recB: 20", + "nullField: null", + "1: null", + "2:", + "a: 2", + "rec:", + "recA: 30", + "recB: 40", + "nullField: null" + ), + cmds.get(4) /* nonFlattenedValObjArray */ , List.of( + "0:", + "a: 100", + "obj: Oop for java/lang/Object", + "1: null", + "2:", + "a: 200", + "obj: Oop for java/lang/Object" ) ); test.run(theApp.getPid(), cmds, expStrMap, null); diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java index 4079ee6f2b23..b8042a65f17e 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java @@ -47,6 +47,7 @@ * @summary Test clhsdb pmap command on a core file * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @library /test/lib * @run main/othervm/timeout=480 ClhsdbPmap true diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java index 8f06cf755188..1d64f9687b4b 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java @@ -47,6 +47,7 @@ * @summary Test clhsdb pstack command on a core file * @requires vm.hasSA * @requires !vm.ubsan + * @requires !vm.asan * @requires vm.gc != "Z" * @library /test/lib * @run main/othervm/timeout=480 ClhsdbPstack true diff --git a/test/hotspot/jtreg/serviceability/sa/LingeredAppWithValueObject.java b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithValueObject.java index 3015ad60d552..fc3478e34dfe 100644 --- a/test/hotspot/jtreg/serviceability/sa/LingeredAppWithValueObject.java +++ b/test/hotspot/jtreg/serviceability/sa/LingeredAppWithValueObject.java @@ -83,10 +83,25 @@ public NonFlattenedValueObj(int a, Object obj) { private static NonFlattenedValueObj nonFlattenedValObj; + private static ValueObj[] valObjArray; + + private static NonFlattenedValueObj[] nonFlattenedValObjArray; + static { valObj = new ValueObj((byte)1, (byte)10, (byte)20); nonNullValObj = new NonNullValueObj((byte)2, (byte)30, (byte)40); nonFlattenedValObj = new NonFlattenedValueObj(100, new Object()); + + valObjArray = new ValueObj[] { + new ValueObj((byte)1, (byte)10, (byte)20), + null, + new ValueObj((byte)2, (byte)30, (byte)40) + }; + nonFlattenedValObjArray = new NonFlattenedValueObj[] { + new NonFlattenedValueObj(100, new Object()), + null, + new NonFlattenedValueObj(200, new Object()) + }; } public static void main(String[] args) { @@ -95,6 +110,8 @@ public static void main(String[] args) { addresses.setProperty("valObj", String.format("0x%x", wb.getObjectAddress(valObj))); addresses.setProperty("nonNullValObj", String.format("0x%x", wb.getObjectAddress(nonNullValObj))); addresses.setProperty("nonFlattenedValObj", String.format("0x%x", wb.getObjectAddress(nonFlattenedValObj))); + addresses.setProperty("valObjArray", String.format("0x%x", wb.getObjectAddress(valObjArray))); + addresses.setProperty("nonFlattenedValObjArray", String.format("0x%x", wb.getObjectAddress(nonFlattenedValObjArray))); try (var out = Files.newOutputStream(ADDR_FILE_PATH)) { addresses.store(out, null); diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java new file mode 100644 index 000000000000..4c8cfb266967 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/examples/SkipTestExample.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package ir_framework.examples; + +import compiler.lib.ir_framework.*; +import compiler.lib.ir_framework.driver.TestVMException; +import compiler.lib.ir_framework.shared.TestFormatException; + +import jdk.test.lib.Asserts; + +/* + * @test + * @summary Example to show how to skip individual IR tests over commenting them out or problemlisting. + * @library /test/lib / + * @run driver ${test.main.class} + */ + +/** + * When a general jtreg test is failing, there are several options to mitigate that problem including uncommenting the + * problematic part of the test out or just problemlisting. + * + *

        + * The IR Framework introduces another way: Use {@link Skip @Skip} at a {@link Test @Test}-annotated method to exclude + * this test from being executed. + * + *

        + * The IR Framework provides the following additional flag to run those skipped tests on the fly without requiring + * any additional test or IR Framework changes (also see README.md): + *

          + *
        • -DIgnoreSkip=true: The IR Framework executes all tests - including {@link Skip @Skip}-annotated + * tests.

        • + *
        + * + *

        + * Below you find some examples. + * + * @see Skip + */ +public class SkipTestExample { + public static void main(String[] args) { + header("SkipTestExample Tests:"); + TestFramework.run(); + try { + header("SkipTestExample Tests - -DIgnoreSkip=true:"); + TestFramework.runWithFlags("-DIgnoreSkip=true"); + Asserts.fail("Should report TestVMException"); + } catch (TestVMException e) { + // Expected, uncomment to see the output: + //throw e; + } + + try { + header("Violation Tests:"); + TestFramework.run(Violation.class); + Asserts.fail("Should report TestFormatException"); + } catch (TestFormatException e) { + // Expected, uncomment to see the output: + //throw e; + } + } + + private static void header(String header) { + System.out.println(header); + System.out.println("=".repeat(header.length())); + } + + // Skipped test that would otherwise fail. + // - -DIgnoreSkip=true: This test will fail and is reported as such. + @Test + @Skip + public void testWithSkip() { + throw new RuntimeException("skipped"); + } + + @Test + @Skip + public void testForRunner() { + } + + // testWithSkip2() is skipped and thus the IR Framework is not executing this @Run method. + // - -DIgnoreSkip=true: This test will fail and is reported as such. + @Run(test = "testForRunner") + public void runWithSkip() { + testForRunner(); + throw new RuntimeException("skipped"); + } +} + +class Violation { + @Test + @Skip + public void testSkip() {} + + @Test + public void testNotSkipped() {} + + // Even though not all tests that are called from this @Run method are skipped, there is no way to modify the + // code to call testSkip() but avoid calling testNotSkipped(). Therefore, the entire @Run method should be excluded + // which in turn could be undesirable. Therefore, the IR Framework rejects such partially skipped tests with a + // TestFormatException by requiring either all @Test methods to be skipped or none. + @Run(test = {"testSkip", + "testNotSkipped"}) + public void runMultipleTests() { + testSkip(); + testNotSkipped(); + } +} diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java index effb18611962..3df645be17ee 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestBadFormat.java @@ -58,6 +58,7 @@ public static void main(String[] args) { expectTestFormatException(BadRunTests.class); expectTestFormatException(BadSetupTest.class); expectTestFormatException(BadCheckTest.class); + expectTestFormatException(BadSkip.class); expectTestFormatException(BadIRAnnotationBeforeFlagVM.class); expectTestFormatException(BadIRAnnotations.class); expectTestFormatException(BadIRAnnotationsAfterTestVM.class); @@ -777,6 +778,43 @@ public void testInvalidRunWithArgAnnotation() {} public void invalidRunWithArgAnnotation(TestInfo info) {} } +class BadSkip { + @NoFail + @Test + public void test() {} + + @NoFail + @Test + public void test2() {} + + @Skip + public void noTest() {} + + @Run(test = "test") + @Skip + public void noTest2() {} + + @Check(test = "test2") + @Skip + public void noTest3() {} + + @Setup + @Skip + public void noTest4() {} + + @NoFail + @Test + @Skip + public static void testSkipOnlyOneTestWithRunMultiple1() {} + + @FailCount(0) // Combined with runTestSkipOnlyOneTestWithRunMultiple() below + @Test + public static void testSkipOnlyOneTestWithRunMultiple2() {} + + @Run(test = {"testSkipOnlyOneTestWithRunMultiple1", + "testSkipOnlyOneTestWithRunMultiple2"}) + public static void runTestSkipOnlyOneTestWithRunMultiple() {} +} class BadIRAnnotationBeforeFlagVM { diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java index 8e9410cdb8d9..156d8d82184d 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestNoMainExecution.java @@ -30,7 +30,7 @@ * @run driver ${test.main.class} */ -package testlibrary_tests.ir_framework.tests; +package ir_framework.tests; import compiler.lib.ir_framework.*; import jdk.test.lib.Asserts; diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java new file mode 100644 index 000000000000..509f899bd694 --- /dev/null +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestSkip.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package ir_framework.tests; + +import compiler.lib.ir_framework.*; +import compiler.lib.ir_framework.driver.TestVMException; +import compiler.lib.ir_framework.driver.irmatching.IRViolationException; +import compiler.lib.ir_framework.shared.TestFormatException; +import jdk.internal.misc.Unsafe; +import jdk.test.lib.Asserts; + +/* + * @test + * @requires vm.debug == true & vm.compMode != "Xint" & vm.compiler1.enabled & vm.compiler2.enabled & vm.flagless + * @summary Test different custom run tests. + * @modules java.base/jdk.internal.misc:+open + * @library /test/lib /testlibrary_tests / + * @run driver ${test.main.class} + */ + +public class TestSkip { + public static void main(String[] args) { + runGood(); + runMix(); + runViolation(); + runVMCrash(); + runIRVerification(); + } + + private static void runGood() { + TestFramework.run(SkipBasics.class); + } + + private static void runMix() { + try { + TestFramework.run(Mix.class); + Asserts.fail("Should have thrown"); + } catch (TestVMException e) { + Asserts.assertTrue(e.getExceptionInfo().contains("testFailNoSkip() no @Skip")); + } + + TestFramework testFramework = new TestFramework(Mix.class); + testFramework.addFlags("-DIgnoreSkip=true"); + try { + testFramework.start(); + Asserts.fail("Should have thrown"); + } catch (TestVMException e) { + Asserts.assertTrue(e.getExceptionInfo().contains("testFailNoSkip() no @Skip")); + Asserts.assertTrue(e.getExceptionInfo().contains("testFailWithSkip() with @Skip")); + Asserts.assertTrue(e.getExceptionInfo().contains("testWithSingleTest() with @Skip")); + Asserts.assertTrue(e.getExceptionInfo().contains("testWithMultipleTestsFail() with @Skip")); + } + } + + private static void runViolation() { + runViolation(new TestFramework(Violation.class)); + runViolation(new TestFramework(Violation.class).addFlags("-DIgnoreSkip=true")); + } + + private static void runViolation(TestFramework testFramework) { + try { + testFramework.start(); + Asserts.fail("Should throw"); + } catch (TestFormatException e) { + String message = e.getMessage(); + Asserts.assertTrue(message.contains("Violations (2)")); + Asserts.assertTrue(message.contains("@Run-methods with multiple tests can only either skip none or all " + + "methods with @Skip")); + Asserts.assertTrue(message.contains("run1()")); + Asserts.assertTrue(message.contains("two()")); + Asserts.assertTrue(message.contains("run2()")); + Asserts.assertTrue(message.contains("missing")); + } + } + + private static void runVMCrash() { + new TestFramework(VMCrash.class) + .addFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED") + .start(); + + try { + new TestFramework(VMCrash.class) + .addFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED", "-DIgnoreSkip=true") + .start(); + Asserts.fail("Should have thrown"); + } catch (TestVMException e) { + // Expected. + Asserts.assertTrue(e.getExceptionInfo().contains("A fatal error has been detected")); + } + } + + private static void runIRVerification() { + TestFramework.run(IRVerification.class); + + try { + new TestFramework(IRVerification.class) + .addFlags("-DIgnoreSkip=true") + .start(); + Asserts.fail("Should have thrown"); + } catch (IRViolationException e) { + // Expected. + String message = e.getExceptionInfo(); + Asserts.assertTrue(message.contains("Failed IR Rules (3) of Methods (3)")); + Asserts.assertTrue(message.contains("Method \"ir_framework.tests." + + "TestSkip$IRVerification::testFailingIrCustomRunTest")); + Asserts.assertTrue(message.contains("Method \"ir_framework.tests." + + "TestSkip$IRVerification::testFailingIrCheckedTest")); + Asserts.assertTrue(message.contains("Method \"ir_framework.tests." + + "TestSkip$IRVerification::testFailingIrBaseTest")); + } + } + + static class SkipBasics { + @Skip + @Test + public static void testSkip() { + throw new RuntimeException("testSkip() with @Skip"); + } + + @Skip + @Test + public static void testWithSingleTest() { + throw new RuntimeException("testSkipWithRun() with @Skip"); + } + + @Run(test = "testWithSingleTest") + public static void runWithSingleTest() { + try { + testWithSingleTest(); + } catch (RuntimeException e) { + throw new RuntimeException("From @Run", e); + } + } + + @Skip + @Test + public static void testWithMultipleTestsAllSkipFail1() { + throw new RuntimeException("testWithMultipleTestsAllSkipFail1() with @Skip"); + } + + @Skip + @Test + public static void testWithMultipleTestsAllSkipFail2() { + throw new RuntimeException("testWithMultipleTestsAllSkipFail2() with @Skip"); + } + + @Run(test = {"testWithMultipleTestsAllSkipFail1", + "testWithMultipleTestsAllSkipFail2"}) + public static void runWithMultipleTestsAllSkipFail() { + testWithMultipleTestsAllSkipFail1(); + testWithMultipleTestsAllSkipFail2(); + } + } + + static class IRVerification { + static int iFld; + + @Skip + @Test + @IR(failOn = IRNode.STORE_I) + public static void testFailingIrBaseTest() { + iFld = 34; + } + + @Skip + @Test + @IR(failOn = IRNode.STORE_I) + public static void testFailingIrCheckedTest() { + iFld = 34; + } + + @Check(test = "testFailingIrCheckedTest") + public static void checkTest() {} + + @Skip + @Test + @IR(failOn = IRNode.STORE_I) + public static void testFailingIrCustomRunTest() { + iFld = 34; + } + + @Run(test = "testFailingIrCustomRunTest") + public static void runCustomRunTest() { + testFailingIrCustomRunTest(); + } + } + + static class Mix { + @Skip + @Test + public static void testFailWithSkip() { + throw new RuntimeException("testFailWithSkip() with @Skip"); + } + + @Test + public static void testFailNoSkip() { + throw new RuntimeException("testFailNoSkip() no @Skip"); + } + + @Test + public static void testSuccessNoSkip() { + } + + @Test + @Skip + public static void testSuccessWithSkip() { + } + + @Skip + @Test + public static void testWithSingleTest() { + throw new RuntimeException("testWithSingleTest() with @Skip"); + } + + @Run(test = "testWithSingleTest") + public static void runWithSingleTest() { + testWithSingleTest(); + } + + // If one is skipped, + @Skip + @Test + public static void testWithMultipleTestsFail() { + throw new RuntimeException("testWithMultipleTestsFail() with @Skip"); + } + + @Skip + @Test + public static void testWithMultipleTestsSuccess() { + } + + @Run(test = {"testWithMultipleTestsFail", + "testWithMultipleTestsSuccess"}) + public static void runWithMultipleTestsOneFail() { + testWithMultipleTestsFail(); + testWithMultipleTestsSuccess(); + } + } + + static class Violation { + Violation() { + throw new RuntimeException("should not be constructed"); + } + + @Test + @Skip + void one() {} + + // Forbidden, should also be reported with -DIgnoreSkip=true + @Test + void two() {} + + @Run(test = {"one", "two"}) + void run1() { + one(); + two(); + } + + @Test + @Skip + void skipped1() {} + + @Test + @Skip + void skipped2() {} + + @Run(test = {"skipped1", "skipped2", "missing"}) + void run2() { + one(); + two(); + } + } + + static class VMCrash { + @Test + @Skip + public void testCrashVM() { + Unsafe unsafe = Unsafe.getUnsafe(); + unsafe.putAddress(0, 0); // Crash + } + } +} diff --git a/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java b/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java index b27975260bde..ba54f72c8005 100644 --- a/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java +++ b/test/hotspot/jtreg/vmTestbase/jit/t/t105/t105.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,7 @@ * @library /vmTestbase * /test/lib * @run main/othervm -XX:-OmitStackTraceInFastThrow jit.t.t105.t105 - * @run main/othervm -XX:CompileCommand=MemLimit,*.*,0 -XX:-OmitStackTraceInFastThrow -Xbatch -XX:Tier0BackedgeNotifyFreqLog=0 -XX:Tier2BackedgeNotifyFreqLog=0 -XX:Tier3BackedgeNotifyFreqLog=0 -XX:Tier2BackEdgeThreshold=1 -XX:Tier3BackEdgeThreshold=1 -XX:Tier4BackEdgeThreshold=1 jit.t.t105.t105 + * @run main/othervm -XX:-OmitStackTraceInFastThrow -Xbatch -XX:Tier0BackedgeNotifyFreqLog=0 -XX:Tier2BackedgeNotifyFreqLog=0 -XX:Tier3BackedgeNotifyFreqLog=0 -XX:Tier2BackEdgeThreshold=1 -XX:Tier3BackEdgeThreshold=1 -XX:Tier4BackEdgeThreshold=1 jit.t.t105.t105 * * This test must be run with OmitStackTraceInFastThrow disabled to avoid preallocated * exceptions. They don't have the detailed message that this test relies on. diff --git a/test/jdk/ProblemList-enable-preview.txt b/test/jdk/ProblemList-enable-preview.txt index 962cd33c979b..84aa30f843d8 100644 --- a/test/jdk/ProblemList-enable-preview.txt +++ b/test/jdk/ProblemList-enable-preview.txt @@ -34,3 +34,5 @@ ############################################################################# # Value Objects failures start here: + +sun/tools/jhsdb/HeapDumpTestWithActiveProcess.java 8394812 generic-all \ No newline at end of file diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index a9ed48c86434..30509fa70330 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -403,6 +403,10 @@ java/net/MulticastSocket/SetGetNetworkInterfaceTest.java 8308807 aix-ppc6 java/net/MulticastSocket/SetLoopbackMode.java 7122846,8308807 macosx-all,aix-ppc64 java/net/MulticastSocket/SetOutgoingIf.java 8308807 aix-ppc64 java/net/MulticastSocket/Test.java 7145658 macosx-all +java/net/httpclient/ConnectTimeoutTest.java#sync 8393427 macosx-27.0 +java/net/httpclient/ConnectTimeoutTest.java#sync-proxy 8393427 macosx-27.0 +java/net/httpclient/ConnectTimeoutTest.java#async 8393427 macosx-27.0 +java/net/httpclient/ConnectTimeoutTest.java#async-proxy 8393427 macosx-27.0 ############################################################################ @@ -417,6 +421,7 @@ java/nio/channels/DatagramChannel/MulticastSendReceiveTests.java 8144003 macosx- java/nio/channels/DatagramChannel/Promiscuous.java 8144003 macosx-all java/nio/channels/SocketChannel/CloseDuringConnect.java 8375658 macosx-all java/nio/channels/SocketChannel/OpenLeak.java 8375658 macosx-all +java/nio/channels/AsyncCloseAndInterrupt.java 8393427 macosx-27.0 ############################################################################ @@ -458,6 +463,9 @@ sun/security/smartcardio/TestTransmit.java 8039280 generic- sun/security/pkcs11/sslecc/ClientJSSEServerJSSE.java 8316183 linux-ppc64le +sun/security/pkcs11/Provider/CheckRegistration.java 8394846 generic-all +sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java 8394846 generic-all + ############################################################################ # jdk_sound diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 574e1e61ef76..0945c71f39c4 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -189,7 +189,8 @@ jdk_util = \ valhalla_adopted = \ java/util/Collections/AddAll.java \ java/util/Arrays \ - java/util/Collections + java/util/Collections \ + java/util/HashMap # All util components not part of some other util category jdk_util_other = \ diff --git a/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java b/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java index 6b2606365858..3d302793fedf 100644 --- a/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java +++ b/test/jdk/com/sun/net/httpserver/ContextPathMatcherPathPrefixTest.java @@ -222,8 +222,7 @@ private void expect0(int statusCode, String requestLinePrefix) throws IOExceptio // Write the request byte[] requestBytes = String - // `Connection: close` is required for `BufferedReader::readLine` to work. - .format("%s HTTP/1.1\r\nConnection: close\r\n\r\n", requestLinePrefix) + .format("%s HTTP/1.0\r\n\r\n", requestLinePrefix) .getBytes(CHARSET); outputStream.write(requestBytes); outputStream.flush(); diff --git a/test/jdk/com/sun/net/httpserver/Test10.java b/test/jdk/com/sun/net/httpserver/Test10.java index a256acbca118..5eb8b1a7cd3f 100644 --- a/test/jdk/com/sun/net/httpserver/Test10.java +++ b/test/jdk/com/sun/net/httpserver/Test10.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,9 +25,11 @@ * @test * @bug 7005016 * @summary pit jdk7 b121 sqe test jhttp/HttpServer150013 failing - * @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3 Test10 * @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3 - * -Djava.net.preferIPv6Addresses Test10 + * -Dsun.net.httpserver.disableHostValidation=true Test10 + * @run main/othervm -Dsun.net.httpserver.clockTick=1000 -Dsun.net.httpserver.idleInterval=3 + * -Djava.net.preferIPv6Addresses + * -Dsun.net.httpserver.disableHostValidation=true Test10 */ import com.sun.net.httpserver.*; @@ -42,6 +44,7 @@ */ public class Test10 extends Test { + static volatile boolean handlerInvoked; public static void main (String[] args) throws Exception { System.out.print ("Test10: "); Handler handler = new Handler(); @@ -66,10 +69,10 @@ public static void main (String[] args) throws Exception { } static class Handler implements HttpHandler { - volatile int invocation = 0; public void handle (HttpExchange t) throws IOException { + handlerInvoked = true; InputStream is = t.getRequestBody(); while (is.read() != -1); Headers map = t.getRequestHeaders(); @@ -89,6 +92,9 @@ public static void doClient (int port) throws Exception { int c; byte[] b = new byte [1024]; while ((c=is.read(b)) != -1) ; + if (!handlerInvoked) { + throw new RuntimeException("Handler not invoked"); + } is.close(); socket.close(); } diff --git a/test/jdk/com/sun/net/httpserver/Test3.java b/test/jdk/com/sun/net/httpserver/Test3.java index bbb307dc9f27..50c364c9cc40 100644 --- a/test/jdk/com/sun/net/httpserver/Test3.java +++ b/test/jdk/com/sun/net/httpserver/Test3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -125,13 +125,13 @@ static void checkBody (InputStream is, String cmp) throws IOException { "HTTP/1.1 200 OK.*Xtest: 3.*"; public static void doClient (int port) throws Exception { - String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+ + String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+ "\r\n" +body1 + - "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+ + "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+ "\r\n"+ body2 + - "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+ body3 + - "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+body4; Socket socket = new Socket (InetAddress.getLoopbackAddress(), port); diff --git a/test/jdk/com/sun/net/httpserver/Test4.java b/test/jdk/com/sun/net/httpserver/Test4.java index ba2f5b4ec825..033425fdad75 100644 --- a/test/jdk/com/sun/net/httpserver/Test4.java +++ b/test/jdk/com/sun/net/httpserver/Test4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -124,13 +124,13 @@ static void checkBody (InputStream is, String cmp) throws IOException { "HTTP/1.1 200 OK.*Xtest: 3.*"; public static void doClient (int port) throws Exception { - String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+ + String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+ "\r\n" +body1 + - "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+ + "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+ "\r\n"+ body2 + - "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+ body3 + - "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+body4; Socket socket = new Socket (InetAddress.getLoopbackAddress(), port); diff --git a/test/jdk/com/sun/net/httpserver/Test5.java b/test/jdk/com/sun/net/httpserver/Test5.java index 68b7d7b357bc..91d18a7d934d 100644 --- a/test/jdk/com/sun/net/httpserver/Test5.java +++ b/test/jdk/com/sun/net/httpserver/Test5.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -123,13 +123,13 @@ static void checkBody (InputStream is, String cmp) throws IOException { "HTTP/1.1 200 OK.*Xtest: 3.*"; public static void doClient (int port) throws Exception { - String s = "GET /test/1.html HTTP/1.1\r\nContent-length: 20\r\n"+ + String s = "GET /test/1.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 20\r\n"+ "\r\n" +body1 + - "GET /test/2.html HTTP/1.1\r\nContent-length: 30\r\n"+ + "GET /test/2.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 30\r\n"+ "\r\n"+ body2 + - "GET /test/3.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/3.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+ body3 + - "GET /test/4.html HTTP/1.1\r\nContent-length: 10\r\n"+ + "GET /test/4.html HTTP/1.1\r\nHost: localhost\r\nContent-length: 10\r\n"+ "\r\n"+body4; Socket socket = new Socket (InetAddress.getLoopbackAddress(), port); diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6361557.java b/test/jdk/com/sun/net/httpserver/bugs/B6361557.java index a92bb9adfdc5..a8c274e9b100 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/B6361557.java +++ b/test/jdk/com/sun/net/httpserver/bugs/B6361557.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,8 @@ import java.nio.*; import java.nio.channels.*; import java.net.*; +import java.util.concurrent.atomic.AtomicInteger; + import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY; /** @@ -49,12 +51,14 @@ public class B6361557 { public static boolean error = false; static final int NUM = 1000; + static final AtomicInteger count = new AtomicInteger(0); static class Handler implements HttpHandler { int invocation = 1; public void handle (HttpExchange t) throws IOException { + count.getAndIncrement(); InputStream is = t.getRequestBody(); Headers map = t.getRequestHeaders(); Headers rmap = t.getResponseHeaders(); @@ -65,7 +69,9 @@ public void handle (HttpExchange t) } } - final static String request = "GET /test/foo.html HTTP/1.1\r\nContent-length: 0\r\n\r\n"; + final static String request = "GET /test/foo.html HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Content-length: 0\r\n\r\n"; final static ByteBuffer requestBuf = ByteBuffer.wrap(request.getBytes()); public static void main (String[] args) throws Exception { @@ -127,6 +133,9 @@ public static void main (String[] args) throws Exception { break; } } + if (count.get() != NUM) { + throw new RuntimeException("Unexpected number of handled requests: " +count.get()); + } server.stop(0); selector.close(); executor.shutdown (); diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6393710.java b/test/jdk/com/sun/net/httpserver/bugs/B6393710.java index 68a3bac259f3..7d7206eaa817 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/B6393710.java +++ b/test/jdk/com/sun/net/httpserver/bugs/B6393710.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,11 +58,13 @@ public class B6393710 { */ static String cmd = "POST /test/foo HTTP/1.1"+CRLF+ + "Host: localhost"+CRLF+ "Content-Length: 22"+CRLF+ "Pragma: no-cache"+CRLF+ "Cache-Control: no-cache"+CRLF+ CRLF+ ""+ "POST /test/foo HTTP/1.1"+CRLF+ + "Host: localhost"+CRLF+ "Content-Length: 22"+CRLF+ "Pragma: no-cache"+CRLF+ "Authorization: Basic ZnJlZDpmcmVkcGFzc3dvcmQ="+CRLF+ diff --git a/test/jdk/com/sun/net/httpserver/bugs/B6433018.java b/test/jdk/com/sun/net/httpserver/bugs/B6433018.java index 1f6876bf8727..ad8c863cd5f9 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/B6433018.java +++ b/test/jdk/com/sun/net/httpserver/bugs/B6433018.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,6 +46,7 @@ public class B6433018 { static final String cmd = "POST /test/item HTTP/1.1"+CRLF+ + "Host: araku:9999"+CRLF+ "Keep-Alive: 300"+CRLF+ "Proxy-Connection: keep-alive"+CRLF+ "Content-Type: text/xml"+CRLF+ diff --git a/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java b/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java index 95a3d1787df8..1ea1e62be8bf 100644 --- a/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java +++ b/test/jdk/com/sun/net/httpserver/bugs/TruncatedRequestBody.java @@ -112,7 +112,8 @@ public static void main(String[] args) throws IOException, InterruptedException // Test 1: fixed length Socket sock = new Socket(loopback, port); - String s1 = "POST /foo HTTP/1.1\r\nContent-length: 200000\r\n" + String s1 = "POST /foo HTTP/1.1\r\nHost: localhost\r\n" + + "Content-length: 200000\r\n" + "\r\nfoo bar99"; OutputStream os = sock.getOutputStream(); @@ -123,7 +124,8 @@ public static void main(String[] args) throws IOException, InterruptedException // Test 2: chunked - String s2 = "POST /foo HTTP/1.1\r\nTransfer-encoding: chunked\r\n\r\n" + + String s2 = "POST /foo HTTP/1.1\r\nHost: localhost\r\n" + + "Transfer-encoding: chunked\r\n\r\n" + "100\r\nFoo bar"; sock = new Socket(loopback, port); os = sock.getOutputStream(); diff --git a/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java b/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java index 0b060178b6c9..e3368b0304ab 100644 --- a/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java +++ b/test/jdk/com/sun/net/httpserver/property/DrainAmountPropertyTest.java @@ -170,7 +170,7 @@ private static void sendRequest( throws IOException { LOGGER.info("Sending request (target=%s, bodyLength=%s)".formatted(requestTarget, requestBodyLength)); var socketOutput = clientSocket.getOutputStream(); - var request = "%s %s HTTP/1.1%s\r\n\r\n".formatted( + var request = "%s %s HTTP/1.1%s\r\nHost: localhost\r\n\r\n".formatted( requestMethod, requestTarget, requestBodyLength == null ? "" : "\r\nContent-Length: " + requestBodyLength); socketOutput.write(request.getBytes(US_ASCII)); diff --git a/test/jdk/java/awt/font/GlyphVector/GlyphVectorIOBE.java b/test/jdk/java/awt/font/GlyphVector/GlyphVectorIOBE.java new file mode 100644 index 000000000000..d381cb476e23 --- /dev/null +++ b/test/jdk/java/awt/font/GlyphVector/GlyphVectorIOBE.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8392424 + */ + +import java.awt.Font; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphVector; +import java.awt.geom.Point2D; + +public class GlyphVectorIOBE { + + static Font font = new Font(Font.DIALOG, Font.PLAIN, 12); + static FontRenderContext frc = new FontRenderContext(null, true, true); + static GlyphVector gv = font.createGlyphVector(frc, "A"); + + public static void main(String[] args) { + runTests(-1); + runTests(gv.getNumGlyphs()); + runTests(2); + runTests(Integer.MIN_VALUE); + + // test no exception for these cases. + gv.getGlyphPosition(gv.getNumGlyphs()); + gv.setGlyphPosition(gv.getNumGlyphs(), new Point2D.Float()); + } + + static void runTests(int ix) { + if (ix != gv.getNumGlyphs()) { + test(() -> gv.getGlyphPosition(ix)); + test(() -> gv.setGlyphPosition(ix, new Point2D.Float())); + } + test(() -> gv.getGlyphOutline(ix)); + test(() -> gv.getGlyphOutline(ix, 0f, 0f)); + } + + static void test(Runnable r) { + try { + r.run(); + throw new RuntimeException("Test failed for " + r); + } catch (IndexOutOfBoundsException e) { + } + } +} diff --git a/test/jdk/java/lang/Object/AbstractValueWithFinalizer.jasm b/test/jdk/java/lang/Object/AbstractValueWithFinalizer.jasm new file mode 100644 index 000000000000..6cf239e73398 --- /dev/null +++ b/test/jdk/java/lang/Object/AbstractValueWithFinalizer.jasm @@ -0,0 +1,34 @@ +/* Javac reports an error when a value class declares a method overriding + * Object::finalize. The VM must still handle such class files correctly, so + * this class is supplied as jasm. It corresponds to the following source: + * + * abstract value class AbstractValueWithFinalizer { + * CountDownLatch latch; + * AbstractValueWithFinalizer(CountDownLatch latch) { + * this.latch = latch; + * } + * @Override + * protected void finalize() { + * latch.countDown(); + * } + * } + */ +abstract class AbstractValueWithFinalizer version 72:65535 { + final strict Field latch:"Ljava/util/concurrent/CountDownLatch;"; + + Method "":"(Ljava/util/concurrent/CountDownLatch;)V" stack 2 locals 2 { + aload_0; + aload_1; + putfield Field latch:"Ljava/util/concurrent/CountDownLatch;"; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + protected Method finalize:"()V" stack 1 locals 1 { + aload_0; + getfield Field latch:"Ljava/util/concurrent/CountDownLatch;"; + invokevirtual Method java/util/concurrent/CountDownLatch.countDown:"()V"; + return; + } +} diff --git a/test/jdk/java/lang/Object/ValueObjects.java b/test/jdk/java/lang/Object/ValueObjects.java index 50122d1a401d..f2fcaeac2726 100644 --- a/test/jdk/java/lang/Object/ValueObjects.java +++ b/test/jdk/java/lang/Object/ValueObjects.java @@ -26,6 +26,7 @@ * @summary Basic test of Object methods on value objects * @enablePreview * @library /test/lib + * @compile ValueWithFinalizer.jasm AbstractValueWithFinalizer.jasm ValueObjects.java * @run junit ${test.main.class} */ @@ -102,22 +103,16 @@ class Holder { /** * Test that the finalize method on a value class is not invoked by the GC. + * + * ValueWithFinalizer is supplied as ValueWithFinalizer.jasm rather than a local value + * class here, since javac no longer allows a value class to declare a method that + * overrides Object::finalize (JDK-8339188). See the jasm file for the corresponding + * source. */ @Test void testValueClassFinalize() throws Exception { - value class V { - CountDownLatch latch; - V(CountDownLatch latch) { - this.latch = latch; - } - @Override - protected void finalize() { - latch.countDown(); - } - } - var latch = new TimeoutAdjustingLatch(); - var obj = new V(latch); + var obj = new ValueWithFinalizer(latch); obj = null; for (int i = 0; i < 3; i++) { System.gc(); @@ -128,20 +123,15 @@ protected void finalize() { /** * Test that the finalize method on an abstract value value is not invoked by the GC. + * + * AbstractValueWithFinalizer is supplied as AbstractValueWithFinalizer.jasm rather than + * a local abstract value class here, since javac no longer allows a value class to + * declare a method that overrides Object::finalize (JDK-8339188). See the jasm file for + * the corresponding source. */ @Test void testAbstractValueClassFinalize() throws Exception { - abstract value class AV { - CountDownLatch latch; - AV(CountDownLatch latch) { - this.latch = latch; - } - @Override - protected void finalize() { - latch.countDown(); - } - } - /*identity*/ class C extends AV { + /*identity*/ class C extends AbstractValueWithFinalizer { C(CountDownLatch latch) { super(latch); } diff --git a/test/jdk/java/lang/Object/ValueWithFinalizer.jasm b/test/jdk/java/lang/Object/ValueWithFinalizer.jasm new file mode 100644 index 000000000000..4ef9d09a53a9 --- /dev/null +++ b/test/jdk/java/lang/Object/ValueWithFinalizer.jasm @@ -0,0 +1,34 @@ +/* Javac reports an error when a value class declares a method overriding + * Object::finalize. The VM must still handle such class files correctly, so + * this class is supplied as jasm. It corresponds to the following source: + * + * value class ValueWithFinalizer { + * CountDownLatch latch; + * ValueWithFinalizer(CountDownLatch latch) { + * this.latch = latch; + * } + * @Override + * protected void finalize() { + * latch.countDown(); + * } + * } + */ +final class ValueWithFinalizer version 72:65535 { + final strict Field latch:"Ljava/util/concurrent/CountDownLatch;"; + + Method "":"(Ljava/util/concurrent/CountDownLatch;)V" stack 2 locals 2 { + aload_0; + aload_1; + putfield Field latch:"Ljava/util/concurrent/CountDownLatch;"; + aload_0; + invokespecial Method java/lang/Object."":"()V"; + return; + } + + protected Method finalize:"()V" stack 1 locals 1 { + aload_0; + getfield Field latch:"Ljava/util/concurrent/CountDownLatch;"; + invokevirtual Method java/util/concurrent/CountDownLatch.countDown:"()V"; + return; + } +} diff --git a/test/jdk/java/lang/ScopedValue/StressStackOverflow.java b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java index 5014a8eeae94..4b34c346d3ad 100644 --- a/test/jdk/java/lang/ScopedValue/StressStackOverflow.java +++ b/test/jdk/java/lang/ScopedValue/StressStackOverflow.java @@ -24,26 +24,22 @@ /* * @test id=default * @summary Stress ScopedValue stack overflow recovery path - * @enablePreview * @run main/othervm/timeout=300 ${test.main.class} */ /* * @test id=no-TieredCompilation - * @enablePreview * @run main/othervm/timeout=300 -XX:-TieredCompilation ${test.main.class} */ /* * @test id=TieredStopAtLevel1 - * @enablePreview * @run main/othervm/timeout=300 -XX:TieredStopAtLevel=1 ${test.main.class} */ /* * @test id=no-vmcontinuations * @requires vm.continuations - * @enablePreview * @run main/othervm/timeout=300 -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations ${test.main.class} */ diff --git a/test/jdk/java/lang/Thread/virtual/stress/PingPong.java b/test/jdk/java/lang/Thread/virtual/stress/PingPong.java index 2266d192fcaa..e9aea22e7a3a 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/PingPong.java +++ b/test/jdk/java/lang/Thread/virtual/stress/PingPong.java @@ -35,6 +35,20 @@ * @run main PingPong LTQ 500000 */ +/* + * @test id=sq-no-lse + * @summary Stress test virtual threads with a SynchronousQueue without LSE + * @requires os.arch == "aarch64" & vm.debug != true + * @run main/othervm -XX:-UseLSE PingPong SQ 500000 + */ + +/* + * @test id=ltq-no-lse + * @summary Stress test virtual threads with a LinkedTransferQueue without LSE + * @requires os.arch == "aarch64" & vm.debug != true + * @run main/othervm -XX:-UseLSE PingPong LTQ 500000 + */ + import java.time.Duration; import java.time.Instant; import java.util.concurrent.SynchronousQueue; diff --git a/test/jdk/java/lang/Thread/virtual/stress/Skynet.java b/test/jdk/java/lang/Thread/virtual/stress/Skynet.java index 06f3b5ff235d..38cf00fbca09 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/Skynet.java +++ b/test/jdk/java/lang/Thread/virtual/stress/Skynet.java @@ -37,6 +37,14 @@ * -XX:+ZVerifyOops -XX:ZCollectionInterval=0.01 -Xmx1500m Skynet */ +/* + * @test id=no-lse + * @summary Stress test virtual threads with a variation of the Skynet 1M benchmark without LSE + * @requires os.arch == "aarch64" & vm.continuations + * @requires !vm.debug | vm.gc != "Z" + * @run main/othervm/timeout=1600 -XX:-UseLSE -Xmx1500m Skynet 2 + */ + import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; diff --git a/test/jdk/java/lang/management/MemoryMXBean/CollectionUsageThreshold.java b/test/jdk/java/lang/management/MemoryMXBean/CollectionUsageThreshold.java index 33f18a4e533c..7b1d927b7405 100644 --- a/test/jdk/java/lang/management/MemoryMXBean/CollectionUsageThreshold.java +++ b/test/jdk/java/lang/management/MemoryMXBean/CollectionUsageThreshold.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -72,7 +72,11 @@ public class CollectionUsageThreshold { */ public static void main(String a[]) throws Throwable { final String main = "CollectionUsageThreshold$TestMain"; - RunUtil.runTestKeepGcOpts(main); + // CollectionUsageThreshold$TestMain expects only System.gc() triggered + // GCs. ZGC's Adaptive Heap Sizing thus requires some buffer to not + // trigger heuristic GCs. Similarly this test is incompatible with any + // period GC heuristics / options like ZCollectionInterval. + RunUtil.runTestKeepGcOpts(main, "-Xms128m", "-Xmx512m"); RunUtil.runTestClearGcOpts(main, "-XX:+UseSerialGC"); RunUtil.runTestClearGcOpts(main, "-XX:+UseParallelGC"); RunUtil.runTestClearGcOpts(main, "-XX:+UseG1GC"); @@ -259,9 +263,11 @@ private void checkResult() throws InterruptedException, BrokenBarrierException { } private void fail(String msg) { - // reset the barrier to cause BrokenBarrierException to avoid hanging - barrier.reset(); - throw new RuntimeException(msg); + System.out.println("Checker failed: " + msg); + // We cannot simply do a barrier.reset() here as the main thread may + // not yet have reached the barrier yet. For example in the case we + // got an unexpected GC. + System.exit(-1); } } } diff --git a/test/jdk/java/net/SocketOption/OptionsTest.java b/test/jdk/java/net/SocketOption/OptionsTest.java index 004b94d5c63b..d2f64d48dcb5 100644 --- a/test/jdk/java/net/SocketOption/OptionsTest.java +++ b/test/jdk/java/net/SocketOption/OptionsTest.java @@ -50,6 +50,10 @@ static Test create(SocketOption option, T value) { return new Test(option, value); } + @Override + public String toString() { + return "Test SocketOption: " + option + " with value: " + value; + } } // The tests set the option using the new API, read back the set value @@ -131,6 +135,7 @@ static void testEqual(SocketOption option, T value1, T value2) { static void test(Socket s, Test test) throws Exception { SocketOption option = test.option; + System.out.println(s.getClass().getName() + " - " + test); s.setOption(option, test.value); T value1 = s.getOption(test.option); T value2 = (T) legacyGetOption(Socket.class, s, test.option); @@ -139,6 +144,7 @@ static void test(Socket s, Test test) throws Exception { static void test(ServerSocket ss, Test test) throws Exception { SocketOption option = test.option; + System.out.println(ss.getClass().getName() + " - " + test); ss.setOption(option, test.value); T value1 = ss.getOption(test.option); T value2 = (T) legacyGetOption(ServerSocket.class, ss, test.option); @@ -147,6 +153,7 @@ static void test(ServerSocket ss, Test test) throws Exception { static void test(DatagramSocket ds, Test test) throws Exception { SocketOption option = test.option; + System.out.println(ds.getClass().getName() + " - " + test); ds.setOption(option, test.value); T value1 = ds.getOption(test.option); T value2 = (T) legacyGetOption(ds.getClass(), ds, test.option); diff --git a/test/jdk/java/net/httpclient/http2/ConnectionFlowControlTest.java b/test/jdk/java/net/httpclient/http2/ConnectionFlowControlTest.java index 36c46ed3aba9..459d3c7a223f 100644 --- a/test/jdk/java/net/httpclient/http2/ConnectionFlowControlTest.java +++ b/test/jdk/java/net/httpclient/http2/ConnectionFlowControlTest.java @@ -162,6 +162,7 @@ void test(String uri) throws Exception { is.readAllBytes(); } System.out.printf("%s did not fail: %s%n", query, response.statusCode()); + responses.remove(query, response); } catch (AssertionError t1) { // since we won't pull all responses, the client // will not exit unless we ask it to shutdown now. @@ -189,6 +190,21 @@ void test(String uri) throws Exception { t0 = t0.getCause(); } t = t0; + // make sure to pull on the already received responses + // if any - otherwise attempting to close the client will + // block forever; This may happen if this line above throws: + // var response = cc.send(request, BodyHandlers.ofInputStream()); + if (!responses.isEmpty() && !client.isTerminated()) { + for (var e : responses.entrySet()) { + System.out.println("Pulling response: " + e.getKey()); + try (var is = e.getValue().body()) { + is.readAllBytes(); + System.out.println("Got response for: " + e.getKey()); + } catch (Exception x) { + System.out.println("Got exception for: " + e.getKey() + ": " + x); + } + } + } } if (t == null) { // we could fail here if we haven't waited long enough diff --git a/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java b/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java index 83237bf76c00..a94d42c41434 100644 --- a/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java +++ b/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java @@ -34,6 +34,7 @@ import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodyHandlers; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -41,14 +42,16 @@ import java.util.List; import java.util.Map; import java.util.Random; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLServerSocket; +import jdk.internal.net.http.frame.DataFrame; +import jdk.internal.net.http.frame.FramesEncoder; import jdk.internal.net.http.frame.HeadersFrame; import jdk.internal.net.http.frame.Http2Frame; import jdk.internal.net.http.frame.SettingsFrame; @@ -62,14 +65,17 @@ import org.junit.jupiter.api.Test; import static java.net.http.HttpClient.Builder.NO_PROXY; import static java.net.http.HttpClient.Version.HTTP_2; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; /* * @test - * @bug 8385131 + * @bug 8385131 8394607 * @summary Verifies that if the HttpClient receives a GOAWAY frame on a HTTP/2 connection, * then the HttpClient terminates the connection as promptly as appropriate instead - * of relying on the idle connection management to do so. + * of relying on the idle connection management to do so. Also verifies that if + * the server closes the connection first, the client can still receive the response + * bytes after the connection is closed. * @library /test/lib * @build jdk.test.lib.net.SimpleSSLContext jdk.test.lib.RandomFactory jdk.test.lib.net.URIBuilder * @comment An arbitrary high value for idle connection timeout to prevent idle @@ -84,14 +90,22 @@ class H2GoAwayPromptConnectionClose { private static final String REQUEST_PATH = "/" + H2GoAwayPromptConnectionClose.class.getSimpleName(); + private static final String XKEY = "x-send-response-and-close"; + private static final String RESPONSE = """ + Lorem ipsum dolor sit amet, + caesarem legato alacrem. + """; + private static final Random random = RandomFactory.getRandom(); + private static final SSLContext sslCtx = SimpleSSLContext.findSSLContext(); private static final ExecutorService executor = Executors.newCachedThreadPool(); private static HttpClient client; private static MinimalH2OnlyServer server; - // a latch that is counted down when the server notices a EOF on the connection's input stream - // due to the client closing the connection. - private static final CountDownLatch connTerminationLatch = new CountDownLatch(1); + // a semaphore that is released when the server notices an EOF on the connection's + // input stream due to the client closing the connection, or when the server + // closes the connection without waiting for the client + private static final Semaphore connTerminationLatch = new Semaphore(0); @BeforeAll static void beforeAll() throws Exception { @@ -142,6 +156,7 @@ void testConnectionTermination() throws Exception { .port(serverAddr.getPort()) .path(REQUEST_PATH) .build(); + assertEquals(0, connTerminationLatch.availablePermits()); final HttpRequest req = HttpRequest.newBuilder().version(HTTP_2).uri(reqURI).build(); System.err.println("issuing request: " + req); final HttpResponse resp = client.send(req, BodyHandlers.ofString()); @@ -149,11 +164,71 @@ void testConnectionTermination() throws Exception { assertEquals(HTTP_2, resp.version(), "unexpected HTTP version in response"); System.err.println("awaiting connection termination"); - connTerminationLatch.await(); + connTerminationLatch.acquire(); + } + + /* + * Issues a "https" HTTP/2 request and expects the request to complete normally. + * On the server side, in addition to sending the HTTP/2 response, the server also sends + * a GOAWAY frame to the client. In this scenario, the server sends a response body + * composed of several DATA frames, then immediately closes the connection after + * having sent the response body bytes and GOAWAY. + * The client waits for the server to close the connection, then waits for a short + * delay before starting to pull the response body bytes from the HttpResponseInputStream. + * The test completes successfully only if the client is able to pull all the response bytes + * from the input stream, despite the server having already closed the connection. + * If it fails to pull all the bytes, then it's a sign that the exchange was + * cancelled immediately on reception of the connection FIN, despite having buffered all + * the response bytes. + * This scenario will usually fail without the fix for JDK-8394607. + */ + @Test + void testBufferedResponseReceived() throws Exception { + System.out.println("testBufferedResponseReceived"); + final InetSocketAddress serverAddr = server.getServerAddress(); + final URI reqURI = URIBuilder.newBuilder() + .scheme("https") + .host(serverAddr.getAddress()) + .port(serverAddr.getPort()) + .path(REQUEST_PATH) + .build(); + final HttpRequest req = HttpRequest.newBuilder().version(HTTP_2) + .uri(reqURI) + .header(XKEY, "yes") + .build(); + for (int i=0; i < 5; i++) { + assertEquals(0, connTerminationLatch.availablePermits()); + System.out.printf("issuing request[%s]: %s%n", i, req); + System.err.printf("issuing request[%s]: %s%n", i, req); + final HttpResponse resp = client.send(req, BodyHandlers.ofInputStream()); + assertEquals(200, resp.statusCode(), "unexpected status code"); + assertEquals(HTTP_2, resp.version(), "unexpected HTTP version in response"); + System.out.println("awaiting connection termination"); + System.err.println("awaiting connection termination"); + connTerminationLatch.acquire(); + + // At this point the server has closed the connection. + // Wait a little bit to give a chance to the client to + // observe connection closure, since having closed the + // connection from the server side doesn't guarantee that + // the TCP FIN has reached the client side and has already + // been processed. + Thread.sleep(random.nextLong(100, 1000)); + + // Start pulling the bytes from the input stream. The bytes + // should have been all received and buffered, so it should + // be possible to pull them even though the connection was closed + System.out.println("Pulling response bytes"); + System.err.println("Pulling response bytes"); + byte[] bytes = resp.body().readAllBytes(); + assertArrayEquals(RESPONSE.getBytes(StandardCharsets.UTF_8), bytes); + System.out.printf("Got expected response for request[%s]%n", i); + System.err.printf("Got expected response for request[%s]%n", i); + } } /* - * The test requires fine grained control over when the server sends the GOAWAY frame + * The test requires fine-grained control over when the server sends the GOAWAY frame * over the HTTP/2 connection and also to detect that the client has closed the underlying * connection. So we read/write HTTP/2 protocol messages directly over a * ServerSocket/Socket pair, instead of using the HTTP servers in the test library. @@ -210,7 +285,6 @@ public void close() throws IOException { // Processes the data on a single HTTP/2 socket connection private static final class Handler implements Runnable { - private static final Random random = RandomFactory.getRandom(); // number of bytes that are taken by the (common) Length and Type fields of // a HTTP/2 frame private static final int SIZE_OF_LENGTH_AND_TYPE_FIELDS = 9; @@ -224,6 +298,8 @@ private static final class Handler implements Runnable { private final Socket socket; private final String logId; + private final FramesEncoder framesEncoder = new FramesEncoder(); + private record ParsedRequest(int streamId, Map> headers) { /* @@ -236,6 +312,17 @@ private String getRequestPath() { } return vals.getFirst(); } + + // the XKEY header indicates whether the client + // wants the server to send response bytes and + // immediately close the connection after that + private String getXKey() { + final List vals = headers.get(XKEY); + if (vals == null || vals.isEmpty()) { + return null; + } + return vals.getFirst(); + } } private Handler(final Socket socket) { @@ -246,6 +333,7 @@ private Handler(final Socket socket) { private void log(final String msg) { System.err.println("[" + this.logId + "] " + msg); + System.out.println("[" + this.logId + "] " + msg); } @Override @@ -283,6 +371,14 @@ private void handleSocketConnection() throws IOException { throw new IOException("unexpected request path " + reqPath); } log("received HTTP/2 request " + req.headers + " on stream " + req.streamId); + + // if XKEY is "yes" we will send a response body and close + // the socket from the server side immediately after + log(XKEY +": " + req.getXKey()); + final String responseBody = "yes".equals(req.getXKey()) + ? RESPONSE + : null; + // send the HTTP/2 response for the stream as well as a GOAWAY frame on the connection. // we randomly decide which one gets sent first and it shouldn't matter // which order they are sent, the end result for the client should be the same @@ -292,16 +388,25 @@ private void handleSocketConnection() throws IOException { if (sendGoAwayFirst) { sendGoAway(req.streamId); log("sent GOAWAY"); - sendResponse(req.streamId); + sendResponse(req.streamId, responseBody); log("sent a HTTP/2 response"); } else { - sendResponse(req.streamId); + sendResponse(req.streamId, responseBody); log("sent a HTTP/2 response"); sendGoAway(req.streamId); log("sent GOAWAY"); } - // wait for the client to close the connection - waitForEOF(); + + if (responseBody == null) { + // testConnectionTermination: + // wait for the client to close the connection + waitForEOF(); + } else { + // testBufferedResponseReceived: + // close the connection and notify the client + socket.close(); + connTerminationLatch.release(); + } } private void readClientPreface() throws IOException { @@ -460,7 +565,7 @@ private void sendGoAway(final int lastStreamId) throws IOException { out.flush(); } - private void sendResponse(final int streamId) throws IOException { + private void sendResponse(final int streamId, String response) throws IOException { // HPACK encoder final Encoder encoder = new Encoder(MAX_HEADER_TABLE_CAPACITY); encoder.header(":status", "200"); @@ -477,7 +582,11 @@ private void sendResponse(final int streamId) throws IOException { frame[1] = (byte) ((fieldBlock.length >> 8) & 0xFF); frame[2] = (byte) (fieldBlock.length & 0xFF); frame[3] = 0x01; // HEADERS frame type - frame[4] = 0x05; // flags = END_STREAM (0x01) | END_HEADERS (0x04) + if (response == null) { + frame[4] = 0x05; // flags = END_STREAM (0x01) | END_HEADERS (0x04) + } else { + frame[4] = 0x04; // flags = END_HEADERS (0x04) + } frame[5] = (byte) ((streamId >> 24) & 0x7F); frame[6] = (byte) ((streamId >> 16) & 0xFF); frame[7] = (byte) ((streamId >> 8) & 0xFF); @@ -488,6 +597,35 @@ private void sendResponse(final int streamId) throws IOException { final OutputStream out = socket.getOutputStream(); out.write(frame); out.flush(); + if (response != null) { + writeResponseBody(out, streamId, response); + } + } + + private void writeResponseBody(OutputStream out, int streamId, String response) throws IOException { + log("Sending response body"); + byte[] bytes = response.getBytes(StandardCharsets.UTF_8); + ByteBuffer payload = ByteBuffer.wrap(bytes); + assert bytes.length > 10 : "response body needs more than 10 bytes"; + // send body in several data frames, by chunks of 10 bytes + for (int i = 0 ; i < bytes.length ; i += 10) { + payload.position(i); + payload.limit(Math.min(i + 10, bytes.length)); + int flags = payload.limit() < bytes.length + ? 0 + : DataFrame.END_STREAM; + DataFrame dataFrame = new DataFrame(streamId, flags, payload); + log("Sending DataFrame: " + dataFrame); + for (ByteBuffer buffer : framesEncoder.encodeFrame(dataFrame)) { + if (buffer.hasRemaining()) { + byte[] towrite = new byte[buffer.remaining()]; + log("Writing %s bytes".formatted(towrite.length)); + buffer.get(towrite); + out.write(towrite); + out.flush(); + } + } + } } private void waitForEOF() throws IOException { @@ -495,7 +633,7 @@ private void waitForEOF() throws IOException { final Instant start = Instant.now(); this.socket.getInputStream().readAllBytes(); // let the test know that the connection has been terminated - connTerminationLatch.countDown(); + connTerminationLatch.release(); final Instant end = Instant.now(); log("took " + Duration.between(start, end) + " to reach EOF"); } diff --git a/test/jdk/java/net/httpclient/http3/H3RespondGoAwayAndClose.java b/test/jdk/java/net/httpclient/http3/H3RespondGoAwayAndClose.java new file mode 100644 index 000000000000..722c3ba03481 --- /dev/null +++ b/test/jdk/java/net/httpclient/http3/H3RespondGoAwayAndClose.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8394607 + * @library /test/lib /test/jdk/java/net/httpclient/lib + * @build jdk.test.lib.net.SimpleSSLContext jdk.httpclient.test.lib.common.TestUtil + * jdk.httpclient.test.lib.http2.Http2TestServer + * @run junit/othervm ${test.main.class} + * @summary This test verifies that if a server sends a GOAWAY and/or + * closes the connection right after sending response body bytes, + * the client will still be able to pull the response bytes if + * they have been buffered. + */ + + +// Interesting additional settings for debugging and manual testing: +// ----------------------------------------------------------------- +// -Djdk.httpclient.HttpClient.log=requests,errors,quic:retransmit:control,http3 +// -Djdk.internal.httpclient.debug=err + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Builder; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.BodyHandlers; +import java.nio.charset.StandardCharsets; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Semaphore; +import javax.net.ssl.SSLContext; + +import jdk.httpclient.test.lib.common.HttpServerAdapters; +import jdk.httpclient.test.lib.http3.Http3ServerExchange; +import jdk.internal.net.http.quic.streams.QuicSenderStream.SendingStreamState; +import jdk.test.lib.RandomFactory; +import jdk.test.lib.net.SimpleSSLContext; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static java.net.http.HttpClient.Version.HTTP_3; +import static java.net.http.HttpOption.H3_DISCOVERY; +import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY; +import static jdk.internal.net.http.quic.streams.QuicSenderStream.SendingStreamState.DATA_RECVD; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class H3RespondGoAwayAndClose implements HttpServerAdapters { + private static HttpTestServer httpsServer; + private static HttpClient client = null; + static String httpsURIString; + private static final SSLContext sslContext = SimpleSSLContext.findSSLContext(); + private static final ExecutorService serverExec = + Executors.newThreadPerTaskExecutor(Thread.ofVirtual() + .name("server-vt-worker-", 1).factory()); + private static final String RESPONSE = """ + Lorem ipsum dolor sit amet, + caesarem legato alacrem. + """; + private static final Semaphore gate = new Semaphore(0); + private static final Random random = RandomFactory.getRandom(); + + private static void initialize() throws Exception { + try { + client = getClient(); + + httpsServer = HttpTestServer.create(HTTP_3_URI_ONLY, sslContext, serverExec); + httpsServer.addHandler(new TestHandler(), "/"); + httpsURIString = "https://" + httpsServer.serverAuthority() + "/bar/"; + + httpsServer.start(); + } catch (Throwable e) { + System.err.println("Throwing now"); + e.printStackTrace(); + throw e; + } + } + + public static void main(String[] args) throws Exception { + new H3RespondGoAwayAndClose().test(); + } + + @Test + public void test() throws Exception { + try { + initialize(); + HttpRequest request = HttpRequest.newBuilder(URI.create(httpsURIString)) + .version(HTTP_3) + .setOption(H3_DISCOVERY, HTTP_3_URI_ONLY) + .GET().build(); + + for (int i=0 ; i < 5; i++) { + assertEquals(0, gate.availablePermits()); + System.out.printf("issuing request[%s]: %s%n", i, request); + System.err.printf("issuing request[%s]: %s%n", i, request); + var resp = client.send(request, BodyHandlers.ofInputStream()); + Assertions.assertEquals(200, resp.statusCode()); + assertEquals(HTTP_3, resp.version(), "unexpected HTTP version in response"); + + System.out.println("awaiting connection termination"); + System.err.println("awaiting connection termination"); + gate.acquire(); + + // At this point the server has closed the connection. + // Wait a little bit to give a chance to the client to + // observe connection closure, since having closed the + // connection from the server side doesn't guarantee that + // the ConnectionCloseFrame has reached the client side + // and has already been processed. + Thread.sleep(random.nextLong(100, 1000)); + + // Start pulling the bytes from the input stream. The bytes + // should have been all received and buffered, so it should + // be possible to pull them even though the connection was closed + System.out.println("Pulling response bytes"); + System.err.println("Pulling response bytes"); + byte[] bytes = resp.body().readAllBytes(); + assertArrayEquals(RESPONSE.getBytes(StandardCharsets.UTF_8), bytes); + System.out.printf("Got expected response for request[%s]%n", i); + System.err.printf("Got expected response for request[%s]%n", i); + } + } catch (Throwable tt) { + System.err.println("tt caught"); + tt.printStackTrace(); + throw tt; + } finally { + client.close(); + httpsServer.stop(); + serverExec.close(); + } + } + + static HttpClient createClient(SSLContext sslContext, ExecutorService clientExec) { + var builder = HttpServerAdapters.createClientBuilderForH3() + .sslContext(sslContext) + .version(HTTP_3) + .proxy(Builder.NO_PROXY); + if (clientExec != null) { + builder = builder.executor(clientExec); + } + return builder.build(); + } + + static HttpClient getClient() { + if (client == null) { + client = createClient(sslContext, null); + } + return client; + } + + private final static byte[] RESPONSE_BYTES = RESPONSE.getBytes(StandardCharsets.UTF_8); + + private static class TestHandler implements HttpTestHandler { + @Override + public void handle(HttpTestExchange t) throws IOException { + try (var in = t.getRequestBody()) { + byte[] input = in.readAllBytes(); + // passing -1 here ensures that each call to + // out.write() will create a DataFrame. + t.sendResponseHeaders(200, -1); + assert RESPONSE_BYTES.length > 10 + : "Insufficient body length: " + RESPONSE_BYTES.length; + try (var out = t.getResponseBody()) { + for (int i=0; i sendPing() { final QuicConnectionImpl quicConn = serverConn.quicConnection(); diff --git a/test/jdk/java/nio/channels/AsynchronousSocketChannel/StressLoopback.java b/test/jdk/java/nio/channels/AsynchronousSocketChannel/StressLoopback.java index 331da97845ae..ee4898b47250 100644 --- a/test/jdk/java/nio/channels/AsynchronousSocketChannel/StressLoopback.java +++ b/test/jdk/java/nio/channels/AsynchronousSocketChannel/StressLoopback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,8 +24,6 @@ /* @test * @bug 6834246 6842687 * @summary Stress test connections through the loopback interface - * @run main StressLoopback - * @run main/othervm/timeout=480 -Djdk.net.useFastTcpLoopback StressLoopback * @key randomness */ diff --git a/test/jdk/java/text/Format/CompactNumberFormat/PluralProcessingTest.java b/test/jdk/java/text/Format/CompactNumberFormat/PluralProcessingTest.java new file mode 100644 index 000000000000..410a4cb65bca --- /dev/null +++ b/test/jdk/java/text/Format/CompactNumberFormat/PluralProcessingTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8393920 + * @summary Ensure that the plural processing in CompactNumberFormat can support its implementation + * limit of 2048 characters, even with a reduced stack size. + * @run junit/othervm -Xss1m -Xint PluralProcessingTest + */ + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.FieldSource; + +import java.text.CompactNumberFormat; +import java.text.DecimalFormatSymbols; +import java.util.List; +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class PluralProcessingTest { + + // Plural rules that are excessively long + private static final List LONG_PLURAL_RULES = List.of( + Arguments.argumentSet("range_list", + "one:n=1" + ",1".repeat(1_000)), + Arguments.argumentSet("and_condition", + "one:n=1" + "andn=1".repeat(340)), + Arguments.argumentSet("expr", + "one:n" + "/1".repeat(1_000) + "=1") + ); + + @FieldSource("LONG_PLURAL_RULES") + @ParameterizedTest + void longPluralRulesTest(String rule) { + assertDoesNotThrow(() -> + new CompactNumberFormat("#", DecimalFormatSymbols.getInstance(Locale.ROOT), + new String[]{""}, rule)); + } +} diff --git a/test/jdk/java/util/Collections/AddAll.java b/test/jdk/java/util/Collections/AddAll.java index f81e149fad7d..fe30ece8884b 100644 --- a/test/jdk/java/util/Collections/AddAll.java +++ b/test/jdk/java/util/Collections/AddAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,11 +27,8 @@ * @summary Basic test for Collections.addAll * @author Josh Bloch * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -49,11 +46,6 @@ public static void main(String[] args) { test(new LinkedList()); test(new HashSet()); test(new LinkedHashSet()); - - testTuple(new ArrayList()); - testTuple(new LinkedList()); - testTuple(new HashSet()); - testTuple(new LinkedHashSet()); } private static Random rnd = new Random(); @@ -82,29 +74,4 @@ private static Integer[] range(int from, int to) { result[j] = new Integer(i); return result; } - - static void testTuple(Collection c) { - int x = 0; - for (int i = 0; i < N; i++) { - int rangeLen = rnd.nextInt(10); - if (Collections.addAll(c, rangeTuple(x, x + rangeLen)) != - (rangeLen != 0)) - throw new RuntimeException("" + rangeLen); - x += rangeLen; - } - if (c instanceof List) { - if (!c.equals(Arrays.asList(rangeTuple(0, x)))) - throw new RuntimeException(x + ": " + c); - } else { - if (!c.equals(new HashSet(Arrays.asList(rangeTuple(0, x))))) - throw new RuntimeException(x + ": " + c); - } - } - - private static VClass[] rangeTuple(int from, int to) { - VClass[] result = new VClass[to - from]; - for (int i = from, j = 0; i < to; i++, j++) - result[j] = new VClass(i, new int[] { i }); - return result; - } } diff --git a/test/jdk/java/util/Collections/AsLifoQueue.java b/test/jdk/java/util/Collections/AsLifoQueue.java index 95825c4c64f2..c573e32f49bc 100644 --- a/test/jdk/java/util/Collections/AsLifoQueue.java +++ b/test/jdk/java/util/Collections/AsLifoQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 6301085 6192552 6365601 * @summary Basic tests for asLifoQueue * @author Martin Buchholz - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.*; import java.util.concurrent.*; @@ -74,17 +72,6 @@ private static void realMain(String[] args) throws Throwable { } catch (Throwable t) { unexpected(t); } THROWS(NullPointerException.class, () -> Collections.asLifoQueue(null)); - - try { - Deque deq = new ArrayDeque<>(); - Queue q = Collections.asLifoQueue(deq); - check(q.add(new VClass(1, new int[] { 1 }))); - check(q.add(new VClass(2, new int[] { 2 }))); - equal(q.peek(), new VClass(2, new int[] { 2 })); - equal(q.remove(), new VClass(2, new int[] { 2 })); - equal(q.poll(), new VClass(1, new int[] { 1 })); - check(q.isEmpty()); - } catch (Throwable t) { unexpected(t); } } //--------------------- Infrastructure --------------------------- diff --git a/test/jdk/java/util/Collections/BigBinarySearch.java b/test/jdk/java/util/Collections/BigBinarySearch.java index 5a1681e4a4e3..b10081c7582a 100644 --- a/test/jdk/java/util/Collections/BigBinarySearch.java +++ b/test/jdk/java/util/Collections/BigBinarySearch.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 5045582 * @summary binarySearch of Collections larger than 1<<30 * @author Martin Buchholz - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.AbstractList; import java.util.Collections; import java.util.Comparator; @@ -40,13 +38,6 @@ public class BigBinarySearch { - static class SparseTupleList extends AbstractList implements RandomAccess { - final Map m = new HashMap<>(); - public VClass get(int i) { return m.getOrDefault(i, new VClass(0, new int[] { 0 })); } - public int size() { return Collections.max(m.keySet()) + 1; } - public VClass set(int i, VClass v) { return m.put(i, v); } - } - // Allows creation of very "big" collections without using too // many real resources static class SparseIntegerList @@ -111,14 +102,6 @@ private static void realMain(String[] args) throws Throwable { big.set(i, - big.get(i)); for (int i : ints) checkBinarySearch(big, i, reverse); - - System.out.println("binarySearch(SparseTupleList, Tuple)"); - SparseTupleList vl = new SparseTupleList(); - vl.set(0, new VClass(0, new int[] { 0 })); - vl.set(1, new VClass(1, new int[] { 1 })); - vl.set(n - 2, new VClass(n - 2, new int[] { n - 2 })); - vl.set(n - 1, new VClass(n - 1, new int[] { n - 1 })); - equal(n - 1, Collections.binarySearch(vl, new VClass(n - 1, new int[] { n - 1 }))); } //--------------------- Infrastructure --------------------------- diff --git a/test/jdk/java/util/Collections/BinarySearchNullComparator.java b/test/jdk/java/util/Collections/BinarySearchNullComparator.java index 972e252a9923..62bf55f9b0ea 100644 --- a/test/jdk/java/util/Collections/BinarySearchNullComparator.java +++ b/test/jdk/java/util/Collections/BinarySearchNullComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2004, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,26 +25,18 @@ * @test * @bug 4528331 5006032 * @summary Test Collections.binarySearch() with a null comparator - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.Arrays; import java.util.Collections; import java.util.List; public class BinarySearchNullComparator { - public static void main(String[] args) throws Exception { List list = Arrays.asList(new String[] {"I", "Love", "You"}); int result = Collections.binarySearch(list, "You", null); if (result != 2) throw new Exception("Result: " + result); - - List values = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 })); - int vresult = Collections.binarySearch(values, new VClass(3, new int[] { 3 }), null); - if (vresult != 2) - throw new Exception("Value result: " + vresult); } } diff --git a/test/jdk/java/util/Collections/CheckedListBash.java b/test/jdk/java/util/Collections/CheckedListBash.java index 8d19df4dced9..1adfc0ddd4a7 100644 --- a/test/jdk/java/util/Collections/CheckedListBash.java +++ b/test/jdk/java/util/Collections/CheckedListBash.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,10 +27,8 @@ * @summary Unit test for Collections.checkedList * @author Josh Bloch * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -184,7 +182,6 @@ public static void main(String[] args) { || !l.equals(Arrays.asList(ia).subList(0, listSize))) fail("toArray(Object[]) is hosed (3)"); - testValueCheckedList(); } // Done inefficiently so as to exercise toArray @@ -228,19 +225,4 @@ static void AddRandoms(List s, int n) { static void fail(String s) { throw new RuntimeException(s); } - - static void testValueCheckedList() { - List list = Collections.checkedList(new ArrayList<>(), VClass.class); - list.add(new VClass(1, new int[] { 1 })); - list.add(new VClass(2, new int[] { 2 })); - if (!list.contains(new VClass(1, new int[] { 1 })) || list.indexOf(new VClass(2, new int[] { 2 })) != 1) - fail("value checkedList lookup failed"); - VClass[] a = list.toArray(new VClass[0]); - if (!Arrays.asList(a).equals(list)) - fail("value checkedList toArray failed"); - try { - ((List) list).add("not a Tuple"); - fail("value checkedList accepted wrong type"); - } catch (ClassCastException expected) { } - } } diff --git a/test/jdk/java/util/Collections/CheckedListReplaceAll.java b/test/jdk/java/util/Collections/CheckedListReplaceAll.java index d79a83b2f3df..19042db24d6b 100644 --- a/test/jdk/java/util/Collections/CheckedListReplaceAll.java +++ b/test/jdk/java/util/Collections/CheckedListReplaceAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,15 +26,12 @@ * @bug 8047795 8053938 * @summary Ensure that replaceAll operator cannot add bad elements * @author Mike Duigou - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.*; import java.util.function.UnaryOperator; public class CheckedListReplaceAll { - public static void main(String[] args) { List unwrapped = Arrays.asList(new Object[]{1, 2, 3}); List wrapped = Collections.checkedList(unwrapped, Integer.class); @@ -60,16 +57,5 @@ public static void main(String[] args) { thwarted.printStackTrace(System.out); System.out.println("Curses! Foiled again!"); } - - List vList = Collections.checkedList(new ArrayList(Arrays.asList(new VClass(1, new int[] { 1 }))), VClass.class); - vList.replaceAll(v -> new VClass(v.x + 1, new int[] { v.x + 1 })); - if (!vList.get(0).equals(new VClass(2, new int[] { 2 }))) - throw new RuntimeException("value checkedList replaceAll failed"); - - List raw = Collections.checkedList(new ArrayList(Arrays.asList(new VClass(1, new int[] { 1 }))), VClass.class); - try { - raw.replaceAll(e -> "not a Tuple"); - throw new RuntimeException("value checkedList replaceAll accepted wrong type"); - } catch (ClassCastException expected) { } } } diff --git a/test/jdk/java/util/Collections/CheckedMapBash.java b/test/jdk/java/util/Collections/CheckedMapBash.java index 8a03f5bca31a..a8e3fd9e9528 100644 --- a/test/jdk/java/util/Collections/CheckedMapBash.java +++ b/test/jdk/java/util/Collections/CheckedMapBash.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,12 +26,10 @@ * @bug 4904067 5023830 7129185 8072015 8292955 * @summary Unit test for Collections.checkedMap * @author Josh Bloch - * @key randomness - * @library /test/lib * @run testng CheckedMapBash + * @key randomness */ -import jdk.test.lib.valueclass.VClass; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -193,18 +191,4 @@ public static void testCheckedMapMerge() { Assert.assertThrows(ClassCastException.class, () -> m.merge("key", "value", (v1, v2) -> null)); Assert.assertThrows(ClassCastException.class, () -> m.merge("key", 3, (v1, v2) -> v2)); } - - @Test - public static void testValueCheckedMap() { - Map m = Collections.checkedMap(new HashMap<>(), VClass.class, VClass.class); - m.put(new VClass(1, new int[] { 1 }), new VClass(10, new int[] { 10 })); - if (!m.containsKey(new VClass(1, new int[] { 1 })) || !m.containsValue(new VClass(10, new int[] { 10 }))) - fail("value checkedMap lookup failed"); - if (!new HashMap<>(m).equals(m)) - fail("value checkedMap equals failed"); - try { - ((Map) m).put("not a Tuple", new VClass(2, new int[] { 2 })); - fail("value checkedMap accepted wrong type"); - } catch (ClassCastException expected) { } - } } diff --git a/test/jdk/java/util/Collections/CheckedMapReplaceAll.java b/test/jdk/java/util/Collections/CheckedMapReplaceAll.java index 6fd4fb455449..05c4da3ad5ed 100644 --- a/test/jdk/java/util/Collections/CheckedMapReplaceAll.java +++ b/test/jdk/java/util/Collections/CheckedMapReplaceAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,15 +26,12 @@ * @bug 8047795 * @summary Ensure that replaceAll operator cannot add bad elements * @author Mike Duigou - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.*; import java.util.function.BiFunction; public class CheckedMapReplaceAll { - public static void main(String[] args) { Map unwrapped = new HashMap<>(); unwrapped.put(1, 1.0); @@ -53,18 +50,5 @@ public static void main(String[] args) { thwarted.printStackTrace(System.out); System.out.println("Curses! Foiled again!"); } - - Map vMap = Collections.checkedMap(new HashMap<>(), VClass.class, VClass.class); - vMap.put(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 })); - vMap.replaceAll((k, v) -> new VClass(v.x + 1, new int[] { v.x + 1 })); - if (!vMap.get(new VClass(1, new int[] { 1 })).equals(new VClass(3, new int[] { 3 }))) - throw new RuntimeException("value checkedMap replaceAll failed"); - - Map raw = Collections.checkedMap(new HashMap(), VClass.class, VClass.class); - raw.put(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 })); - try { - raw.replaceAll((k, v) -> "not a Tuple"); - throw new RuntimeException("value checkedMap replaceAll accepted wrong type"); - } catch (ClassCastException expected) { } } } diff --git a/test/jdk/java/util/Collections/CheckedQueue.java b/test/jdk/java/util/Collections/CheckedQueue.java index ea3ebb517b5e..fb47e2e5ca16 100644 --- a/test/jdk/java/util/Collections/CheckedQueue.java +++ b/test/jdk/java/util/Collections/CheckedQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,9 @@ * @test * @bug 5020931 8048207 * @summary Unit test for Collections.checkedQueue - * @library /test/lib * @run testng CheckedQueue */ -import jdk.test.lib.valueclass.VClass; -import java.util.ArrayDeque; import java.util.Collections; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; @@ -152,16 +149,4 @@ public void testOffer() { // no room at the inn! assertFalse(q.offer("1"), "queue should be full"); } - - @Test - public void testValueCheckedQueue() { - Queue q = Collections.checkedQueue(new ArrayDeque<>(), VClass.class); - q.add(new VClass(1, new int[] { 1 })); - if (!q.peek().equals(new VClass(1, new int[] { 1 }))) - fail("value checkedQueue peek failed"); - try { - ((Queue) q).add("not a Tuple"); - fail("value checkedQueue accepted wrong type"); - } catch (ClassCastException expected) { } - } } diff --git a/test/jdk/java/util/Collections/CheckedSetBash.java b/test/jdk/java/util/Collections/CheckedSetBash.java index 96780ff178a3..16dfb11b4497 100644 --- a/test/jdk/java/util/Collections/CheckedSetBash.java +++ b/test/jdk/java/util/Collections/CheckedSetBash.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -26,12 +26,10 @@ * @bug 4904067 7129185 * @summary Unit test for Collections.checkedSet * @author Josh Bloch - * @key randomness - * @library /test/lib * @run testng CheckedSetBash + * @key randomness */ -import jdk.test.lib.valueclass.VClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -179,21 +177,4 @@ public static Collection makeCheckedSets() { }; return Arrays.asList(params); } - - @Test - public static void testValueCheckedSet() { - Set s = Collections.checkedSet(new HashSet<>(), VClass.class); - s.add(new VClass(1, new int[] { 1 })); - s.add(new VClass(2, new int[] { 2 })); - if (!s.contains(new VClass(1, new int[] { 1 }))) - fail("value checkedSet lookup failed"); - Set copy = Collections.checkedSet(new HashSet<>(), VClass.class); - copy.addAll(Arrays.asList(s.toArray(new VClass[0]))); - if (!s.equals(copy)) - fail("value checkedSet addAll failed"); - try { - ((Set) s).add("not a Tuple"); - fail("value checkedSet accepted wrong type"); - } catch (ClassCastException expected) { } - } } diff --git a/test/jdk/java/util/Collections/Disjoint.java b/test/jdk/java/util/Collections/Disjoint.java index dc109dc55734..13bdac86d5e4 100644 --- a/test/jdk/java/util/Collections/Disjoint.java +++ b/test/jdk/java/util/Collections/Disjoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,12 +27,9 @@ * @summary Basic test for Collections.disjoint * @author Josh Bloch * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -74,11 +71,5 @@ public static void main(String[] args) { throw new RuntimeException("C: " + i + ", " + j); } } - - List va = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 })); - List vb = Arrays.asList(new VClass(3, new int[] { 3 }), new VClass(4, new int[] { 4 })); - List vc = Arrays.asList(new VClass(2, new int[] { 2 }), new VClass(5, new int[] { 5 })); - if (!Collections.disjoint(va, vb)) throw new RuntimeException("value disjoint failed"); - if (Collections.disjoint(va, vc)) throw new RuntimeException("value non-disjoint failed"); } } diff --git a/test/jdk/java/util/Collections/Enum.java b/test/jdk/java/util/Collections/Enum.java index 3d7b16aa90f5..432d226aaca4 100644 --- a/test/jdk/java/util/Collections/Enum.java +++ b/test/jdk/java/util/Collections/Enum.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,18 +25,13 @@ * @test * @bug 4323074 * @summary Basic test for new Enumeration -> List converter - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.Collections; import java.util.List; import java.util.Vector; public class Enum { - - static final int SIZE = 10; - public static void main(String[] args) throws Exception { int[] sizes = {0, 10, 100}; for (int i=0; i vv = new Vector<>(); - for (int j = 0; j < SIZE; j++) vv.add(new VClass(j, new int[] { j })); - List vl = Collections.list(vv.elements()); - if (!vl.equals(vv)) - throw new RuntimeException("value Enumeration -> List failed"); } } diff --git a/test/jdk/java/util/Collections/EnumerationAsIterator.java b/test/jdk/java/util/Collections/EnumerationAsIterator.java index 3c9da3bfce3c..4901c40303c8 100644 --- a/test/jdk/java/util/Collections/EnumerationAsIterator.java +++ b/test/jdk/java/util/Collections/EnumerationAsIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -25,11 +25,9 @@ * @test * @bug 8072726 * @summary Tests for Enumeration-to-Iterator conversion. - * @library /test/lib * @run testng EnumerationAsIterator */ -import jdk.test.lib.valueclass.VClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -48,7 +46,6 @@ @Test public class EnumerationAsIterator { - static Object[] of(String description, Supplier> s, Collection exp) { return new Object[]{description, s, exp}; } @@ -114,11 +111,7 @@ public static Iterator others() { of("Arrays.asList(...)", Arrays.asList("a", "b", "c"), - Arrays.asList("a", "b", "c")), - - of("Value Collections.enumeration()", - () -> Collections.enumeration(Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }))), - Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }))) + Arrays.asList("a", "b", "c")) ).iterator(); } diff --git a/test/jdk/java/util/Collections/FindSubList.java b/test/jdk/java/util/Collections/FindSubList.java index 3f5b93d3d3dc..31bfdc7fb249 100644 --- a/test/jdk/java/util/Collections/FindSubList.java +++ b/test/jdk/java/util/Collections/FindSubList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,8 @@ * @test * @bug 4323074 * @summary Basic test for Collections.indexOfSubList/lastIndexOfSubList - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -37,7 +35,6 @@ import java.util.Vector; public class FindSubList { - public static void main(String[] args) throws Exception { int N = 500; List source = new ArrayList(3 * N); @@ -106,12 +103,5 @@ public static void main(String[] args) throws Exception { "0")) != -1) throw new Exception(s.getClass()+" lastIndexOfSubList: big tgt"); } - - List vsource = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 })); - List vtarget = Arrays.asList(new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 })); - if (Collections.indexOfSubList(vsource, vtarget) != 1) - throw new Exception("value indexOfSubList failed"); - if (Collections.lastIndexOfSubList(vsource, vtarget) != 3) - throw new Exception("value lastIndexOfSubList failed"); } } diff --git a/test/jdk/java/util/Collections/Frequency.java b/test/jdk/java/util/Collections/Frequency.java index 72b2c4454a31..b10ecc63edd6 100644 --- a/test/jdk/java/util/Collections/Frequency.java +++ b/test/jdk/java/util/Collections/Frequency.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 4193200 * @summary Basic test for Collections.frequency * @author Josh Bloch - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -37,11 +35,9 @@ public class Frequency { static final int N = 100; - public static void main(String[] args) { test(new ArrayList()); test(new LinkedList()); - testValue(); } static void test(List list) { @@ -54,15 +50,4 @@ static void test(List list) { if (Collections.frequency(list, i) != i) throw new RuntimeException(list.getClass() + ": " + i); } - - static void testValue() { - List values = new ArrayList<>(); - for (int i = 0; i < N; i++) - for (int j = 0; j < i; j++) - values.add(new VClass(i, new int[] { i })); - Collections.shuffle(values); - for (int i = 0; i < N; i++) - if (Collections.frequency(values, new VClass(i, new int[] { i })) != i) - throw new RuntimeException("value frequency: " + i); - } } diff --git a/test/jdk/java/util/Collections/NCopies.java b/test/jdk/java/util/Collections/NCopies.java index 7fe206ac72d5..48ba92ad842a 100644 --- a/test/jdk/java/util/Collections/NCopies.java +++ b/test/jdk/java/util/Collections/NCopies.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 6267846 6275009 * @summary Test Collections.nCopies * @author Martin Buchholz - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.AbstractList; @@ -144,15 +142,6 @@ private static void checkReversed() { check(empty.equals(empty.reversed())); } - private static void checkValueCopies() { - List copies = Collections.nCopies(10, new VClass(7, new int[] { 7 })); - check(copies.indexOf(new VClass(7, new int[] { 7 })) == 0); - check(copies.lastIndexOf(new VClass(7, new int[] { 7 })) == 9); - check(copies.equals(referenceNCopies(10, new VClass(7, new int[] { 7 })))); - check(copies.hashCode() == referenceNCopies(10, new VClass(7, new int[] { 7 })).hashCode()); - check(copies.equals(copies.reversed())); - } - public static void main(String[] args) { try { List empty = Collections.nCopies(0, "foo"); @@ -169,8 +158,6 @@ public static void main(String[] args) { checkReversed(); - checkValueCopies(); - } catch (Throwable t) { unexpected(t); } System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); diff --git a/test/jdk/java/util/Collections/NullComparator.java b/test/jdk/java/util/Collections/NullComparator.java index e43525ab2ce0..fd1134cb9051 100644 --- a/test/jdk/java/util/Collections/NullComparator.java +++ b/test/jdk/java/util/Collections/NullComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,17 +25,14 @@ * @test * @bug 4224271 * @summary A null Comparator is now specified to indicate natural ordering. - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; public class NullComparator { - public static void main(String[] args) throws Exception { List list = new ArrayList(100); for (int i=0; i<100; i++) @@ -64,18 +61,5 @@ public static void main(String[] args) throws Exception { throw new Exception("Collections.min"); if (!Collections.max(list, null).equals(new Integer(99))) throw new Exception("Collections.max"); - - List vlist = new ArrayList<>(); - for (int i = 0; i < 100; i++) vlist.add(new VClass(i, new int[] { i })); - Collections.shuffle(vlist); - Collections.sort(vlist, null); - for (int i = 0; i < 100; i++) - if (!vlist.get(i).equals(new VClass(i, new int[] { i }))) throw new Exception("value Collections.sort"); - if (Collections.binarySearch(vlist, new VClass(69, new int[] { 69 }), null) != 69) - throw new Exception("value binarySearch"); - if (!Collections.min(vlist, null).equals(new VClass(0, new int[] { 0 }))) - throw new Exception("value min"); - if (!Collections.max(vlist, null).equals(new VClass(99, new int[] { 99 }))) - throw new Exception("value max"); } } diff --git a/test/jdk/java/util/Collections/ReplaceAll.java b/test/jdk/java/util/Collections/ReplaceAll.java index 3eeb57908fbd..498e4c25ce6e 100644 --- a/test/jdk/java/util/Collections/ReplaceAll.java +++ b/test/jdk/java/util/Collections/ReplaceAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,8 @@ * @test * @bug 4323074 * @summary Basic test for new replaceAll algorithm - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -59,12 +57,5 @@ public static void main(String[] args) throws Exception { if (Collections.replaceAll(lst, "love", "hate")) throw new Exception("True return value: "+i); } - - List values = new ArrayList<>(); - for (int j = 1; j <= SIZE; j++) values.add(new VClass(j % 3, new int[] { j % 3 })); - if (!Collections.replaceAll(values, new VClass(1, new int[] { 1 }), new VClass(99, new int[] { 99 }))) - throw new Exception("value false return"); - if (Collections.replaceAll(values, new VClass(100, new int[] { 100 }), new VClass(0, new int[] { 0 }))) - throw new Exception("value true return for absent element"); } } diff --git a/test/jdk/java/util/Collections/ReverseOrder.java b/test/jdk/java/util/Collections/ReverseOrder.java index e634ceadaf1a..ceaa5441a0dc 100644 --- a/test/jdk/java/util/Collections/ReverseOrder.java +++ b/test/jdk/java/util/Collections/ReverseOrder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 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 @@ -26,10 +26,8 @@ * @bug 4593209 8001667 * @summary Reverse comparator was subtly broken * @author Josh Bloch - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; @@ -40,7 +38,6 @@ import java.util.List; public class ReverseOrder { - static byte[] serialBytes(Object o) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -82,11 +79,6 @@ public static void main(String[] args) throws Exception { Collections.sort(list2, clone); if (!list2.equals(goldenList)) throw new Exception(list.toString()); - - List vl = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(3, new int[] { 3 }), new VClass(2, new int[] { 2 })); - Collections.sort(vl, Collections.reverseOrder()); - if (!vl.equals(Arrays.asList(new VClass(3, new int[] { 3 }), new VClass(2, new int[] { 2 }), new VClass(1, new int[] { 1 })))) - throw new RuntimeException("value reverseOrder failed"); } } diff --git a/test/jdk/java/util/Collections/ReverseOrder2.java b/test/jdk/java/util/Collections/ReverseOrder2.java index 4aa88ef0002b..0369b389a80e 100644 --- a/test/jdk/java/util/Collections/ReverseOrder2.java +++ b/test/jdk/java/util/Collections/ReverseOrder2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 4809442 6366832 4974878 6372554 4890211 6483125 * @summary Basic test for Collections.reverseOrder * @author Josh Bloch, Martin Buchholz - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -37,7 +35,6 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.LinkedList; @@ -69,12 +66,6 @@ static void realMain(String[] args) throws Throwable { test(new LinkedList()); test2(new ArrayList()); test2(new LinkedList()); - - List values = new ArrayList<>(Arrays.asList(new VClass(0, new int[] { 0 }), new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }))); - Collections.shuffle(values); - Collections.sort(values, Collections.reverseOrder()); - if (!values.equals(Arrays.asList(new VClass(2, new int[] { 2 }), new VClass(1, new int[] { 1 }), new VClass(0, new int[] { 0 })))) - throw new RuntimeException("value reverseOrder2 failed"); } static void test(List list) { diff --git a/test/jdk/java/util/Collections/Rotate.java b/test/jdk/java/util/Collections/Rotate.java index 204812c14bbf..7ec42f9d2e94 100644 --- a/test/jdk/java/util/Collections/Rotate.java +++ b/test/jdk/java/util/Collections/Rotate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,12 +26,9 @@ * @bug 4323074 * @summary Basic test for new rotate algorithm * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -72,10 +69,5 @@ public static void main(String[] args) throws Exception { ", should be "+index); } } - - List vl = new ArrayList<>(Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 }), new VClass(4, new int[] { 4 }))); - Collections.rotate(vl, 1); - if (!vl.equals(Arrays.asList(new VClass(4, new int[] { 4 }), new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), new VClass(3, new int[] { 3 })))) - throw new RuntimeException("value rotate failed"); } } diff --git a/test/jdk/java/util/Collections/Ser.java b/test/jdk/java/util/Collections/Ser.java index de99cdea8bbc..f1a8b949e73f 100644 --- a/test/jdk/java/util/Collections/Ser.java +++ b/test/jdk/java/util/Collections/Ser.java @@ -23,7 +23,7 @@ /* * @test - * @bug 4190323 + * @bug 4190323 8384611 * @summary EMPTY_SET, EMPTY_LIST, and the collections returned by * nCopies and singleton were spec'd to be serializable, but weren't. * @library /test/lib @@ -36,8 +36,6 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collections; -import java.util.List; -import java.util.Set; public class Ser { @@ -56,91 +54,27 @@ private static class SerProxy implements Serializable { } public static void main(String[] args) throws Exception { + checkSerialization(Collections.EMPTY_SET, "empty set"); + checkSerialization(Collections.EMPTY_LIST, "empty list"); + checkSerialization(Collections.singleton("gumby"), "singleton"); + checkSerialization(Collections.nCopies(50, "gumby"), "nCopies"); + checkSerialization(Collections.singleton(new SerV(1)), "value singleton"); + checkSerialization(Collections.nCopies(5, new SerV(2)), "value nCopies"); + } + private static void checkSerialization(Object obj, String label) throws Exception { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); - out.writeObject(Collections.EMPTY_SET); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - - if (!Collections.EMPTY_SET.equals(in.readObject())) - throw new RuntimeException("empty set Ser/Deser failure."); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize empty set:" + e); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - out.writeObject(Collections.EMPTY_LIST); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - - if (!Collections.EMPTY_LIST.equals(in.readObject())) - throw new RuntimeException("empty list Ser/Deser failure."); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize empty list:" + e); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - Set gumby = Collections.singleton("gumby"); - out.writeObject(gumby); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - - if (!gumby.equals(in.readObject())) - throw new RuntimeException("Singleton Ser/Deser failure."); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize singleton:" + e); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - List gumbies = Collections.nCopies(50, "gumby"); - out.writeObject(gumbies); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - - if (!gumbies.equals(in.readObject())) - throw new RuntimeException("nCopies Ser/Deser failure."); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize nCopies:" + e); - } - - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - Set one = Collections.singleton(new SerV(1)); - out.writeObject(one); + out.writeObject(obj); out.flush(); ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream(bos.toByteArray())); - if (!one.equals(in.readObject())) - throw new RuntimeException("value singleton Ser/Deser failure"); - } catch (Exception e) { - throw new RuntimeException("Failed to serialize value singleton: " + e); - } - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream out = new ObjectOutputStream(bos); - List many = Collections.nCopies(5, new SerV(2)); - out.writeObject(many); - out.flush(); - ObjectInputStream in = new ObjectInputStream( - new ByteArrayInputStream(bos.toByteArray())); - if (!many.equals(in.readObject())) - throw new RuntimeException("value nCopies Ser/Deser failure"); + if (!obj.equals(in.readObject())) + throw new RuntimeException(label + " Ser/Deser failure."); } catch (Exception e) { - throw new RuntimeException("Failed to serialize value nCopies: " + e); + throw new RuntimeException("Failed to serialize " + label + ":" + e); } } } diff --git a/test/jdk/java/util/Collections/Shuffle.java b/test/jdk/java/util/Collections/Shuffle.java index e0242f6dc9b5..3a33f27f473e 100644 --- a/test/jdk/java/util/Collections/Shuffle.java +++ b/test/jdk/java/util/Collections/Shuffle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 8294693 * @summary Basic test for Collections.shuffle * @key randomness - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -44,7 +42,6 @@ public class Shuffle { public static void main(String[] args) { test(new ArrayList<>()); test(new LinkedList<>()); - testValue(); } static void test(List list) { @@ -65,16 +62,6 @@ static void test(List list) { checkRandom(list, l -> Collections.shuffle(l, generator.copy())); } - static void testValue() { - List values = new ArrayList<>(); - for (int i = 0; i < N; i++) values.add(new VClass(i, new int[] { i })); - Collections.shuffle(values, new Random(1)); - if (values.size() != N) throw new RuntimeException("value shuffle size"); - for (int i = 0; i < N; i++) - if (Collections.frequency(values, new VClass(i, new int[] { i })) != 1) - throw new RuntimeException("value shuffle lost " + i); - } - private static void checkRandom(List list, Consumer> randomizer) { list.sort(null); randomizer.accept(list); diff --git a/test/jdk/java/util/Collections/SingletonIterator.java b/test/jdk/java/util/Collections/SingletonIterator.java index 006d06261846..0f7dfd172521 100644 --- a/test/jdk/java/util/Collections/SingletonIterator.java +++ b/test/jdk/java/util/Collections/SingletonIterator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,11 +24,9 @@ /* * @test * @bug 8024500 8166446 - * @library /test/lib * @run testng SingletonIterator */ -import jdk.test.lib.valueclass.VClass; import java.util.Collections; import java.util.Iterator; import java.util.NoSuchElementException; @@ -65,18 +63,6 @@ public void testForEachRemaining() { static class SingletonException extends RuntimeException { } - @Test - public void testValueSingletonIterator() { - Iterator it = Collections.singleton(new VClass(42, new int[] { 42 })).iterator(); - AtomicInteger cnt = new AtomicInteger(0); - it.forEachRemaining(v -> { - if (!v.equals(new VClass(42, new int[] { 42 }))) throw new RuntimeException("wrong value"); - cnt.incrementAndGet(); - }); - assertEquals(cnt.get(), 1); - assertIteratorExhausted(it); - } - public void testThrowFromForEachRemaining() { Iterator it = Collections.singleton("TheOne").iterator(); diff --git a/test/jdk/java/util/Collections/Swap.java b/test/jdk/java/util/Collections/Swap.java index 726b6d1a7546..488feeda1eae 100644 --- a/test/jdk/java/util/Collections/Swap.java +++ b/test/jdk/java/util/Collections/Swap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,10 +26,8 @@ * @bug 4323074 * @summary Basic test for newly public swap algorithm * @author Josh Bloch - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -47,11 +45,5 @@ public static void main(String[] args) throws Exception { l2.set(SIZE-1, Boolean.TRUE); if (!l.equals(l2)) throw new RuntimeException("Wrong result"); - - List vl = new ArrayList<>(Collections.nCopies(SIZE, new VClass(0, new int[] { 0 }))); - vl.set(0, new VClass(1, new int[] { 1 })); - for (int i = 0; i < SIZE - 1; i++) Collections.swap(vl, i, i + 1); - if (!vl.get(SIZE - 1).equals(new VClass(1, new int[] { 1 }))) - throw new RuntimeException("value swap failed"); } } diff --git a/test/jdk/java/util/Collections/T6433170.java b/test/jdk/java/util/Collections/T6433170.java index fe59ffff0a68..65de4ccdbedc 100644 --- a/test/jdk/java/util/Collections/T6433170.java +++ b/test/jdk/java/util/Collections/T6433170.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,10 +25,8 @@ * @test * @bug 6433170 * @summary CheckedCollection.addAll should be all-or-nothing - * @library /test/lib */ -import jdk.test.lib.valueclass.VClass; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -42,7 +40,6 @@ @SuppressWarnings("unchecked") public class T6433170 { - private void checkEmpty(Collection x) { check(x.isEmpty()); check(x.size() == 0); @@ -59,12 +56,6 @@ void test(String[] args) throws Throwable { test(checkedCollection( checkedCollection(new Vector(), String.class), Object.class)); - - Collection vc = checkedCollection(new ArrayList<>(), VClass.class); - List mixed = Arrays.asList(new VClass(1, new int[] { 1 }), new VClass(2, new int[] { 2 }), "not a Tuple"); - THROWS(ClassCastException.class, - new F(){void f(){ vc.addAll(mixed); }}); - checkEmpty(vc); } void test(final Collection checked) { diff --git a/test/jdk/java/util/HashMap/PutNullKey.java b/test/jdk/java/util/HashMap/PutNullKey.java index 72aade00186e..d7caa431242d 100644 --- a/test/jdk/java/util/HashMap/PutNullKey.java +++ b/test/jdk/java/util/HashMap/PutNullKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,12 +23,15 @@ /** * @test - * @bug 8046085 + * @bug 8046085 8384611 * @summary Ensure that when trees are being used for collisions that null key * insertion still works. + * @library /test/lib */ +import jdk.test.lib.valueclass.AsValueClass; import java.util.*; +import java.util.function.IntFunction; import java.util.stream.IntStream; public class PutNullKey { @@ -61,30 +64,63 @@ public int hashCode() { @Override public boolean equals(Object o) { - if (null == o) { - return false; - } + return o instanceof CollidingHash t && value == t.value; + } + + @Override + public int compareTo(CollidingHash o) { + return Integer.compare(value, o.value); + } + } - if (o.getClass() != CollidingHash.class) { - return false; - } + // Not Comparable, so colliding keys in a tree bin are ordered by + // HashMap.TreeNode.tieBreakOrder, which uses System.identityHashCode. + @AsValueClass + public static class CollidingHashValue { - return value == ((CollidingHash) o).value; + private final int value; + + public CollidingHashValue(int value) { + this.value = value; } @Override - public int compareTo(CollidingHash o) { - return value - o.value; + public int hashCode() { + // intentionally bad hashcode. Force into first bin. + return 0; + } + + @Override + public boolean equals(Object o) { + return o instanceof CollidingHashValue t && value == t.value; } } public static void main(String[] args) throws Exception { + testCollidingHash(CollidingHash::new); + testCollidingHash(CollidingHashValue::new); + } + + private static void testCollidingHash(IntFunction function) { Map m = new HashMap<>(INITIAL_CAPACITY, LOAD_FACTOR); IntStream.range(0, SIZE) - .mapToObj(CollidingHash::new) + .mapToObj(function) .forEach(e -> { m.put(e, e); }); + for (int i = 0; i < SIZE; i++) { + Object key = function.apply(i); + if (!key.equals(m.get(key))) + throw new RuntimeException("Missing key " + i); + } + // kaboom? m.put(null, null); + + for (int i = 0; i < SIZE; i++) { + if (m.remove(function.apply(i)) == null) + throw new RuntimeException("Failed to remove key " + i); + } + if (m.size() != 1 || !m.containsKey(null)) + throw new RuntimeException("Unexpected map contents: " + m); } } diff --git a/test/jdk/java/util/concurrent/BlockingQueue/ProducerConsumerLoops.java b/test/jdk/java/util/concurrent/BlockingQueue/ProducerConsumerLoops.java index 9ea23980740c..c7096de05dbb 100644 --- a/test/jdk/java/util/concurrent/BlockingQueue/ProducerConsumerLoops.java +++ b/test/jdk/java/util/concurrent/BlockingQueue/ProducerConsumerLoops.java @@ -38,6 +38,15 @@ * @library /test/lib */ +/* + * @test id=no-lse + * @bug 4486658 + * @summary multiple producers and consumers using blocking queues without LSE + * @requires os.arch == "aarch64" + * @library /test/lib + * @run main/othervm -XX:-UseLSE ProducerConsumerLoops + */ + import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; diff --git a/test/jdk/java/util/concurrent/BlockingQueue/SingleProducerMultipleConsumerLoops.java b/test/jdk/java/util/concurrent/BlockingQueue/SingleProducerMultipleConsumerLoops.java index 6f672ef7181a..44ee737a7d3f 100644 --- a/test/jdk/java/util/concurrent/BlockingQueue/SingleProducerMultipleConsumerLoops.java +++ b/test/jdk/java/util/concurrent/BlockingQueue/SingleProducerMultipleConsumerLoops.java @@ -38,6 +38,15 @@ * @library /test/lib */ +/* + * @test id=no-lse + * @bug 4486658 + * @summary check ordering for blocking queues with 1 producer and multiple consumers without LSE + * @requires os.arch == "aarch64" + * @library /test/lib + * @run main/othervm -XX:-UseLSE SingleProducerMultipleConsumerLoops + */ + import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/StressCancellation.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StressCancellation.java index 3fdc582a29c1..62ee6d3861bd 100644 --- a/test/jdk/java/util/concurrent/StructuredTaskScope/StressCancellation.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StressCancellation.java @@ -24,7 +24,6 @@ /* * @test * @summary Stress test of StructuredTaskScope cancellation with running and starting threads - * @enablePreview * @run junit ${test.main.class} */ diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java index fa08834a559f..7712feb1f3d8 100644 --- a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredTaskScopeTest.java @@ -23,15 +23,13 @@ /* * @test id=platform - * @bug 8284199 8296779 8306647 8380109 + * @bug 8284199 8296779 8306647 8380109 8391460 * @summary Basic tests for StructuredTaskScope - * @enablePreview * @run junit/othervm -DthreadFactory=platform ${test.main.class} */ /* * @test id=virtual - * @enablePreview * @run junit/othervm -DthreadFactory=virtual ${test.main.class} */ @@ -53,6 +51,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.SynchronousQueue; import java.util.concurrent.StructuredTaskScope; import java.util.concurrent.StructuredTaskScope.Configuration; import java.util.concurrent.StructuredTaskScope.Joiner; @@ -73,6 +72,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; class StructuredTaskScopeTest { @@ -328,13 +328,24 @@ void testForkAfterClose(ThreadFactory factory) { * Test fork with a ThreadFactory that rejects creating a thread. */ @Test - void testForkRejectedExecutionException() { + void testForkWithRejectingThreadFactory() { ThreadFactory factory = task -> null; try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null)); } } + /** + * Test fork with a ThreadFactory that throws. + */ + @Test + void testForkWithThrowingThreadFactory() { + ThreadFactory factory = task -> { throw new FooException(); }; + try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { + assertThrows(FooException.class, () -> scope.fork(() -> null)); + } + } + /** * Test join with no subtasks. */ @@ -496,50 +507,74 @@ void testJoinConfined(ThreadFactory factory) throws Exception { } /** - * Test join with interrupted status set. + * Test interrupting join. */ - @ParameterizedTest - @MethodSource("factories") - void testInterruptJoin1(ThreadFactory factory) throws Exception { + void testInterruptJoin(ThreadFactory factory, Runnable interrupter) throws Exception { try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { + // subtask1 completes + var queue = new SynchronousQueue(); + Subtask subtask1 = scope.fork(() -> { + queue.put(Thread.currentThread()); + return "foo"; + }); - Subtask subtask = scope.fork(() -> { + // wait for thread executing subtask to terminate + Thread thread = queue.take(); + thread.join(); + + // subtask2 does not complete, causes join to wait + Subtask subtask2 = scope.fork(() -> { Thread.sleep(Duration.ofDays(1)); return "foo"; }); + // before join + assertEquals(Subtask.State.SUCCESS, subtask1.state()); + assertEquals(Subtask.State.UNAVAILABLE, subtask2.state()); + // join should throw - Thread.currentThread().interrupt(); - try { - scope.join(); - fail("join did not throw"); - } catch (InterruptedException expected) { - assertFalse(Thread.interrupted()); // interrupted status should be cleared - } + interrupter.run(); + assertThrows(InterruptedException.class, scope::join); + assertFalse(Thread.interrupted()); + + // after interrupted join + assertEquals(Subtask.State.SUCCESS, subtask1.state()); + assertThrows(IllegalStateException.class, subtask1::get); + assertThrows(IllegalStateException.class, subtask1::exception); + assertEquals(Subtask.State.UNAVAILABLE, subtask2.state()); + assertThrows(IllegalStateException.class, subtask2::get); + assertThrows(IllegalStateException.class, subtask2::exception); + + // close after interrupted join + scope.close(); + + // after close + assertEquals(Subtask.State.SUCCESS, subtask1.state()); + assertThrows(IllegalStateException.class, subtask1::get); + assertThrows(IllegalStateException.class, subtask1::exception); + assertEquals(Subtask.State.UNAVAILABLE, subtask2.state()); + assertThrows(IllegalStateException.class, subtask2::get); + assertThrows(IllegalStateException.class, subtask2::exception); } } + /** + * Test join with interrupted status set. + */ + @ParameterizedTest + @MethodSource("factories") + void testInterruptJoin1(ThreadFactory factory) throws Exception { + testInterruptJoin(factory, () -> Thread.currentThread().interrupt()); + } + /** * Test interrupt of thread blocked in join. */ @ParameterizedTest @MethodSource("factories") void testInterruptJoin2(ThreadFactory factory) throws Exception { - try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { - Subtask subtask = scope.fork(() -> { - Thread.sleep(Duration.ofDays(1)); - return "foo"; - }); - - // interrupt main thread when it blocks in join - scheduleInterruptAt("java.util.concurrent.StructuredTaskScopeImpl.join"); - try { - scope.join(); - fail("join did not throw"); - } catch (InterruptedException expected) { - assertFalse(Thread.interrupted()); // interrupted status should be clear - } - } + testInterruptJoin(factory, + () -> scheduleInterruptAt("java.util.concurrent.StructuredTaskScopeImpl.join")); } /** @@ -738,7 +773,7 @@ void testCloseWithoutJoin1() { } /** - * Test close without join, subtasks forked. + * Test close without join, forked subtask does not complete before close. */ @ParameterizedTest @MethodSource("factories") @@ -752,15 +787,62 @@ void testCloseWithoutJoin2(ThreadFactory factory) { // first call to close should throw assertThrows(IllegalStateException.class, scope::close); + // subtask result not available after first close + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + // subsequent calls to close should not throw for (int i = 0; i < 3; i++) { scope.close(); + + // subtask result not available after additional calls to close + assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); } + } + } - // subtask result/exception not available - assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + /** + * Test close without join, forked subtask completes before close. + */ + @ParameterizedTest + @MethodSource("factories") + void testCloseWithoutJoin3(ThreadFactory factory) throws Exception { + try (var scope = StructuredTaskScope.open(cf -> cf.withThreadFactory(factory))) { + var queue = new SynchronousQueue(); + Subtask subtask = scope.fork(() -> { + queue.put(Thread.currentThread()); + return "foo"; + }); + + // wait for thread executing subtask to terminate + Thread thread = queue.take(); + thread.join(); + + // subtask result not available before close + assertEquals(Subtask.State.SUCCESS, subtask.state()); assertThrows(IllegalStateException.class, subtask::get); assertThrows(IllegalStateException.class, subtask::exception); + + // close should throw + assertThrows(IllegalStateException.class, scope::close); + + // subtask result not available after first close + assertEquals(Subtask.State.SUCCESS, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + + // subsequent calls to close should not throw + for (int i = 0; i < 3; i++) { + scope.close(); + + // subtask result not available after additional calls to close + assertEquals(Subtask.State.SUCCESS, subtask.state()); + assertThrows(IllegalStateException.class, subtask::get); + assertThrows(IllegalStateException.class, subtask::exception); + } } } @@ -907,22 +989,45 @@ void testInterruptClose2(ThreadFactory factory) throws Exception { */ @Test void testCloseThrowsStructureViolation() throws Exception { - try (var scope1 = StructuredTaskScope.open()) { - try (var scope2 = StructuredTaskScope.open()) { + var joiner = new Joiner() { + Subtask completedSubtask; + @Override + public boolean onComplete(Subtask subtask) { + completedSubtask = subtask; + return false; + } + @Override + public Void result() { + return null; + } + @Override + public Void timeout() { + throw new UnsupportedOperationException(); + } + }; + try (var scope1 = StructuredTaskScope.open()) { + try (var scope2 = StructuredTaskScope.open(joiner)) { // close enclosing scope - try { - scope1.close(); - fail("close did not throw"); - } catch (StructureViolationException expected) { } + assertThrows(StructureViolationException.class, scope1::close); - // underlying flock should be closed + // nested scope is borked var executed = new AtomicBoolean(); - Subtask subtask = scope2.fork(() -> executed.set(true)); - assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertThrows(IllegalStateException.class, + () -> scope2.fork(() -> executed.set(true))); + + // onComplete should have been invoked + Subtask subtask = joiner.completedSubtask; + assertEquals(Subtask.State.FAILED, subtask.state()); + + // before join + assertThrows(IllegalStateException.class, subtask::exception); + scope2.join(); + + // after join assertFalse(executed.get()); - assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); + assertTrue(subtask.exception() instanceof IllegalStateException); } } } @@ -1048,6 +1153,65 @@ public Void timeout() { } } + /** + * Test Joiner.onComplete is invoked by main thread when fork unable to create thread. + */ + @ParameterizedTest + @ValueSource(booleans = { true, false }) + void testOnCompleteAfterForkFails(boolean cancel) throws Exception { + Thread mainThread = Thread.currentThread(); + var joiner = new Joiner() { + @Override + public boolean onComplete(Subtask subtask) { + assertSame(mainThread, Thread.currentThread()); + assertEquals(Subtask.State.FAILED, subtask.state()); + assertTrue(subtask.exception() instanceof RejectedExecutionException); + return cancel; + } + @Override + public Void result() { + return null; + } + @Override + public Void timeout() { + return null; + } + }; + ThreadFactory factory = task -> null; + try (var scope = StructuredTaskScope.open(joiner, cf -> cf.withThreadFactory(factory))) { + assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null)); + assertEquals(cancel, scope.isCancelled()); + } + } + + /** + * Test Joiner.onComplete throwing exception when invoked by main thread when fork + * unable to create thread. + */ + @Test + void testOnCompleteThrowsAfterForkFails() throws Exception { + var joiner = new Joiner() { + @Override + public boolean onComplete(Subtask subtask) { + throw new FooException(); + } + @Override + public Void result() { + return null; + } + @Override + public Void timeout() { + return null; + } + }; + ThreadFactory factory = task -> null; + try (var scope = StructuredTaskScope.open(joiner, cf -> cf.withThreadFactory(factory))) { + var e = assertThrows(RejectedExecutionException.class, () -> scope.fork(() -> null)); + Throwable[] suppressed = e.getSuppressed(); + assertTrue(suppressed[0] instanceof FooException); + } + } + /** * Test Joiner.timeout() invoked by owner thread when timeout expires. */ @@ -1321,11 +1485,8 @@ void testAllSuccessfulOrThrow3(ThreadFactory factory) throws Throwable { cf -> cf.withThreadFactory(factory))) { scope.fork(() -> "foo"); scope.fork(() -> { throw new FooException(); }); - try { - scope.join(); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof FooException); - } + var e = assertThrows(ExecutionException.class, scope::join); + assertTrue(e.getCause() instanceof FooException); } } @@ -1405,11 +1566,8 @@ void testAllSuccessfulOrThrow6() throws Exception { @Test void testAnySuccessfulOrThrow1() throws Exception { try (var scope = StructuredTaskScope.open(Joiner.anySuccessfulOrThrow())) { - try { - scope.join(); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof NoSuchElementException); - } + var e = assertThrows(ExecutionException.class, scope::join); + assertTrue(e.getCause() instanceof NoSuchElementException); } } @@ -1562,11 +1720,8 @@ void testAwaitSuccessfulOrThrow3(ThreadFactory factory) throws Throwable { cf -> cf.withThreadFactory(factory))) { scope.fork(() -> "foo"); scope.fork(() -> { throw new FooException(); }); - try { - scope.join(); - } catch (ExecutionException e) { - assertTrue(e.getCause() instanceof FooException); - } + var e = assertThrows(ExecutionException.class, scope::join); + assertInstanceOf(FooException.class, e.getCause()); } } diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java index 372d3e9a543c..d6d06a9720b2 100644 --- a/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/StructuredThreadDumpTest.java @@ -25,7 +25,6 @@ * @test * @bug 8284199 8296779 8306647 * @summary Test thread dumps with StructuredTaskScope - * @enablePreview * @library /test/lib * @run junit/othervm ${test.main.class} */ diff --git a/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java b/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java index 3c5a928cba2a..432bdba2d260 100644 --- a/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java +++ b/test/jdk/java/util/concurrent/StructuredTaskScope/WithScopedValue.java @@ -25,7 +25,6 @@ * @test * @bug 8284199 8296779 8306647 * @summary Basic tests for StructuredTaskScope with scoped values - * @enablePreview * @run junit ${test.main.class} */ @@ -145,14 +144,11 @@ class Box { // underlying flock should be closed and fork should fail to start a thread StructuredTaskScope scope = box.scope; - AtomicBoolean ran = new AtomicBoolean(); - Subtask subtask = scope.fork(() -> { - ran.set(true); - return null; - }); - scope.join(); - assertEquals(Subtask.State.UNAVAILABLE, subtask.state()); - assertFalse(ran.get()); + var executed = new AtomicBoolean(); + assertThrows(IllegalStateException.class, + () -> scope.fork(() -> executed.set(true))); + assertThrows(ExecutionException.class, scope::join); + assertFalse(executed.get()); } finally { StructuredTaskScope scope = box.scope; if (scope != null) { diff --git a/test/jdk/java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java b/test/jdk/java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java index c48cf9495466..7aad90a414fc 100644 --- a/test/jdk/java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java +++ b/test/jdk/java/util/concurrent/forkjoin/AsyncShutdownNowInvokeAnyRace.java @@ -82,6 +82,10 @@ public void testInvokeAny() throws Exception { ThreadLocalRandom rnd = ThreadLocalRandom.current(); int falseAlarms = 0; for (int rep = 1; rep < reps; rep++) { + + if (Thread.interrupted()) // Ensure that the test respects interruptions + throw new InterruptedException(); + ForkJoinPool pool = new ForkJoinPool(1); CountDownLatch pleaseShutdownNow = new CountDownLatch(1); int nTasks = rnd.nextInt(2, 5); @@ -106,40 +110,37 @@ public void testInvokeAny() throws Exception { } catch (Throwable t) { throw new AssertionError(t); } }; Future shutdownResult = CompletableFuture.runAsync(shutdown); - try { - try { - if (rnd.nextBoolean()) - pool.invokeAny(tasks, 10L, SECONDS); - else - pool.invokeAny(tasks); - throw new AssertionError("should throw"); - } catch (RejectedExecutionException re) { - falseAlarms++; - } catch (CancellationException re) { - } catch (ExecutionException ex) { - Throwable cause = ex.getCause(); - if (!(cause instanceof InterruptedException) && - !(cause instanceof CancellationException)) - throw ex; - } catch (TimeoutException ex) { - dumpTestThreads(); - int i = rep; - String detail = String.format( - "invokeAny timed out, " - + "nTasks=%d rep=%d threadsStarted=%d%n" - + "poolAtShutdownNow=%s%n" - + "poolAtTimeout=%s%n" - + "queuedTaskCount=%d queuedSubmissionCount=%d", - nTasks, i, threadsStarted.get(), - poolAtShutdownNow, - pool, - pool.getQueuedTaskCount(), - pool.getQueuedSubmissionCount()); - throw new AssertionError(detail, ex); - } - } finally { - pool.shutdown(); + + try (pool) { // Ensure that the pool is terminated after each iteration + if (rnd.nextBoolean()) + pool.invokeAny(tasks, 10L, SECONDS); + else + pool.invokeAny(tasks); + throw new AssertionError("should throw"); + } catch (RejectedExecutionException re) { + falseAlarms++; + } catch (CancellationException re) { + } catch (ExecutionException ex) { + Throwable cause = ex.getCause(); + if (!(cause instanceof InterruptedException) && + !(cause instanceof CancellationException)) + throw ex; + } catch (TimeoutException ex) { + dumpTestThreads(); + String detail = String.format( + "invokeAny timed out, " + + "nTasks=%d rep=%d threadsStarted=%d%n" + + "poolAtShutdownNow=%s%n" + + "poolAtTimeout=%s%n" + + "queuedTaskCount=%d queuedSubmissionCount=%d", + nTasks, rep, threadsStarted.get(), + poolAtShutdownNow, + pool, + pool.getQueuedTaskCount(), + pool.getQueuedSubmissionCount()); + throw new AssertionError(detail, ex); } + if (falseAlarms != 0) System.out.println("Premature shutdowns = " + falseAlarms); shutdownResult.get(); diff --git a/test/jdk/javax/management/loading/ArrayClassTest.java b/test/jdk/javax/management/loading/ArrayClassTest.java index fc911c8018df..3e48e7f27e33 100644 --- a/test/jdk/javax/management/loading/ArrayClassTest.java +++ b/test/jdk/javax/management/loading/ArrayClassTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,8 +24,7 @@ /* * @test * @bug 4974913 - * @summary Test that array classes can be found in signatures always - * and can be deserialized by the deprecated MBeanServer.deserialize method + * @summary Test that array classes found in signatures can be deserialized * @author Eamonn McManus * * @run clean ArrayClassTest @@ -72,16 +71,20 @@ public static void main(String[] args) throws Exception { oout.writeObject(new Z[0]); oout.close(); byte[] bytes = bout.toByteArray(); - ObjectInputStream oin = mbs.deserialize(testName, bytes); - Object zarray = oin.readObject(); + + ClassLoader newLoader = mbs.getClassLoaderFor(testName); + ByteLoaderStream byteLoaderStream = new ByteLoaderStream(new ByteArrayInputStream(bytes), newLoader); + Object zarray = byteLoaderStream.readObject(); + String failed = null; - if (zarray instanceof Z[]) + if (zarray instanceof Z[]) { failed = "read back a real Z[]"; - else if (!zarray.getClass().getName().equals(Z[].class.getName())) { - failed = "returned object of wrong type: " + - zarray.getClass().getName(); - } else if (Array.getLength(zarray) != 0) + } else if (!zarray.getClass().getName().equals(Z[].class.getName())) { + failed = "returned object of wrong type: " + zarray.getClass().getName(); + } else if (Array.getLength(zarray) != 0) { failed = "returned array of wrong size: " + Array.getLength(zarray); + } + if (failed != null) { System.out.println("TEST FAILED: " + failed); System.exit(1); @@ -122,4 +125,22 @@ public Class findClass(String name) throws ClassNotFoundException { return c; } } + + public static class ByteLoaderStream extends ObjectInputStream { + private ClassLoader loader; + + public ByteLoaderStream(InputStream is, ClassLoader loader) throws IOException { + super(is); + this.loader = loader; + } + + @Override + protected Class resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException { + try { + return Class.forName(osc.getName(), false, loader); + } catch (ClassNotFoundException cnfe) { + return super.resolveClass(osc); + } + } + } } diff --git a/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java b/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java index bfd7ca715771..a06671b982cf 100644 --- a/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java +++ b/test/jdk/javax/security/auth/Subject/CallAsWithScopedValue.java @@ -24,9 +24,7 @@ /* * @test * @bug 8296244 - * @enablePreview * @summary Implement Subject.current and Subject.callAs using scoped values. - * Need enablePreview to use StructuredTaskScope. * @run main/othervm ${test.main.class} true */ import com.sun.security.auth.UserPrincipal; diff --git a/test/jdk/javax/sql/test/rowset/cachedrowset/CachedRowSetInsertionTest.java b/test/jdk/javax/sql/test/rowset/cachedrowset/CachedRowSetInsertionTest.java new file mode 100644 index 000000000000..d7ccaf43c20d --- /dev/null +++ b/test/jdk/javax/sql/test/rowset/cachedrowset/CachedRowSetInsertionTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package test.rowset.cachedrowset; + +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; +import javax.sql.rowset.RowSetMetaDataImpl; +import javax.sql.rowset.RowSetProvider; + +import org.junit.jupiter.api.Test; +import util.StubConnection; +import util.StubDatabaseMetaData; +import util.StubPreparedStatement; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +// This test uses stub classes to simulate the bug without an actual +// connection to a database. +public class CachedRowSetInsertionTest { + + /* + * Address a bug where insertions would fail with AIOOBE if the amount of + * primary key columns in the datasource were greater than the amount of + * columns that were selected in the insertion. + */ + @Test + void primaryKeyInsertionFailureTest() throws SQLException { + try (var conn = new TestStubConnection(); + var rows = RowSetProvider.newFactory().createCachedRowSet()) { + RowSetMetaDataImpl metadata = new RowSetMetaDataImpl(); + metadata.setColumnCount(1); + metadata.setColumnType(1, Types.INTEGER); + metadata.setColumnName(1, "N"); + rows.setMetaData(metadata); + rows.setTableName("T"); + // See `TestStubDatabaseMetaData` which creates the two primary keys cols: A and B. + // We only select one column: N for our insertion, which allows the bug to be reproduced + // because N (1) < A & B (2). + rows.moveToInsertRow(); + rows.updateInt("N", -1); + rows.insertRow(); + rows.moveToCurrentRow(); + // Before the fix in 8394152, this line would fail + assertDoesNotThrow(() -> rows.acceptChanges(conn), + "insertion fails when there are more primary key columns than selected columns"); + } + } + + // All the stub classes implement the minimal methods required to exercise + // the proper writeData -> insertNewRow path. The one that really matters and sets + // critical data is `TestStubDatabaseMetaData.getPrimaryKeys()`. + private static class TestStubConnection extends StubConnection { + + @Override + public DatabaseMetaData getMetaData() { + return new TestStubDatabaseMetaData(); + } + + @Override + public PreparedStatement prepareStatement(String sql) { + return new TestStubPreparedStatement(this); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) { + return new TestStubPreparedStatement(this); + } + + @Override + public void commit() {} + } + + /* + * This stub class is what is important and relevant to the bug! + * What we populate in here is what the `CachedRowSetWriter` will query to determine + * the primary keys related to the underlying data source. + */ + private static class TestStubDatabaseMetaData extends StubDatabaseMetaData { + + // Populate the metadata to have two primary keys: A and B. + // The returned set consist of 1 col, with 2 rows whose values are A and B. + @Override + public ResultSet getPrimaryKeys(String catalog, String schema, String table) + throws SQLException { + var crs = RowSetProvider.newFactory().createCachedRowSet(); + RowSetMetaDataImpl metadata = new RowSetMetaDataImpl(); + metadata.setColumnCount(1); + metadata.setColumnType(1, Types.VARCHAR); + metadata.setColumnName(1, "COLUMN_NAME"); + crs.setMetaData(metadata); + for (var pk : List.of("A", "B")) { + crs.moveToInsertRow(); + crs.updateString("COLUMN_NAME", pk); + crs.insertRow(); + } + crs.moveToCurrentRow(); + crs.beforeFirst(); + return crs; + } + } + + private static class TestStubPreparedStatement extends StubPreparedStatement { + + public TestStubPreparedStatement(StubConnection con) { + super(con); + } + + @Override + public ResultSet executeQuery() throws SQLException { + return RowSetProvider.newFactory().createCachedRowSet(); + } + + @Override + public void setObject(int parameterIndex, Object x) {} + + @Override + public int executeUpdate() { + return 1; + } + + @Override + public void close() {} + } +} diff --git a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java index 023adc8254b0..79839c223240 100644 --- a/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java +++ b/test/jdk/javax/sql/test/rowset/webrowset/CommonWebRowSetTests.java @@ -22,29 +22,27 @@ */ package test.rowset.webrowset; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.InputStreamReader; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStreamWriter; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import test.rowset.cachedrowset.CommonCachedRowSetTests; + +import javax.sql.rowset.WebRowSet; +import java.io.*; import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.Arrays; -import javax.sql.rowset.WebRowSet; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import test.rowset.cachedrowset.CommonCachedRowSetTests; public abstract class CommonWebRowSetTests extends CommonCachedRowSetTests { @@ -61,7 +59,39 @@ public abstract class CommonWebRowSetTests extends CommonCachedRowSetTests { = XMLFILEPATH + "INSERTED_COFFEE_ROWS.xml"; protected final String UPDATED_INSERTED_COFFEE_ROWS_XML = XMLFILEPATH + "UPDATED_INSERTED_COFFEE_ROWS.xml"; + private static final String ROWSET_VALIDATION_PROPERTY + = "jdk.sql.rowset.webrowsetValidation"; + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } + /** + * Runs the given action with the WebRowSet validation system property set + * to the requested value, then restores the previous value. + * + * @param value the temporary property value, or {@code null} to clear it + * @param action the action to run while the property is set + */ + private static synchronized void withRowSetValidation(String value, + ThrowingRunnable action) throws Exception { + String previous = System.getProperty(ROWSET_VALIDATION_PROPERTY); + try { + if (value == null) { + System.clearProperty(ROWSET_VALIDATION_PROPERTY); + } else { + System.setProperty(ROWSET_VALIDATION_PROPERTY, value); + } + action.run(); + } finally { + if (previous == null) { + System.clearProperty(ROWSET_VALIDATION_PROPERTY); + } else { + System.setProperty(ROWSET_VALIDATION_PROPERTY, previous); + } + } + } /* * Utility method to write a WebRowSet XML file via an OutputStream @@ -101,7 +131,7 @@ protected WebRowSet readWebRowSetWithOInputStream(ByteArrayOutputStream baos) th } /* - * Utility method to write a WebRowSet XML file via an Writer + * Utility method to write a WebRowSet XML file via a Writer */ protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(WebRowSet rs) throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -111,7 +141,7 @@ protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(WebRowS } /* - * Utility method to write a WebRowSet XML file via an Writer and populating + * Utility method to write a WebRowSet XML file via a Writer and populating * the WebRowSet via a ResultSet */ protected ByteArrayOutputStream writeWebRowSetWithOutputStreamWithWriter(ResultSet rs) throws Exception { @@ -418,4 +448,253 @@ public void WebRowSetTest0012(WebRowSet wrs1) throws Exception { wrs1.close(); } + /** + * Verifies that an updated row written as XML can be read back with schema + * validation enabled. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testUpdatedRows(WebRowSet wrs) throws Exception { + try { + readCoffeeRows(wrs); + wrs.absolute(3); + wrs.updateInt(5, 21); + wrs.updateInt(6, 69); + wrs.updateRow(); + + ByteArrayOutputStream baos = + writeWebRowSetWithOutputStreamWithWriter(wrs); + withRowSetValidation(null, () -> { + // Clearing the property ensures validation is enabled; + // verifies the writer emits schema-compliant + // modifyRow/updateValue XML. + try (WebRowSet wrs1 = + readWebRowSetWithOInputStreamWithReader(baos)) { + assertEquals(COFFEES_ROWS, wrs1.size()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1)); + + assertTrue(findRowByPrimaryKey(wrs1, 3, 1)); + assertTrue(wrs1.rowUpdated()); + assertEquals(21, wrs1.getInt(5)); + assertEquals(69, wrs1.getInt(6)); + } + }); + } finally { + wrs.close(); + } + } + + /** + * Verifies that a deleted row written as XML can be read back with schema + * validation enabled. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testDeletedRows(WebRowSet wrs) + throws Exception { + try { + readCoffeeRows(wrs); + assertTrue(deleteRowByPrimaryKey(wrs, 2, 1)); + + ByteArrayOutputStream baos = + writeWebRowSetWithOutputStreamWithWriter(wrs); + withRowSetValidation(null, () -> { + // Clearing the property ensures validation is enabled; parsing + // fails if the writer omits schema-required empty updateValue + // elements. + try (WebRowSet wrs1 = + readWebRowSetWithOInputStreamWithReader(baos)) { + Object[] expectedRows = {1, 3, 4, 5}; + assertEquals(COFFEES_ROWS, wrs1.size()); + assertArrayEquals(expectedRows, getPrimaryKeys(wrs1)); + + wrs1.setShowDeleted(true); + assertTrue(findRowByPrimaryKey(wrs1, 2, 1)); + assertTrue(wrs1.rowDeleted()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1)); + } + }); + } finally { + wrs.close(); + } + } + + /** + * Verifies that null properties are written as schema-compliant empty + * elements and read back as null. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testNullProperties(WebRowSet wrs) throws Exception { + try { + readCoffeeRows(wrs); + assertNull(wrs.getCommand()); + assertNull(wrs.getDataSourceName()); + assertNull(wrs.getUrl()); + + ByteArrayOutputStream baos = + writeWebRowSetWithOutputStreamWithWriter(wrs); + withRowSetValidation(null, () -> { + // Reading with validation enabled fails if the writer emits the + // legacy invalid form for nullable properties. + try (WebRowSet wrs1 = + readWebRowSetWithOInputStreamWithReader(baos)) { + assertEquals(COFFEES_ROWS, wrs1.size()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs1)); + assertNull(wrs1.getCommand()); + assertNull(wrs1.getDataSourceName()); + assertNull(wrs1.getUrl()); + } + }); + } finally { + wrs.close(); + } + } + + /** + * Verifies that legacy XML is rejected since schema validation is enabled + * by default. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testLegacyXmlWithValidation(WebRowSet wrs) + throws Exception { + String legacyXml = getLegacyXml(); + withRowSetValidation(null, () -> { + try { + assertThrows(SQLException.class, + () -> wrs.readXml(new StringReader(legacyXml))); + } finally { + wrs.close(); + } + }); + } + + /** + * Verifies that legacy XML is accepted when schema validation is explicitly + * disabled. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testLegacyXmlWithoutValidation(WebRowSet wrs) + throws Exception { + String legacyXml = getLegacyXml() + // Exercise the path where an empty element follows a value bearing + // element. This is for JDK-8386783 + .replace("jdbc:derby://localhost:1527/testDB;create=true", ""); + withRowSetValidation("false", () -> { + try { + wrs.readXml(new StringReader(legacyXml)); + assertNull(wrs.getUrl()); + assertEquals(COFFEES_ROWS, wrs.size()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs)); + + wrs.beforeFirst(); + boolean foundUpdatedRow = false; + while (wrs.next()) { + if (wrs.getInt(1) == 3) { + foundUpdatedRow = true; + assertTrue(wrs.rowUpdated()); + assertEquals(21, wrs.getInt(5)); + assertEquals(69, wrs.getInt(6)); + } + } + assertTrue(foundUpdatedRow); + } finally { + wrs.close(); + } + }); + } + + /** + * Verifies that legacy null elements are rejected since schema validation + * is enabled by default. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testLegacyNullXmlWithValidation(WebRowSet wrs) + throws Exception { + String legacyXml = getLegacyNullXml(); + withRowSetValidation(null, () -> { + try { + // Legacy property values are not valid against the + // standard schema. + assertThrows(SQLException.class, + () -> wrs.readXml(new StringReader(legacyXml))); + } finally { + wrs.close(); + } + }); + } + + /** + * Verifies that legacy null elements are accepted when schema validation is + * explicitly disabled. + */ + @ParameterizedTest(autoCloseArguments = false) + @MethodSource("rowSetType") + public void testLegacyNullXmlWithoutValidation(WebRowSet wrs) + throws Exception { + String legacyXml = getLegacyNullXml(); + withRowSetValidation("false", () -> { + try { + wrs.readXml(new StringReader(legacyXml)); + assertEquals(COFFEES_ROWS, wrs.size()); + assertArrayEquals(COFFEES_PRIMARY_KEYS, getPrimaryKeys(wrs)); + + // With validation disabled, preserve the old reader behavior + // for nullable properties and metadata represented by . + assertNull(wrs.getCommand()); + assertNull(wrs.getDataSourceName()); + assertNull(wrs.getUrl()); + + ResultSetMetaData md = wrs.getMetaData(); + for (int i = 1; i <= md.getColumnCount(); i++) { + assertEquals("", md.getColumnLabel(i)); + assertEquals("", md.getColumnTypeName(i)); + } + } finally { + wrs.close(); + } + }); + } + + /** + * Builds a legacy document from the schema-compliant UPDATED_COFFEE_ROWS_XML + * file. Older WebRowSet XML used currentRow/updateRow for modified rows, + * while the webrowset schema requires modifyRow/updateValue. + */ + private String getLegacyXml() throws Exception { + return Files.readString(Path.of(UPDATED_COFFEE_ROWS_XML)) + .replace("", "") + .replace("", "") + .replace("updateValue", "updateRow"); + } + + /** + * Builds a legacy document from the schema-compliant COFFEE_ROWS_XML file. + * Older WebRowSet XML used nested {@code } elements for nullable + * string properties and metadata, while the webrowset schema requires + * simple string content. + */ + private String getLegacyNullXml() throws Exception { + return Files.readString(Path.of(COFFEE_ROWS_XML)) + .replace("", "") + .replace("", "") + .replace("", "") + .replace("", + "") + .replace("", + ""); + } + + /** + * Reads COFFEE_ROWS_XML and populates the given WebRowSet. + */ + private void readCoffeeRows(WebRowSet wrs) throws Exception { + try (FileInputStream fis = new FileInputStream(COFFEE_ROWS_XML)) { + wrs.readXml(fis); + } + } + } diff --git a/test/jdk/javax/sql/xml/COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/COFFEE_ROWS.xml index e42eb4405b0a..7337206243e9 100644 --- a/test/jdk/javax/sql/xml/COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/COFFEE_ROWS.xml @@ -2,9 +2,9 @@ - + 1008 - + true 1000 0 @@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j ResultSet.TYPE_SCROLL_INSENSITIVE false COFFEES - + com.sun.rowset.providers.RIOptimisticProvider Oracle Corporation @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + diff --git a/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml index 6fd38ff4e44e..c256461a8695 100644 --- a/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/DELETED_COFFEE_ROWS.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j SELECT * FROM COFFEES 1008 - + true 1000 0 @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + @@ -156,12 +156,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 - 2 - French_Roast - 49 - 8.99 - 0 - 0 + 2 + French_Roast + 49 + 8.99 + 0 + 0 3 @@ -172,12 +172,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 - 4 - Colombian_Decaf - 101 - 8.99 - 0 - 0 + 4 + Colombian_Decaf + 101 + 8.99 + 0 + 0 5 diff --git a/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml index a700bab5c6c5..8b6a0112f3bc 100644 --- a/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/INSERTED_COFFEE_ROWS.xml @@ -2,9 +2,9 @@ - + 1008 - + true 1000 0 @@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j ResultSet.TYPE_SCROLL_INSENSITIVE false COFFEES - + com.sun.rowset.providers.RIOptimisticProvider Oracle Corporation @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + diff --git a/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml index 2c71a5ffd3ad..d6256bd7cce0 100644 --- a/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/MODFIED_DELETED_COFFEE_ROWS.xml @@ -2,9 +2,9 @@ - + 1008 - + true 1000 0 @@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j ResultSet.TYPE_SCROLL_INSENSITIVE false COFFEES - + com.sun.rowset.providers.RIOptimisticProvider Oracle Corporation @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + @@ -156,12 +156,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 - 2 - French_Roast - 49 - 8.99 - 0 - 0 + 2 + French_Roast + 49 + 8.99 + 0 + 0 3 @@ -172,12 +172,12 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 - 4 - Colombian_Decaf - 101 - 8.99 - 0 - 0 + 4 + Colombian_Decaf + 101 + 8.99 + 0 + 0 5 diff --git a/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml index 449ba120b175..8d497e87d817 100644 --- a/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/UPDATED_COFFEE_ROWS.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j SELECT * FROM COFFEES 1008 - + true 1000 0 @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + @@ -163,16 +163,20 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 0 0 - + 3 + 3 Espresso + Espresso 150 + 150 9.99 + 9.99 0 - 21 + 21 0 - 69 - + 69 + 4 Colombian_Decaf diff --git a/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml b/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml index 0cfc3c9fc732..9be10b56c6b9 100644 --- a/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml +++ b/test/jdk/javax/sql/xml/UPDATED_INSERTED_COFFEE_ROWS.xml @@ -2,9 +2,9 @@ - + 1008 - + true 1000 0 @@ -20,7 +20,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j ResultSet.TYPE_SCROLL_INSENSITIVE false COFFEES - + com.sun.rowset.providers.RIOptimisticProvider Oracle Corporation @@ -40,7 +40,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + COF_NAME 0 @@ -48,7 +48,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 2 @@ -59,7 +59,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SUP_ID 0 @@ -67,7 +67,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 12 - + 3 @@ -78,7 +78,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + PRICE 0 @@ -86,7 +86,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 4 @@ -97,7 +97,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + SALES 10 @@ -105,7 +105,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 2 - + 5 @@ -116,7 +116,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - + TOTAL 0 @@ -124,7 +124,7 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j 4 - + 6 @@ -135,15 +135,15 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j false false 0 - - + + 0 0 4 - + @@ -192,10 +192,10 @@ xsi:schemaLocation="http://java.sun.com/xml/ns/jdbc http://java.sun.com/xml/ns/j Mocha 49 9.99 - 12.99 + 12.99 20 35 - 125 + 125 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/LICENSE.txt b/test/jdk/jdk/incubator/json/JSONTestSuite/LICENSE.txt new file mode 100644 index 000000000000..c4b3621d5d59 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Nicolas Seriot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS 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. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json new file mode 100644 index 000000000000..ae4c7b71f620 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_double_huge_neg_exp.json @@ -0,0 +1 @@ +[123.456e-789] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_huge_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_huge_exp.json new file mode 100644 index 000000000000..9b5efa236823 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_huge_exp.json @@ -0,0 +1 @@ +[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json new file mode 100644 index 000000000000..3abd58a5c41a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_neg_int_huge_exp.json @@ -0,0 +1 @@ +[-1e+9999] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json new file mode 100644 index 000000000000..e10a7eb62a55 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_pos_double_huge_exp.json @@ -0,0 +1 @@ +[1.5e+9999] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json new file mode 100644 index 000000000000..3d628a9943f9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_neg_overflow.json @@ -0,0 +1 @@ +[-123123e100000] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json new file mode 100644 index 000000000000..54d7d3dcdb19 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_pos_overflow.json @@ -0,0 +1 @@ +[123123e100000] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_underflow.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_underflow.json new file mode 100644 index 000000000000..c5236eb26bfd --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_real_underflow.json @@ -0,0 +1 @@ +[123e-10000000] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json new file mode 100644 index 000000000000..dfa384619749 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_neg_int.json @@ -0,0 +1 @@ +[-123123123123123123123123123123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json new file mode 100644 index 000000000000..338a8c3c0be7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_too_big_pos_int.json @@ -0,0 +1 @@ +[100000000000000000000] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json new file mode 100644 index 000000000000..e2d9738c2390 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_number_very_big_negative_int.json @@ -0,0 +1 @@ +[-237462374673276894279832749832423479823246327846] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_object_key_lone_2nd_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_object_key_lone_2nd_surrogate.json new file mode 100644 index 000000000000..5be7ebaf98d4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_object_key_lone_2nd_surrogate.json @@ -0,0 +1 @@ +{"\uDFAA":0} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_surrogate_but_2nd_missing.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_surrogate_but_2nd_missing.json new file mode 100644 index 000000000000..3b9e37c67aa0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_surrogate_but_2nd_missing.json @@ -0,0 +1 @@ +["\uDADA"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json new file mode 100644 index 000000000000..487592832ed2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_1st_valid_surrogate_2nd_invalid.json @@ -0,0 +1 @@ +["\uD888\u1234"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-16LE_with_BOM.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-16LE_with_BOM.json new file mode 100644 index 000000000000..2a79c0629a49 Binary files /dev/null and b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-16LE_with_BOM.json differ diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-8_invalid_sequence.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-8_invalid_sequence.json new file mode 100644 index 000000000000..e2a968a15942 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF-8_invalid_sequence.json @@ -0,0 +1 @@ +["日шú"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json new file mode 100644 index 000000000000..916bff920fa1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_UTF8_surrogate_U+D800.json @@ -0,0 +1 @@ +["í €"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json new file mode 100644 index 000000000000..3cb11d2294b0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_and_escape_valid.json @@ -0,0 +1 @@ +["\uD800\n"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_pair.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_pair.json new file mode 100644 index 000000000000..38ec23bb0a95 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogate_pair.json @@ -0,0 +1 @@ +["\uDd1ea"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogates_escape_valid.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogates_escape_valid.json new file mode 100644 index 000000000000..c9cd6f6c31da --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_incomplete_surrogates_escape_valid.json @@ -0,0 +1 @@ +["\uD800\uD800\n"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_lonely_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_lonely_surrogate.json new file mode 100644 index 000000000000..3abbd8d8d789 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_lonely_surrogate.json @@ -0,0 +1 @@ +["\ud800"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_surrogate.json new file mode 100644 index 000000000000..ffddc04f52fb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_surrogate.json @@ -0,0 +1 @@ +["\ud800abc"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json new file mode 100644 index 000000000000..8e45a7ecae23 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_invalid_utf-8.json @@ -0,0 +1 @@ +["ÿ"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_inverted_surrogates_U+1D11E.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_inverted_surrogates_U+1D11E.json new file mode 100644 index 000000000000..0d5456cc38f4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_inverted_surrogates_U+1D11E.json @@ -0,0 +1 @@ +["\uDd1e\uD834"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_iso_latin_1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_iso_latin_1.json new file mode 100644 index 000000000000..9389c982311c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_iso_latin_1.json @@ -0,0 +1 @@ +["é"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_second_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_second_surrogate.json new file mode 100644 index 000000000000..1dbd397f3924 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_second_surrogate.json @@ -0,0 +1 @@ +["\uDFAA"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json new file mode 100644 index 000000000000..729337c0a354 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_lone_utf8_continuation_byte.json @@ -0,0 +1 @@ +["�"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_not_in_unicode_range.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_not_in_unicode_range.json new file mode 100644 index 000000000000..df90a2916c67 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_not_in_unicode_range.json @@ -0,0 +1 @@ +["ô¿¿¿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json new file mode 100644 index 000000000000..c8cee5e0a120 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_2_bytes.json @@ -0,0 +1 @@ +["À¯"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json new file mode 100644 index 000000000000..9a91da791a9e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes.json @@ -0,0 +1 @@ +["üƒ¿¿¿¿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json new file mode 100644 index 000000000000..d24fffdd9821 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_overlong_sequence_6_bytes_null.json @@ -0,0 +1 @@ +["ü€€€€€"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_truncated-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_truncated-utf-8.json new file mode 100644 index 000000000000..63c7777fb183 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_truncated-utf-8.json @@ -0,0 +1 @@ +["àÿ"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16BE_no_BOM.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16BE_no_BOM.json new file mode 100644 index 000000000000..57e5392ff630 Binary files /dev/null and b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16BE_no_BOM.json differ diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16LE_no_BOM.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16LE_no_BOM.json new file mode 100644 index 000000000000..c49c1b25d8e5 Binary files /dev/null and b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_string_utf16LE_no_BOM.json differ diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_500_nested_arrays.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_500_nested_arrays.json new file mode 100644 index 000000000000..7118405898f5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_500_nested_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_UTF-8_BOM_empty_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_UTF-8_BOM_empty_object.json new file mode 100644 index 000000000000..22fdca1b2686 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/i_structure_UTF-8_BOM_empty_object.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_1_true_without_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_1_true_without_comma.json new file mode 100644 index 000000000000..c14e3f6b1e9d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_1_true_without_comma.json @@ -0,0 +1 @@ +[1 true] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_a_invalid_utf8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_a_invalid_utf8.json new file mode 100644 index 000000000000..38a86e2e6512 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_a_invalid_utf8.json @@ -0,0 +1 @@ +[aå] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_colon_instead_of_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_colon_instead_of_comma.json new file mode 100644 index 000000000000..0d02ad448332 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_colon_instead_of_comma.json @@ -0,0 +1 @@ +["": 1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_after_close.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_after_close.json new file mode 100644 index 000000000000..2ccba8d95033 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_after_close.json @@ -0,0 +1 @@ +[""], \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_and_number.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_and_number.json new file mode 100644 index 000000000000..d2c84e374a2a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_comma_and_number.json @@ -0,0 +1 @@ +[,1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_comma.json new file mode 100644 index 000000000000..0431712bc1db --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_comma.json @@ -0,0 +1 @@ +[1,,2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_extra_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_extra_comma.json new file mode 100644 index 000000000000..3f01d3129238 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_double_extra_comma.json @@ -0,0 +1 @@ +["x",,] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_close.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_close.json new file mode 100644 index 000000000000..c12f9fae1cbe --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_close.json @@ -0,0 +1 @@ +["x"]] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_comma.json new file mode 100644 index 000000000000..5f8ce18e4b2a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_extra_comma.json @@ -0,0 +1 @@ +["",] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete.json new file mode 100644 index 000000000000..cc65b0b512a0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete.json @@ -0,0 +1 @@ +["x" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete_invalid_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete_invalid_value.json new file mode 100644 index 000000000000..c21a8f6cff9b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_incomplete_invalid_value.json @@ -0,0 +1 @@ +[x \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_inner_array_no_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_inner_array_no_comma.json new file mode 100644 index 000000000000..c70b716471b7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_inner_array_no_comma.json @@ -0,0 +1 @@ +[3[4]] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_invalid_utf8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_invalid_utf8.json new file mode 100644 index 000000000000..6099d3441a41 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_invalid_utf8.json @@ -0,0 +1 @@ +[ÿ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_items_separated_by_semicolon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_items_separated_by_semicolon.json new file mode 100644 index 000000000000..d4bd7314ca87 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_items_separated_by_semicolon.json @@ -0,0 +1 @@ +[1:2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_comma.json new file mode 100644 index 000000000000..9d7077c6804a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_comma.json @@ -0,0 +1 @@ +[,] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_minus.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_minus.json new file mode 100644 index 000000000000..29501c6ca23d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_just_minus.json @@ -0,0 +1 @@ +[-] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_missing_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_missing_value.json new file mode 100644 index 000000000000..3a6ba86f3ab5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_missing_value.json @@ -0,0 +1 @@ +[ , ""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_newlines_unclosed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_newlines_unclosed.json new file mode 100644 index 000000000000..646680065295 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_newlines_unclosed.json @@ -0,0 +1,3 @@ +["a", +4 +,1, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_comma.json new file mode 100644 index 000000000000..13f6f1d18a44 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_comma.json @@ -0,0 +1 @@ +[1,] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_several_commas.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_several_commas.json new file mode 100644 index 000000000000..0ac408cb8aeb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_number_and_several_commas.json @@ -0,0 +1 @@ +[1,,] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_spaces_vertical_tab_formfeed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_spaces_vertical_tab_formfeed.json new file mode 100644 index 000000000000..6cd7cf5855d7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_spaces_vertical_tab_formfeed.json @@ -0,0 +1 @@ +[" a"\f] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_star_inside.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_star_inside.json new file mode 100644 index 000000000000..5a5194647ade --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_star_inside.json @@ -0,0 +1 @@ +[*] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed.json new file mode 100644 index 000000000000..060733059011 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed.json @@ -0,0 +1 @@ +["" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_trailing_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_trailing_comma.json new file mode 100644 index 000000000000..6604698ffcd4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_trailing_comma.json @@ -0,0 +1 @@ +[1, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_new_lines.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_new_lines.json new file mode 100644 index 000000000000..4f61de3fb1d0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_new_lines.json @@ -0,0 +1,3 @@ +[1, +1 +,1 \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_object_inside.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_object_inside.json new file mode 100644 index 000000000000..043a87e2db92 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_array_unclosed_with_object_inside.json @@ -0,0 +1 @@ +[{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_false.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_false.json new file mode 100644 index 000000000000..eb18c6a1437e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_false.json @@ -0,0 +1 @@ +[fals] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_null.json new file mode 100644 index 000000000000..c18ef5385103 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_null.json @@ -0,0 +1 @@ +[nul] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_true.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_true.json new file mode 100644 index 000000000000..f451ac6d2e7a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_incomplete_true.json @@ -0,0 +1 @@ +[tru] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_multidigit_number_then_00.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_multidigit_number_then_00.json new file mode 100644 index 000000000000..c22507b864f7 Binary files /dev/null and b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_multidigit_number_then_00.json differ diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_++.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_++.json new file mode 100644 index 000000000000..bdb62aaf4d90 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_++.json @@ -0,0 +1 @@ +[++1234] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+1.json new file mode 100644 index 000000000000..3cbe58c92b1d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+1.json @@ -0,0 +1 @@ +[+1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+Inf.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+Inf.json new file mode 100644 index 000000000000..871ae14d535f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_+Inf.json @@ -0,0 +1 @@ +[+Inf] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-01.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-01.json new file mode 100644 index 000000000000..0df32bac8076 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-01.json @@ -0,0 +1 @@ +[-01] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-1.0..json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-1.0..json new file mode 100644 index 000000000000..7cf55a85a739 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-1.0..json @@ -0,0 +1 @@ +[-1.0.] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-2..json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-2..json new file mode 100644 index 000000000000..9be84365d096 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-2..json @@ -0,0 +1 @@ +[-2.] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-NaN.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-NaN.json new file mode 100644 index 000000000000..f61615d404ea --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_-NaN.json @@ -0,0 +1 @@ +[-NaN] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.-1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.-1.json new file mode 100644 index 000000000000..1c9f2dd1b71d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.-1.json @@ -0,0 +1 @@ +[.-1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.2e-3.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.2e-3.json new file mode 100644 index 000000000000..c6c976f25776 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_.2e-3.json @@ -0,0 +1 @@ +[.2e-3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.1.2.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.1.2.json new file mode 100644 index 000000000000..c83a25621e31 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.1.2.json @@ -0,0 +1 @@ +[0.1.2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e+.json new file mode 100644 index 000000000000..a55a1bfefa33 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e+.json @@ -0,0 +1 @@ +[0.3e+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e.json new file mode 100644 index 000000000000..3dd5df4b3a85 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.3e.json @@ -0,0 +1 @@ +[0.3e] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.e1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.e1.json new file mode 100644 index 000000000000..c92c71ccb23f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0.e1.json @@ -0,0 +1 @@ +[0.e1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E+.json new file mode 100644 index 000000000000..3ba2c7d6d093 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E+.json @@ -0,0 +1 @@ +[0E+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E.json new file mode 100644 index 000000000000..5301840d1c31 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0_capital_E.json @@ -0,0 +1 @@ +[0E] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e+.json new file mode 100644 index 000000000000..8ab0bc4b8b2c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e+.json @@ -0,0 +1 @@ +[0e+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e.json new file mode 100644 index 000000000000..47ec421bb624 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_0e.json @@ -0,0 +1 @@ +[0e] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e+.json new file mode 100644 index 000000000000..cd84b9f69e3b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e+.json @@ -0,0 +1 @@ +[1.0e+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e-.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e-.json new file mode 100644 index 000000000000..4eb7afa0f99c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e-.json @@ -0,0 +1 @@ +[1.0e-] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e.json new file mode 100644 index 000000000000..21753f4c745f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1.0e.json @@ -0,0 +1 @@ +[1.0e] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1_000.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1_000.json new file mode 100644 index 000000000000..7b18b66b38ef --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1_000.json @@ -0,0 +1 @@ +[1 000.0] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1eE2.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1eE2.json new file mode 100644 index 000000000000..4318a341d710 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_1eE2.json @@ -0,0 +1 @@ +[1eE2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e+3.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e+3.json new file mode 100644 index 000000000000..4442f394dd73 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e+3.json @@ -0,0 +1 @@ +[2.e+3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e-3.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e-3.json new file mode 100644 index 000000000000..a65060edfcfd --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e-3.json @@ -0,0 +1 @@ +[2.e-3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e3.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e3.json new file mode 100644 index 000000000000..66f7cf701bc2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_2.e3.json @@ -0,0 +1 @@ +[2.e3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_9.e+.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_9.e+.json new file mode 100644 index 000000000000..732a7b11ce60 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_9.e+.json @@ -0,0 +1 @@ +[9.e+] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_Inf.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_Inf.json new file mode 100644 index 000000000000..c40c734c3cde --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_Inf.json @@ -0,0 +1 @@ +[Inf] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_NaN.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_NaN.json new file mode 100644 index 000000000000..4992317909e5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_NaN.json @@ -0,0 +1 @@ +[NaN] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_U+FF11_fullwidth_digit_one.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_U+FF11_fullwidth_digit_one.json new file mode 100644 index 000000000000..b14587e5ebbb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_U+FF11_fullwidth_digit_one.json @@ -0,0 +1 @@ +[1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_expression.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_expression.json new file mode 100644 index 000000000000..76fdbc8a4999 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_expression.json @@ -0,0 +1 @@ +[1+2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_1_digit.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_1_digit.json new file mode 100644 index 000000000000..3b214880c630 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_1_digit.json @@ -0,0 +1 @@ +[0x1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_2_digits.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_2_digits.json new file mode 100644 index 000000000000..83e516ab0e34 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_hex_2_digits.json @@ -0,0 +1 @@ +[0x42] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_infinity.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_infinity.json new file mode 100644 index 000000000000..8c2baf783a8e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_infinity.json @@ -0,0 +1 @@ +[Infinity] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid+-.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid+-.json new file mode 100644 index 000000000000..1cce602b518f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid+-.json @@ -0,0 +1 @@ +[0e+-1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-negative-real.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-negative-real.json new file mode 100644 index 000000000000..5fc3c1efb60c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-negative-real.json @@ -0,0 +1 @@ +[-123.123foo] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-bigger-int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-bigger-int.json new file mode 100644 index 000000000000..3b97e580e859 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-bigger-int.json @@ -0,0 +1 @@ +[123å] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-exponent.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-exponent.json new file mode 100644 index 000000000000..ea35d723cdee --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-exponent.json @@ -0,0 +1 @@ +[1e1å] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-int.json new file mode 100644 index 000000000000..371226e4cd93 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_invalid-utf-8-in-int.json @@ -0,0 +1 @@ +[0å] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_infinity.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_infinity.json new file mode 100644 index 000000000000..cf4133d22d68 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_infinity.json @@ -0,0 +1 @@ +[-Infinity] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_sign_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_sign_with_trailing_garbage.json new file mode 100644 index 000000000000..a6d8e78e7ce3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_sign_with_trailing_garbage.json @@ -0,0 +1 @@ +[-foo] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_space_1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_space_1.json new file mode 100644 index 000000000000..9a5ebedf6797 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_minus_space_1.json @@ -0,0 +1 @@ +[- 1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_int_starting_with_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_int_starting_with_zero.json new file mode 100644 index 000000000000..67af0960af79 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_int_starting_with_zero.json @@ -0,0 +1 @@ +[-012] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_real_without_int_part.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_real_without_int_part.json new file mode 100644 index 000000000000..1f2a43496ed9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_real_without_int_part.json @@ -0,0 +1 @@ +[-.123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_with_garbage_at_end.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_with_garbage_at_end.json new file mode 100644 index 000000000000..2aa73119fbbc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_neg_with_garbage_at_end.json @@ -0,0 +1 @@ +[-1x] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_garbage_after_e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_garbage_after_e.json new file mode 100644 index 000000000000..9213dfca8de9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_garbage_after_e.json @@ -0,0 +1 @@ +[1ea] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_with_invalid_utf8_after_e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_with_invalid_utf8_after_e.json new file mode 100644 index 000000000000..1e52ef964cf0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_with_invalid_utf8_after_e.json @@ -0,0 +1 @@ +[1eå] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_without_fractional_part.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_without_fractional_part.json new file mode 100644 index 000000000000..1de287cf892d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_real_without_fractional_part.json @@ -0,0 +1 @@ +[1.] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_starting_with_dot.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_starting_with_dot.json new file mode 100644 index 000000000000..f682dbdce039 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_starting_with_dot.json @@ -0,0 +1 @@ +[.123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha.json new file mode 100644 index 000000000000..1e42d81822b2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha.json @@ -0,0 +1 @@ +[1.2a-3] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha_char.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha_char.json new file mode 100644 index 000000000000..b79daccb8a6c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_alpha_char.json @@ -0,0 +1 @@ +[1.8011670033376514H-308] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_leading_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_leading_zero.json new file mode 100644 index 000000000000..7106da1f3b85 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_number_with_leading_zero.json @@ -0,0 +1 @@ +[012] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bad_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bad_value.json new file mode 100644 index 000000000000..a03a8c03b72a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bad_value.json @@ -0,0 +1 @@ +["x", truth] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bracket_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bracket_key.json new file mode 100644 index 000000000000..cc443b483214 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_bracket_key.json @@ -0,0 +1 @@ +{[: "x"} diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_comma_instead_of_colon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_comma_instead_of_colon.json new file mode 100644 index 000000000000..8d56377087d2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_comma_instead_of_colon.json @@ -0,0 +1 @@ +{"x", null} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_double_colon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_double_colon.json new file mode 100644 index 000000000000..80e8c7b89a90 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_double_colon.json @@ -0,0 +1 @@ +{"x"::"b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_emoji.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_emoji.json new file mode 100644 index 000000000000..cb4078eaa101 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_emoji.json @@ -0,0 +1 @@ +{🇨🇭} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_garbage_at_end.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_garbage_at_end.json new file mode 100644 index 000000000000..80c42cbadcfc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_garbage_at_end.json @@ -0,0 +1 @@ +{"a":"a" 123} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_key_with_single_quotes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_key_with_single_quotes.json new file mode 100644 index 000000000000..77c32759962b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_key_with_single_quotes.json @@ -0,0 +1 @@ +{key: 'value'} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json new file mode 100644 index 000000000000..aa2cb637cd36 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_lone_continuation_byte_in_key_and_trailing_comma.json @@ -0,0 +1 @@ +{"¹":"0",} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_colon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_colon.json new file mode 100644 index 000000000000..b98eff62da4f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_colon.json @@ -0,0 +1 @@ +{"a" b} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_key.json new file mode 100644 index 000000000000..b4fb0f528edb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_key.json @@ -0,0 +1 @@ +{:"b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_semicolon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_semicolon.json new file mode 100644 index 000000000000..e3451384f81a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_semicolon.json @@ -0,0 +1 @@ +{"a" "b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_value.json new file mode 100644 index 000000000000..3ef538a60e69 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_missing_value.json @@ -0,0 +1 @@ +{"a": \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_no-colon.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_no-colon.json new file mode 100644 index 000000000000..f3797b357646 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_no-colon.json @@ -0,0 +1 @@ +{"a" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key.json new file mode 100644 index 000000000000..b9945b34b449 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key.json @@ -0,0 +1 @@ +{1:1} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key_but_huge_number_instead.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key_but_huge_number_instead.json new file mode 100644 index 000000000000..b37fa86c0e02 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_non_string_key_but_huge_number_instead.json @@ -0,0 +1 @@ +{9999E9999:1} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_repeated_null_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_repeated_null_null.json new file mode 100644 index 000000000000..f7d2959d0d48 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_repeated_null_null.json @@ -0,0 +1 @@ +{null:null,null:null} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_several_trailing_commas.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_several_trailing_commas.json new file mode 100644 index 000000000000..3c9afe8dc90d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_several_trailing_commas.json @@ -0,0 +1 @@ +{"id":0,,,,,} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_single_quote.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_single_quote.json new file mode 100644 index 000000000000..e5cdf976ad63 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_single_quote.json @@ -0,0 +1 @@ +{'a':0} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comma.json new file mode 100644 index 000000000000..a4b02509459f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comma.json @@ -0,0 +1 @@ +{"id":0,} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment.json new file mode 100644 index 000000000000..a372c6553d2e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment.json @@ -0,0 +1 @@ +{"a":"b"}/**/ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_open.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_open.json new file mode 100644 index 000000000000..d557f41ca452 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_open.json @@ -0,0 +1 @@ +{"a":"b"}/**// \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open.json new file mode 100644 index 000000000000..e335136c0796 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open.json @@ -0,0 +1 @@ +{"a":"b"}// \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json new file mode 100644 index 000000000000..d892e49f1767 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_trailing_comment_slash_open_incomplete.json @@ -0,0 +1 @@ +{"a":"b"}/ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_two_commas_in_a_row.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_two_commas_in_a_row.json new file mode 100644 index 000000000000..7c639ae64969 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_two_commas_in_a_row.json @@ -0,0 +1 @@ +{"a":"b",,"c":"d"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unquoted_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unquoted_key.json new file mode 100644 index 000000000000..8ba137293c3a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unquoted_key.json @@ -0,0 +1 @@ +{a: "b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unterminated-value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unterminated-value.json new file mode 100644 index 000000000000..7fe699a6a3fe --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_unterminated-value.json @@ -0,0 +1 @@ +{"a":"a \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_single_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_single_string.json new file mode 100644 index 000000000000..d63f7fbb7e3c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_single_string.json @@ -0,0 +1 @@ +{ "foo" : "bar", "a" } \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_trailing_garbage.json new file mode 100644 index 000000000000..787c8f0a8c03 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a":"b"}# \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_single_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_single_space.json new file mode 100644 index 000000000000..0519ecba6ea9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_single_space.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape.json new file mode 100644 index 000000000000..acec66d8f4bf --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape.json @@ -0,0 +1 @@ +["\uD800\"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json new file mode 100644 index 000000000000..e834b05e9637 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u.json @@ -0,0 +1 @@ +["\uD800\u"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json new file mode 100644 index 000000000000..a04cd348928d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1.json @@ -0,0 +1 @@ +["\uD800\u1"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json new file mode 100644 index 000000000000..bfbd234098fe --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_1_surrogate_then_escape_u1x.json @@ -0,0 +1 @@ +["\uD800\u1x"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_accentuated_char_no_quotes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_accentuated_char_no_quotes.json new file mode 100644 index 000000000000..fd6895693f52 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_accentuated_char_no_quotes.json @@ -0,0 +1 @@ +[é] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_backslash_00.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_backslash_00.json new file mode 100644 index 000000000000..b5bf267b5d4e Binary files /dev/null and b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_backslash_00.json differ diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escape_x.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escape_x.json new file mode 100644 index 000000000000..fae291938d9d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escape_x.json @@ -0,0 +1 @@ +["\x00"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_backslash_bad.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_backslash_bad.json new file mode 100644 index 000000000000..016fcb47ef52 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_backslash_bad.json @@ -0,0 +1 @@ +["\\\"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_ctrl_char_tab.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_ctrl_char_tab.json new file mode 100644 index 000000000000..f35ea382bb03 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_ctrl_char_tab.json @@ -0,0 +1 @@ +["\ "] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_emoji.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_emoji.json new file mode 100644 index 000000000000..a27775421363 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_escaped_emoji.json @@ -0,0 +1 @@ +["\🌀"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escape.json new file mode 100644 index 000000000000..3415c33ca8ab --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escape.json @@ -0,0 +1 @@ +["\"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escaped_character.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escaped_character.json new file mode 100644 index 000000000000..0f2197ea2901 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_escaped_character.json @@ -0,0 +1 @@ +["\u00A"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate.json new file mode 100644 index 000000000000..75504a656188 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate.json @@ -0,0 +1 @@ +["\uD834\uDd"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate_escape_invalid.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate_escape_invalid.json new file mode 100644 index 000000000000..bd9656060df4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_incomplete_surrogate_escape_invalid.json @@ -0,0 +1 @@ +["\uD800\uD800\x"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid-utf-8-in-escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid-utf-8-in-escape.json new file mode 100644 index 000000000000..0c43006430d3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid-utf-8-in-escape.json @@ -0,0 +1 @@ +["\uå"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_backslash_esc.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_backslash_esc.json new file mode 100644 index 000000000000..d1eb60921a08 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_backslash_esc.json @@ -0,0 +1 @@ +["\a"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_unicode_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_unicode_escape.json new file mode 100644 index 000000000000..7608cb6ba896 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_unicode_escape.json @@ -0,0 +1 @@ +["\uqqqq"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_utf8_after_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_utf8_after_escape.json new file mode 100644 index 000000000000..2f757a25b545 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_invalid_utf8_after_escape.json @@ -0,0 +1 @@ +["\å"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_leading_uescaped_thinspace.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_leading_uescaped_thinspace.json new file mode 100644 index 000000000000..7b297c6365af --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_leading_uescaped_thinspace.json @@ -0,0 +1 @@ +[\u0020"asd"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_no_quotes_with_bad_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_no_quotes_with_bad_escape.json new file mode 100644 index 000000000000..01bc70abae62 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_no_quotes_with_bad_escape.json @@ -0,0 +1 @@ +[\n] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_doublequote.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_doublequote.json new file mode 100644 index 000000000000..9d68933c44f1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_doublequote.json @@ -0,0 +1 @@ +" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_quote.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_quote.json new file mode 100644 index 000000000000..caff239bfc36 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_quote.json @@ -0,0 +1 @@ +['single quote'] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_string_no_double_quotes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_string_no_double_quotes.json new file mode 100644 index 000000000000..f2ba8f84ab5c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_single_string_no_double_quotes.json @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_start_escape_unclosed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_start_escape_unclosed.json new file mode 100644 index 000000000000..db62a46fcbc6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_start_escape_unclosed.json @@ -0,0 +1 @@ +["\ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_ctrl_char.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_ctrl_char.json new file mode 100644 index 000000000000..9f21348071d3 Binary files /dev/null and b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_ctrl_char.json differ diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_newline.json new file mode 100644 index 000000000000..700d36086914 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_newline.json @@ -0,0 +1,2 @@ +["new +line"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_tab.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_tab.json new file mode 100644 index 000000000000..160264a2d995 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unescaped_tab.json @@ -0,0 +1 @@ +[" "] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unicode_CapitalU.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unicode_CapitalU.json new file mode 100644 index 000000000000..17332bb174cb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_unicode_CapitalU.json @@ -0,0 +1 @@ +"\UA66D" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_with_trailing_garbage.json new file mode 100644 index 000000000000..efe3bd272c4e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_string_with_trailing_garbage.json @@ -0,0 +1 @@ +""x \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_100000_opening_arrays.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_100000_opening_arrays.json new file mode 100644 index 000000000000..a4823eeccebc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_100000_opening_arrays.json @@ -0,0 +1 @@ +[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_U+2060_word_joined.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_U+2060_word_joined.json new file mode 100644 index 000000000000..81156a6996ed --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_U+2060_word_joined.json @@ -0,0 +1 @@ +[â� ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_UTF8_BOM_no_data.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_UTF8_BOM_no_data.json new file mode 100644 index 000000000000..5f282702bb03 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_UTF8_BOM_no_data.json @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_..json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_..json new file mode 100644 index 000000000000..a56fef0b021f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_..json @@ -0,0 +1 @@ +<.> \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json new file mode 100644 index 000000000000..617f26254934 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_angle_bracket_null.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_trailing_garbage.json new file mode 100644 index 000000000000..5a745e6f3c36 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_trailing_garbage.json @@ -0,0 +1 @@ +[1]x \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_extra_array_close.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_extra_array_close.json new file mode 100644 index 000000000000..6cfb1398d224 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_extra_array_close.json @@ -0,0 +1 @@ +[1]] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_unclosed_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_unclosed_string.json new file mode 100644 index 000000000000..ba6b1788b672 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_array_with_unclosed_string.json @@ -0,0 +1 @@ +["asd] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_ascii-unicode-identifier.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_ascii-unicode-identifier.json new file mode 100644 index 000000000000..ef2ab62fe763 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_ascii-unicode-identifier.json @@ -0,0 +1 @@ +aÃ¥ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_capitalized_True.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_capitalized_True.json new file mode 100644 index 000000000000..7cd88469ab3d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_capitalized_True.json @@ -0,0 +1 @@ +[True] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_close_unopened_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_close_unopened_array.json new file mode 100644 index 000000000000..d2af0c646a8f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_close_unopened_array.json @@ -0,0 +1 @@ +1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_comma_instead_of_closing_brace.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_comma_instead_of_closing_brace.json new file mode 100644 index 000000000000..ac61b820068b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_comma_instead_of_closing_brace.json @@ -0,0 +1 @@ +{"x": true, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_double_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_double_array.json new file mode 100644 index 000000000000..058d1626e5af --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_double_array.json @@ -0,0 +1 @@ +[][] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_end_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_end_array.json new file mode 100644 index 000000000000..54caf60b1367 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_end_array.json @@ -0,0 +1 @@ +] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_incomplete_UTF8_BOM.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_incomplete_UTF8_BOM.json new file mode 100644 index 000000000000..bfcdd514f330 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_incomplete_UTF8_BOM.json @@ -0,0 +1 @@ +ï»{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-invalid-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-invalid-utf-8.json new file mode 100644 index 000000000000..8b1296cad20a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-invalid-utf-8.json @@ -0,0 +1 @@ +å \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-open-bracket.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-open-bracket.json new file mode 100644 index 000000000000..8e2f0bef135b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_lone-open-bracket.json @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_no_data.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_no_data.json new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_null-byte-outside-string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_null-byte-outside-string.json new file mode 100644 index 000000000000..326db14422a7 Binary files /dev/null and b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_null-byte-outside-string.json differ diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_number_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_number_with_trailing_garbage.json new file mode 100644 index 000000000000..0746539d246c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_number_with_trailing_garbage.json @@ -0,0 +1 @@ +2@ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_followed_by_closing_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_followed_by_closing_object.json new file mode 100644 index 000000000000..aa9ebaec5709 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_followed_by_closing_object.json @@ -0,0 +1 @@ +{}} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_unclosed_no_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_unclosed_no_value.json new file mode 100644 index 000000000000..17d045147fe5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_unclosed_no_value.json @@ -0,0 +1 @@ +{"": \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_comment.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_comment.json new file mode 100644 index 000000000000..ed1b569b7049 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_comment.json @@ -0,0 +1 @@ +{"a":/*comment*/"b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_trailing_garbage.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_trailing_garbage.json new file mode 100644 index 000000000000..9ca2336d7451 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_object_with_trailing_garbage.json @@ -0,0 +1 @@ +{"a": true} "x" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_apostrophe.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_apostrophe.json new file mode 100644 index 000000000000..8bebe3af09a7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_apostrophe.json @@ -0,0 +1 @@ +[' \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_comma.json new file mode 100644 index 000000000000..6295fdc36db0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_comma.json @@ -0,0 +1 @@ +[, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_object.json new file mode 100644 index 000000000000..e870445b2e25 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_object.json @@ -0,0 +1 @@ +[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"": diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_object.json new file mode 100644 index 000000000000..7a63c8c57c8a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_object.json @@ -0,0 +1 @@ +[{ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_string.json new file mode 100644 index 000000000000..9822a6baf7e5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_open_string.json @@ -0,0 +1 @@ +["a \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_string.json new file mode 100644 index 000000000000..42a619362021 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_array_string.json @@ -0,0 +1 @@ +["a" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object.json new file mode 100644 index 000000000000..81750b96f9d8 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object.json @@ -0,0 +1 @@ +{ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_close_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_close_array.json new file mode 100644 index 000000000000..eebc700a1013 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_close_array.json @@ -0,0 +1 @@ +{] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_comma.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_comma.json new file mode 100644 index 000000000000..47bc9106f86a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_comma.json @@ -0,0 +1 @@ +{, \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_array.json new file mode 100644 index 000000000000..381ede5dea5f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_array.json @@ -0,0 +1 @@ +{[ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_string.json new file mode 100644 index 000000000000..328c30cd782a --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_open_string.json @@ -0,0 +1 @@ +{"a \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_string_with_apostrophes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_string_with_apostrophes.json new file mode 100644 index 000000000000..9dba17090cf6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_object_string_with_apostrophes.json @@ -0,0 +1 @@ +{'a' \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_open.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_open.json new file mode 100644 index 000000000000..841fd5f8640e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_open_open.json @@ -0,0 +1 @@ +["\{["\{["\{["\{ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_eacute.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_eacute.json new file mode 100644 index 000000000000..92a39f398b80 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_eacute.json @@ -0,0 +1 @@ +é \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_star.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_star.json new file mode 100644 index 000000000000..f59ec20aabf5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_single_star.json @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_NUMBER_SIGN.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_NUMBER_SIGN.json new file mode 100644 index 000000000000..8986110875af --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_trailing_NUMBER_SIGN.json @@ -0,0 +1 @@ +{"a":"b"}#{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_uescaped_LF_before_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_uescaped_LF_before_string.json new file mode 100644 index 000000000000..df2f0f242244 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_uescaped_LF_before_string.json @@ -0,0 +1 @@ +[\u000A""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array.json new file mode 100644 index 000000000000..11209515c8dd --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array.json @@ -0,0 +1 @@ +[1 \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_partial_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_partial_null.json new file mode 100644 index 000000000000..0d591762c127 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_partial_null.json @@ -0,0 +1 @@ +[ false, nul \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_false.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_false.json new file mode 100644 index 000000000000..a2ff8504a9c6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_false.json @@ -0,0 +1 @@ +[ true, fals \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_true.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_true.json new file mode 100644 index 000000000000..3149e8f5a7a5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_array_unfinished_true.json @@ -0,0 +1 @@ +[ false, tru \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_object.json new file mode 100644 index 000000000000..694d69dbd00c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unclosed_object.json @@ -0,0 +1 @@ +{"asd":"asd" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unicode-identifier.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unicode-identifier.json new file mode 100644 index 000000000000..7284aea33d3f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_unicode-identifier.json @@ -0,0 +1 @@ +Ã¥ \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_U+2060_word_joiner.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_U+2060_word_joiner.json new file mode 100644 index 000000000000..81156a6996ed --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_U+2060_word_joiner.json @@ -0,0 +1 @@ +[â� ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_formfeed.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_formfeed.json new file mode 100644 index 000000000000..a9ea535d1bbe --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/n_structure_whitespace_formfeed.json @@ -0,0 +1 @@ +[ ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_arraysWithSpaces.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_arraysWithSpaces.json new file mode 100644 index 000000000000..582290798f43 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_arraysWithSpaces.json @@ -0,0 +1 @@ +[[] ] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty-string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty-string.json new file mode 100644 index 000000000000..93b6be2bccad --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty-string.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty.json new file mode 100644 index 000000000000..0637a088a01e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_empty.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_ending_with_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_ending_with_newline.json new file mode 100644 index 000000000000..eac5f7b46e04 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_ending_with_newline.json @@ -0,0 +1 @@ +["a"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_false.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_false.json new file mode 100644 index 000000000000..67b2f07601e4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_false.json @@ -0,0 +1 @@ +[false] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_heterogeneous.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_heterogeneous.json new file mode 100644 index 000000000000..d3c1e264845e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_heterogeneous.json @@ -0,0 +1 @@ +[null, 1, "1", {}] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_null.json new file mode 100644 index 000000000000..500db4a86aa3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_null.json @@ -0,0 +1 @@ +[null] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_1_and_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_1_and_newline.json new file mode 100644 index 000000000000..994825500ae3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_1_and_newline.json @@ -0,0 +1,2 @@ +[1 +] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_leading_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_leading_space.json new file mode 100644 index 000000000000..18bfe6422c78 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_leading_space.json @@ -0,0 +1 @@ + [1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_several_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_several_null.json new file mode 100644 index 000000000000..99f6c5d1d883 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_several_null.json @@ -0,0 +1 @@ +[1,null,null,null,2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_trailing_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_trailing_space.json new file mode 100644 index 000000000000..de9e7a94492d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_array_with_trailing_space.json @@ -0,0 +1 @@ +[2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number.json new file mode 100644 index 000000000000..e5f5cc3340d2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number.json @@ -0,0 +1 @@ +[123e65] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e+1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e+1.json new file mode 100644 index 000000000000..d1d3967065cf --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e+1.json @@ -0,0 +1 @@ +[0e+1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e1.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e1.json new file mode 100644 index 000000000000..3283a7936514 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_0e1.json @@ -0,0 +1 @@ +[0e1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_after_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_after_space.json new file mode 100644 index 000000000000..623570d96040 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_after_space.json @@ -0,0 +1 @@ +[ 4] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_double_close_to_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_double_close_to_zero.json new file mode 100644 index 000000000000..96555ff78230 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_double_close_to_zero.json @@ -0,0 +1 @@ +[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_int_with_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_int_with_exp.json new file mode 100644 index 000000000000..a4ca9e754fb0 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_int_with_exp.json @@ -0,0 +1 @@ +[20e1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_minus_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_minus_zero.json new file mode 100644 index 000000000000..37af1312ab5d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_minus_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_int.json new file mode 100644 index 000000000000..8e30f8bd9660 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_int.json @@ -0,0 +1 @@ +[-123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_one.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_one.json new file mode 100644 index 000000000000..99d21a2a0f09 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_one.json @@ -0,0 +1 @@ +[-1] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_zero.json new file mode 100644 index 000000000000..37af1312ab5d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_negative_zero.json @@ -0,0 +1 @@ +[-0] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e.json new file mode 100644 index 000000000000..6edbdfcb180d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e.json @@ -0,0 +1 @@ +[1E22] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_neg_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_neg_exp.json new file mode 100644 index 000000000000..0a01bd3ef4ca --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_neg_exp.json @@ -0,0 +1 @@ +[1E-2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_pos_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_pos_exp.json new file mode 100644 index 000000000000..5a8fc0972425 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_capital_e_pos_exp.json @@ -0,0 +1 @@ +[1E+2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_exponent.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_exponent.json new file mode 100644 index 000000000000..da2522d61a1e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_exponent.json @@ -0,0 +1 @@ +[123e45] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_fraction_exponent.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_fraction_exponent.json new file mode 100644 index 000000000000..3944a7a454b3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_fraction_exponent.json @@ -0,0 +1 @@ +[123.456e78] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_neg_exp.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_neg_exp.json new file mode 100644 index 000000000000..ca40d3c25538 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_neg_exp.json @@ -0,0 +1 @@ +[1e-2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_pos_exponent.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_pos_exponent.json new file mode 100644 index 000000000000..343601d51fdd --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_real_pos_exponent.json @@ -0,0 +1 @@ +[1e+2] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_int.json new file mode 100644 index 000000000000..e47f69afcf45 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_int.json @@ -0,0 +1 @@ +[123] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_real.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_real.json new file mode 100644 index 000000000000..b02878e5fc19 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_number_simple_real.json @@ -0,0 +1 @@ +[123.456789] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object.json new file mode 100644 index 000000000000..78262eda3fa9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object.json @@ -0,0 +1 @@ +{"asd":"sdf", "dfg":"fgh"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_basic.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_basic.json new file mode 100644 index 000000000000..646bbe7bb1f8 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_basic.json @@ -0,0 +1 @@ +{"asd":"sdf"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key.json new file mode 100644 index 000000000000..bbc2e1ce433c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key.json @@ -0,0 +1 @@ +{"a":"b","a":"c"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key_and_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key_and_value.json new file mode 100644 index 000000000000..211581c20717 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_duplicated_key_and_value.json @@ -0,0 +1 @@ +{"a":"b","a":"b"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty.json new file mode 100644 index 000000000000..9e26dfeeb6e6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty_key.json new file mode 100644 index 000000000000..c0013d3b8bd2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_empty_key.json @@ -0,0 +1 @@ +{"":0} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_escaped_null_in_key.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_escaped_null_in_key.json new file mode 100644 index 000000000000..593f0f67f9cb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_escaped_null_in_key.json @@ -0,0 +1 @@ +{"foo\u0000bar": 42} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_extreme_numbers.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_extreme_numbers.json new file mode 100644 index 000000000000..a0d3531c32f9 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_extreme_numbers.json @@ -0,0 +1 @@ +{ "min": -1.0e+28, "max": 1.0e+28 } \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_long_strings.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_long_strings.json new file mode 100644 index 000000000000..bdc4a08719ef --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_long_strings.json @@ -0,0 +1 @@ +{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_simple.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_simple.json new file mode 100644 index 000000000000..dacac917fb7b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_simple.json @@ -0,0 +1 @@ +{"a":[]} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_string_unicode.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_string_unicode.json new file mode 100644 index 000000000000..8effdb297c78 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_string_unicode.json @@ -0,0 +1 @@ +{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" } \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_with_newlines.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_with_newlines.json new file mode 100644 index 000000000000..246ec6b34d5e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_object_with_newlines.json @@ -0,0 +1,3 @@ +{ +"a": "b" +} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json new file mode 100644 index 000000000000..9967ddeb8b11 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_1_2_3_bytes_UTF-8_sequences.json @@ -0,0 +1 @@ +["\u0060\u012a\u12AB"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pair.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pair.json new file mode 100644 index 000000000000..996875cc8c96 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pair.json @@ -0,0 +1 @@ +["\uD801\udc37"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pairs.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pairs.json new file mode 100644 index 000000000000..3401021ecef5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_accepted_surrogate_pairs.json @@ -0,0 +1 @@ +["\ud83d\ude39\ud83d\udc8d"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_allowed_escapes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_allowed_escapes.json new file mode 100644 index 000000000000..7f495532fb37 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_allowed_escapes.json @@ -0,0 +1 @@ +["\"\\\/\b\f\n\r\t"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_and_u_escaped_zero.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_and_u_escaped_zero.json new file mode 100644 index 000000000000..d4439eda73ac --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_and_u_escaped_zero.json @@ -0,0 +1 @@ +["\\u0000"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_doublequotes.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_doublequotes.json new file mode 100644 index 000000000000..ae03243b6742 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_backslash_doublequotes.json @@ -0,0 +1 @@ +["\""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_comments.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_comments.json new file mode 100644 index 000000000000..2260c20c2f86 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_comments.json @@ -0,0 +1 @@ +["a/*b*/c/*d//e"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_a.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_a.json new file mode 100644 index 000000000000..6715d6f4049f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_a.json @@ -0,0 +1 @@ +["\\a"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_n.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_n.json new file mode 100644 index 000000000000..44ca56c4d942 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_double_escape_n.json @@ -0,0 +1 @@ +["\\n"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_control_character.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_control_character.json new file mode 100644 index 000000000000..5b014a9c25b5 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_control_character.json @@ -0,0 +1 @@ +["\u0012"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_noncharacter.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_noncharacter.json new file mode 100644 index 000000000000..2ff52e2c50bc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_escaped_noncharacter.json @@ -0,0 +1 @@ +["\uFFFF"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array.json new file mode 100644 index 000000000000..21d7ae4cd80b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array.json @@ -0,0 +1 @@ +["asd"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array_with_leading_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array_with_leading_space.json new file mode 100644 index 000000000000..9e1887c1e4d2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_in_array_with_leading_space.json @@ -0,0 +1 @@ +[ "asd"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_last_surrogates_1_and_2.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_last_surrogates_1_and_2.json new file mode 100644 index 000000000000..3919cef76579 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_last_surrogates_1_and_2.json @@ -0,0 +1 @@ +["\uDBFF\uDFFF"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json new file mode 100644 index 000000000000..2085ab1a1c77 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nbsp_uescaped.json @@ -0,0 +1 @@ +["new\u00A0line"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json new file mode 100644 index 000000000000..059e4d9dd0f4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+10FFFF.json @@ -0,0 +1 @@ +["ô�¿¿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json new file mode 100644 index 000000000000..4c913bd41a08 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_nonCharacterInUTF-8_U+FFFF.json @@ -0,0 +1 @@ +["ï¿¿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_null_escape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_null_escape.json new file mode 100644 index 000000000000..c1ad844043e6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_null_escape.json @@ -0,0 +1 @@ +["\u0000"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_one-byte-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_one-byte-utf-8.json new file mode 100644 index 000000000000..157185923ac7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_one-byte-utf-8.json @@ -0,0 +1 @@ +["\u002c"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_pi.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_pi.json new file mode 100644 index 000000000000..9df11ae88bde --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_pi.json @@ -0,0 +1 @@ +["Ï€"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json new file mode 100644 index 000000000000..10a33a1717ec --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_reservedCharacterInUTF-8_U+1BFFF.json @@ -0,0 +1 @@ +["𛿿"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_simple_ascii.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_simple_ascii.json new file mode 100644 index 000000000000..8cadf7d051df --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_simple_ascii.json @@ -0,0 +1 @@ +["asd "] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_space.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_space.json new file mode 100644 index 000000000000..efd782cc3250 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_space.json @@ -0,0 +1 @@ +" " \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json new file mode 100644 index 000000000000..7620b66559f3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json @@ -0,0 +1 @@ +["\uD834\uDd1e"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_three-byte-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_three-byte-utf-8.json new file mode 100644 index 000000000000..108f1d67dffc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_three-byte-utf-8.json @@ -0,0 +1 @@ +["\u0821"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_two-byte-utf-8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_two-byte-utf-8.json new file mode 100644 index 000000000000..461503c31001 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_two-byte-utf-8.json @@ -0,0 +1 @@ +["\u0123"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2028_line_sep.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2028_line_sep.json new file mode 100644 index 000000000000..897b6021af72 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2028_line_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2029_par_sep.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2029_par_sep.json new file mode 100644 index 000000000000..8cd998c89ea3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_u+2029_par_sep.json @@ -0,0 +1 @@ +["
"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uEscape.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uEscape.json new file mode 100644 index 000000000000..f7b41a02fa5e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uEscape.json @@ -0,0 +1 @@ +["\u0061\u30af\u30EA\u30b9"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uescaped_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uescaped_newline.json new file mode 100644 index 000000000000..3a5a220b69cc --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_uescaped_newline.json @@ -0,0 +1 @@ +["new\u000Aline"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unescaped_char_delete.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unescaped_char_delete.json new file mode 100644 index 000000000000..7d064f498715 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unescaped_char_delete.json @@ -0,0 +1 @@ +[""] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode.json new file mode 100644 index 000000000000..3598095b79be --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode.json @@ -0,0 +1 @@ +["\uA66D"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicodeEscapedBackslash.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicodeEscapedBackslash.json new file mode 100644 index 000000000000..0bb3b51e7eae --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicodeEscapedBackslash.json @@ -0,0 +1 @@ +["\u005C"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_2.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_2.json new file mode 100644 index 000000000000..a7dcb97683f7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_2.json @@ -0,0 +1 @@ +["â�‚㈴â�‚"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json new file mode 100644 index 000000000000..9a8370b96a1c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+10FFFE_nonchar.json @@ -0,0 +1 @@ +["\uDBFF\uDFFE"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json new file mode 100644 index 000000000000..c51f8ae45c3c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+1FFFE_nonchar.json @@ -0,0 +1 @@ +["\uD83F\uDFFE"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json new file mode 100644 index 000000000000..626d5f81572d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json @@ -0,0 +1 @@ +["\u200B"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json new file mode 100644 index 000000000000..1e23972c65e3 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+2064_invisible_plus.json @@ -0,0 +1 @@ +["\u2064"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json new file mode 100644 index 000000000000..18ef151b4f21 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FDD0_nonchar.json @@ -0,0 +1 @@ +["\uFDD0"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json new file mode 100644 index 000000000000..13d261fdad59 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_U+FFFE_nonchar.json @@ -0,0 +1 @@ +["\uFFFE"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_escaped_double_quote.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_escaped_double_quote.json new file mode 100644 index 000000000000..4e6257856dd2 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_unicode_escaped_double_quote.json @@ -0,0 +1 @@ +["\u0022"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_utf8.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_utf8.json new file mode 100644 index 000000000000..40878435f978 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_utf8.json @@ -0,0 +1 @@ +["€ð�„ž"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_with_del_character.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_with_del_character.json new file mode 100644 index 000000000000..8bd24907d9ea --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_string_with_del_character.json @@ -0,0 +1 @@ +["aa"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_false.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_false.json new file mode 100644 index 000000000000..02e4a84d62c4 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_false.json @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_int.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_int.json new file mode 100644 index 000000000000..f70d7bba4ae1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_int.json @@ -0,0 +1 @@ +42 \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_negative_real.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_negative_real.json new file mode 100644 index 000000000000..b5135a207dee --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_negative_real.json @@ -0,0 +1 @@ +-0.1 \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_null.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_null.json new file mode 100644 index 000000000000..ec747fa47ddb --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_null.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_string.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_string.json new file mode 100644 index 000000000000..b6e982ca96aa --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_string.json @@ -0,0 +1 @@ +"asd" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_true.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_true.json new file mode 100644 index 000000000000..f32a5804e292 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_lonely_true.json @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_string_empty.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_string_empty.json new file mode 100644 index 000000000000..3cc762b5501e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_string_empty.json @@ -0,0 +1 @@ +"" \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_trailing_newline.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_trailing_newline.json new file mode 100644 index 000000000000..0c3426d4c287 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_trailing_newline.json @@ -0,0 +1 @@ +["a"] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_true_in_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_true_in_array.json new file mode 100644 index 000000000000..de601e305f4f --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_true_in_array.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_whitespace_array.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_whitespace_array.json new file mode 100644 index 000000000000..2bedf7f2de5c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_parsing/y_structure_whitespace_array.json @@ -0,0 +1 @@ + [] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775808.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775808.json new file mode 100644 index 000000000000..f8853ff83d5e --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775808.json @@ -0,0 +1 @@ +[-9223372036854775808] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775809.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775809.json new file mode 100644 index 000000000000..5dca91fde6d6 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_-9223372036854775809.json @@ -0,0 +1 @@ +[-9223372036854775809] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.0.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.0.json new file mode 100644 index 000000000000..0ea64a68f2d1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.0.json @@ -0,0 +1 @@ +[1.0] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.000000000000000005.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.000000000000000005.json new file mode 100644 index 000000000000..4c3849de2c62 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1.000000000000000005.json @@ -0,0 +1 @@ +[1.000000000000000005] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1000000000000000.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1000000000000000.json new file mode 100644 index 000000000000..cd38afa77680 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1000000000000000.json @@ -0,0 +1 @@ +[1000000000000000] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_10000000000000000999.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_10000000000000000999.json new file mode 100644 index 000000000000..fa37dfa7c6ca --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_10000000000000000999.json @@ -0,0 +1 @@ +[10000000000000000999] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e-999.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e-999.json new file mode 100644 index 000000000000..61ce7529afad --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e-999.json @@ -0,0 +1 @@ +[1E-999] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e6.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e6.json new file mode 100644 index 000000000000..b55cfc4b2fa7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_1e6.json @@ -0,0 +1 @@ +[1E6] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775807.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775807.json new file mode 100644 index 000000000000..f899e4c407ba --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775807.json @@ -0,0 +1 @@ +[9223372036854775807] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775808.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775808.json new file mode 100644 index 000000000000..15757d3e5d29 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/number_9223372036854775808.json @@ -0,0 +1 @@ +[9223372036854775808] diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfc_nfd.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfc_nfd.json new file mode 100644 index 000000000000..e4cbc1dc114d --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfc_nfd.json @@ -0,0 +1 @@ +{"é":"NFC","eÌ�":"NFD"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfd_nfc.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfd_nfc.json new file mode 100644 index 000000000000..b04ece18df70 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_key_nfd_nfc.json @@ -0,0 +1 @@ +{"eÌ�":"NFD","é":"NFC"} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_different_values.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_different_values.json new file mode 100644 index 000000000000..0c4547df378b --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_different_values.json @@ -0,0 +1 @@ +{"a":1,"a":2} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_same_value.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_same_value.json new file mode 100644 index 000000000000..e1070184de79 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_same_value.json @@ -0,0 +1 @@ +{"a":1,"a":1} \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_unclear_values.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_unclear_values.json new file mode 100644 index 000000000000..8a76bd4ffeee --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/object_same_key_unclear_values.json @@ -0,0 +1 @@ +{"a":0, "a":-0} diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_escaped_invalid_codepoint.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_escaped_invalid_codepoint.json new file mode 100644 index 000000000000..8e6247319f54 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_escaped_invalid_codepoint.json @@ -0,0 +1 @@ +["\uD800"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_invalid_codepoint.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_invalid_codepoint.json new file mode 100644 index 000000000000..916bff920fa1 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_1_invalid_codepoint.json @@ -0,0 +1 @@ +["í €"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_escaped_invalid_codepoints.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_escaped_invalid_codepoints.json new file mode 100644 index 000000000000..93568e2c776c --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_escaped_invalid_codepoints.json @@ -0,0 +1 @@ +["\uD800\uD800"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_invalid_codepoints.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_invalid_codepoints.json new file mode 100644 index 000000000000..043a72e813cf --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_2_invalid_codepoints.json @@ -0,0 +1 @@ +["í €í €"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_escaped_invalid_codepoints.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_escaped_invalid_codepoints.json new file mode 100644 index 000000000000..407dc6575080 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_escaped_invalid_codepoints.json @@ -0,0 +1 @@ +["\uD800\uD800\uD800"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_invalid_codepoints.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_invalid_codepoints.json new file mode 100644 index 000000000000..2fcb0927d103 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_3_invalid_codepoints.json @@ -0,0 +1 @@ +["í €í €í €"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_with_escaped_NULL.json b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_with_escaped_NULL.json new file mode 100644 index 000000000000..8ca2be59e602 --- /dev/null +++ b/test/jdk/jdk/incubator/json/JSONTestSuite/test_transform/string_with_escaped_NULL.json @@ -0,0 +1 @@ +["A\u0000B"] \ No newline at end of file diff --git a/test/jdk/jdk/incubator/json/TestJSONParsing.java b/test/jdk/jdk/incubator/json/TestJSONParsing.java new file mode 100644 index 000000000000..603cdfc5c2e7 --- /dev/null +++ b/test/jdk/jdk/incubator/json/TestJSONParsing.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @modules jdk.incubator.json + * @run junit TestJSONParsing + */ + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import jdk.incubator.json.*; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TestJSONParsing { + + private static final String SRC_DIR = System.getProperty("test.src", "."); + private static final String TEST_DIR_PARSING = "JSONTestSuite/test_parsing"; + private static final String TEST_DIR_TRANSFORM = "JSONTestSuite/test_transform"; + + // Excluding these test cases, as our implementation does not parse in bytes + private static final List PARSING_I_FAIL = Arrays.asList( + "i_string_UTF-16LE_with_BOM.json", + "i_string_utf16BE_no_BOM.json", + "i_string_utf16LE_no_BOM.json", + "i_structure_UTF-8_BOM_empty_object.json"); + + // See RFC 8259.4 - "The names within an object SHOULD be unique." + // These should be recognized as n (pass to method reference) + private static final List PARSING_Y_FAIL = Arrays.asList( + "y_object_duplicated_key_and_value.json", + "y_object_duplicated_key.json"); + + // See unique key note for PARSING_Y_FAIL + private static final List TRANSFORM_FAIL = Arrays.asList( + "object_same_key_different_values.json", + "object_same_key_unclear_values.json", + "object_same_key_same_value.json"); + + // All files in parsing prefixed with y_ or i_ + // that are not found in the designated parsing failure lists + private static Stream parsingPass() throws IOException { + return Files.walk(Paths.get(SRC_DIR, TEST_DIR_PARSING)) + .filter(a -> (a.getFileName().toString().startsWith("y_") + || a.getFileName().toString().startsWith("i_"))) + .filter(a -> !PARSING_I_FAIL.contains(a.getFileName().toString())) + .filter(a -> !PARSING_Y_FAIL.contains(a.getFileName().toString())); + } + + @ParameterizedTest + @MethodSource + public void parsingPass(Path p) { + testJson(p, true); + } + + // All files in parsing prefixed with n_ + // or files found in the designated parsing failure lists + private static Stream parsingFail() throws IOException { + return Stream.concat( + Stream.concat(PARSING_I_FAIL.stream().map( + a -> Path.of(SRC_DIR, "JSONTestSuite/test_parsing/", a)), + PARSING_Y_FAIL.stream().map( + a -> Path.of(SRC_DIR, "JSONTestSuite/test_parsing/", a))), + Files.walk(Paths.get(SRC_DIR, TEST_DIR_PARSING)). + filter(a -> (a.getFileName().toString()).startsWith("n_"))); + } + + @ParameterizedTest + @MethodSource + public void parsingFail(Path p) { + testJson(p, false); + } + + // Transform files that are not found in the designated transform failure list + private static Stream transformPass() throws IOException { + return Files.walk(Paths.get(SRC_DIR, TEST_DIR_TRANSFORM)) + .filter(a -> !TRANSFORM_FAIL.contains(a.getFileName().toString())); + } + + @ParameterizedTest + @MethodSource + public void transformPass(Path p) { + testJson(p, true); + } + + // Transform files found in the designated transform failure list + private static Stream transformFail() { + return TRANSFORM_FAIL.stream().map(a -> Path.of(SRC_DIR, "JSONTestSuite/test_transform/", a)); + } + + @ParameterizedTest + @MethodSource + public void transformFail(Path p) { + testJson(p, false); + } + + private void testJson(Path p, boolean pass) { + if (!p.toString().endsWith(".json")) { + return; + } + var fileName = p.getFileName().toString(); + String json; + try { + json = new String(Files.readAllBytes(p)); + } catch (IOException e) { + throw new RuntimeException(String.format("File: %s not found", fileName)); + } + testJsonString(json, fileName, pass); + } + + // Test public Json.parse(String) + private static void testJsonString(String json, String file, boolean pass) { + if (pass) { + // Check the JSON String can be parsed + var jsonValue = assertDoesNotThrow(() -> Json.parse(json), + "Parsing failed for : " + json); + // Check the toString() representation of the JsonValue + var jsonString = assertDoesNotThrow(jsonValue::toString, + "toString failed for : " + json); + // Check the value representation of the JsonValue as well as the + // round trip for supplying the value to the factory + switch (jsonValue) { + case JsonObject jo -> JsonObject.of(jo.asMap()); + case JsonArray ja -> JsonArray.of(ja.asList()); + case JsonString js -> JsonString.of(js.asString()); + case JsonNumber jn -> { + try { + JsonNumber.of(jn.asLong()); + } catch (JsonValueException _) { + JsonNumber.of(jn.asDouble()); + } + } + case JsonBoolean jb -> JsonBoolean.of(jb.asBoolean()); + case JsonNull _ -> {} + } + // Check round trip for parsing the toString() representation + assertDoesNotThrow(() -> Json.parse(jsonString), + "Parse round trip for toString() failed : " + json); + } else { + assertThrows(JsonParseException.class, () -> Json.parse(json), + "Parsing did not throw JsonParseException for : " + json); + } + } +} diff --git a/test/jdk/jdk/internal/icu/UBiDiPropsDataValidation.java b/test/jdk/jdk/internal/icu/UBiDiPropsDataValidation.java new file mode 100644 index 000000000000..3b8acf563bb6 --- /dev/null +++ b/test/jdk/jdk/internal/icu/UBiDiPropsDataValidation.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8393882 + * @summary Tests the validation of the ICU UBiDiProps data + * @modules java.base/jdk.internal.icu.impl:+open + * @run junit UBiDiPropsDataValidation + */ + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.ByteBuffer; + +import jdk.internal.icu.impl.ICUBinary; +import jdk.internal.icu.impl.UBiDiProps; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.Assert.assertThrows; + +public class UBiDiPropsDataValidation { + // Constants copied from UBiDiProps.java + private static final String DATA_FILE_NAME = + "/jdk/internal/icu/impl/data/icudata/ubidi.icu"; + private static final int FMT = 0x42694469; + private static final int IX_MIRROR_LENGTH = 3; + private static final int IX_JG_LIMIT = 5; + private static final int IX_JG_LIMIT2 = 7; + // end of copied constants + + private static final ByteBuffer ORIGINAL = ICUBinary.getRequiredData(DATA_FILE_NAME); + private static final int SIZE = ORIGINAL.remaining(); + private static final int INDEXES_START; + static { + try { + ICUBinary.readHeader(ORIGINAL, FMT, null); + } catch (IOException ioe) { + throw new ExceptionInInitializerError(ioe); + } + INDEXES_START = ORIGINAL.position(); + } + + @ParameterizedTest + @ValueSource (ints = {0, IX_MIRROR_LENGTH, IX_JG_LIMIT, IX_JG_LIMIT2}) + void invalidDataSizeTest(int index) throws IOException { + ORIGINAL.position(0); + ByteBuffer data = ByteBuffer.allocate(SIZE) + .order(ORIGINAL.order()) + .put(ORIGINAL) + .flip(); + data.position(INDEXES_START); + + // Test with Integer.MAX_VALUE + data.putInt(INDEXES_START + index * Integer.BYTES, Integer.MAX_VALUE); + if (index == IX_JG_LIMIT || index == IX_JG_LIMIT2) { + // set start to 0 + data.putInt(INDEXES_START + (index - 1) * Integer.BYTES, 0); + } + data.position(0); + assertThrows(IOException.class, () -> readTestData(data)); + + // Test with a negative size + data.putInt(INDEXES_START + index * Integer.BYTES, -1); + data.position(0); + assertThrows(IOException.class, () -> readTestData(data)); + } + + private static void readTestData(ByteBuffer data) throws Throwable { + var constructor = UBiDiProps.class.getDeclaredConstructor(); + constructor.setAccessible(true); + var target = constructor.newInstance(); + var readData = UBiDiProps.class.getDeclaredMethod( + "readData", ByteBuffer.class); + readData.setAccessible(true); + + try { + readData.invoke(target, data); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } +} diff --git a/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java b/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java index 3fc143f38c83..82ca2069f966 100644 --- a/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java +++ b/test/jdk/jdk/internal/misc/ThreadFlock/ThreadFlockTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -895,12 +895,7 @@ void testInterruptClose2(ThreadFactory factory) { void testStructureViolation() { try (var flock1 = ThreadFlock.open("flock1")) { try (var flock2 = ThreadFlock.open("flock2")) { - try { - flock1.close(); - fail("close did not throw"); - } catch (RuntimeException e) { - assertTrue(e.toString().contains("Structure")); - } + assertThrows(StructureViolationException.class, flock1::close); assertTrue(flock1.isClosed()); assertTrue(flock2.isClosed()); } diff --git a/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java b/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java index 19b829bc7e81..86556be0980b 100644 --- a/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java +++ b/test/jdk/jdk/internal/misc/ThreadFlock/WithScopedValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,6 @@ /* * @test * @summary Test ThreadFlock with scoped values - * @enablePreview * @modules java.base/jdk.internal.misc * @run junit WithScopedValue */ diff --git a/test/jdk/jdk/jfr/event/gc/detailed/TestZUncommitEvent.java b/test/jdk/jdk/jfr/event/gc/detailed/TestZUncommitEvent.java index f77ada9bf1bf..320ffb66889c 100644 --- a/test/jdk/jdk/jfr/event/gc/detailed/TestZUncommitEvent.java +++ b/test/jdk/jdk/jfr/event/gc/detailed/TestZUncommitEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ * @requires vm.hasJFR & vm.gc.Z * @requires vm.flagless * @library /test/lib /test/jdk /test/hotspot/jtreg - * @run main/othervm -XX:+UseZGC -Xms32M -Xmx128M -Xlog:gc,gc+heap -XX:+ZUncommit -XX:ZUncommitDelay=1 jdk.jfr.event.gc.detailed.TestZUncommitEvent + * @run main/othervm -XX:+UseZGC -Xms32M -Xmx128M -Xlog:gc,gc+heap -XX:+ZUncommit -XX:ZUncommitDelay=1 -XX:-ZAdaptiveHeapSizing jdk.jfr.event.gc.detailed.TestZUncommitEvent */ public class TestZUncommitEvent { diff --git a/test/jdk/sun/security/lib/CheckBlockedCerts.java b/test/jdk/sun/security/lib/CheckBlockedCerts.java index 5afbf4b610b9..f739b022bb29 100644 --- a/test/jdk/sun/security/lib/CheckBlockedCerts.java +++ b/test/jdk/sun/security/lib/CheckBlockedCerts.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,21 +56,19 @@ public static void main(String[] args) throws Exception { } } - // All certs in the pem files - Set blocked = new HashSet<>(); - // Assumes the full src is available File blockedCertsFile = new File(System.getProperty("test.src"), "../../../../../src/java.base/share/data/blockedcertsconverter/blocked.certs.pem"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); + int expectedCount = 0; try (FileInputStream fis = new FileInputStream(blockedCertsFile)) { Collection certs = cf.generateCertificates(fis); System.out.println(certs.size()); for (Certificate c: certs) { - blocked.add(c); X509Certificate cert = ((X509Certificate)c); + expectedCount += cert.getSigAlgName().contains("ECDSA") ? 4 : 2; if (!UntrustedCertificates.isUntrusted(cert)) { System.out.println(cert.getSubjectX500Principal() + " is trusted"); @@ -101,11 +99,11 @@ public static void main(String[] args) throws Exception { failed = true; } // There are two unique fingerprints for each RSA certificate - if (ccount != blocked.size() * 2 - && !blocked.isEmpty()) { + if (ccount != expectedCount + && expectedCount != 0) { System.out.println("Wrong blocked.certs size: " - + ccount + " fingerprints, " - + blocked.size() + " certs"); + + ccount + " fingerprints, expected" + + expectedCount); failed = true; } } diff --git a/test/jdk/sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java b/test/jdk/sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java new file mode 100644 index 000000000000..bedac5341ab6 --- /dev/null +++ b/test/jdk/sun/security/pkcs11/tls/TestExtendedMasterSecretHandshake.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2026, IBM Corporation. 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 8389135 + * @summary Verifies TLS 1.2 Extended Master Secret negotiation using SunPKCS11 + * @library /test/lib .. + * @modules java.base/sun.security.internal.spec + * + * @run main/othervm + * -Djdk.tls.useExtendedMasterSecret=true + * TestExtendedMasterSecretHandshake true + * + * @run main/othervm + * -Djdk.tls.useExtendedMasterSecret=false + * TestExtendedMasterSecretHandshake false + */ + +import jdk.test.lib.security.SSLSocketTest; +import jtreg.SkippedException; +import sun.security.internal.spec.TlsMasterSecretParameterSpec; + +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.security.Provider; +import java.security.Security; + +public class TestExtendedMasterSecretHandshake extends PKCS11Test { + + private static boolean useExtendedMasterSecret; + + public static void main(String[] args) throws Exception { + useExtendedMasterSecret = Boolean.parseBoolean(args[0]); + + main(new TestExtendedMasterSecretHandshake(), args); + } + + @Override + public void main(Provider p) throws Exception { + initialize(p); + + if (!shouldRun(p)) { + throw new SkippedException( + "Test skipped: SunTls12ExtendedMasterSecret not supported by provider " + p.getName()); + } + + if (useExtendedMasterSecret) { + verifyDirectEMSGeneration(p); + } + + System.setProperty("javax.net.debug", "ssl,handshake"); + + String log = runHandshake(); + + if (!log.contains("TLS handshake and application data exchange succeeded")) { + throw new RuntimeException("Handshake failed"); + } + + if (useExtendedMasterSecret) { + if (countOccurrences(log, "Consumed extension: extended_master_secret") != 2 || + countOccurrences(log, "Ignore unavailable extension: extended_master_secret") != 0) { + throw new RuntimeException("EMS extension not negotiated on both sides"); + } + + if (!log.contains("TLS handshake and application data exchange succeeded")) { + throw new RuntimeException("Handshake did not complete"); + } + + System.out.println("Verified EMS derivation through PKCS11 enabled"); + } else { + if (countOccurrences(log, "Consumed extension: extended_master_secret") != 0 || + countOccurrences(log, "Ignore unavailable extension: extended_master_secret") < 2) { + throw new RuntimeException("EMS extension negotiated although jdk.tls.useExtendedMasterSecret=false"); + } + + System.out.println("Verified EMS derivation through PKCS11 disabled"); + } + } + + private static void initialize(Provider p) { + Security.insertProviderAt(p, 1); + } + + private static boolean shouldRun(Provider p) { + return p.getService("KeyGenerator", "SunTls12ExtendedMasterSecret") != null; + } + + private static void verifyDirectEMSGeneration(Provider p) throws Exception { + KeyGenerator kg = KeyGenerator.getInstance("SunTls12ExtendedMasterSecret", p); + if (kg.getProvider() != p) { + throw new RuntimeException("Unexpected provider: " + kg.getProvider().getName()); + } + + byte[] premaster = new byte[48]; + byte[] sessionHash = new byte[32]; + SecretKey premasterKey = new SecretKeySpec(premaster, "TlsPremasterSecret"); + + TlsMasterSecretParameterSpec spec = new TlsMasterSecretParameterSpec( + premasterKey, + 3, 3, // TLS 1.2 + sessionHash, + "SHA-256", + 32, + 64); + kg.init(spec); + SecretKey key = kg.generateKey(); + + if (key == null) { + throw new RuntimeException("Generated EMS key is null"); + } + + if (!"TlsMasterSecret".equals(key.getAlgorithm())) { + throw new RuntimeException("Unexpected algorithm: " + key.getAlgorithm()); + } + } + + private static int countOccurrences(String text, String needle) { + int count = 0; + int index = 0; + while ((index = text.indexOf(needle, index)) >= 0) { + count++; + index += needle.length(); + } + return count; + } + + private static String runHandshake() throws Exception { + ByteArrayOutputStream byteLogOutput = new ByteArrayOutputStream(); + + PrintStream ps = new PrintStream(byteLogOutput, true); + + PrintStream oldOut = System.out; + PrintStream oldErr = System.err; + + try { + System.setOut(ps); + System.setErr(ps); + + new HandshakeTest().run(); + + } finally { + System.setOut(oldOut); + System.setErr(oldErr); + } + + return byteLogOutput.toString(); + } + + private static class HandshakeTest extends SSLSocketTest { + private SSLSession clientSession; + private SSLSession serverSession; + + @Override + protected void configureServerSocket(SSLServerSocket socket) { + socket.setEnabledProtocols(new String[]{"TLSv1.2"}); + } + + @Override + protected void runServerApplication(SSLSocket socket) throws Exception { + InputStream in = socket.getInputStream(); + OutputStream out = socket.getOutputStream(); + + int value = in.read(); + if (value != 42) { + throw new RuntimeException("Unexpected client value: " + value); + } + + out.write(99); + out.flush(); + + serverSession = socket.getSession(); + } + + @Override + protected void runClientApplication(SSLSocket socket) throws Exception { + OutputStream out = socket.getOutputStream(); + InputStream in = socket.getInputStream(); + + out.write(42); + out.flush(); + + int response = in.read(); + if (response != 99) { + throw new RuntimeException("Unexpected server value: " + response); + } + + clientSession = socket.getSession(); + } + + @Override + public void run() throws Exception { + super.run(); + + if (clientSession == null) { + throw new RuntimeException("Client session missing"); + } + if (serverSession == null) { + throw new RuntimeException("Server session missing"); + } + + if (!"TLSv1.2".equals(clientSession.getProtocol())) { + throw new RuntimeException("Unexpected protocol: " + clientSession.getProtocol()); + } + + if (!"TLSv1.2".equals(serverSession.getProtocol())) { + throw new RuntimeException("Unexpected protocol: " + serverSession.getProtocol()); + } + + System.out.println("TLS handshake and application data exchange succeeded"); + } + } +} \ No newline at end of file diff --git a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java index a54bb501f786..d978db72781d 100644 --- a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java +++ b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java @@ -31,11 +31,9 @@ * java.base/com.sun.crypto.provider * @library /test/lib ../.. * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.2 - * -Djdk.tls.useExtendedMasterSecret=false * -Djdk.tls.client.enableSessionTicketExtension=false FipsModeTLS - * @comment SunPKCS11 does not support (TLS1.2) SunTlsExtendedMasterSecret yet. - * Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669. - * NSS-FIPS does not support ML-KEM, so configures the list of named groups. + * @comment Stateless resumption doesn't currently work with NSS-FIPS, see JDK-8368669. + * NSS-FIPS does not support ML-KEM, so configures the list of named groups. * @run main/othervm/timeout=120 * -Djdk.tls.client.protocols=TLSv1.3 * -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1,secp521r1,x448,ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192 @@ -135,6 +133,7 @@ private static boolean shouldRun() { KeyGenerator.getInstance( "SunTls12RsaPremasterSecret", sunPKCS11NSSProvider); KeyGenerator.getInstance("SunTls12Prf", sunPKCS11NSSProvider); + KeyGenerator.getInstance("SunTls12ExtendedMasterSecret", sunPKCS11NSSProvider); } } catch (NoSuchAlgorithmException e) { return false; diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java index 8d9dfe1ef18f..98633de2a8a0 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Camerfirma.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ * @modules java.base/sun.security.validator * @run main/othervm Camerfirma after policyOn invalid * @run main/othervm Camerfirma after policyOff valid - * @run main/othervm Camerfirma before policyOn valid + * @run main/othervm Camerfirma before policyOn invalid * @run main/othervm Camerfirma before policyOff valid */ @@ -50,7 +50,7 @@ public class Camerfirma { // named "-chain.pem". private static final String ROOT_TO_TEST = "camerfirmachambersca"; - // Date after the restrictions take effect + // Date after the restrictions took effect when distrust was first announced private static final ZonedDateTime DISTRUST_DATE = LocalDate.of(2025, 04, 16).atStartOfDay(ZoneOffset.UTC); diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java index 8512640bb015..ce94daccdcd9 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Chunghwa.java @@ -42,6 +42,7 @@ * @run main/othervm Chunghwa before policyOff valid */ + public class Chunghwa { private static final String CERT_PATH = "chains" + File.separator + "chunghwa"; @@ -50,12 +51,18 @@ public class Chunghwa { // named "-chain.pem". private static final String ROOT_TO_TEST = "chunghwaepkirootca"; - // Date after the restrictions take effect + // Date after the restrictions took effect when distrust was first announced private static final ZonedDateTime DISTRUST_DATE = LocalDate.of(2026, 03, 18).atStartOfDay(ZoneOffset.UTC); + private static final LocalDate APRIL_19_2027 = LocalDate.of(2027, 04, 19); + private static final LocalDate TODAY = LocalDate.now(); public static void main(String[] args) throws Exception { + if (args[1].equals("policyOn") && TODAY.isAfter(APRIL_19_2027)) { + args[2] = "invalid"; + } + Distrust distrust = new Distrust(args); X509TrustManager[] tms = new X509TrustManager[]{ diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java index 9fd4b0038d1e..3f25ab4a2132 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Entrust.java @@ -35,7 +35,7 @@ * @modules java.base/sun.security.validator * @run main/othervm Entrust after policyOn invalid * @run main/othervm Entrust after policyOff valid - * @run main/othervm Entrust before policyOn valid + * @run main/othervm Entrust before policyOn invalid * @run main/othervm Entrust before policyOff valid */ @@ -48,7 +48,7 @@ public class Entrust { private static final String[] ROOTS_TO_TEST = new String[]{ "entrustevca", "entrustrootcaec1", "entrustrootcag2", "entrust2048ca"}; - // Date when the restrictions take effect + // Date after the restrictions took effect when distrust was first announced private static final ZonedDateTime DISTRUST_DATE = LocalDate.of(2024, 11, 12).atStartOfDay(ZoneOffset.UTC); diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java index 4e099a8de8de..2bfb1c7f6b7a 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/distrust/Symantec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,7 @@ * @modules java.base/sun.security.validator * @run main/othervm Symantec after policyOn invalid * @run main/othervm Symantec after policyOff valid - * @run main/othervm Symantec before policyOn valid + * @run main/othervm Symantec before policyOn invalid * @run main/othervm Symantec before policyOff valid */ @@ -57,7 +57,7 @@ public class Symantec { // chain stored in a file named "-chain.pem". private static String[] subCAsToTest = new String[]{"appleistca8g1"}; - // Date when the restrictions take effect + // Date after the restrictions took effect when distrust was first announced private static final ZonedDateTime ROOTS_DISTRUST_DATE = LocalDate.of(2019, 4, 17).atStartOfDay(ZoneOffset.UTC); @@ -87,9 +87,5 @@ public static void main(String[] args) throws Exception { // test code-signing chain (should be valid as restrictions don't apply) Date validationDate = new Date(1544197375493L); distrust.testCodeSigningChain(certPath, "verisignclass3g5ca-codesigning", validationDate); - - // test chains issued through subCAs - notBefore = distrust.getNotBefore(SUBCA_DISTRUST_DATE); - distrust.testCertificateChain(certPath, notBefore, tms, subCAsToTest); } } diff --git a/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java b/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java index 63eb4057f8e6..64f7c5113f67 100644 --- a/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java +++ b/test/jdk/sun/security/util/HostnameChecker/TestHostnameChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -196,6 +196,7 @@ public static void main(String[] args) throws Exception { check(checker, "altfoo1.com", cert3, true); check(checker, "altfoo2.com", cert3, true); check(checker, "5.6.7.8", cert3, true); + check(checker, "::ffff:5.6.7.8", cert3, true); check(checker, "foo.bar.com", cert4, true); check(checker, "altfoo.bar.com", cert4, false); check(checker, "2001:db8:3c4d:15::1a2f:1a2b", cert5, true); @@ -221,6 +222,7 @@ public static void main(String[] args) throws Exception { check(checker, "altfoo1.com", cert3, true); check(checker, "altfoo2.com", cert3, true); check(checker, "5.6.7.8", cert3, true); + check(checker, "::ffff:5.6.7.8", cert3, true); check(checker, "foo.bar.com", cert4, true); check(checker, "altfoo.bar.com", cert4, false); } @@ -239,11 +241,11 @@ private static void check(HostnameChecker checker, String name, throws Exception { try { checker.match(name, cert); - if (expectedResult == false) { + if (!expectedResult) { throw new Exception("Passed invalid test: " + name); } } catch (CertificateException e) { - if (expectedResult == true) { + if (expectedResult) { throw new Exception("Failed valid test: " + name, e); } } diff --git a/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java b/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java index 63772eef3351..6ca664fb1e6b 100644 --- a/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java +++ b/test/jdk/sun/security/x509/IPAddressName/ConstrainsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,58 +21,88 @@ * questions. */ -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import sun.security.x509.GeneralNameInterface; -import sun.security.x509.IPAddressName; +import static jdk.test.lib.Asserts.assertEquals; +import static sun.security.x509.GeneralNameInterface.NAME_MATCH; +import static sun.security.x509.GeneralNameInterface.NAME_NARROWS; +import static sun.security.x509.GeneralNameInterface.NAME_SAME_TYPE; +import static sun.security.x509.GeneralNameInterface.NAME_WIDENS; import java.io.IOException; - -import static org.testng.Assert.assertEquals; +import java.util.List; +import sun.security.x509.IPAddressName; /* * @test * @summary Verify IPAddressName.constrains * @bug 8267617 + * @library /test/lib * @modules java.base/sun.security.x509 - * @run testng ConstrainsTest */ + public class ConstrainsTest { IPAddressName ipv4Addr = new IPAddressName("127.0.0.1"); IPAddressName ipv4Mask = new IPAddressName("127.0.0.0/255.0.0.0"); - IPAddressName ipv6Addr = new IPAddressName("::1"); - IPAddressName ipv6Mask = new IPAddressName("::/0"); + IPAddressName ipv6Addr = new IPAddressName("2001:db8::1"); + IPAddressName ipv6Mask = new IPAddressName("2001:db8::/124"); + + IPAddressName ipv4Addr2 = new IPAddressName("128.0.0.1"); + IPAddressName ipv4NarrowMask = new IPAddressName("127.0.0.0/255.255.0.0"); + IPAddressName ipv4DisjointMask = new IPAddressName("128.0.0.0/255.0.0.0"); + + IPAddressName ipv6Addr2 = new IPAddressName("2001:db8:1::1"); + IPAddressName ipv6NarrowMask = new IPAddressName("2001:db8::/125"); + IPAddressName ipv6DisjointMask = new IPAddressName("2001:db8:1::/124"); public ConstrainsTest() throws IOException { } - @DataProvider(name = "names") - public Object[][] names() { - Object[][] data = { - {ipv4Addr, ipv4Addr, GeneralNameInterface.NAME_MATCH}, - {ipv4Addr, ipv4Mask, GeneralNameInterface.NAME_WIDENS}, - {ipv4Addr, ipv6Addr, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv4Addr, ipv6Mask, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv4Mask, ipv4Addr, GeneralNameInterface.NAME_NARROWS}, - {ipv4Mask, ipv4Mask, GeneralNameInterface.NAME_MATCH}, - {ipv4Mask, ipv6Addr, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv4Mask, ipv6Mask, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Addr, ipv4Addr, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Addr, ipv4Mask, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Addr, ipv6Addr, GeneralNameInterface.NAME_MATCH}, - {ipv6Addr, ipv6Mask, GeneralNameInterface.NAME_WIDENS}, - {ipv6Mask, ipv4Addr, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Mask, ipv4Mask, GeneralNameInterface.NAME_SAME_TYPE}, - {ipv6Mask, ipv6Addr, GeneralNameInterface.NAME_NARROWS}, - {ipv6Mask, ipv6Mask, GeneralNameInterface.NAME_MATCH}, - }; - return data; + private Object[][] names() { + return new Object[][]{ + // Basic matching + {ipv4Addr, ipv4Addr, NAME_MATCH}, + {ipv4Addr, ipv4Mask, NAME_WIDENS}, + {ipv4Addr, ipv6Addr, NAME_SAME_TYPE}, + {ipv4Addr, ipv6Mask, NAME_SAME_TYPE}, + {ipv4Mask, ipv4Addr, NAME_NARROWS}, + {ipv4Mask, ipv4Mask, NAME_MATCH}, + {ipv4Mask, ipv6Addr, NAME_SAME_TYPE}, + {ipv4Mask, ipv6Mask, NAME_SAME_TYPE}, + {ipv6Addr, ipv4Addr, NAME_SAME_TYPE}, + {ipv6Addr, ipv4Mask, NAME_SAME_TYPE}, + {ipv6Addr, ipv6Addr, NAME_MATCH}, + {ipv6Addr, ipv6Mask, NAME_WIDENS}, + {ipv6Mask, ipv4Addr, NAME_SAME_TYPE}, + {ipv6Mask, ipv4Mask, NAME_SAME_TYPE}, + {ipv6Mask, ipv6Addr, NAME_NARROWS}, + {ipv6Mask, ipv6Mask, NAME_MATCH}, + + // Same-family host mismatches + {ipv4Addr, ipv4Addr2, NAME_SAME_TYPE}, + {ipv6Addr, ipv6Addr2, NAME_SAME_TYPE}, + + // Host to subnet match, nothing matches + {ipv4Addr2, ipv4Mask, NAME_SAME_TYPE}, + {ipv6Addr2, ipv6Mask, NAME_SAME_TYPE}, + + // Subnet to subnet match: narrow/widen/disjoint + {ipv4Mask, ipv4NarrowMask, NAME_NARROWS}, + {ipv4NarrowMask, ipv4Mask, NAME_WIDENS}, + {ipv4Mask, ipv4DisjointMask, NAME_SAME_TYPE}, + + {ipv6Mask, ipv6NarrowMask, NAME_NARROWS}, + {ipv6NarrowMask, ipv6Mask, NAME_WIDENS}, + {ipv6Mask, ipv6DisjointMask, NAME_SAME_TYPE}, + }; } - @Test(dataProvider = "names") - public void testNameContains(IPAddressName addr1, IPAddressName addr2, int result) throws IOException { - assertEquals(addr1.constrains(addr2), result); + public static void main(String[] args) throws Exception { + List.of(new ConstrainsTest().names()).forEach(v -> testNameContains( + (IPAddressName) v[0], (IPAddressName) v[1], (int) v[2])); } + private static void testNameContains(IPAddressName addr1, + IPAddressName addr2, int result) { + assertEquals(addr1.constrains(addr2), result); + } } diff --git a/test/jdk/sun/text/resources/BreakIterator/BreakIteratorDataValidation.java b/test/jdk/sun/text/resources/BreakIterator/BreakIteratorDataValidation.java new file mode 100644 index 000000000000..319790fee3c6 --- /dev/null +++ b/test/jdk/sun/text/resources/BreakIterator/BreakIteratorDataValidation.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8393882 + * @summary Tests the validation of the BreakIterator data + * @modules java.base/sun.text + * @run junit BreakIteratorDataValidation + */ + +import java.nio.ByteBuffer; +import java.util.MissingResourceException; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import sun.text.RuleBasedBreakIterator; + +import static org.junit.Assert.assertThrows; + +public class BreakIteratorDataValidation { + private static final int DATASIZE = + 48 // size including checksum + + 1 // one endState, avoiding zero division with endStates.length + + 1_024; // BMPindices + @ParameterizedTest + @ValueSource (ints = {12, 16, 20, 24, 28, 32, 36}) + void hugeDataSizeTest(int index) { + byte[] data = ByteBuffer.allocate(DATASIZE) + .put(new byte[]{'B', 'I', 'd', 'a', 't', 'a', 0, 1}) + .putInt(DATASIZE - 12) // totalDataSize + .putInt(20, 1) // endStates size + .putInt(index, Integer.MAX_VALUE) // huge table length + .array(); + + assertThrows("Malformed data was accepted", + MissingResourceException.class, + () -> new RuleBasedBreakIterator("test", data)); + } + + @ParameterizedTest + @ValueSource (ints = {12, 16, 20, 24, 28, 32, 36}) + void negativeDataSizeTest(int index) { + byte[] data = ByteBuffer.allocate(DATASIZE) + .put(new byte[]{'B', 'I', 'd', 'a', 't', 'a', 0, 1}) + .putInt(DATASIZE - 12) // totalDataSize + .putInt(20, 1) // endStates size + .putInt(index, -1) // negative data length + .array(); + + assertThrows("Malformed data was accepted", + MissingResourceException.class, + () -> new RuleBasedBreakIterator("test", data)); + } +} diff --git a/test/jdk/sun/util/calendar/ZoneRulesDataValidation.java b/test/jdk/sun/util/calendar/ZoneRulesDataValidation.java new file mode 100644 index 000000000000..04b16e8c6d14 --- /dev/null +++ b/test/jdk/sun/util/calendar/ZoneRulesDataValidation.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +/* + * @test + * @bug 8393882 + * @summary Tests the validation of the ZoneRules data + * @modules java.base/sun.util.calendar + * @run junit ZoneRulesDataValidation + */ + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.StreamCorruptedException; +import java.nio.ByteBuffer; +import java.util.List; +import sun.util.calendar.ZoneInfoFile; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class ZoneRulesDataValidation { + private static List testOffsets() { + return List.of( + Arguments.of(1, true, 1_024), // stdOffsets + Arguments.of(6, true, 1_024), // savOffsets + Arguments.of(11, false, 16) // ruleSize + ); + } + + private static ByteBuffer validData() { + return ByteBuffer.allocate(12) + .put((byte)1) // ZoneRules format + .putInt(0) // stdSize + .put((byte)0) // stdOffsets[0]: UTC + .putInt(0) // savSize + .put((byte)0) // savOffsets[0]: UTC + .put((byte)0); // ruleSize + } + + @Test + void invalidTypeTest() throws IOException { + var data = validData(); + data.put(0, (byte)0); + final var in = new DataInputStream(new ByteArrayInputStream(data.array())); + assertThrows(StreamCorruptedException.class, () -> ZoneInfoFile.getZoneInfo(in, "Test")); + in.close(); + } + + @ParameterizedTest + @MethodSource("testOffsets") + void tooLargeDataSizeTest(int offset, boolean isInt, int max) throws IOException { + var data = validData(); + if (isInt) { + data.putInt(offset, max + 1); + } else { + data.put(offset, (byte)(max + 1)); + } + final var in = new DataInputStream(new ByteArrayInputStream(data.array())); + assertThrows(StreamCorruptedException.class, () -> ZoneInfoFile.getZoneInfo(in, "Test")); + in.close(); + } + + @ParameterizedTest + @MethodSource("testOffsets") + void negativeDataSizeTest(int offset, boolean isInt) throws IOException { + var data = validData(); + if (isInt) { + data.putInt(offset, -1); + } else { + data.put(offset, (byte)(-1)); + } + data.position(0); + final var in = new DataInputStream(new ByteArrayInputStream(data.array())); + assertThrows(StreamCorruptedException.class, () -> ZoneInfoFile.getZoneInfo(in, "Test")); + in.close(); + } + + @Test + void validDataSizeTest() throws IOException { + final var in = new DataInputStream(new ByteArrayInputStream(validData().array())); + assertDoesNotThrow(() -> ZoneInfoFile.getZoneInfo(in, "Test")); + in.close(); + } +} diff --git a/test/jdk/tools/jlink/plugins/SecurityPropertiesPluginTest.java b/test/jdk/tools/jlink/plugins/SecurityPropertiesPluginTest.java new file mode 100644 index 000000000000..d550abb149c4 --- /dev/null +++ b/test/jdk/tools/jlink/plugins/SecurityPropertiesPluginTest.java @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.List; +import java.util.Properties; +import java.util.Random; + +import jdk.test.lib.Asserts; +import jdk.test.lib.util.FileUtils; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import jtreg.SkippedException; +import tests.Helper; +import tests.JImageGenerator; +import tests.JImageGenerator.JLinkTask; +import tests.JImageGenerator.JModTask; +import tests.Result; + +/* @test + * @bug 8377819 + * @summary Test the --security-properties plugin + * @library ../../lib /test/lib + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jimage + * jdk.jlink/jdk.tools.jlink.internal + * @build tests.* + * @run main/othervm SecurityPropertiesPluginTest + */ + +public class SecurityPropertiesPluginTest { + + private static Helper helper; + + private static final String SECPROPS_PATH = "conf/security/java.security"; + private static final String TEST_DIR = System.getProperty("test.dir", "."); + private static final boolean LINKABLE_RUNTIME = + LinkableRuntimeImage.isLinkableRuntime(); + private static final String DELIMS = "=: "; + + // Replacement for java.security file + private static final String JAVA_SECURITY_REPLACEMENT = + """ + # Normal comment + # Comment with leading spaces + ! Comment with '!' + \t# Comment with leading tab + \f# Comment with leading form-feed + # Next 3 are blank lines + + \n + \r\n + # Normal property + foo=bar + # JDK property + jdk.certpath.disabledAlgorithms=MD4 + # Property with leading spaces + bar=foo + # Property with delimiter ("=") in name, must be escaped + ba\\=z=foo + # Multi-lined property with space delimiter + fruits apple, banana, pear, \\ + cantaloupe, watermelon, \\ + kiwi, mango + # Property with ':' delimiter + Truth:Beauty + # Property with EOF immediately after continuation character + sport=baseball\\ + """; + + /** + * Properties to add or override. + * Each entry contains the key, value, the expected key and the expected + * value after loading/parsing. + */ + private static final String[][] EXTRA_PROPS = new String[][] { + // overrides a current property + {"keystore.type", "bogus", "keystore.type", "bogus"}, + // simple user-defined property + {"foo", "bar", "foo", "bar"}, + // two include properties (it should only use the 2nd one) + {"include", "doNotUse", "include", "use"}, + {"include", "use", "include", "use"}, + // overrides a multi-lined value property + {"jdk.certpath.disabledAlgorithms", "MD2", + "jdk.certpath.disabledAlgorithms", "MD2"}, + // value with a char that is encoded differently in ISO-8859-1 vs. UTF-8 + {"iso_8859_1_char", "é", "iso_8859_1_char", "é"}, + // value with an empty string + {"empty", "", "empty", ""}, + // value with an equal sign + {"equalSign", "\\=", "equalSign", "="}, + // value with a unicode escaped euro character + {"aaa", "euro_\\u20AC_value", "aaa", "euro_\u20AC_value"}, + // key with a unicode escaped euro character + {"euro_\\u20AC_key", "111", "euro_\u20AC_key", "111"}, + // key with an escaped space character + {"spaced\\ key", "333", "spaced key", "333"}, + // value with an escaped slash character + {"abb", "slash_\\\\_value", "abb", "slash_\\_value"}, + // key with an escaped slash character + {"slash_\\\\_key", "222", "slash_\\_key", "222"}, + // value with indented spaces + {"zyy", "\\ indented value", "zyy", " indented value"}, + // multi-line value with newline character + {"zzz", "multi-line\\nvalue", "zzz", "multi-line\nvalue"} + }; + + // Invalid java.security file containing duplicate properties + private static final String DUPLICATE_PROPS = + """ + foo=123 + bar=789 + foo=456 + """; + + // Invalid java.security file containing include statement + private static final String INCLUDE_STATEMENT = + """ + include notAllowed + """; + + public static void main(String[] args) throws Throwable { + + helper = Helper.newHelper(LINKABLE_RUNTIME); + if (helper == null) { + throw new SkippedException("Test not run: no linkable runtime"); + } + + writePropsToFile("test.security"); + + testWithDefaultJDKImage(); + testWithCustomJDKImage(); + + // test illegal/bad options + testBadOptions(); + } + + /** + * Create image using current java.security file. Test that properties + * are overridden and compliant with java.util.Properties specification. + */ + private static void testWithDefaultJDKImage() throws Exception { + + helper.generateDefaultJModule("defaultModule"); + Path image = helper.generateDefaultImage( + new String[] { "--security-properties", "test.security" }, + "defaultModule").assertSuccess(); + helper.checkImage(image, "defaultModule", null, null, + new String[] { SECPROPS_PATH }); + + testImage(image); + } + + /** + * Create image using custom java.security file. Test that properties + * are overridden and compliant with java.util.Properties specification. + */ + private static void testWithCustomJDKImage() throws Exception { + + if (!helper.jdkHasPackagedModules()) { + System.out.println("Skipping testWithCustomJDKImage test case, " + + "no jmods in image"); + return; + } + + // Copy JDK's jmods directory + Path jdkJmodsPath = Path.of(System.getProperty("test.jdk"), "jmods"); + Path jdkJmodsCopyPath = Path.of(TEST_DIR, "jmodsCopy"); + FileUtils.copyDirectory(jdkJmodsPath, jdkJmodsCopyPath); + + // Extract contents of jmods copy + JModTask jmodTask = JImageGenerator.getJModTask() + .jmod(jdkJmodsCopyPath.resolve("java.base.jmod")) + .option("--dir") + .option("java.base.jmod.extracted"); + jmodTask.extract().assertSuccess(); + + // Create a new java.base.jmod with replacement java.security file + createReplacementJmod(JAVA_SECURITY_REPLACEMENT); + + // Create default module for testing + Path customModule = + helper.generateDefaultJModule("customModule").assertSuccess(); + + // Create second image using custom module and new java.base.jmod + Path customImage = Path.of(TEST_DIR, "images/customModule1.image"); + createSecondImage(customImage).assertSuccess(); + + testImage(customImage); + + Files.delete(Path.of(TEST_DIR, "jmods/java.base.jmod")); + createReplacementJmod(DUPLICATE_PROPS); + + // Create second image using custom module and new java.base.jmod + // Expect failure because java.security file has duplicate properties. + customImage = Path.of(TEST_DIR, "images/customModule2.image"); + createSecondImage(customImage) + .assertFailure("Parsing error, duplicate property"); + + Files.delete(Path.of(TEST_DIR, "jmods/java.base.jmod")); + createReplacementJmod(INCLUDE_STATEMENT); + + // Create second image using custom module and new java.base.jmod + // Expect failure because java.security file has an include statement. + customImage = Path.of(TEST_DIR, "images/customModule3.image"); + createSecondImage(customImage) + .assertFailure("Parsing error, include statement disallowed"); + } + + private static void createReplacementJmod(String jsProps) throws IOException{ + // Replace JDK's java.security file with test version. First, sanity + // check syntax of replacement by loading it in Properties object. + Properties props = new Properties(); + props.load(new ByteArrayInputStream( + jsProps.getBytes(StandardCharsets.ISO_8859_1))); + + // Now replace the extracted JDK's java.security file + Path extractedPath = Path.of(TEST_DIR, "java.base.jmod.extracted"); + Files.copy(new ByteArrayInputStream( + jsProps.getBytes(StandardCharsets.ISO_8859_1)), + extractedPath.resolve(SECPROPS_PATH), + StandardCopyOption.REPLACE_EXISTING); + + // Create a new java.base.jmod with replacement java.security file + JModTask jmodTask = JImageGenerator.getJModTask() + .addClassPath(extractedPath.resolve("classes")) + .addCmds(extractedPath.resolve("bin")) + .addConfig(extractedPath.resolve("conf")) + .addNativeLibraries(extractedPath.resolve("lib")) + .jmod(Path.of(TEST_DIR, "jmods/java.base.jmod")); + jmodTask.create().assertSuccess(); + } + + private static Result createSecondImage(Path customImage) { + JLinkTask jLinkTask = JImageGenerator.getJLinkTask() + .modulePath(Path.of(TEST_DIR, "jmods").toString()) + .output(customImage) + .addMods("customModule") + .limitMods("customModule") + .option("--security-properties") + .option("test.security"); + return jLinkTask.call(); + } + + private static void testImage(Path image) throws Exception { + + Path secPropsPath = image.resolve(SECPROPS_PATH); + Properties javasecProps = new Properties(); + try (FileInputStream fis = + new FileInputStream(secPropsPath.toFile())) { + javasecProps.load(fis); + } + + for (String[] prop : EXTRA_PROPS) { + Asserts.assertEquals(prop[3], javasecProps.getProperty(prop[2])); + Asserts.assertTrue(javasecProps.containsKey(prop[2])); + } + + // check include is last line + List lines = Files.readAllLines(secPropsPath, + StandardCharsets.ISO_8859_1); + Asserts.assertEquals(lines.getLast(), "include use"); + + // Make sure override of "jdk.certpath.disabledAlgorithms" + // is in the right place + Asserts.assertLessThan( + lines.indexOf("jdk.certpath.disabledAlgorithms=MD2"), + lines.size() - EXTRA_PROPS.length); + + // Check property with EOF after \ was parsed correctly + if (image.endsWith("customModule1.image")) { + Asserts.assertEquals("baseball", javasecProps.getProperty("sport")); + } + } + + private static void testBadOptions() throws Exception { + + // non-existent props file + String module = "testBad"; + helper.generateDefaultJModule(module); + helper.generateDefaultImage(new String[] + { "--security-properties", "nonexistent-file" }, module) + .assertFailure("java.io.FileNotFoundException: " + + "nonexistent-file"); + } + + private static void writePropsToFile(String filename) throws Exception { + Random r = new Random(); + Path p = Path.of(TEST_DIR, filename); + // write some comments in both formats ('#' and '!') and blank line + StringBuilder sb = new StringBuilder(); + sb.append("# Test properties file\n") + .append(" # Test properties file\n") + .append("! Test properties file\n") + .append(" "); + // use random delimiter + for (String[] prop : EXTRA_PROPS) { + sb.append(prop[0] + DELIMS.charAt(r.nextInt(DELIMS.length())) + + prop[1] + System.lineSeparator()); + } + Files.writeString(p, sb, StandardCharsets.ISO_8859_1); + } +} diff --git a/test/jdk/tools/lib/tests/JImageGenerator.java b/test/jdk/tools/lib/tests/JImageGenerator.java index 5e006459101c..602b4db6266d 100644 --- a/test/jdk/tools/lib/tests/JImageGenerator.java +++ b/test/jdk/tools/lib/tests/JImageGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -486,6 +486,10 @@ public Result call() { return cmd(""); } + public Result extract() { + return cmd("extract"); + } + private Result cmd(String cmd) { String[] args = optionsJMod(cmd); System.err.println("jmod options: " + optionsPrettyPrint(args)); diff --git a/test/langtools/jdk/javadoc/doclet/testSimpleTag/TestSimpleTag.java b/test/langtools/jdk/javadoc/doclet/testSimpleTag/TestSimpleTag.java index 47f5bd832a96..98c8f89e4b58 100644 --- a/test/langtools/jdk/javadoc/doclet/testSimpleTag/TestSimpleTag.java +++ b/test/langtools/jdk/javadoc/doclet/testSimpleTag/TestSimpleTag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,21 +23,27 @@ /* * @test - * @bug 4695326 4750173 4920381 8078320 8071982 8239804 + * @bug 4695326 4750173 4920381 8078320 8071982 8239804 8393742 * @summary Test the declaration of simple tags using -tag. Verify that * "-tag name" is a shortcut for "-tag name:a:Name:". Also verity that * you can escape the ":" character with a back slash so that it is not * considered a separator when parsing the simple tag argument. - * @library ../../lib + * @library /tools/lib ../../lib * @modules jdk.javadoc/jdk.javadoc.internal.tool - * @build javadoc.tester.* + * @build toolbox.ToolBox javadoc.tester.* * @run main TestSimpleTag */ import javadoc.tester.JavadocTester; +import toolbox.ToolBox; + +import java.io.IOException; +import java.nio.file.Path; public class TestSimpleTag extends JavadocTester { + private final ToolBox tb = new ToolBox(); + public static void main(String... args) throws Exception { var tester = new TestSimpleTag(); tester.runTests(); @@ -65,4 +71,81 @@ public void test() {
        Parameters:
        arg - this is an int argument.
        """); } + + + // Multiple custom tags should be encoded as individual
        elements, + // while preserving comma-separated list format for other simple tags. + @Test + public void testMultipleTags(Path base) throws IOException { + Path src = base.resolve("src"); + + tb.writeJavaFiles(src, """ + package test; + /** + * Test class with multiple tags. + * + * @warning + * First warning. + * + * @version 1.0 + * @author Duke + * @author Leo + * @author Lisa + * @version 1.0.1 + * + * @note This is a note. + * + * @note Second note with multiple paragraphs. + *

        Second paragraph of second note.

        + *

        Third paragraph of second note.

        + * + * @note Third note. + * + * @warning Another warning. + *
          + *
        • First list item. + *
        • Second list item. + *
        + */ + public class MultiTag { + } + """); + + javadoc("-d", base.resolve("out").toString(), + "-author", "-version", + "-tag", "warning:a:Warning:", + "-tag", "note:a:Note:", + "-tag", "author", + "-tag", "version", + "-sourcepath", src.toString(), + "test"); + + checkExit(Exit.OK); + + checkOutput("test/MultiTag.html", true, """ +
        +
        Warning:
        +
        First warning.
        +
        Another warning. +
          +
        • First list item. +
        • Second list item. +
        + """, + """ +
        Note:
        +
        This is a note.
        +
        Second note with multiple paragraphs. +

        Second paragraph of second note.

        +

        Third paragraph of second note.

        +
        Third note.
        + """, + """ +
        Author:
        +
        Duke, Leo, Lisa
        +
        Version:
        +
        1.0, 1.0.1
        +
        + """); + } } diff --git a/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java new file mode 100644 index 000000000000..bb44ff275bb4 --- /dev/null +++ b/test/langtools/jdk/jshell/RemoteAgentHandshakeTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test 8384736 + * @summary Verify the correct handshake is used for remote agents. + * @run junit RemoteAgentHandshakeTest + */ + +import java.time.Duration; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import jdk.jfr.consumer.RecordingStream; +import jdk.jshell.JShell; +import jdk.jshell.execution.JdiExecutionControlProvider; +import jdk.jshell.execution.RemoteExecutionControl; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class RemoteAgentHandshakeTest { + + @Test + public void testCustomAgentOldHandshake() throws Exception { + assertThrows(IllegalStateException.class, () -> { + try (JShell jshell = + JShell.builder() + .executionEngine(new JdiExecutionControlProvider(), + Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT, + OldHandShake.class.getName())) + .remoteVMOptions("-classpath", System.getProperty("test.classes")) + .build()) { + jshell.eval("1"); + } + }); + } + + @Test + public void testCustomAgentNewHandshake() throws Exception { + try (JShell jshell = + JShell.builder() + .executionEngine(new JdiExecutionControlProvider(), + Map.of(JdiExecutionControlProvider.PARAM_REMOTE_AGENT, + NewHandShake.class.getName())) + .remoteVMOptions("-classpath", System.getProperty("test.classes")) + .build()) { + jshell.eval("1"); + } + } + + @Test + public void testDefaultAgentUsesNewHandshake() throws Exception { + try (var rs = new RecordingStream()) { + rs.enable("jdk.ProcessStart"); + rs.enable("jdk.CPULoad").withPeriod(Duration.ofMillis(10)); + + CompletableFuture command = new CompletableFuture<>(); + + rs.onEvent("jdk.ProcessStart", evt -> { + String commandLine = evt.getValue("command"); + + if (commandLine.contains("RemoteExecutionControl")) { + command.complete(commandLine); + } + }); + + CountDownLatch running = new CountDownLatch(1); + + rs.onEvent("jdk.CPULoad", evt -> { + running.countDown(); + }); + + rs.startAsync(); + + running.await(); + + try (JShell jshell = JShell.create()) { + jshell.eval("1"); + } + + String commandLine = command.get(10, TimeUnit.SECONDS); + if (!commandLine.endsWith(" -1")) { + throw new AssertionError("Does not contain a correct port: '" + commandLine + "'"); + } + } + } + + public static class OldHandShake extends RemoteExecutionControl { + public static void main(String[] args) throws Exception { + if (Integer.parseInt(args[0]) <= 0) { + throw new AssertionError("Unexpected port number!"); + } + + RemoteExecutionControl.main(args); + } + } + + public static class NewHandShake extends RemoteExecutionControl { + public static void main(String[] args) throws Exception { + if (Integer.parseInt(args[0]) != (-1)) { + throw new AssertionError("Unexpected port number!"); + } + + RemoteExecutionControl.main(args); + } + } +} diff --git a/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java b/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java index 094c705bce64..28edb4c26b05 100644 --- a/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java +++ b/test/langtools/jdk/jshell/UserJdiUserRemoteTest.java @@ -37,6 +37,7 @@ import jdk.jshell.spi.ExecutionControl; import jdk.jshell.spi.ExecutionControl.ExecutionControlException; import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -63,8 +64,8 @@ public void testVarValue() { @Test public void testExtension() throws ExecutionControlException { assertEval("42;"); - Object res = currentEC.extensionCommand("FROG", "test"); - assertEquals("ribbit", res); + Assertions.assertThrows(ExecutionControl.NotImplementedException.class, + () -> currentEC.extensionCommand("FROG", "test")); } @Test diff --git a/test/langtools/tools/javac/cast/IncompleteSymbol.java b/test/langtools/tools/javac/cast/IncompleteSymbol.java new file mode 100644 index 000000000000..96a5e7ebd1df --- /dev/null +++ b/test/langtools/tools/javac/cast/IncompleteSymbol.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8391830 + * @summary Verify that casting conversion works properly even for interfaces + * whose symbols where not completed yet. + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit IncompleteSymbol + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import toolbox.JavacTask; +import toolbox.ToolBox; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class IncompleteSymbol { + Path base; + ToolBox tb = new ToolBox(); + + @Test + void testIncompleteSymbol() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .outdir(classes) + .sources(""" + package p; + public sealed interface A permits B {} + """, + """ + package p; + public sealed interface B extends A permits C {} + """, + """ + package p; + public abstract class X {} + """, + """ + package p; + public final class C extends X implements B {} + """) + .run() + .writeAll(); + new JavacTask(tb) + .outdir(classes) + .classpath(classes) + .sources(""" + package q; + + import p.A; + import p.X; + + class Test { + void test(A a) { + if (a instanceof X x) { } + } + } + """) + .run() + .writeAll(); + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } +} diff --git a/test/langtools/tools/javac/code/GenCondCast.java b/test/langtools/tools/javac/code/GenCondCast.java new file mode 100644 index 000000000000..530086bbc515 --- /dev/null +++ b/test/langtools/tools/javac/code/GenCondCast.java @@ -0,0 +1,785 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8392648 + * @summary Verify casts to boolean and Boolean work reasonably in condition + * position, w.r.t. DA/DU. + * @modules java.base/jdk.internal.classfile.impl + * @compile GenCondCast.java + * @run main GenCondCast + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; +import java.lang.classfile.Attributes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassModel; +import java.lang.classfile.MethodModel; +import java.lang.classfile.TypeAnnotation.TargetInfo; +import java.lang.classfile.TypeAnnotation.TypeArgumentTarget; +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import java.util.List; +import java.util.Objects; +import java.util.function.ToIntFunction; +import jdk.internal.classfile.impl.LabelImpl; + +public class GenCondCast { + static int test1(boolean b) { + int result; + if ((boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int test1_flip(boolean b) { + int result; + if ((boolean) ((result = 1) > 0) && b) return result; + return 0; + } + + static int test1_anno(boolean b) { + int result; + if ((@Ann boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int test1_flip_anno(boolean b) { + int result; + if ((@Ann boolean) ((result = 1) > 0) && b) return result; + return 0; + } + + static int test2(boolean b) { + int result; + if ((Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int test2_flip(boolean b) { + int result; + if ((Boolean) ((result = 1) > 0) && b) return result; + return 0; + } + + static int test2_anno(boolean b) { + int result; + if ((@Ann Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int test2_flip_anno(boolean b) { + int result; + if ((@Ann Boolean) ((result = 1) > 0) && b) return result; + return 0; + } + + static int test3(boolean b) { + if ((boolean) id(b)) return 1; + else return 0; + } + + static int test3_anno(boolean b) { + if ((@Ann boolean) id(b)) return 1; + else return 0; + } + + static int test4(boolean b) { + if ((Boolean) id(b)) return 1; + else return 0; + } + + static int test4_anno(boolean b) { + if ((@Ann Boolean) id(b)) return 1; + else return 0; + } + + static int testLarge_anno(boolean b) { + int result; + if ((@Ann Boolean) (b && (result = 1) > 0)) { + //enough code here to force long jumps: + record R(String s0, String s1, String s2, String s3, String s4) {} + Object r = new R("", "", "", "", ""); + switch (r) { + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + case R(var s0, var s1, var s2, var s3, var s4) when !s0.isEmpty() -> + System.out.print(s0 + s1 + s2 + s3 + s4); + default -> {} + } + return result; + } else { + return 0; + } + } + + static int testRedundant1_anno(boolean b) { + int result; + if ((@Ann Boolean) (@Ann Boolean) (@Ann Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int testRedundant2_anno(boolean b) { + int result; + if ((@Ann Boolean) (@Ann boolean) (@Ann Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int testCastChainObject1_anno(boolean b) { + int result; + if ((@Ann Boolean) (@Ann Object) (@Ann Boolean) (b && (result = 1) > 0)) return result; + return 0; + } + + static int testCastChainObject2_anno(boolean b) { + int result; + if ((@Ann Boolean) (@Ann Object) (b && (result = 1) > 0)) return result; + return 0; + } + + static int testExpectCCE(boolean b) { + int result; + if ((@Ann Boolean) (@Ann Object) (@Ann String) (@Ann Object) (@Ann Boolean) (b && (result = 1) > 0)) { + return result; + } + return 0; + } + + static int annotated(boolean b) { + { + int result; + if ((@Ann boolean) (b && (result = 1) > 0)) return result; + } + { + int result; + if ((@Ann boolean) ((result = 1) > 0) && b) return result; + } + //note that the boxing&unboxing is duplicated, but the type annotations + //is only assigned to the second duplicate. That's consistent with + //how duplicated code for e.g. try-finally works: + { + int result; + if ((@Ann Boolean) (b && (result = 1) > 0)) return result; + } + { + int result; + if ((@Ann Boolean) ((result = 1) > 0) && b) return result; + } + boolean _ = (@Ann boolean) b; + boolean _ = (@Ann Boolean) b; + return 0; + } + + static Object id(boolean b) { + return b; + } + + static void doTest(ToIntFunction testMethod) { + if (testMethod.applyAsInt(true) != 1) { + throw new IllegalStateException("Wrong value!"); + } + if (testMethod.applyAsInt(false) != 0) { + throw new IllegalStateException("Wrong value!"); + } + } + + public static void main(String... args) throws Exception { + doTest(GenCondCast::test1); + doTest(GenCondCast::test1_flip); + doTest(GenCondCast::test1_anno); + doTest(GenCondCast::test1_flip_anno); + doTest(GenCondCast::test2); + doTest(GenCondCast::test2_flip); + doTest(GenCondCast::test2_anno); + doTest(GenCondCast::test2_flip_anno); + doTest(GenCondCast::test3); + doTest(GenCondCast::test3_anno); + doTest(GenCondCast::test4); + doTest(GenCondCast::test4_anno); + doTest(GenCondCast::testLarge_anno); + doTest(GenCondCast::testRedundant1_anno); + doTest(GenCondCast::testRedundant2_anno); + doTest(GenCondCast::testCastChainObject1_anno); + doTest(GenCondCast::testCastChainObject2_anno); + try { + testExpectCCE(true); + throw new AssertionError("Should have got an exception."); + } catch (ClassCastException _) { + //correct + } + try { + testExpectCCE(false); + throw new AssertionError("Should have got an exception."); + } catch (ClassCastException _) { + //correct + } + + byte[] classBytes = GenCondCast.class.getClassLoader().getResourceAsStream(GenCondCast.class.getName().replace('.', '/') + ".class").readAllBytes(); + ClassModel model = ClassFile.of().parse(classBytes); + { + MethodModel annotated = model.methods().stream().filter(m -> m.methodName().equalsString("annotated")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = annotated.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:7, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:15, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:44, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:66, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:77, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:82, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + { + MethodModel testLarge_anno = model.methods().stream().filter(m -> m.methodName().equalsString("testLarge_anno")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = testLarge_anno.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:32, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + { + MethodModel testRedundant1_anno = model.methods().stream().filter(m -> m.methodName().equalsString("testRedundant1_anno")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = testRedundant1_anno.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:20, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:20, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:20, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + { + MethodModel testRedundant2_anno = model.methods().stream().filter(m -> m.methodName().equalsString("testRedundant2_anno")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = testRedundant2_anno.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:34, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:24, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:20, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + { + MethodModel testCastChainObject_anno = model.methods().stream().filter(m -> m.methodName().equalsString("testCastChainObject1_anno")).findAny().orElseThrow(); + RuntimeInvisibleTypeAnnotationsAttribute typeAnnotations = testCastChainObject_anno.code().orElseThrow().findAttribute(Attributes.runtimeInvisibleTypeAnnotations()).orElseThrow(); + List actualTAs = typeAnnotations.annotations().stream().map(ta -> toString(ta.targetInfo()) + ", " + ta.targetPath() + ", " + ta.annotation()).toList(); + List expectedTAs = List.of( + "CAST:0:23, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:23, [], Annotation[LGenCondCast$Ann;]", + "CAST:0:23, [], Annotation[LGenCondCast$Ann;]" + ); + + if (!Objects.equals(expectedTAs, actualTAs)) { + throw new AssertionError("expected: " + expectedTAs + ", got: " + actualTAs); + } + } + } + + static String toString(TargetInfo ti) { + return switch (ti) { + case TypeArgumentTarget tat -> tat.targetType() + ":" + tat.typeArgumentIndex() + ":" + ((LabelImpl) tat.target()).getBCI(); + default -> ti.getClass().getName(); + }; + } + + @Target(ElementType.TYPE_USE) + @interface Ann {} +} diff --git a/test/langtools/tools/javac/constructors/SuperFieldAccess.java b/test/langtools/tools/javac/constructors/SuperFieldAccess.java new file mode 100644 index 000000000000..9c7677913c95 --- /dev/null +++ b/test/langtools/tools/javac/constructors/SuperFieldAccess.java @@ -0,0 +1,276 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8389737 + * @summary Verify super field access is checked properly. + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit SuperFieldAccess + */ + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import toolbox.JavaTask; +import toolbox.JavacTask; +import toolbox.ToolBox; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import toolbox.Task; + +import static org.junit.jupiter.api.Assertions.*; + +public class SuperFieldAccess { + Path base; + ToolBox tb = new ToolBox(); + + @Test + void testIllegalSuperAccessInEarlyContext() throws Exception { + //cases illegal per JLS 15.11.2 + failingCase(""" + class Super { + int field; + } + + class Sub extends Super { + + int field; + + Sub(int ignored) { + } + + Sub() { + int v = super.field; + this(v); + } + } + """, + "Super.java:13:22: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class Super { + int field; + } + + class T2 { + class Nested extends Super { + Nested() { + super.field = 0; //illegal per JLS 15.11.2? + super(); + } + } + } + """, + "Super.java:8:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + + failingCase(""" + class NotInheritedTest2 { + private int field; + class Nested extends NotInheritedTest2 { + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest2.java:5:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class NotInheritedTest2 { + private int field; + class Nested extends NotInheritedTest2 { + private int field; + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest2.java:6:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class NotInheritedTest2 { + int field; + class Nested extends NotInheritedTest2 { + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest2.java:5:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class Super { + int field; + } + class T3 { + class Nested extends Super { + Nested() { + Nested.super.field = 0; + super(); + } + } + } + """, + "Super.java:7:25: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + failingCase(""" + class Super { + int field; + } + + class T1 extends Super { + T1() { + class Nested { + Nested() { + super(); + T1.super.field = 0; + } + } + super(); + } + } + """, + "Super.java:10:19: compiler.err.cant.ref.before.ctor.called: super", + "1 error"); + failingCase(""" + class NotInheritedTest { + private int field; + class Intermediate extends NotInheritedTest {} + class Nested extends Intermediate { + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest.java:6:18: compiler.err.report.access: field, private, NotInheritedTest", + "1 error"); + failingCase(""" + class NotInheritedTest { + int field; + class Intermediate extends NotInheritedTest {} + class Nested extends Intermediate { + int field; + Nested() { + super.field = 0; + super(); + } + } + } + """, + "NotInheritedTest.java:7:18: compiler.err.cant.ref.before.ctor.called: field", + "1 error"); + } + + private void failingCase(String code, String... errors) throws IOException { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + List log = new JavacTask(tb) + .outdir(classes) + .options("-XDrawDiagnostics") + .sources(code) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + List expected = List.of(errors); + + assertEquals(expected, log); + } + + @Test + void testLegalSuperAccessInEarlyContext() throws Exception { + passingCase(""" + class Test { + class Super { + int field1; + int field2; + } + + class T1 extends Super { + class Nested { + Nested() { + T1.super.field1 = 1; + field2 = 2; + super(); + } + } + } + void main() { + T1 t1 = new T1(); + t1.new Nested(); + System.out.println(t1.field1); + System.out.println(t1.field2); + } + } + """, + "1", + "2"); + } + + private void passingCase(String code, String... output) throws IOException { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .outdir(classes) + .options("-XDrawDiagnostics") + .sources(code) + .run(Task.Expect.SUCCESS) + .writeAll(); + + List log = new JavaTask(tb) + .classpath(classes.toString()) + .className("Test") + .run() + .writeAll() + .getOutputLines(Task.OutputKind.STDOUT); + + List expected = List.of(output); + + assertEquals(expected, log); + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } +} diff --git a/test/langtools/tools/javac/diags/examples/AbstractValueClassWithFinalizeMethod.java b/test/langtools/tools/javac/diags/examples/AbstractValueClassWithFinalizeMethod.java new file mode 100644 index 000000000000..367b6c68229a --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/AbstractValueClassWithFinalizeMethod.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +// key: compiler.err.value.finalize +// key: compiler.note.preview.filename +// key: compiler.note.preview.recompile +// key: compiler.warn.has.been.deprecated.for.removal +// options: --enable-preview -source ${jdk.version} + +abstract value class AbstractValueClassWithFinalizeMethod { + protected void finalize() throws Throwable { } +} diff --git a/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java b/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java index 9e651b7f0b6a..33abd050708c 100644 --- a/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java +++ b/test/langtools/tools/javac/diags/examples/ValueClassWithFinalizeMethod.java @@ -21,7 +21,7 @@ * questions. */ -// key: compiler.warn.value.finalize +// key: compiler.err.value.finalize // key: compiler.note.preview.filename // key: compiler.note.preview.recompile // key: compiler.warn.has.been.deprecated.for.removal diff --git a/test/langtools/tools/javac/records/RecordCompilationTests.java b/test/langtools/tools/javac/records/RecordCompilationTests.java index 48b5cdd85881..760945c48971 100644 --- a/test/langtools/tools/javac/records/RecordCompilationTests.java +++ b/test/langtools/tools/javac/records/RecordCompilationTests.java @@ -1075,6 +1075,19 @@ record R(int x) { """); } + @Test + void testLocalFinalCanBeAssignedInCompactConstructor() { + assertOK( + """ + record R() { + R { + final int c; + c = 0; + } + } + """); + } + @Test void testNoNativeMethods() { assertFail("compiler.err.mod.not.allowed.here", "record R(int x) { # }", diff --git a/test/langtools/tools/javac/recovery/AttrRecovery.java b/test/langtools/tools/javac/recovery/AttrRecovery.java index fb852b5b3f26..37e6bd69f1e9 100644 --- a/test/langtools/tools/javac/recovery/AttrRecovery.java +++ b/test/langtools/tools/javac/recovery/AttrRecovery.java @@ -23,7 +23,8 @@ /* * @test - * @bug 8301580 8322159 8333107 8332230 8338678 8351260 8366196 8372336 8373094 8384229 8387865 + * @bug 8301580 8322159 8333107 8332230 8338678 8351260 8366196 8372336 8373094 8384229 + * 8387865 8389478 * @summary Verify error recovery w.r.t. Attr * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -887,6 +888,41 @@ public C() { assertEquals(expected, actual); } + @Test //JDK-8389478 + public void testCheckAccessFromSerializableElement() throws Exception { + String code = """ + import java.io.Serializable; + + public class Test { + interface SerializableFunction extends Serializable { + R apply(T t); + } + + public static void main(String[] args) { + SerializableFunction f = (x) -> { + // Map is not imported + return Map.Entry.FIELD; + }; + } + } + """; + List actual = new JavacTask(tb) + .options("-XDrawDiagnostics", "-XDdev", + "-XDshould-stop.at=WARN", "-Xlint:all") + .sources(code) + .outdir(base) + .run(Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + + List expected = List.of( + "Test.java:11:23: compiler.err.doesnt.exist: Map", + "1 error" + ); + + assertEquals(expected, actual); + } + @BeforeEach public void setUp(TestInfo info) throws IOException { base = Path.of(info.getTestMethod().orElseThrow().getName()); diff --git a/test/langtools/tools/javac/valhalla/value-objects/LocalProxyVariablesRuntime.java b/test/langtools/tools/javac/valhalla/value-objects/LocalProxyVariablesRuntime.java index 42e33c7e445b..8e612a20b3ad 100644 --- a/test/langtools/tools/javac/valhalla/value-objects/LocalProxyVariablesRuntime.java +++ b/test/langtools/tools/javac/valhalla/value-objects/LocalProxyVariablesRuntime.java @@ -23,6 +23,7 @@ /** * @test + * @bug 8393065 * @enablePreview * @compile LocalProxyVariablesRuntime.java * @run main LocalProxyVariablesRuntime @@ -37,6 +38,8 @@ public static void main(String... args) { new Value3(0); new Value4(); new Value4(0); + new ValueCopy(new ValueCopy(1), 1, false); + new ValueCopy(new ValueCopy(2), 2, '0'); } private static value class Value1 { @@ -95,4 +98,26 @@ public Value4() {} public Value4(int x) {} } + + private static value class ValueCopy { + private final int value; + + public ValueCopy(int value) { + this.value = value; + } + + public ValueCopy(ValueCopy original, int expected, boolean b) { + this.value = original.value; + if (value != expected) { + throw new IllegalStateException("Expected: " + expected + ", got: " + value); + } + } + + public ValueCopy(ValueCopy original, int expected, char c) { + (this).value = original.value; + if (value != expected) { + throw new IllegalStateException("Expected: " + expected + ", got: " + value); + } + } + } } diff --git a/test/lib/jdk/test/lib/cds/CDSAppTester.java b/test/lib/jdk/test/lib/cds/CDSAppTester.java index d434e1c1faec..449a2674df9a 100644 --- a/test/lib/jdk/test/lib/cds/CDSAppTester.java +++ b/test/lib/jdk/test/lib/cds/CDSAppTester.java @@ -455,7 +455,7 @@ public OutputAnalyzer productionRun(String[] extraVmArgs, String[] extraAppArgs) } else if (isDynamicWorkflow()) { cmdLine = StringArrayUtils.concat(cmdLine, "-Xshare:on", "-XX:SharedArchiveFile=" + dynamicArchiveFile); } else if (isAOTWorkflow()) { - cmdLine = StringArrayUtils.concat(cmdLine, "-XX:AOTMode=on", "-XX:AOTCache=" + aotCacheFile); + cmdLine = StringArrayUtils.concat(cmdLine, "-XX:AOTMode=required", "-XX:AOTCache=" + aotCacheFile); } if (extraVmArgs != null) { diff --git a/test/lib/jdk/test/lib/security/DerUtils.java b/test/lib/jdk/test/lib/security/DerUtils.java index 2323065b5952..ed59dabf7d41 100644 --- a/test/lib/jdk/test/lib/security/DerUtils.java +++ b/test/lib/jdk/test/lib/security/DerUtils.java @@ -30,6 +30,7 @@ import sun.security.util.ObjectIdentifier; import java.io.IOException; +import java.util.Locale; public class DerUtils { /** @@ -37,9 +38,10 @@ public class DerUtils { *

        * The location of the inner DerValue is expressed as a string, in which * each character is a step from the outer DerValue into the inner one. - * If it's a number n, the n'th element (starting from 0) of a sequence - * is the next step. If it's 'c', the content of an OctetString parsed - * as a DerValue is the next step. Note that n cannot be bigger than 9. + * If it's a number n (in uppercase HEX, from 0 to F), the n'th element + * of a sequence is the next step. If it's 'c', the content of an OCTET + * STRING parsed as a DerValue is the next step. Note that n cannot be + * bigger than 15 (HEX "F"). *

        * Attention: do not reuse the return value. DerValue is mutable and * reading it advances a pointer inside. @@ -80,7 +82,7 @@ public static DerValue innerDerValue(byte[] data, String location) } else { DerInputStream ins = v.getData(); // skip n DerValue in the sequence - for (int i = 0; i < step - '0'; i++) { + for (int i = 0; i < Integer.parseInt(step + "", 16); i++) { ins.getDerValue(); } if (ins.available() > 0) { @@ -183,8 +185,8 @@ private static DerOutputStream modify0(byte[] data, String now, String target, } else { for (int i = 0; ; i++) { // We only support locations of one digit now - if (i > 9) throw new IllegalStateException("Too big " + i); - String pos = now + i; + if (i > 15) throw new IllegalStateException("Too big " + i); + String pos = now + Integer.toHexString(i).toUpperCase(Locale.ROOT); var sub = DerUtils.innerDerValue(data, pos); // current value if (sub == null) break; // at the end if (target.equals(pos)) { // the one we want to change diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index 1c40fef15284..c871dc6b6c47 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -336,6 +336,15 @@ public Object[] parseCommandLine(String commandline, char delim, Dia */ public native long[] g1GetMixedGCInfo(int liveness); + // Shenandoah + public native int shenandoahRegionSize(); + public native int shenandoahRegionCount(); + public native long shenandoahOldMarkGC(int timeoutSeconds); + public long shenandoahOldMarkGC() { + final int TIMEOUT_SECONDS = 10; + return shenandoahOldMarkGC(TIMEOUT_SECONDS); + } + // NMT public native long NMTMalloc(long size); public native void NMTFree(long mem); diff --git a/test/micro/org/openjdk/bench/java/net/SocketChannelConnectionSetup.java b/test/micro/org/openjdk/bench/java/net/SocketChannelConnectionSetup.java index 652ed134c391..c7fd41c489e2 100644 --- a/test/micro/org/openjdk/bench/java/net/SocketChannelConnectionSetup.java +++ b/test/micro/org/openjdk/bench/java/net/SocketChannelConnectionSetup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -94,14 +94,5 @@ public static void main(String[] args) throws RunnerException { .build(); new Runner(opt).run(); - - opt = new OptionsBuilder() - .include(org.openjdk.bench.java.net.SocketChannelConnectionSetup.class.getSimpleName()) - .jvmArgs("-Djdk.net.useFastTcpLoopback=true") - .warmupForks(1) - .forks(2) - .build(); - - new Runner(opt).run(); } } diff --git a/test/micro/org/openjdk/bench/jdk/incubator/vector/ReplHFRegBenchmark.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/ReplHFRegBenchmark.java new file mode 100644 index 000000000000..becc1be2fd0b --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/ReplHFRegBenchmark.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.bench.jdk.incubator.vector; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +public class ReplHFRegBenchmark { + static final int LEN = 32; + + static final int N = 16; + + static final int K = 512; + + short[] src = new short[LEN]; + short[] dst = new short[LEN]; + + float[] vals = new float[K + N]; + + @Setup + public void setup() { + Arrays.fill(src, Float.floatToFloat16(1.0f)); + for (int i = 0; i < vals.length; i++) { + // Small magnitudes so the adds stay in FP16 range. + vals[i] = 0.125f + (i % 7) * 0.0625f; + } + } + + private static short hfAdd(short a, float b) { + return Float.floatToFloat16(Float.float16ToFloat(a) + + Float.float16ToFloat(Float.floatToFloat16(b))); + } + + @Benchmark + public int manyBroadcasts() { + short[] d = dst; + short[] s = src; + float[] v = vals; + for (int k = 0; k < K; k++) { + float v0 = v[k], v1 = v[k + 1], v2 = v[k + 2], v3 = v[k + 3]; + float v4 = v[k + 4], v5 = v[k + 5], v6 = v[k + 6], v7 = v[k + 7]; + float v8 = v[k + 8], v9 = v[k + 9], v10 = v[k + 10], v11 = v[k + 11]; + float v12 = v[k + 12], v13 = v[k + 13], v14 = v[k + 14], v15 = v[k + 15]; + for (int i = 0; i < LEN; i++) { + short a = s[i]; + int t = hfAdd(a, v0) ^ hfAdd(a, v1) ^ hfAdd(a, v2) ^ hfAdd(a, v3) + ^ hfAdd(a, v4) ^ hfAdd(a, v5) ^ hfAdd(a, v6) ^ hfAdd(a, v7) + ^ hfAdd(a, v8) ^ hfAdd(a, v9) ^ hfAdd(a, v10) ^ hfAdd(a, v11) + ^ hfAdd(a, v12) ^ hfAdd(a, v13) ^ hfAdd(a, v14) ^ hfAdd(a, v15); + d[i] = (short) t; + } + } + return d[0] & 0xffff; + } +}