diff --git a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp index 89ae6bc10e0..73b631029a0 100644 --- a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.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. * Copyright (c) 2014, 2021, Red Hat Inc. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -722,22 +722,20 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; - { // Bypass the barrier for non-static methods - __ ldrh(rscratch1, Address(rmethod, Method::access_flags_offset())); - __ andsw(zr, rscratch1, JVM_ACC_STATIC); - __ br(Assembler::EQ, L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + __ ldrh(rscratch1, Address(rmethod, Method::access_flags_offset())); + __ andsw(zr, rscratch1, JVM_ACC_STATIC); + __ br(Assembler::EQ, L_skip_barrier); // non-static - __ load_method_holder(rscratch2, rmethod); - __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); - __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); + __ load_method_holder(rscratch2, rmethod); + __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); + __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->c2i_entry_barrier(masm); @@ -1508,7 +1506,8 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // SVC, HVC, or SMC. Make it a NOP. __ nop(); - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; __ mov_metadata(rscratch2, method->method_holder()); // InstanceKlass* __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp index cde142b39ac..07b469650f0 100644 --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.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. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -2290,7 +2290,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, __ subs(zr, temp, (int) code); // have we resolved this bytecode? // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); __ br(Assembler::NE, L_clinit_barrier_slow); __ ldr(temp, Address(Rcache, in_bytes(ResolvedMethodEntry::method_offset()))); __ load_method_holder(temp, temp); @@ -2340,8 +2341,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, __ subs(zr, temp, (int) code); // have we resolved this bytecode? // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = temp; __ br(Assembler::NE, L_clinit_barrier_slow); diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp index fc865be015e..f7bf457f72c 100644 --- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp @@ -1109,11 +1109,11 @@ void InterpreterMacroAssembler::verify_method_data_pointer() { lhz(R11_scratch1, in_bytes(DataLayout::bci_offset()), R28_mdx); ld(R12_scratch2, in_bytes(Method::const_offset()), R19_method); addi(R11_scratch1, R11_scratch1, in_bytes(ConstMethod::codes_offset())); - add(R11_scratch1, R12_scratch2, R12_scratch2); + add(R11_scratch1, R11_scratch1, R12_scratch2); cmpd(CR0, R11_scratch1, R14_bcp); beq(CR0, verify_continue); - call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::verify_mdp ), R19_method, R14_bcp, R28_mdx); + call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::verify_mdp), R19_method, R14_bcp, R28_mdx); bind(verify_continue); #endif diff --git a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp index 4e427ace404..4eb2028f529 100644 --- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp +++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -1237,26 +1237,24 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; - { // Bypass the barrier for non-static methods - __ lhz(R0, in_bytes(Method::access_flags_offset()), R19_method); - __ andi_(R0, R0, JVM_ACC_STATIC); - __ beq(CR0, L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + __ lhz(R0, in_bytes(Method::access_flags_offset()), R19_method); + __ andi_(R0, R0, JVM_ACC_STATIC); + __ beq(CR0, L_skip_barrier); // non-static - Register klass = R11_scratch1; - __ load_method_holder(klass, R19_method); - __ clinit_barrier(klass, R16_thread, &L_skip_barrier /*L_fast_path*/); + Register klass = R11_scratch1; + __ load_method_holder(klass, R19_method); + __ clinit_barrier(klass, R16_thread, &L_skip_barrier /*L_fast_path*/); - __ load_const_optimized(klass, SharedRuntime::get_handle_wrong_method_stub(), R0); - __ mtctr(klass); - __ bctr(); + __ load_const_optimized(klass, SharedRuntime::get_handle_wrong_method_stub(), R0); + __ mtctr(klass); + __ bctr(); - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->c2i_entry_barrier(masm, /* tmp register*/ ic_klass, /* tmp register*/ receiver_klass, /* tmp register*/ code); @@ -2210,7 +2208,8 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, // -------------------------------------------------------------------------- vep_start_pc = (intptr_t)__ pc(); - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; Register klass = r_temp_1; // Notify OOP recorder (don't need the relocation) diff --git a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp index 8d61ba1b2d7..8a3af748fa1 100644 --- a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/templateTable_ppc_64.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. * Copyright (c) 2013, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -2199,7 +2199,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, Register Rca __ isync(); // Order load wrt. succeeding loads. // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register method = Rscratch; const Register klass = Rscratch; @@ -2244,8 +2245,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, Register Rcac __ isync(); // Order load wrt. succeeding loads. // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = R4_ARG2; // InterpreterRuntime::resolve_get_put sets field_holder and finally release-stores put_code. diff --git a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp index eeb6fad1b59..44a6f6c0dc0 100644 --- a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp +++ b/src/hotspot/cpu/riscv/sharedRuntime_riscv.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. * Copyright (c) 2014, 2020, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -637,22 +637,20 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; - { // Bypass the barrier for non-static methods - __ load_unsigned_short(t0, Address(xmethod, Method::access_flags_offset())); - __ test_bit(t1, t0, exact_log2(JVM_ACC_STATIC)); - __ beqz(t1, L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + __ load_unsigned_short(t0, Address(xmethod, Method::access_flags_offset())); + __ test_bit(t1, t0, exact_log2(JVM_ACC_STATIC)); + __ beqz(t1, L_skip_barrier); // non-static - __ load_method_holder(t1, xmethod); - __ clinit_barrier(t1, t0, &L_skip_barrier); - __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); + __ load_method_holder(t1, xmethod); + __ clinit_barrier(t1, t0, &L_skip_barrier); + __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->c2i_entry_barrier(masm); @@ -1443,7 +1441,8 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, __ nop(); // 4 bytes } - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; __ mov_metadata(t1, method->method_holder()); // InstanceKlass* __ clinit_barrier(t1, t0, &L_skip_barrier); diff --git a/src/hotspot/cpu/riscv/templateTable_riscv.cpp b/src/hotspot/cpu/riscv/templateTable_riscv.cpp index ca41583e4bc..5a3644f70bb 100644 --- a/src/hotspot/cpu/riscv/templateTable_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateTable_riscv.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. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -2192,7 +2192,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, __ mv(t0, (int) code); // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); __ bne(temp, t0, L_clinit_barrier_slow); // have we resolved this bytecode? __ ld(temp, Address(Rcache, in_bytes(ResolvedMethodEntry::method_offset()))); __ load_method_holder(temp, temp); @@ -2243,8 +2244,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, __ mv(t0, (int) code); // have we resolved this bytecode? // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = temp; __ bne(temp, t0, L_clinit_barrier_slow); diff --git a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp index 5b6f7dcd984..00a830a80cd 100644 --- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp +++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -1567,7 +1567,8 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, //--------------------------------------------------------------------- wrapper_VEPStart = __ offset(); - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; Register klass = Z_R1_scratch; // Notify OOP recorder (don't need the relocation) @@ -2378,24 +2379,22 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; - { // Bypass the barrier for non-static methods - __ testbit_ushort(Address(Z_method, Method::access_flags_offset()), JVM_ACC_STATIC_BIT); - __ z_bfalse(L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + __ testbit_ushort(Address(Z_method, Method::access_flags_offset()), JVM_ACC_STATIC_BIT); + __ z_bfalse(L_skip_barrier); // non-static - Register klass = Z_R11; - __ load_method_holder(klass, Z_method); - __ clinit_barrier(klass, Z_thread, &L_skip_barrier /*L_fast_path*/); + Register klass = Z_R11; + __ load_method_holder(klass, Z_method); + __ clinit_barrier(klass, Z_thread, &L_skip_barrier /*L_fast_path*/); - __ load_const_optimized(klass, SharedRuntime::get_handle_wrong_method_stub()); - __ z_br(klass); + __ load_const_optimized(klass, SharedRuntime::get_handle_wrong_method_stub()); + __ z_br(klass); - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); gen_c2i_adapter(masm, total_args_passed, comp_args_on_stack, sig_bt, regs, skip_fixup); return; diff --git a/src/hotspot/cpu/s390/templateTable_s390.cpp b/src/hotspot/cpu/s390/templateTable_s390.cpp index 4e8fdf275e4..647915ef4fa 100644 --- a/src/hotspot/cpu/s390/templateTable_s390.cpp +++ b/src/hotspot/cpu/s390/templateTable_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -2377,7 +2377,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, __ z_cli(Address(Rcache, bc_offset), code); // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register method = Z_R1_scratch; const Register klass = Z_R1_scratch; __ z_brne(L_clinit_barrier_slow); @@ -2427,8 +2428,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, __ z_cli(Address(cache, code_offset), code); // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = index; __ z_brne(L_clinit_barrier_slow); diff --git a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp index 5a4a5b1809e..bbd43c1a0e8 100644 --- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp +++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.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 @@ -1043,26 +1043,24 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; - Register method = rbx; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; + Register method = rbx; - { // Bypass the barrier for non-static methods - Register flags = rscratch1; - __ load_unsigned_short(flags, Address(method, Method::access_flags_offset())); - __ testl(flags, JVM_ACC_STATIC); - __ jcc(Assembler::zero, L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + Register flags = rscratch1; + __ load_unsigned_short(flags, Address(method, Method::access_flags_offset())); + __ testl(flags, JVM_ACC_STATIC); + __ jcc(Assembler::zero, L_skip_barrier); // non-static - Register klass = rscratch1; - __ load_method_holder(klass, method); - __ clinit_barrier(klass, &L_skip_barrier /*L_fast_path*/); + Register klass = rscratch1; + __ load_method_holder(klass, method); + __ clinit_barrier(klass, &L_skip_barrier /*L_fast_path*/); - __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path + __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->c2i_entry_barrier(masm); @@ -1904,7 +1902,8 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, int vep_offset = ((intptr_t)__ pc()) - start; - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; Register klass = r10; __ mov_metadata(klass, method->method_holder()); // InstanceKlass* @@ -3602,4 +3601,3 @@ RuntimeStub* SharedRuntime::generate_jfr_return_lease() { } #endif // INCLUDE_JFR - diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp index 3e5593322d5..7d5dee6a5df 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.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 @@ -64,6 +64,39 @@ static address kyberAvx512ConstsAddr(int offset) { const Register scratch = r10; +ATTRIBUTE_ALIGNED(64) static const uint8_t kyberAvx512_12To16Dup[] = { +// 0 - 63 + 0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11, 12, 13, 13, 14, 15, 16, + 16, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, + 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 40, 41, 42, 43, 43, 44, + 45, 46, 46, 47 + }; + +static address kyberAvx512_12To16DupAddr() { + return (address) kyberAvx512_12To16Dup; +} + +ATTRIBUTE_ALIGNED(64) static const uint16_t kyberAvx512_12To16Shift[] = { +// 0 - 31 + 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, + 4, 0, 4, 0, 4, 0, 4 + }; + +static address kyberAvx512_12To16ShiftAddr() { + return (address) kyberAvx512_12To16Shift; +} + +ATTRIBUTE_ALIGNED(64) static const uint64_t kyberAvx512_12To16And[] = { +// 0 - 7 + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF + }; + +static address kyberAvx512_12To16AndAddr() { + return (address) kyberAvx512_12To16And; +} + ATTRIBUTE_ALIGNED(64) static const uint16_t kyberAvx512NttPerms[] = { // 0 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, @@ -822,10 +855,65 @@ address generate_kyber12To16_avx512(StubGenerator *stubgen, const Register perms = r11; - Label Loop; + Label Loop, VBMILoop; __ addptr(condensed, condensedOffs); + if (VM_Version::supports_avx512_vbmi()) { + // mask load for the first 48 bytes of each vector + __ mov64(rax, 0x0000FFFFFFFFFFFF); + __ kmovql(k1, rax); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16DupAddr())); + __ evmovdqub(xmm20, Address(perms), Assembler::AVX_512bit); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16ShiftAddr())); + __ evmovdquw(xmm21, Address(perms), Assembler::AVX_512bit); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16AndAddr())); + __ evmovdquq(xmm22, Address(perms), Assembler::AVX_512bit); + + __ align(OptoLoopAlignment); + __ BIND(VBMILoop); + + __ evmovdqub(xmm0, k1, Address(condensed, 0), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm1, k1, Address(condensed, 48), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm2, k1, Address(condensed, 96), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm3, k1, Address(condensed, 144), false, + Assembler::AVX_512bit); + + __ evpermb(xmm4, k0, xmm20, xmm0, false, Assembler::AVX_512bit); + __ evpermb(xmm5, k0, xmm20, xmm1, false, Assembler::AVX_512bit); + __ evpermb(xmm6, k0, xmm20, xmm2, false, Assembler::AVX_512bit); + __ evpermb(xmm7, k0, xmm20, xmm3, false, Assembler::AVX_512bit); + + __ evpsrlvw(xmm4, xmm4, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm5, xmm5, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm6, xmm6, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm7, xmm7, xmm21, Assembler::AVX_512bit); + + __ evpandq(xmm0, xmm22, xmm4, Assembler::AVX_512bit); + __ evpandq(xmm1, xmm22, xmm5, Assembler::AVX_512bit); + __ evpandq(xmm2, xmm22, xmm6, Assembler::AVX_512bit); + __ evpandq(xmm3, xmm22, xmm7, Assembler::AVX_512bit); + + store4regs(parsed, 0, xmm0_3, _masm); + + __ addptr(condensed, 192); + __ addptr(parsed, 256); + __ subl(parsedLength, 128); + __ jcc(Assembler::greater, VBMILoop); + + __ leave(); // required for proper stackwalking of RuntimeStub frame + __ mov64(rax, 0); // return 0 + __ ret(0); + + return start; + } + __ lea(perms, ExternalAddress(kyberAvx512_12To16PermsAddr())); load4regs(xmm24_27, perms, 0, _masm); diff --git a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp index 42392b84833..db7749ec482 100644 --- a/src/hotspot/cpu/x86/templateTable_x86.cpp +++ b/src/hotspot/cpu/x86/templateTable_x86.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 @@ -2216,7 +2216,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, __ cmpl(temp, code); // have we resolved this bytecode? // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register method = temp; const Register klass = temp; @@ -2264,8 +2265,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, __ cmpl(temp, code); // have we resolved this bytecode? // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = temp; __ jcc(Assembler::notEqual, L_clinit_barrier_slow); diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index f88729cdc66..d7c1911a914 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -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. * Copyright (c) 2012, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -258,10 +258,18 @@ bool os::free_memory(physical_memory_size_type& value) { return Aix::available_memory(value); } +bool os::Machine::free_memory(physical_memory_size_type& value) { + return Aix::available_memory(value); +} + bool os::available_memory(physical_memory_size_type& value) { return Aix::available_memory(value); } +bool os::Machine::available_memory(physical_memory_size_type& value) { + return Aix::available_memory(value); +} + bool os::Aix::available_memory(physical_memory_size_type& value) { os::Aix::meminfo_t mi; if (os::Aix::get_meminfo(&mi)) { @@ -273,6 +281,10 @@ bool os::Aix::available_memory(physical_memory_size_type& value) { } bool os::total_swap_space(physical_memory_size_type& value) { + return Machine::total_swap_space(value); +} + +bool os::Machine::total_swap_space(physical_memory_size_type& value) { perfstat_memory_total_t memory_info; if (libperfstat::perfstat_memory_total(nullptr, &memory_info, sizeof(perfstat_memory_total_t), 1) == -1) { return false; @@ -282,6 +294,10 @@ bool os::total_swap_space(physical_memory_size_type& value) { } bool os::free_swap_space(physical_memory_size_type& value) { + return Machine::free_swap_space(value); +} + +bool os::Machine::free_swap_space(physical_memory_size_type& value) { perfstat_memory_total_t memory_info; if (libperfstat::perfstat_memory_total(nullptr, &memory_info, sizeof(perfstat_memory_total_t), 1) == -1) { return false; @@ -294,6 +310,10 @@ physical_memory_size_type os::physical_memory() { return Aix::physical_memory(); } +physical_memory_size_type os::Machine::physical_memory() { + return Aix::physical_memory(); +} + size_t os::rss() { return (size_t)0; } // Cpu architecture string @@ -2264,6 +2284,10 @@ int os::active_processor_count() { return ActiveProcessorCount; } + return Machine::active_processor_count(); +} + +int os::Machine::active_processor_count() { int online_cpus = ::sysconf(_SC_NPROCESSORS_ONLN); assert(online_cpus > 0 && online_cpus <= processor_count(), "sanity check"); return online_cpus; diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 61de48bb7fa..667810bd6ca 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -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 @@ -137,10 +137,18 @@ bool os::available_memory(physical_memory_size_type& value) { return Bsd::available_memory(value); } +bool os::Machine::available_memory(physical_memory_size_type& value) { + return Bsd::available_memory(value); +} + bool os::free_memory(physical_memory_size_type& value) { return Bsd::available_memory(value); } +bool os::Machine::free_memory(physical_memory_size_type& value) { + return Bsd::available_memory(value); +} + // Available here means free. Note that this number is of no much use. As an estimate // for future memory pressure it is far too conservative, since MacOS will use a lot // of unused memory for caches, and return it willingly in case of needs. @@ -181,6 +189,10 @@ void os::Bsd::print_uptime_info(outputStream* st) { } bool os::total_swap_space(physical_memory_size_type& value) { + return Machine::total_swap_space(value); +} + +bool os::Machine::total_swap_space(physical_memory_size_type& value) { #if defined(__APPLE__) struct xsw_usage vmusage; size_t size = sizeof(vmusage); @@ -195,6 +207,10 @@ bool os::total_swap_space(physical_memory_size_type& value) { } bool os::free_swap_space(physical_memory_size_type& value) { + return Machine::free_swap_space(value); +} + +bool os::Machine::free_swap_space(physical_memory_size_type& value) { #if defined(__APPLE__) struct xsw_usage vmusage; size_t size = sizeof(vmusage); @@ -212,6 +228,10 @@ physical_memory_size_type os::physical_memory() { return Bsd::physical_memory(); } +physical_memory_size_type os::Machine::physical_memory() { + return Bsd::physical_memory(); +} + size_t os::rss() { size_t rss = 0; #ifdef __APPLE__ @@ -2189,6 +2209,10 @@ int os::active_processor_count() { return ActiveProcessorCount; } + return Machine::active_processor_count(); +} + +int os::Machine::active_processor_count() { return _processor_count; } diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index f9c6f794ebd..e49d070890e 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.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 @@ -631,22 +631,20 @@ void CgroupSubsystemFactory::cleanup(CgroupInfo* cg_infos) { * return: * true if there were no errors. false otherwise. */ -bool CgroupSubsystem::active_processor_count(int& value) { - int cpu_count; - int result = -1; - +bool CgroupSubsystem::active_processor_count(double& value) { // We use a cache with a timeout to avoid performing expensive // computations in the event this function is called frequently. // [See 8227006]. - CachingCgroupController* contrl = cpu_controller(); - CachedMetric* cpu_limit = contrl->metrics_cache(); + CachingCgroupController* contrl = cpu_controller(); + CachedMetric* cpu_limit = contrl->metrics_cache(); if (!cpu_limit->should_check_metric()) { - value = (int)cpu_limit->value(); - log_trace(os, container)("CgroupSubsystem::active_processor_count (cached): %d", value); + value = cpu_limit->value(); + log_trace(os, container)("CgroupSubsystem::active_processor_count (cached): %.2f", value); return true; } - cpu_count = os::Linux::active_processor_count(); + int cpu_count = os::Linux::active_processor_count(); + double result = -1; if (!CgroupUtil::processor_count(contrl->controller(), cpu_count, result)) { return false; } @@ -671,8 +669,8 @@ bool CgroupSubsystem::active_processor_count(int& value) { */ bool CgroupSubsystem::memory_limit_in_bytes(physical_memory_size_type upper_bound, physical_memory_size_type& value) { - CachingCgroupController* contrl = memory_controller(); - CachedMetric* memory_limit = contrl->metrics_cache(); + CachingCgroupController* contrl = memory_controller(); + CachedMetric* memory_limit = contrl->metrics_cache(); if (!memory_limit->should_check_metric()) { value = memory_limit->value(); return true; diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp index 522b64f8816..8aafbf49c63 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.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 @@ -181,9 +181,10 @@ class CgroupController: public CHeapObj { static bool limit_from_str(char* limit_str, physical_memory_size_type& value); }; +template class CachedMetric : public CHeapObj{ private: - volatile physical_memory_size_type _metric; + volatile MetricType _metric; volatile jlong _next_check_counter; public: CachedMetric() { @@ -193,8 +194,8 @@ class CachedMetric : public CHeapObj{ bool should_check_metric() { return os::elapsed_counter() > _next_check_counter; } - physical_memory_size_type value() { return _metric; } - void set_value(physical_memory_size_type value, jlong timeout) { + MetricType value() { return _metric; } + void set_value(MetricType value, jlong timeout) { _metric = value; // Metric is unlikely to change, but we want to remain // responsive to configuration changes. A very short grace time @@ -205,19 +206,19 @@ class CachedMetric : public CHeapObj{ } }; -template +template class CachingCgroupController : public CHeapObj { private: T* _controller; - CachedMetric* _metrics_cache; + CachedMetric* _metrics_cache; public: CachingCgroupController(T* cont) { _controller = cont; - _metrics_cache = new CachedMetric(); + _metrics_cache = new CachedMetric(); } - CachedMetric* metrics_cache() { return _metrics_cache; } + CachedMetric* metrics_cache() { return _metrics_cache; } T* controller() { return _controller; } }; @@ -277,7 +278,7 @@ class CgroupMemoryController: public CHeapObj { class CgroupSubsystem: public CHeapObj { public: bool memory_limit_in_bytes(physical_memory_size_type upper_bound, physical_memory_size_type& value); - bool active_processor_count(int& value); + bool active_processor_count(double& value); virtual bool pids_max(uint64_t& value) = 0; virtual bool pids_current(uint64_t& value) = 0; @@ -286,8 +287,8 @@ class CgroupSubsystem: public CHeapObj { virtual char * cpu_cpuset_cpus() = 0; virtual char * cpu_cpuset_memory_nodes() = 0; virtual const char * container_type() = 0; - virtual CachingCgroupController* memory_controller() = 0; - virtual CachingCgroupController* cpu_controller() = 0; + virtual CachingCgroupController* memory_controller() = 0; + virtual CachingCgroupController* cpu_controller() = 0; virtual CgroupCpuacctController* cpuacct_controller() = 0; bool cpu_quota(int& value); diff --git a/src/hotspot/os/linux/cgroupUtil_linux.cpp b/src/hotspot/os/linux/cgroupUtil_linux.cpp index 7aa07d53148..570b335940b 100644 --- a/src/hotspot/os/linux/cgroupUtil_linux.cpp +++ b/src/hotspot/os/linux/cgroupUtil_linux.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, 2025, Red Hat, Inc. + * 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 @@ -25,9 +26,8 @@ #include "cgroupUtil_linux.hpp" #include "os_linux.hpp" -bool CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int upper_bound, int& value) { +bool CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int upper_bound, double& value) { assert(upper_bound > 0, "upper bound of cpus must be positive"); - int limit_count = upper_bound; int quota = -1; int period = -1; if (!cpu_ctrl->cpu_quota(quota)) { @@ -37,20 +37,15 @@ bool CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int upper_bound, return false; } int quota_count = 0; - int result = upper_bound; + double result = upper_bound; - if (quota > -1 && period > 0) { - quota_count = ceilf((float)quota / (float)period); - log_trace(os, container)("CPU Quota count based on quota/period: %d", quota_count); + if (quota > 0 && period > 0) { // Use quotas + double cpu_quota = static_cast(quota) / period; + log_trace(os, container)("CPU Quota based on quota/period: %.2f", cpu_quota); + result = MIN2(result, cpu_quota); } - // Use quotas - if (quota_count != 0) { - limit_count = quota_count; - } - - result = MIN2(upper_bound, limit_count); - log_trace(os, container)("OSContainer::active_processor_count: %d", result); + log_trace(os, container)("OSContainer::active_processor_count: %.2f", result); value = result; return true; } @@ -73,11 +68,11 @@ physical_memory_size_type CgroupUtil::get_updated_mem_limit(CgroupMemoryControll // Get an updated cpu limit. The return value is strictly less than or equal to the // passed in 'lowest' value. -int CgroupUtil::get_updated_cpu_limit(CgroupCpuController* cpu, +double CgroupUtil::get_updated_cpu_limit(CgroupCpuController* cpu, int lowest, int upper_bound) { assert(lowest > 0 && lowest <= upper_bound, "invariant"); - int cpu_limit_val = -1; + double cpu_limit_val = -1; if (CgroupUtil::processor_count(cpu, upper_bound, cpu_limit_val) && cpu_limit_val != upper_bound) { assert(cpu_limit_val <= upper_bound, "invariant"); if (lowest > cpu_limit_val) { @@ -172,7 +167,7 @@ void CgroupUtil::adjust_controller(CgroupCpuController* cpu) { assert(cg_path[0] == '/', "cgroup path must start with '/'"); int host_cpus = os::Linux::active_processor_count(); int lowest_limit = host_cpus; - int cpus = get_updated_cpu_limit(cpu, lowest_limit, host_cpus); + double cpus = get_updated_cpu_limit(cpu, lowest_limit, host_cpus); int orig_limit = lowest_limit != host_cpus ? lowest_limit : host_cpus; char* limit_cg_path = nullptr; while ((last_slash = strrchr(cg_path, '/')) != cg_path) { diff --git a/src/hotspot/os/linux/cgroupUtil_linux.hpp b/src/hotspot/os/linux/cgroupUtil_linux.hpp index d72bbd1cf1e..1fd2a7d872b 100644 --- a/src/hotspot/os/linux/cgroupUtil_linux.hpp +++ b/src/hotspot/os/linux/cgroupUtil_linux.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Red Hat, Inc. + * 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 @@ -31,7 +32,7 @@ class CgroupUtil: AllStatic { public: - static bool processor_count(CgroupCpuController* cpu, int upper_bound, int& value); + static bool processor_count(CgroupCpuController* cpu, int upper_bound, double& value); // Given a memory controller, adjust its path to a point in the hierarchy // that represents the closest memory limit. static void adjust_controller(CgroupMemoryController* m); @@ -42,9 +43,7 @@ class CgroupUtil: AllStatic { static physical_memory_size_type get_updated_mem_limit(CgroupMemoryController* m, physical_memory_size_type lowest, physical_memory_size_type upper_bound); - static int get_updated_cpu_limit(CgroupCpuController* c, - int lowest, - int upper_bound); + static double get_updated_cpu_limit(CgroupCpuController* c, int lowest, int upper_bound); }; #endif // CGROUP_UTIL_LINUX_HPP diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp index 2df604083d2..c8f5a290c99 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.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 @@ -328,8 +328,8 @@ CgroupV1Subsystem::CgroupV1Subsystem(CgroupV1Controller* cpuset, _pids(pids) { CgroupUtil::adjust_controller(memory); CgroupUtil::adjust_controller(cpu); - _memory = new CachingCgroupController(memory); - _cpu = new CachingCgroupController(cpu); + _memory = new CachingCgroupController(memory); + _cpu = new CachingCgroupController(cpu); } bool CgroupV1Subsystem::is_containerized() { diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp index f556bc57f26..af8d0efd378 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.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 @@ -214,15 +214,15 @@ class CgroupV1Subsystem: public CgroupSubsystem { const char * container_type() override { return "cgroupv1"; } - CachingCgroupController* memory_controller() override { return _memory; } - CachingCgroupController* cpu_controller() override { return _cpu; } + CachingCgroupController* memory_controller() override { return _memory; } + CachingCgroupController* cpu_controller() override { return _cpu; } CgroupCpuacctController* cpuacct_controller() override { return _cpuacct; } private: /* controllers */ - CachingCgroupController* _memory = nullptr; + CachingCgroupController* _memory = nullptr; CgroupV1Controller* _cpuset = nullptr; - CachingCgroupController* _cpu = nullptr; + CachingCgroupController* _cpu = nullptr; CgroupV1CpuacctController* _cpuacct = nullptr; CgroupV1Controller* _pids = nullptr; diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp index c61d30e9236..30e1affc646 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, 2025, Red Hat Inc. - * 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 @@ -156,8 +156,8 @@ CgroupV2Subsystem::CgroupV2Subsystem(CgroupV2MemoryController * memory, _unified(unified) { CgroupUtil::adjust_controller(memory); CgroupUtil::adjust_controller(cpu); - _memory = new CachingCgroupController(memory); - _cpu = new CachingCgroupController(cpu); + _memory = new CachingCgroupController(memory); + _cpu = new CachingCgroupController(cpu); _cpuacct = cpuacct; } diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp index 39a4fabe9f6..998145c0ff9 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, 2024, Red Hat Inc. - * 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 @@ -152,8 +152,8 @@ class CgroupV2Subsystem: public CgroupSubsystem { /* One unified controller */ CgroupV2Controller _unified; /* Caching wrappers for cpu/memory metrics */ - CachingCgroupController* _memory = nullptr; - CachingCgroupController* _cpu = nullptr; + CachingCgroupController* _memory = nullptr; + CachingCgroupController* _cpu = nullptr; CgroupCpuacctController* _cpuacct = nullptr; @@ -175,8 +175,8 @@ class CgroupV2Subsystem: public CgroupSubsystem { const char * container_type() override { return "cgroupv2"; } - CachingCgroupController* memory_controller() override { return _memory; } - CachingCgroupController* cpu_controller() override { return _cpu; } + CachingCgroupController* memory_controller() override { return _memory; } + CachingCgroupController* cpu_controller() override { return _cpu; } CgroupCpuacctController* cpuacct_controller() override { return _cpuacct; }; }; diff --git a/src/hotspot/os/linux/osContainer_linux.cpp b/src/hotspot/os/linux/osContainer_linux.cpp index 15a6403d07f..fe1dbc17239 100644 --- a/src/hotspot/os/linux/osContainer_linux.cpp +++ b/src/hotspot/os/linux/osContainer_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 @@ -86,8 +86,8 @@ void OSContainer::init() { // 2.) On a physical Linux system with a limit enforced by other means (like systemd slice) physical_memory_size_type mem_limit_val = value_unlimited; (void)memory_limit_in_bytes(mem_limit_val); // discard error and use default - int host_cpus = os::Linux::active_processor_count(); - int cpus = host_cpus; + double host_cpus = os::Linux::active_processor_count(); + double cpus = host_cpus; (void)active_processor_count(cpus); // discard error and use default any_mem_cpu_limit_present = mem_limit_val != value_unlimited || host_cpus != cpus; if (any_mem_cpu_limit_present) { @@ -127,8 +127,7 @@ bool OSContainer::available_memory_in_bytes(physical_memory_size_type& value) { return false; } -bool OSContainer::available_swap_in_bytes(physical_memory_size_type host_free_swap, - physical_memory_size_type& value) { +bool OSContainer::available_swap_in_bytes(physical_memory_size_type& value) { physical_memory_size_type mem_limit = 0; physical_memory_size_type mem_swap_limit = 0; if (memory_limit_in_bytes(mem_limit) && @@ -179,8 +178,7 @@ bool OSContainer::available_swap_in_bytes(physical_memory_size_type host_free_sw assert(num < 25, "buffer too small"); mem_limit_buf[num] = '\0'; log_trace(os,container)("OSContainer::available_swap_in_bytes: container_swap_limit=%s" - " container_mem_limit=%s, host_free_swap: " PHYS_MEM_TYPE_FORMAT, - mem_swap_buf, mem_limit_buf, host_free_swap); + " container_mem_limit=%s", mem_swap_buf, mem_limit_buf); } return false; } @@ -252,7 +250,7 @@ char * OSContainer::cpu_cpuset_memory_nodes() { return cgroup_subsystem->cpu_cpuset_memory_nodes(); } -bool OSContainer::active_processor_count(int& value) { +bool OSContainer::active_processor_count(double& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); return cgroup_subsystem->active_processor_count(value); } @@ -291,11 +289,13 @@ template struct metric_fmt; template<> struct metric_fmt { static constexpr const char* fmt = "%llu"; }; template<> struct metric_fmt { static constexpr const char* fmt = "%lu"; }; template<> struct metric_fmt { static constexpr const char* fmt = "%d"; }; +template<> struct metric_fmt { static constexpr const char* fmt = "%.2f"; }; template<> struct metric_fmt { static constexpr const char* fmt = "%s"; }; template void OSContainer::print_container_metric(outputStream*, const char*, unsigned long long int, const char*); template void OSContainer::print_container_metric(outputStream*, const char*, unsigned long int, const char*); template void OSContainer::print_container_metric(outputStream*, const char*, int, const char*); +template void OSContainer::print_container_metric(outputStream*, const char*, double, const char*); template void OSContainer::print_container_metric(outputStream*, const char*, const char*, const char*); template diff --git a/src/hotspot/os/linux/osContainer_linux.hpp b/src/hotspot/os/linux/osContainer_linux.hpp index 11c3e086feb..96b59b98db8 100644 --- a/src/hotspot/os/linux/osContainer_linux.hpp +++ b/src/hotspot/os/linux/osContainer_linux.hpp @@ -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 @@ -72,8 +72,7 @@ class OSContainer: AllStatic { static const char * container_type(); static bool available_memory_in_bytes(physical_memory_size_type& value); - static bool available_swap_in_bytes(physical_memory_size_type host_free_swap, - physical_memory_size_type& value); + static bool available_swap_in_bytes(physical_memory_size_type& value); static bool memory_limit_in_bytes(physical_memory_size_type& value); static bool memory_and_swap_limit_in_bytes(physical_memory_size_type& value); static bool memory_and_swap_usage_in_bytes(physical_memory_size_type& value); @@ -84,7 +83,7 @@ class OSContainer: AllStatic { static bool rss_usage_in_bytes(physical_memory_size_type& value); static bool cache_usage_in_bytes(physical_memory_size_type& value); - static bool active_processor_count(int& value); + static bool active_processor_count(double& value); static char * cpu_cpuset_cpus(); static char * cpu_cpuset_memory_nodes(); diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index 6a2a3974a16..7190845a8ba 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -211,15 +211,58 @@ static bool suppress_primordial_thread_resolution = false; // utility functions +bool os::is_containerized() { + return OSContainer::is_containerized(); +} + +bool os::Container::memory_limit(physical_memory_size_type& value) { + physical_memory_size_type result = 0; + if (OSContainer::memory_limit_in_bytes(result) && result != value_unlimited) { + value = result; + return true; + } + return false; +} + +bool os::Container::memory_soft_limit(physical_memory_size_type& value) { + physical_memory_size_type result = 0; + if (OSContainer::memory_soft_limit_in_bytes(result) && result != 0 && result != value_unlimited) { + value = result; + return true; + } + return false; +} + +bool os::Container::memory_throttle_limit(physical_memory_size_type& value) { + physical_memory_size_type result = 0; + if (OSContainer::memory_throttle_limit_in_bytes(result) && result != value_unlimited) { + value = result; + return true; + } + return false; +} + +bool os::Container::used_memory(physical_memory_size_type& value) { + return OSContainer::memory_usage_in_bytes(value); +} + bool os::available_memory(physical_memory_size_type& value) { - if (OSContainer::is_containerized() && OSContainer::available_memory_in_bytes(value)) { + if (is_containerized() && Container::available_memory(value)) { log_trace(os)("available container memory: " PHYS_MEM_TYPE_FORMAT, value); return true; } + return Machine::available_memory(value); +} + +bool os::Machine::available_memory(physical_memory_size_type& value) { return Linux::available_memory(value); } +bool os::Container::available_memory(physical_memory_size_type& value) { + return OSContainer::available_memory_in_bytes(value); +} + bool os::Linux::available_memory(physical_memory_size_type& value) { physical_memory_size_type avail_mem = 0; @@ -251,11 +294,15 @@ bool os::Linux::available_memory(physical_memory_size_type& value) { } bool os::free_memory(physical_memory_size_type& value) { - if (OSContainer::is_containerized() && OSContainer::available_memory_in_bytes(value)) { + if (is_containerized() && Container::available_memory(value)) { log_trace(os)("free container memory: " PHYS_MEM_TYPE_FORMAT, value); return true; } + return Machine::free_memory(value); +} + +bool os::Machine::free_memory(physical_memory_size_type& value) { return Linux::free_memory(value); } @@ -274,21 +321,30 @@ bool os::Linux::free_memory(physical_memory_size_type& value) { } bool os::total_swap_space(physical_memory_size_type& value) { - if (OSContainer::is_containerized()) { - physical_memory_size_type mem_swap_limit = value_unlimited; - physical_memory_size_type memory_limit = value_unlimited; - if (OSContainer::memory_and_swap_limit_in_bytes(mem_swap_limit) && - OSContainer::memory_limit_in_bytes(memory_limit)) { - if (memory_limit != value_unlimited && mem_swap_limit != value_unlimited && - mem_swap_limit >= memory_limit /* ensure swap is >= 0 */) { - value = mem_swap_limit - memory_limit; - return true; - } - } - } // fallback to the host swap space if the container returned unlimited + if (is_containerized() && Container::total_swap_space(value)) { + return true; + } // fallback to the host swap space if the container value fails + return Machine::total_swap_space(value); +} + +bool os::Machine::total_swap_space(physical_memory_size_type& value) { return Linux::host_swap(value); } +bool os::Container::total_swap_space(physical_memory_size_type& value) { + physical_memory_size_type mem_swap_limit = value_unlimited; + physical_memory_size_type memory_limit = value_unlimited; + if (OSContainer::memory_and_swap_limit_in_bytes(mem_swap_limit) && + OSContainer::memory_limit_in_bytes(memory_limit)) { + if (memory_limit != value_unlimited && mem_swap_limit != value_unlimited && + mem_swap_limit >= memory_limit /* ensure swap is >= 0 */) { + value = mem_swap_limit - memory_limit; + return true; + } + } + return false; +} + static bool host_free_swap_f(physical_memory_size_type& value) { struct sysinfo si; int ret = sysinfo(&si); @@ -309,32 +365,45 @@ bool os::free_swap_space(physical_memory_size_type& value) { return false; } physical_memory_size_type host_free_swap_val = MIN2(total_swap_space, host_free_swap); - if (OSContainer::is_containerized()) { - if (OSContainer::available_swap_in_bytes(host_free_swap_val, value)) { + if (is_containerized()) { + if (Container::free_swap_space(value)) { return true; } // Fall through to use host value log_trace(os,container)("os::free_swap_space: containerized value unavailable" " returning host value: " PHYS_MEM_TYPE_FORMAT, host_free_swap_val); } + value = host_free_swap_val; return true; } +bool os::Machine::free_swap_space(physical_memory_size_type& value) { + return host_free_swap_f(value); +} + +bool os::Container::free_swap_space(physical_memory_size_type& value) { + return OSContainer::available_swap_in_bytes(value); +} + physical_memory_size_type os::physical_memory() { - if (OSContainer::is_containerized()) { + if (is_containerized()) { physical_memory_size_type mem_limit = value_unlimited; - if (OSContainer::memory_limit_in_bytes(mem_limit) && mem_limit != value_unlimited) { + if (Container::memory_limit(mem_limit) && mem_limit != value_unlimited) { log_trace(os)("total container memory: " PHYS_MEM_TYPE_FORMAT, mem_limit); return mem_limit; } } - physical_memory_size_type phys_mem = Linux::physical_memory(); + physical_memory_size_type phys_mem = Machine::physical_memory(); log_trace(os)("total system memory: " PHYS_MEM_TYPE_FORMAT, phys_mem); return phys_mem; } +physical_memory_size_type os::Machine::physical_memory() { + return Linux::physical_memory(); +} + // Returns the resident set size (RSS) of the process. // Falls back to using VmRSS from /proc/self/status if /proc/self/smaps_rollup is unavailable. // Note: On kernels with memory cgroups or shared memory, VmRSS may underreport RSS. @@ -2439,20 +2508,21 @@ bool os::Linux::print_container_info(outputStream* st) { OSContainer::print_container_metric(st, "cpu_memory_nodes", p != nullptr ? p : "not supported"); free(p); - int i = -1; - bool supported = OSContainer::active_processor_count(i); + double cpus = -1; + bool supported = OSContainer::active_processor_count(cpus); if (supported) { - assert(i > 0, "must be"); + assert(cpus > 0, "must be"); if (ActiveProcessorCount > 0) { OSContainer::print_container_metric(st, "active_processor_count", ActiveProcessorCount, "(from -XX:ActiveProcessorCount)"); } else { - OSContainer::print_container_metric(st, "active_processor_count", i); + OSContainer::print_container_metric(st, "active_processor_count", cpus); } } else { OSContainer::print_container_metric(st, "active_processor_count", "not supported"); } + int i = -1; supported = OSContainer::cpu_quota(i); if (supported && i > 0) { OSContainer::print_container_metric(st, "cpu_quota", i); @@ -4737,15 +4807,26 @@ int os::active_processor_count() { return ActiveProcessorCount; } - int active_cpus = -1; - if (OSContainer::is_containerized() && OSContainer::active_processor_count(active_cpus)) { - log_trace(os)("active_processor_count: determined by OSContainer: %d", - active_cpus); - } else { - active_cpus = os::Linux::active_processor_count(); + if (is_containerized()) { + double cpu_quota; + if (Container::processor_count(cpu_quota)) { + int active_cpus = ceilf(cpu_quota); // Round fractional CPU quota up. + assert(active_cpus <= Machine::active_processor_count(), "must be"); + log_trace(os)("active_processor_count: determined by OSContainer: %d", + active_cpus); + return active_cpus; + } } - return active_cpus; + return Machine::active_processor_count(); +} + +int os::Machine::active_processor_count() { + return os::Linux::active_processor_count(); +} + +bool os::Container::processor_count(double& value) { + return OSContainer::active_processor_count(value); } static bool should_warn_invalid_processor_id() { @@ -4882,9 +4963,14 @@ int os::open(const char *path, int oflag, int mode) { oflag |= O_CLOEXEC; int fd = ::open(path, oflag, mode); - if (fd == -1) return -1; + // No further checking is needed if open() returned an error or + // access mode is not read only. + if (fd == -1 || (oflag & O_ACCMODE) != O_RDONLY) { + return fd; + } - //If the open succeeded, the file might still be a directory + // If the open succeeded and is read only, the file might be a directory + // which the JVM doesn't allow to be read. { struct stat buf; int ret = ::fstat(fd, &buf); diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index efbd1fe7c68..b0b7ae18106 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -839,10 +839,18 @@ bool os::available_memory(physical_memory_size_type& value) { return win32::available_memory(value); } +bool os::Machine::available_memory(physical_memory_size_type& value) { + return win32::available_memory(value); +} + bool os::free_memory(physical_memory_size_type& value) { return win32::available_memory(value); } +bool os::Machine::free_memory(physical_memory_size_type& value) { + return win32::available_memory(value); +} + bool os::win32::available_memory(physical_memory_size_type& value) { // Use GlobalMemoryStatusEx() because GlobalMemoryStatus() may return incorrect // value if total memory is larger than 4GB @@ -858,7 +866,11 @@ bool os::win32::available_memory(physical_memory_size_type& value) { } } -bool os::total_swap_space(physical_memory_size_type& value) { +bool os::total_swap_space(physical_memory_size_type& value) { + return Machine::total_swap_space(value); +} + +bool os::Machine::total_swap_space(physical_memory_size_type& value) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); BOOL res = GlobalMemoryStatusEx(&ms); @@ -872,6 +884,10 @@ bool os::total_swap_space(physical_memory_size_type& value) { } bool os::free_swap_space(physical_memory_size_type& value) { + return Machine::free_swap_space(value); +} + +bool os::Machine::free_swap_space(physical_memory_size_type& value) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); BOOL res = GlobalMemoryStatusEx(&ms); @@ -888,6 +904,10 @@ physical_memory_size_type os::physical_memory() { return win32::physical_memory(); } +physical_memory_size_type os::Machine::physical_memory() { + return win32::physical_memory(); +} + size_t os::rss() { size_t rss = 0; PROCESS_MEMORY_COUNTERS_EX pmex; @@ -911,6 +931,10 @@ int os::active_processor_count() { return ActiveProcessorCount; } + return Machine::active_processor_count(); +} + +int os::Machine::active_processor_count() { bool schedules_all_processor_groups = win32::is_windows_11_or_greater() || win32::is_windows_server_2022_or_greater(); if (UseAllWindowsProcessorGroups && !schedules_all_processor_groups && !win32::processor_group_warning_displayed()) { win32::set_processor_group_warning_displayed(true); diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index c775014cfac..20dfad0d980 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.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 @@ -614,6 +614,10 @@ struct StringTableDeleteCheck : StackObj { }; void StringTable::clean_dead_entries(JavaThread* jt) { + // BulkDeleteTask::prepare() may take ConcurrentHashTableResize_lock (nosafepoint-2). + // When NativeHeapTrimmer is enabled, SuspendMark may take NativeHeapTrimmer::_lock (nosafepoint). + // Take SuspendMark first to keep lock order and avoid deadlock. + NativeHeapTrimmer::SuspendMark sm("stringtable"); StringTableHash::BulkDeleteTask bdt(_local_table); if (!bdt.prepare(jt)) { return; @@ -621,7 +625,6 @@ void StringTable::clean_dead_entries(JavaThread* jt) { StringTableDeleteCheck stdc; StringTableDoDelete stdd; - NativeHeapTrimmer::SuspendMark sm("stringtable"); { TraceTime timer("Clean", TRACETIME_LOG(Debug, stringtable, perf)); while(bdt.do_task(jt, stdc, stdd)) { diff --git a/src/hotspot/share/classfile/symbolTable.cpp b/src/hotspot/share/classfile/symbolTable.cpp index ec639a2b4d3..c49aa10fa0d 100644 --- a/src/hotspot/share/classfile/symbolTable.cpp +++ b/src/hotspot/share/classfile/symbolTable.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 @@ -763,6 +763,10 @@ struct SymbolTableDeleteCheck : StackObj { }; void SymbolTable::clean_dead_entries(JavaThread* jt) { + // BulkDeleteTask::prepare() may take ConcurrentHashTableResize_lock (nosafepoint-2). + // When NativeHeapTrimmer is enabled, SuspendMark may take NativeHeapTrimmer::_lock (nosafepoint). + // Take SuspendMark first to keep lock order and avoid deadlock. + NativeHeapTrimmer::SuspendMark sm("symboltable"); SymbolTableHash::BulkDeleteTask bdt(_local_table); if (!bdt.prepare(jt)) { return; @@ -770,7 +774,6 @@ void SymbolTable::clean_dead_entries(JavaThread* jt) { SymbolTableDeleteCheck stdc; SymbolTableDoDelete stdd; - NativeHeapTrimmer::SuspendMark sm("symboltable"); { TraceTime timer("Clean", TRACETIME_LOG(Debug, symboltable, perf)); while (bdt.do_task(jt, stdc, stdd)) { diff --git a/src/hotspot/share/gc/g1/g1CardTableClaimTable.cpp b/src/hotspot/share/gc/g1/g1CardTableClaimTable.cpp index e0cadbdd907..d8cabaa00a4 100644 --- a/src/hotspot/share/gc/g1/g1CardTableClaimTable.cpp +++ b/src/hotspot/share/gc/g1/g1CardTableClaimTable.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 @@ -44,20 +44,20 @@ G1CardTableClaimTable::~G1CardTableClaimTable() { void G1CardTableClaimTable::initialize(uint max_reserved_regions) { assert(_card_claims == nullptr, "Must not be initialized twice"); - _card_claims = NEW_C_HEAP_ARRAY(uint, max_reserved_regions, mtGC); + _card_claims = NEW_C_HEAP_ARRAY(Atomic, max_reserved_regions, mtGC); _max_reserved_regions = max_reserved_regions; reset_all_to_unclaimed(); } void G1CardTableClaimTable::reset_all_to_unclaimed() { for (uint i = 0; i < _max_reserved_regions; i++) { - _card_claims[i] = 0; + _card_claims[i].store_relaxed(0); } } void G1CardTableClaimTable::reset_all_to_claimed() { for (uint i = 0; i < _max_reserved_regions; i++) { - _card_claims[i] = (uint)G1HeapRegion::CardsPerRegion; + _card_claims[i].store_relaxed((uint)G1HeapRegion::CardsPerRegion); } } diff --git a/src/hotspot/share/gc/g1/g1CardTableClaimTable.hpp b/src/hotspot/share/gc/g1/g1CardTableClaimTable.hpp index 4f524b83f97..822ef45c722 100644 --- a/src/hotspot/share/gc/g1/g1CardTableClaimTable.hpp +++ b/src/hotspot/share/gc/g1/g1CardTableClaimTable.hpp @@ -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 @@ -27,6 +27,7 @@ #include "gc/g1/g1CardTable.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class G1HeapRegionClosure; @@ -45,7 +46,7 @@ class G1CardTableClaimTable : public CHeapObj { // Card table iteration claim values for every heap region, from 0 (completely unclaimed) // to (>=) G1HeapRegion::CardsPerRegion (completely claimed). - uint volatile* _card_claims; + Atomic* _card_claims; uint _cards_per_chunk; // For conversion between card index and chunk index. diff --git a/src/hotspot/share/gc/g1/g1CardTableClaimTable.inline.hpp b/src/hotspot/share/gc/g1/g1CardTableClaimTable.inline.hpp index d682f0d17ae..35b2484982c 100644 --- a/src/hotspot/share/gc/g1/g1CardTableClaimTable.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CardTableClaimTable.inline.hpp @@ -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 @@ -29,26 +29,25 @@ #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1HeapRegion.inline.hpp" -#include "runtime/atomicAccess.hpp" bool G1CardTableClaimTable::has_unclaimed_cards(uint region) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - return AtomicAccess::load(&_card_claims[region]) < G1HeapRegion::CardsPerRegion; + return _card_claims[region].load_relaxed() < G1HeapRegion::CardsPerRegion; } void G1CardTableClaimTable::reset_to_unclaimed(uint region) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - AtomicAccess::store(&_card_claims[region], 0u); + _card_claims[region].store_relaxed(0u); } uint G1CardTableClaimTable::claim_cards(uint region, uint increment) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - return AtomicAccess::fetch_then_add(&_card_claims[region], increment, memory_order_relaxed); + return _card_claims[region].fetch_then_add(increment, memory_order_relaxed); } uint G1CardTableClaimTable::claim_chunk(uint region) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - return AtomicAccess::fetch_then_add(&_card_claims[region], cards_per_chunk(), memory_order_relaxed); + return _card_claims[region].fetch_then_add(cards_per_chunk(), memory_order_relaxed); } uint G1CardTableClaimTable::claim_all_cards(uint region) { diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 3a0c4a04441..b6c3c0b0907 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -103,7 +103,6 @@ #include "oops/access.inline.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/cpuTimeCounters.hpp" #include "runtime/handles.inline.hpp" #include "runtime/init.hpp" diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index aff7166d391..a0104d04f4f 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,6 +54,7 @@ #include "memory/allocation.hpp" #include "memory/iterator.hpp" #include "memory/memRegion.hpp" +#include "runtime/atomic.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/threadSMR.hpp" #include "utilities/bitMap.hpp" @@ -124,7 +125,7 @@ class G1JavaThreadsListClaimer : public StackObj { ThreadsListHandle _list; uint _claim_step; - volatile uint _cur_claim; + Atomic _cur_claim; // Attempts to claim _claim_step JavaThreads, returning an array of claimed // JavaThread* with count elements. Returns null (and a zero count) if there diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp index abd61e72d57..577450b3be9 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -41,7 +41,6 @@ #include "gc/shared/markBitMap.inline.hpp" #include "gc/shared/taskqueue.inline.hpp" #include "oops/stackChunkOop.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/threadSMR.inline.hpp" #include "utilities/bitMap.inline.hpp" @@ -53,10 +52,10 @@ inline bool G1STWIsAliveClosure::do_object_b(oop p) { inline JavaThread* const* G1JavaThreadsListClaimer::claim(uint& count) { count = 0; - if (AtomicAccess::load(&_cur_claim) >= _list.length()) { + if (_cur_claim.load_relaxed() >= _list.length()) { return nullptr; } - uint claim = AtomicAccess::fetch_then_add(&_cur_claim, _claim_step); + uint claim = _cur_claim.fetch_then_add(_claim_step); if (claim >= _list.length()) { return nullptr; } diff --git a/src/hotspot/share/gc/g1/g1CollectionSetChooser.cpp b/src/hotspot/share/gc/g1/g1CollectionSetChooser.cpp index 954ca40a77f..d9496410c12 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSetChooser.cpp +++ b/src/hotspot/share/gc/g1/g1CollectionSetChooser.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ #include "gc/g1/g1CollectionSetChooser.hpp" #include "gc/g1/g1HeapRegionRemSet.inline.hpp" #include "gc/shared/space.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/quickSort.hpp" // Determine collection set candidates (from marking): For all regions determine @@ -50,7 +50,7 @@ class G1BuildCandidateRegionsTask : public WorkerTask { G1HeapRegion** _data; - uint volatile _cur_claim_idx; + Atomic _cur_claim_idx; static int compare_region_gc_efficiency(G1HeapRegion** rr1, G1HeapRegion** rr2) { G1HeapRegion* r1 = *rr1; @@ -105,7 +105,7 @@ class G1BuildCandidateRegionsTask : public WorkerTask { // Claim a new chunk, returning its bounds [from, to[. void claim_chunk(uint& from, uint& to) { - uint result = AtomicAccess::add(&_cur_claim_idx, _chunk_size); + uint result = _cur_claim_idx.add_then_fetch(_chunk_size); assert(_max_size > result - 1, "Array too small, is %u should be %u with chunk size %u.", _max_size, result, _chunk_size); @@ -121,14 +121,15 @@ class G1BuildCandidateRegionsTask : public WorkerTask { } void sort_by_gc_efficiency() { - if (_cur_claim_idx == 0) { + uint length = _cur_claim_idx.load_relaxed(); + if (length == 0) { return; } - for (uint i = _cur_claim_idx; i < _max_size; i++) { + for (uint i = length; i < _max_size; i++) { assert(_data[i] == nullptr, "must be"); } - qsort(_data, _cur_claim_idx, sizeof(_data[0]), (_sort_Fn)compare_region_gc_efficiency); - for (uint i = _cur_claim_idx; i < _max_size; i++) { + qsort(_data, length, sizeof(_data[0]), (_sort_Fn)compare_region_gc_efficiency); + for (uint i = length; i < _max_size; i++) { assert(_data[i] == nullptr, "must be"); } } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 456d543fa10..1077939f953 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -67,7 +67,6 @@ #include "nmt/memTracker.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/globals_extension.hpp" #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" @@ -148,25 +147,25 @@ bool G1CMMarkStack::initialize() { } G1CMMarkStack::TaskQueueEntryChunk* G1CMMarkStack::ChunkAllocator::allocate_new_chunk() { - if (_size >= _max_capacity) { + if (_size.load_relaxed() >= _max_capacity) { return nullptr; } - size_t cur_idx = AtomicAccess::fetch_then_add(&_size, 1u); + size_t cur_idx = _size.fetch_then_add(1u); if (cur_idx >= _max_capacity) { return nullptr; } size_t bucket = get_bucket(cur_idx); - if (AtomicAccess::load_acquire(&_buckets[bucket]) == nullptr) { + if (_buckets[bucket].load_acquire() == nullptr) { if (!_should_grow) { // Prefer to restart the CM. return nullptr; } MutexLocker x(G1MarkStackChunkList_lock, Mutex::_no_safepoint_check_flag); - if (AtomicAccess::load_acquire(&_buckets[bucket]) == nullptr) { + if (_buckets[bucket].load_acquire() == nullptr) { size_t desired_capacity = bucket_size(bucket) * 2; if (!try_expand_to(desired_capacity)) { return nullptr; @@ -175,7 +174,7 @@ G1CMMarkStack::TaskQueueEntryChunk* G1CMMarkStack::ChunkAllocator::allocate_new_ } size_t bucket_idx = get_bucket_index(cur_idx); - TaskQueueEntryChunk* result = ::new (&_buckets[bucket][bucket_idx]) TaskQueueEntryChunk; + TaskQueueEntryChunk* result = ::new (&_buckets[bucket].load_relaxed()[bucket_idx]) TaskQueueEntryChunk; result->next = nullptr; return result; } @@ -197,10 +196,10 @@ bool G1CMMarkStack::ChunkAllocator::initialize(size_t initial_capacity, size_t m _max_capacity = max_capacity; _num_buckets = get_bucket(_max_capacity) + 1; - _buckets = NEW_C_HEAP_ARRAY(TaskQueueEntryChunk*, _num_buckets, mtGC); + _buckets = NEW_C_HEAP_ARRAY(Atomic, _num_buckets, mtGC); for (size_t i = 0; i < _num_buckets; i++) { - _buckets[i] = nullptr; + _buckets[i].store_relaxed(nullptr); } size_t new_capacity = bucket_size(0); @@ -240,9 +239,9 @@ G1CMMarkStack::ChunkAllocator::~ChunkAllocator() { } for (size_t i = 0; i < _num_buckets; i++) { - if (_buckets[i] != nullptr) { - MmapArrayAllocator::free(_buckets[i], bucket_size(i)); - _buckets[i] = nullptr; + if (_buckets[i].load_relaxed() != nullptr) { + MmapArrayAllocator::free(_buckets[i].load_relaxed(), bucket_size(i)); + _buckets[i].store_relaxed(nullptr); } } @@ -259,7 +258,7 @@ bool G1CMMarkStack::ChunkAllocator::reserve(size_t new_capacity) { // and the new capacity (new_capacity). This step ensures that there are no gaps in the // array and that the capacity accurately reflects the reserved memory. for (; i <= highest_bucket; i++) { - if (AtomicAccess::load_acquire(&_buckets[i]) != nullptr) { + if (_buckets[i].load_acquire() != nullptr) { continue; // Skip over already allocated buckets. } @@ -279,7 +278,7 @@ bool G1CMMarkStack::ChunkAllocator::reserve(size_t new_capacity) { return false; } _capacity += bucket_capacity; - AtomicAccess::release_store(&_buckets[i], bucket_base); + _buckets[i].release_store(bucket_base); } return true; } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index 752082ce629..7ea9151c6f1 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +37,7 @@ #include "gc/shared/workerThread.hpp" #include "gc/shared/workerUtils.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "utilities/compilerWarnings.hpp" #include "utilities/numberSeq.hpp" @@ -172,9 +173,9 @@ private: size_t _capacity; size_t _num_buckets; bool _should_grow; - TaskQueueEntryChunk* volatile* _buckets; + Atomic* _buckets; char _pad0[DEFAULT_PADDING_SIZE]; - volatile size_t _size; + Atomic _size; char _pad4[DEFAULT_PADDING_SIZE - sizeof(size_t)]; size_t bucket_size(size_t bucket) { @@ -212,7 +213,7 @@ private: bool initialize(size_t initial_capacity, size_t max_capacity); void reset() { - _size = 0; + _size.store_relaxed(0); _should_grow = false; } @@ -556,14 +557,14 @@ public: // mark_in_bitmap call. Updates various statistics data. void add_to_liveness(uint worker_id, oop const obj, size_t size); // Did the last marking find a live object between bottom and TAMS? - bool contains_live_object(uint region) const { return _region_mark_stats[region]._live_words != 0; } + bool contains_live_object(uint region) const { return _region_mark_stats[region].live_words() != 0; } // Live bytes in the given region as determined by concurrent marking, i.e. the amount of // live bytes between bottom and TAMS. - size_t live_bytes(uint region) const { return _region_mark_stats[region]._live_words * HeapWordSize; } + size_t live_bytes(uint region) const { return _region_mark_stats[region].live_words() * HeapWordSize; } // Set live bytes for concurrent marking. - void set_live_bytes(uint region, size_t live_bytes) { _region_mark_stats[region]._live_words = live_bytes / HeapWordSize; } + void set_live_bytes(uint region, size_t live_bytes) { _region_mark_stats[region]._live_words.store_relaxed(live_bytes / HeapWordSize); } // Approximate number of incoming references found during marking. - size_t incoming_refs(uint region) const { return _region_mark_stats[region]._incoming_refs; } + size_t incoming_refs(uint region) const { return _region_mark_stats[region].incoming_refs(); } // Update the TAMS for the given region to the current top. inline void update_top_at_mark_start(G1HeapRegion* r); diff --git a/src/hotspot/share/gc/g1/g1EvacFailureRegions.cpp b/src/hotspot/share/gc/g1/g1EvacFailureRegions.cpp index ffcb5a0022f..37553e2aa56 100644 --- a/src/hotspot/share/gc/g1/g1EvacFailureRegions.cpp +++ b/src/hotspot/share/gc/g1/g1EvacFailureRegions.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. + * 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 @@ -28,7 +29,6 @@ #include "gc/g1/g1EvacFailureRegions.inline.hpp" #include "gc/g1/g1HeapRegion.hpp" #include "memory/allocation.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/bitMap.inline.hpp" G1EvacFailureRegions::G1EvacFailureRegions() : @@ -43,7 +43,7 @@ G1EvacFailureRegions::~G1EvacFailureRegions() { } void G1EvacFailureRegions::pre_collection(uint max_regions) { - AtomicAccess::store(&_num_regions_evac_failed, 0u); + _num_regions_evac_failed.store_relaxed(0u); _regions_evac_failed.resize(max_regions); _regions_pinned.resize(max_regions); _regions_alloc_failed.resize(max_regions); @@ -69,6 +69,6 @@ void G1EvacFailureRegions::par_iterate(G1HeapRegionClosure* closure, G1CollectedHeap::heap()->par_iterate_regions_array(closure, hrclaimer, _evac_failed_regions, - AtomicAccess::load(&_num_regions_evac_failed), + num_regions_evac_failed(), worker_id); } diff --git a/src/hotspot/share/gc/g1/g1EvacFailureRegions.hpp b/src/hotspot/share/gc/g1/g1EvacFailureRegions.hpp index 9d29957b782..f752a3f8ab7 100644 --- a/src/hotspot/share/gc/g1/g1EvacFailureRegions.hpp +++ b/src/hotspot/share/gc/g1/g1EvacFailureRegions.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. + * 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 @@ -25,6 +26,7 @@ #ifndef SHARE_GC_G1_G1EVACFAILUREREGIONS_HPP #define SHARE_GC_G1_G1EVACFAILUREREGIONS_HPP +#include "runtime/atomic.hpp" #include "utilities/bitMap.hpp" class G1AbstractSubTask; @@ -53,14 +55,14 @@ class G1EvacFailureRegions { // Evacuation failed regions (indexes) in the current collection. uint* _evac_failed_regions; // Number of regions evacuation failed in the current collection. - volatile uint _num_regions_evac_failed; + Atomic _num_regions_evac_failed; public: G1EvacFailureRegions(); ~G1EvacFailureRegions(); uint get_region_idx(uint idx) const { - assert(idx < _num_regions_evac_failed, "precondition"); + assert(idx < _num_regions_evac_failed.load_relaxed(), "precondition"); return _evac_failed_regions[idx]; } diff --git a/src/hotspot/share/gc/g1/g1EvacFailureRegions.inline.hpp b/src/hotspot/share/gc/g1/g1EvacFailureRegions.inline.hpp index 6eec9b63e6b..fb456475b56 100644 --- a/src/hotspot/share/gc/g1/g1EvacFailureRegions.inline.hpp +++ b/src/hotspot/share/gc/g1/g1EvacFailureRegions.inline.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. + * 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 @@ -29,10 +30,9 @@ #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1GCPhaseTimes.hpp" -#include "runtime/atomicAccess.hpp" uint G1EvacFailureRegions::num_regions_evac_failed() const { - return AtomicAccess::load(&_num_regions_evac_failed); + return _num_regions_evac_failed.load_relaxed(); } bool G1EvacFailureRegions::has_regions_evac_failed() const { @@ -57,7 +57,7 @@ bool G1EvacFailureRegions::record(uint worker_id, uint region_idx, bool cause_pi bool success = _regions_evac_failed.par_set_bit(region_idx, memory_order_relaxed); if (success) { - size_t offset = AtomicAccess::fetch_then_add(&_num_regions_evac_failed, 1u); + size_t offset = _num_regions_evac_failed.fetch_then_add(1u); _evac_failed_regions[offset] = region_idx; G1CollectedHeap* g1h = G1CollectedHeap::heap(); diff --git a/src/hotspot/share/gc/g1/g1FullCollector.hpp b/src/hotspot/share/gc/g1/g1FullCollector.hpp index ed8225fc004..1fb3af17032 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.hpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.hpp @@ -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 @@ -122,7 +122,7 @@ public: ReferenceProcessor* reference_processor(); size_t live_words(uint region_index) const { assert(region_index < _heap->max_num_regions(), "sanity"); - return _live_stats[region_index]._live_words; + return _live_stats[region_index].live_words(); } void before_marking_update_attribute_table(G1HeapRegion* hr); diff --git a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp index 83c846e84d4..c02b028112b 100644 --- a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.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 @@ -37,7 +37,6 @@ #include "gc/shared/weakProcessor.inline.hpp" #include "logging/log.hpp" #include "memory/iterator.inline.hpp" -#include "runtime/atomicAccess.hpp" class G1AdjustLiveClosure : public StackObj { G1AdjustClosure* _adjust_closure; diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp index fae73a2c6bf..13c7a6a8d3e 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,7 +31,6 @@ #include "memory/allocation.hpp" #include "memory/padded.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" #include "runtime/mutexLocker.hpp" diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp index 878d35397aa..950098c706e 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp @@ -30,7 +30,6 @@ #include "gc/g1/g1CodeRootSet.hpp" #include "gc/g1/g1CollectionSetCandidates.hpp" #include "gc/g1/g1FromCardCache.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/safepoint.hpp" #include "utilities/bitMap.hpp" diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.inline.hpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.inline.hpp index fbd529cb1d3..f621b1318c1 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.inline.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.inline.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 @@ -30,7 +30,6 @@ #include "gc/g1/g1CardSet.inline.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1HeapRegion.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/bitMap.inline.hpp" void G1HeapRegionRemSet::set_state_untracked() { diff --git a/src/hotspot/share/gc/g1/g1MonotonicArena.cpp b/src/hotspot/share/gc/g1/g1MonotonicArena.cpp index a9c6462680f..3f97870a67f 100644 --- a/src/hotspot/share/gc/g1/g1MonotonicArena.cpp +++ b/src/hotspot/share/gc/g1/g1MonotonicArena.cpp @@ -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 @@ -24,7 +24,6 @@ #include "gc/g1/g1MonotonicArena.inline.hpp" #include "memory/allocation.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/vmOperations.hpp" #include "utilities/globalCounter.inline.hpp" @@ -61,13 +60,13 @@ void G1MonotonicArena::SegmentFreeList::bulk_add(Segment& first, size_t num, size_t mem_size) { _list.prepend(first, last); - AtomicAccess::add(&_num_segments, num, memory_order_relaxed); - AtomicAccess::add(&_mem_size, mem_size, memory_order_relaxed); + _num_segments.add_then_fetch(num, memory_order_relaxed); + _mem_size.add_then_fetch(mem_size, memory_order_relaxed); } void G1MonotonicArena::SegmentFreeList::print_on(outputStream* out, const char* prefix) { out->print_cr("%s: segments %zu size %zu", - prefix, AtomicAccess::load(&_num_segments), AtomicAccess::load(&_mem_size)); + prefix, _num_segments.load_relaxed(), _mem_size.load_relaxed()); } G1MonotonicArena::Segment* G1MonotonicArena::SegmentFreeList::get_all(size_t& num_segments, @@ -75,12 +74,12 @@ G1MonotonicArena::Segment* G1MonotonicArena::SegmentFreeList::get_all(size_t& nu GlobalCounter::CriticalSection cs(Thread::current()); Segment* result = _list.pop_all(); - num_segments = AtomicAccess::load(&_num_segments); - mem_size = AtomicAccess::load(&_mem_size); + num_segments = _num_segments.load_relaxed(); + mem_size = _mem_size.load_relaxed(); if (result != nullptr) { - AtomicAccess::sub(&_num_segments, num_segments, memory_order_relaxed); - AtomicAccess::sub(&_mem_size, mem_size, memory_order_relaxed); + _num_segments.sub_then_fetch(num_segments, memory_order_relaxed); + _mem_size.sub_then_fetch(mem_size, memory_order_relaxed); } return result; } @@ -96,8 +95,8 @@ void G1MonotonicArena::SegmentFreeList::free_all() { Segment::delete_segment(cur); } - AtomicAccess::sub(&_num_segments, num_freed, memory_order_relaxed); - AtomicAccess::sub(&_mem_size, mem_size_freed, memory_order_relaxed); + _num_segments.sub_then_fetch(num_freed, memory_order_relaxed); + _mem_size.sub_then_fetch(mem_size_freed, memory_order_relaxed); } G1MonotonicArena::Segment* G1MonotonicArena::new_segment(Segment* const prev) { @@ -115,7 +114,7 @@ G1MonotonicArena::Segment* G1MonotonicArena::new_segment(Segment* const prev) { } // Install it as current allocation segment. - Segment* old = AtomicAccess::cmpxchg(&_first, prev, next); + Segment* old = _first.compare_exchange(prev, next); if (old != prev) { // Somebody else installed the segment, use that one. Segment::delete_segment(next); @@ -126,9 +125,9 @@ G1MonotonicArena::Segment* G1MonotonicArena::new_segment(Segment* const prev) { _last = next; } // Successfully installed the segment into the list. - AtomicAccess::inc(&_num_segments, memory_order_relaxed); - AtomicAccess::add(&_mem_size, next->mem_size(), memory_order_relaxed); - AtomicAccess::add(&_num_total_slots, next->num_slots(), memory_order_relaxed); + _num_segments.add_then_fetch(1u, memory_order_relaxed); + _mem_size.add_then_fetch(next->mem_size(), memory_order_relaxed); + _num_total_slots.add_then_fetch(next->num_slots(), memory_order_relaxed); return next; } } @@ -155,7 +154,7 @@ uint G1MonotonicArena::slot_size() const { } void G1MonotonicArena::drop_all() { - Segment* cur = AtomicAccess::load_acquire(&_first); + Segment* cur = _first.load_acquire(); if (cur != nullptr) { assert(_last != nullptr, "If there is at least one segment, there must be a last one."); @@ -175,25 +174,25 @@ void G1MonotonicArena::drop_all() { cur = next; } #endif - assert(num_segments == _num_segments, "Segment count inconsistent %u %u", num_segments, _num_segments); - assert(mem_size == _mem_size, "Memory size inconsistent"); + assert(num_segments == _num_segments.load_relaxed(), "Segment count inconsistent %u %u", num_segments, _num_segments.load_relaxed()); + assert(mem_size == _mem_size.load_relaxed(), "Memory size inconsistent"); assert(last == _last, "Inconsistent last segment"); - _segment_free_list->bulk_add(*first, *_last, _num_segments, _mem_size); + _segment_free_list->bulk_add(*first, *_last, _num_segments.load_relaxed(), _mem_size.load_relaxed()); } - _first = nullptr; + _first.store_relaxed(nullptr); _last = nullptr; - _num_segments = 0; - _mem_size = 0; - _num_total_slots = 0; - _num_allocated_slots = 0; + _num_segments.store_relaxed(0); + _mem_size.store_relaxed(0); + _num_total_slots.store_relaxed(0); + _num_allocated_slots.store_relaxed(0); } void* G1MonotonicArena::allocate() { assert(slot_size() > 0, "instance size not set."); - Segment* cur = AtomicAccess::load_acquire(&_first); + Segment* cur = _first.load_acquire(); if (cur == nullptr) { cur = new_segment(cur); } @@ -201,7 +200,7 @@ void* G1MonotonicArena::allocate() { while (true) { void* slot = cur->allocate_slot(); if (slot != nullptr) { - AtomicAccess::inc(&_num_allocated_slots, memory_order_relaxed); + _num_allocated_slots.add_then_fetch(1u, memory_order_relaxed); guarantee(is_aligned(slot, _alloc_options->slot_alignment()), "result " PTR_FORMAT " not aligned at %u", p2i(slot), _alloc_options->slot_alignment()); return slot; @@ -213,7 +212,7 @@ void* G1MonotonicArena::allocate() { } uint G1MonotonicArena::num_segments() const { - return AtomicAccess::load(&_num_segments); + return _num_segments.load_relaxed(); } #ifdef ASSERT @@ -238,7 +237,7 @@ uint G1MonotonicArena::calculate_length() const { template void G1MonotonicArena::iterate_segments(SegmentClosure& closure) const { - Segment* cur = AtomicAccess::load_acquire(&_first); + Segment* cur = _first.load_acquire(); assert((cur != nullptr) == (_last != nullptr), "If there is at least one segment, there must be a last one"); diff --git a/src/hotspot/share/gc/g1/g1MonotonicArena.hpp b/src/hotspot/share/gc/g1/g1MonotonicArena.hpp index 211820c5254..d8e658b5a64 100644 --- a/src/hotspot/share/gc/g1/g1MonotonicArena.hpp +++ b/src/hotspot/share/gc/g1/g1MonotonicArena.hpp @@ -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. * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,6 +28,7 @@ #include "gc/shared/freeListAllocator.hpp" #include "nmt/memTag.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/lockFreeStack.hpp" @@ -65,27 +66,27 @@ private: // AllocOptions provides parameters for Segment sizing and expansion. const AllocOptions* _alloc_options; - Segment* volatile _first; // The (start of the) list of all segments. - Segment* _last; // The last segment of the list of all segments. - volatile uint _num_segments; // Number of assigned segments to this allocator. - volatile size_t _mem_size; // Memory used by all segments. + Atomic _first; // The (start of the) list of all segments. + Segment* _last; // The last segment of the list of all segments. + Atomic _num_segments; // Number of assigned segments to this allocator. + Atomic _mem_size; // Memory used by all segments. SegmentFreeList* _segment_free_list; // The global free segment list to preferentially // get new segments from. - volatile uint _num_total_slots; // Number of slots available in all segments (allocated + not yet used). - volatile uint _num_allocated_slots; // Number of total slots allocated ever (including free and pending). + Atomic _num_total_slots; // Number of slots available in all segments (allocated + not yet used). + Atomic _num_allocated_slots; // Number of total slots allocated ever (including free and pending). inline Segment* new_segment(Segment* const prev); DEBUG_ONLY(uint calculate_length() const;) public: - const Segment* first_segment() const { return AtomicAccess::load(&_first); } + const Segment* first_segment() const { return _first.load_relaxed(); } - uint num_total_slots() const { return AtomicAccess::load(&_num_total_slots); } + uint num_total_slots() const { return _num_total_slots.load_relaxed(); } uint num_allocated_slots() const { - uint allocated = AtomicAccess::load(&_num_allocated_slots); + uint allocated = _num_allocated_slots.load_relaxed(); assert(calculate_length() == allocated, "Must be"); return allocated; } @@ -116,11 +117,11 @@ static constexpr uint SegmentPayloadMaxAlignment = 8; class alignas(SegmentPayloadMaxAlignment) G1MonotonicArena::Segment { const uint _slot_size; const uint _num_slots; - Segment* volatile _next; + Atomic _next; // Index into the next free slot to allocate into. Full if equal (or larger) // to _num_slots (can be larger because we atomically increment this value and // check only afterwards if the allocation has been successful). - uint volatile _next_allocate; + Atomic _next_allocate; const MemTag _mem_tag; static size_t header_size() { return align_up(sizeof(Segment), SegmentPayloadMaxAlignment); } @@ -139,21 +140,21 @@ class alignas(SegmentPayloadMaxAlignment) G1MonotonicArena::Segment { Segment(uint slot_size, uint num_slots, Segment* next, MemTag mem_tag); ~Segment() = default; public: - Segment* volatile* next_addr() { return &_next; } + Atomic* next_addr() { return &_next; } void* allocate_slot(); uint num_slots() const { return _num_slots; } - Segment* next() const { return _next; } + Segment* next() const { return _next.load_relaxed(); } void set_next(Segment* next) { assert(next != this, " loop condition"); - _next = next; + _next.store_relaxed(next); } void reset(Segment* next) { - _next_allocate = 0; + _next_allocate.store_relaxed(0); assert(next != this, " loop condition"); set_next(next); memset(payload(0), 0, payload_size()); @@ -166,7 +167,7 @@ public: uint length() const { // _next_allocate might grow larger than _num_slots in multi-thread environments // due to races. - return MIN2(_next_allocate, _num_slots); + return MIN2(_next_allocate.load_relaxed(), _num_slots); } static size_t size_in_bytes(uint slot_size, uint num_slots) { @@ -176,7 +177,7 @@ public: static Segment* create_segment(uint slot_size, uint num_slots, Segment* next, MemTag mem_tag); static void delete_segment(Segment* segment); - bool is_full() const { return _next_allocate >= _num_slots; } + bool is_full() const { return _next_allocate.load_relaxed() >= _num_slots; } }; static_assert(alignof(G1MonotonicArena::Segment) >= SegmentPayloadMaxAlignment, "assert alignment of Segment (and indirectly its payload)"); @@ -186,15 +187,15 @@ static_assert(alignof(G1MonotonicArena::Segment) >= SegmentPayloadMaxAlignment, // performed by multiple threads concurrently. // Counts and memory usage are current on a best-effort basis if accessed concurrently. class G1MonotonicArena::SegmentFreeList { - static Segment* volatile* next_ptr(Segment& segment) { + static Atomic* next_ptr(Segment& segment) { return segment.next_addr(); } using SegmentStack = LockFreeStack; SegmentStack _list; - volatile size_t _num_segments; - volatile size_t _mem_size; + Atomic _num_segments; + Atomic _mem_size; public: SegmentFreeList() : _list(), _num_segments(0), _mem_size(0) { } @@ -210,8 +211,8 @@ public: void print_on(outputStream* out, const char* prefix = ""); - size_t num_segments() const { return AtomicAccess::load(&_num_segments); } - size_t mem_size() const { return AtomicAccess::load(&_mem_size); } + size_t num_segments() const { return _num_segments.load_relaxed(); } + size_t mem_size() const { return _mem_size.load_relaxed(); } }; // Configuration for G1MonotonicArena, e.g slot size, slot number of next Segment. diff --git a/src/hotspot/share/gc/g1/g1MonotonicArena.inline.hpp b/src/hotspot/share/gc/g1/g1MonotonicArena.inline.hpp index dd9ccae1849..cf1b35ccead 100644 --- a/src/hotspot/share/gc/g1/g1MonotonicArena.inline.hpp +++ b/src/hotspot/share/gc/g1/g1MonotonicArena.inline.hpp @@ -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. * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,14 +28,13 @@ #include "gc/g1/g1MonotonicArena.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/globalCounter.inline.hpp" inline void* G1MonotonicArena::Segment::allocate_slot() { - if (_next_allocate >= _num_slots) { + if (_next_allocate.load_relaxed() >= _num_slots) { return nullptr; } - uint result = AtomicAccess::fetch_then_add(&_next_allocate, 1u, memory_order_relaxed); + uint result = _next_allocate.fetch_then_add(1u, memory_order_relaxed); if (result >= _num_slots) { return nullptr; } @@ -48,8 +47,8 @@ inline G1MonotonicArena::Segment* G1MonotonicArena::SegmentFreeList::get() { Segment* result = _list.pop(); if (result != nullptr) { - AtomicAccess::dec(&_num_segments, memory_order_relaxed); - AtomicAccess::sub(&_mem_size, result->mem_size(), memory_order_relaxed); + _num_segments.sub_then_fetch(1u, memory_order_relaxed); + _mem_size.sub_then_fetch(result->mem_size(), memory_order_relaxed); } return result; } diff --git a/src/hotspot/share/gc/g1/g1PageBasedVirtualSpace.cpp b/src/hotspot/share/gc/g1/g1PageBasedVirtualSpace.cpp index d7e0c6e394f..529ef62b44d 100644 --- a/src/hotspot/share/gc/g1/g1PageBasedVirtualSpace.cpp +++ b/src/hotspot/share/gc/g1/g1PageBasedVirtualSpace.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 @@ -28,7 +28,6 @@ #include "nmt/memTracker.hpp" #include "oops/markWord.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/os.hpp" #include "utilities/align.hpp" #include "utilities/bitMap.inline.hpp" diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index e7b02ed68e7..75a8ef1a336 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.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 @@ -42,7 +42,6 @@ #include "memory/allocation.inline.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/prefetch.inline.hpp" #include "utilities/globalDefinitions.hpp" diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 6eef6cbfa87..2847a25c5b4 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -943,7 +943,7 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar phase_times()->sum_thread_work_items(G1GCPhaseTimes::MergePSS, G1GCPhaseTimes::MergePSSToYoungGenCards)); } - record_pause(this_pause, start_time_sec, end_time_sec, allocation_failure); + record_pause(this_pause, start_time_sec, end_time_sec); if (G1GCPauseTypeHelper::is_last_young_pause(this_pause)) { assert(!G1GCPauseTypeHelper::is_concurrent_start_pause(this_pause), @@ -1389,16 +1389,13 @@ void G1Policy::update_gc_pause_time_ratios(G1GCPauseType gc_type, double start_t void G1Policy::record_pause(G1GCPauseType gc_type, double start, - double end, - bool allocation_failure) { + double end) { // Manage the MMU tracker. For some reason it ignores Full GCs. if (gc_type != G1GCPauseType::FullGC) { _mmu_tracker->add_pause(start, end); } - if (!allocation_failure) { - update_gc_pause_time_ratios(gc_type, start, end); - } + update_gc_pause_time_ratios(gc_type, start, end); update_time_to_mixed_tracking(gc_type, start, end); diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index 72fdc6deb5b..cf12a7a8027 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.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 @@ -275,8 +275,7 @@ private: // Record the given STW pause with the given start and end times (in s). void record_pause(G1GCPauseType gc_type, double start, - double end, - bool allocation_failure = false); + double end); void update_gc_pause_time_ratios(G1GCPauseType gc_type, double start_sec, double end_sec); diff --git a/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.hpp b/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.hpp index 3c1a6ed4667..4dcdd33846e 100644 --- a/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.hpp +++ b/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.hpp @@ -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 @@ -27,6 +27,7 @@ #include "memory/allocation.hpp" #include "oops/oop.hpp" +#include "runtime/atomic.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/pair.hpp" @@ -40,20 +41,23 @@ // * the number of incoming references found during marking. This is an approximate // value because we do not mark through all objects. struct G1RegionMarkStats { - size_t _live_words; - size_t _incoming_refs; + Atomic _live_words; + Atomic _incoming_refs; // Clear all members. void clear() { - _live_words = 0; - _incoming_refs = 0; + _live_words.store_relaxed(0); + _incoming_refs.store_relaxed(0); } // Clear all members after a marking overflow. Only needs to clear the number of // incoming references as all objects will be rescanned, while the live words are // gathered whenever a thread can mark an object, which is synchronized. void clear_during_overflow() { - _incoming_refs = 0; + _incoming_refs.store_relaxed(0); } + + size_t live_words() const { return _live_words.load_relaxed(); } + size_t incoming_refs() const { return _incoming_refs.load_relaxed(); } }; // Per-marking thread cache for the region mark statistics. @@ -112,12 +116,16 @@ public: void add_live_words(oop obj); void add_live_words(uint region_idx, size_t live_words) { G1RegionMarkStatsCacheEntry* const cur = find_for_add(region_idx); - cur->_stats._live_words += live_words; + // This method is only ever called single-threaded, so we do not need atomic + // update here. + cur->_stats._live_words.store_relaxed(cur->_stats.live_words() + live_words); } void inc_incoming_refs(uint region_idx) { G1RegionMarkStatsCacheEntry* const cur = find_for_add(region_idx); - cur->_stats._incoming_refs++; + // This method is only ever called single-threaded, so we do not need atomic + // update here. + cur->_stats._incoming_refs.store_relaxed(cur->_stats.incoming_refs() + 1u); } void reset(uint region_idx) { diff --git a/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.inline.hpp b/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.inline.hpp index 6b0ebb34e0d..71cd33e71ae 100644 --- a/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.inline.hpp +++ b/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.inline.hpp @@ -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 @@ -27,8 +27,6 @@ #include "gc/g1/g1RegionMarkStatsCache.hpp" -#include "runtime/atomicAccess.hpp" - inline G1RegionMarkStatsCache::G1RegionMarkStatsCacheEntry* G1RegionMarkStatsCache::find_for_add(uint region_idx) { uint const cache_idx = hash(region_idx); @@ -46,12 +44,12 @@ inline G1RegionMarkStatsCache::G1RegionMarkStatsCacheEntry* G1RegionMarkStatsCac inline void G1RegionMarkStatsCache::evict(uint idx) { G1RegionMarkStatsCacheEntry* cur = &_cache[idx]; - if (cur->_stats._live_words != 0) { - AtomicAccess::add(&_target[cur->_region_idx]._live_words, cur->_stats._live_words); + if (cur->_stats.live_words() != 0) { + _target[cur->_region_idx]._live_words.add_then_fetch(cur->_stats.live_words()); } - if (cur->_stats._incoming_refs != 0) { - AtomicAccess::add(&_target[cur->_region_idx]._incoming_refs, cur->_stats._incoming_refs); + if (cur->_stats.incoming_refs() != 0) { + _target[cur->_region_idx]._incoming_refs.add_then_fetch(cur->_stats.incoming_refs()); } cur->clear(); diff --git a/src/hotspot/share/gc/shared/workerThread.cpp b/src/hotspot/share/gc/shared/workerThread.cpp index 7a9404a195a..e4831d25d26 100644 --- a/src/hotspot/share/gc/shared/workerThread.cpp +++ b/src/hotspot/share/gc/shared/workerThread.cpp @@ -96,8 +96,22 @@ void WorkerThreads::initialize_workers() { } } +bool WorkerThreads::allow_inject_creation_failure() const { + if (!is_init_completed()) { + // Never allow creation failures during VM init + return false; + } + + if (_created_workers == 0) { + // Never allow creation failures of the first worker, it will cause the VM to exit + return false; + } + + return true; +} + WorkerThread* WorkerThreads::create_worker(uint name_suffix) { - if (is_init_completed() && InjectGCWorkerCreationFailure) { + if (InjectGCWorkerCreationFailure && allow_inject_creation_failure()) { return nullptr; } diff --git a/src/hotspot/share/gc/shared/workerThread.hpp b/src/hotspot/share/gc/shared/workerThread.hpp index a1f7282abe4..003ce8a2959 100644 --- a/src/hotspot/share/gc/shared/workerThread.hpp +++ b/src/hotspot/share/gc/shared/workerThread.hpp @@ -104,6 +104,7 @@ public: WorkerThreads(const char* name, uint max_workers); void initialize_workers(); + bool allow_inject_creation_failure() const; uint max_workers() const { return _max_workers; } uint created_workers() const { return _created_workers; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp index fb8cfb36353..e1139765022 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp @@ -129,6 +129,13 @@ protected: #endif } + inline void update_livedata(size_t live) { + _region_union._live_data = live; +#ifdef ASSERT + _union_tag = is_live_data; +#endif + } + inline ShenandoahHeapRegion* get_region() const { assert(_union_tag != is_uninitialized, "Cannot fetch region from uninitialized RegionData"); return _region; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 029a4dd98fb..f2c6e427ea8 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -89,6 +89,17 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll return false; } + // Between consecutive mixed-evacuation cycles, the live data within each candidate region may change due to + // promotions and old-gen evacuations. Re-sort the candidate regions in order to first evacuate regions that have + // the smallest amount of live data. These are easiest to evacuate with least effort. Doing these first allows + // us to more quickly replenish free memory with empty regions. + for (uint i = _next_old_collection_candidate; i < _last_old_collection_candidate; i++) { + ShenandoahHeapRegion* r = _region_data[i].get_region(); + _region_data[i].update_livedata(r->get_mixed_candidate_live_data_bytes()); + } + QuickSort::sort(_region_data + _next_old_collection_candidate, unprocessed_old_collection_candidates(), + compare_by_live); + _first_pinned_candidate = NOT_FOUND; uint included_old_regions = 0; @@ -414,6 +425,8 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { ShenandoahHeapRegion* r = candidates[i].get_region(); size_t region_garbage = r->garbage(); size_t region_free = r->free(); + + r->capture_mixed_candidate_garbage(); candidates_garbage += region_garbage; unfragmented += region_free; } @@ -456,6 +469,8 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { r->index(), ShenandoahHeapRegion::region_state_to_string(r->state())); const size_t region_garbage = r->garbage(); const size_t region_free = r->free(); + + r->capture_mixed_candidate_garbage(); candidates_garbage += region_garbage; unfragmented += region_free; defrag_count++; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 3cd5cdd2ec3..e794a86e473 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -75,6 +75,7 @@ ShenandoahHeapRegion::ShenandoahHeapRegion(HeapWord* start, size_t index, bool c _plab_allocs(0), _live_data(0), _critical_pins(0), + _mixed_candidate_garbage_words(0), _update_watermark(start), _age(0), #ifdef SHENANDOAH_CENSUS_NOISE @@ -565,6 +566,7 @@ void ShenandoahHeapRegion::recycle_internal() { assert(_recycling.is_set() && is_trash(), "Wrong state"); ShenandoahHeap* heap = ShenandoahHeap::heap(); + _mixed_candidate_garbage_words = 0; set_top(bottom()); clear_live_data(); reset_alloc_metadata(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index cf0dc5476d0..9da2816e2c9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -43,6 +43,7 @@ class ShenandoahHeapRegion { friend class VMStructs; friend class ShenandoahHeapRegionStateConstant; private: + /* Region state is described by a state machine. Transitions are guarded by heap lock, which allows changing the state of several regions atomically. @@ -259,6 +260,8 @@ private: volatile size_t _live_data; volatile size_t _critical_pins; + size_t _mixed_candidate_garbage_words; + HeapWord* volatile _update_watermark; uint _age; @@ -398,6 +401,14 @@ public: // above TAMS. inline size_t get_live_data_words() const; + inline size_t get_mixed_candidate_live_data_bytes() const; + inline size_t get_mixed_candidate_live_data_words() const; + + inline void capture_mixed_candidate_garbage(); + + // Returns garbage by calculating difference between used and get_live_data_words. The value returned is only + // meaningful immediately following completion of marking. If there have been subsequent allocations in this region, + // use a different approach to determine garbage, such as (used() - get_mixed_candidate_live_data_bytes()) inline size_t garbage() const; void print_on(outputStream* st) const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp index b9304ee9daa..be982433885 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp @@ -163,6 +163,23 @@ inline size_t ShenandoahHeapRegion::get_live_data_bytes() const { return get_live_data_words() * HeapWordSize; } +inline size_t ShenandoahHeapRegion::get_mixed_candidate_live_data_bytes() const { + shenandoah_assert_heaplocked_or_safepoint(); + assert(used() >= _mixed_candidate_garbage_words * HeapWordSize, "used must exceed garbage"); + return used() - _mixed_candidate_garbage_words * HeapWordSize; +} + +inline size_t ShenandoahHeapRegion::get_mixed_candidate_live_data_words() const { + shenandoah_assert_heaplocked_or_safepoint(); + assert(used() >= _mixed_candidate_garbage_words * HeapWordSize, "used must exceed garbage"); + return used() / HeapWordSize - _mixed_candidate_garbage_words; +} + +inline void ShenandoahHeapRegion::capture_mixed_candidate_garbage() { + shenandoah_assert_heaplocked_or_safepoint(); + _mixed_candidate_garbage_words = garbage() / HeapWordSize; +} + inline bool ShenandoahHeapRegion::has_live() const { return get_live_data_words() != 0; } diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index d8a30f9b5ee..885484020bd 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -66,10 +66,6 @@ #include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" #include "utilities/debug.hpp" -#ifdef LINUX -#include "os_linux.hpp" -#include "osContainer_linux.hpp" -#endif #define NO_TRANSITION(result_type, header) extern "C" { result_type JNICALL header { #define NO_TRANSITION_END } } @@ -400,35 +396,18 @@ JVM_ENTRY_NO_ENV(jboolean, jfr_is_class_instrumented(JNIEnv* env, jclass jvm, jc JVM_END JVM_ENTRY_NO_ENV(jboolean, jfr_is_containerized(JNIEnv* env, jclass jvm)) -#ifdef LINUX - return OSContainer::is_containerized(); -#else - return false; -#endif + return os::is_containerized(); JVM_END JVM_ENTRY_NO_ENV(jlong, jfr_host_total_memory(JNIEnv* env, jclass jvm)) -#ifdef LINUX - // We want the host memory, not the container limit. - // os::physical_memory() would return the container limit. - return static_cast(os::Linux::physical_memory()); -#else - return static_cast(os::physical_memory()); -#endif + return static_cast(os::Machine::physical_memory()); JVM_END JVM_ENTRY_NO_ENV(jlong, jfr_host_total_swap_memory(JNIEnv* env, jclass jvm)) -#ifdef LINUX - // We want the host swap memory, not the container value. - physical_memory_size_type host_swap = 0; - (void)os::Linux::host_swap(host_swap); // Discard return value and treat as no swap - return static_cast(host_swap); -#else physical_memory_size_type total_swap_space = 0; // Return value ignored - defaulting to 0 on failure. - (void)os::total_swap_space(total_swap_space); + (void)os::Machine::total_swap_space(total_swap_space); return static_cast(total_swap_space); -#endif JVM_END JVM_ENTRY_NO_ENV(void, jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes)) diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 71ae8fe44a7..bc8ebaf1869 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -1489,8 +1489,7 @@ Node* GraphKit::must_be_not_null(Node* value, bool do_replace_in_map) { } Node *if_f = _gvn.transform(new IfFalseNode(iff)); Node *frame = _gvn.transform(new ParmNode(C->start(), TypeFunc::FramePtr)); - Node* halt = _gvn.transform(new HaltNode(if_f, frame, "unexpected null in intrinsic")); - C->root()->add_req(halt); + halt(if_f, frame, "unexpected null in intrinsic"); Node *if_t = _gvn.transform(new IfTrueNode(iff)); set_control(if_t); return cast_not_null(value, do_replace_in_map); @@ -2073,6 +2072,12 @@ void GraphKit::increment_counter(Node* counter_addr) { store_to_memory(ctrl, counter_addr, incr, T_LONG, MemNode::unordered); } +void GraphKit::halt(Node* ctrl, Node* frameptr, const char* reason, bool generate_code_in_product) { + Node* halt = new HaltNode(ctrl, frameptr, reason + PRODUCT_ONLY(COMMA generate_code_in_product)); + halt = _gvn.transform(halt); + root()->add_req(halt); +} //------------------------------uncommon_trap---------------------------------- // Bail out to the interpreter in mid-method. Implemented by calling the @@ -2195,11 +2200,15 @@ Node* GraphKit::uncommon_trap(int trap_request, // The debug info is the only real input to this call. // Halt-and-catch fire here. The above call should never return! - HaltNode* halt = new HaltNode(control(), frameptr(), "uncommon trap returned which should never happen" - PRODUCT_ONLY(COMMA /*reachable*/false)); - _gvn.set_type_bottom(halt); - root()->add_req(halt); - + // We only emit code for the HaltNode in debug, which is enough for + // verifying correctness. In product, we don't want to emit it so + // that we can save on code space. HaltNode often get folded because + // the compiler can prove that the unreachable path is dead. But we + // cannot generally expect that for uncommon traps, which are often + // reachable and occasionally taken. + halt(control(), frameptr(), + "uncommon trap returned which should never happen", + false /* don't emit code in product */); stop_and_kill_map(); return call; } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 56e9a949e6f..0537d31ae36 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -709,6 +709,8 @@ class GraphKit : public Phase { void increment_counter(address counter_addr); // increment a debug counter void increment_counter(Node* counter_addr); // increment a debug counter + void halt(Node* ctrl, Node* frameptr, const char* reason, bool generate_code_in_product = true); + // Bail out to the interpreter right now // The optional klass is the one causing the trap. // The optional reason is debug information written to the compile log. diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index a071cff9e3c..9579af84f24 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.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 @@ -327,6 +327,8 @@ public: // e.g. Op_ vector nodes and other intrinsics while guarding with vlen static bool match_rule_supported_vector(int opcode, int vlen, BasicType bt); + // Returns true if the platform efficiently implements the given masked vector + // operation using predicate features, false otherwise. static bool match_rule_supported_vector_masked(int opcode, int vlen, BasicType bt); // Determines if a vector operation needs to be partially implemented with a mask diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index f1d9785a746..5ddc4236b3e 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.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. * Copyright (c) 2024, 2025, Alibaba Group Holding Limited. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -845,7 +845,8 @@ public: Flag_has_swapped_edges = 1ULL << 11, Flag_is_scheduled = 1ULL << 12, Flag_is_expensive = 1ULL << 13, - Flag_is_predicated_vector = 1ULL << 14, + Flag_is_predicated_vector = 1ULL << 14, // Marked on a vector node that has an additional + // mask input controlling the lane operations. Flag_for_post_loop_opts_igvn = 1ULL << 15, Flag_for_merge_stores_igvn = 1ULL << 16, Flag_is_removed_by_peephole = 1ULL << 17, diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 7aa96d2ace3..6122f7e7bfc 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -1229,8 +1229,7 @@ void Parse::do_method_entry() { Node* not_subtype_ctrl = gen_subtype_check(receiver_obj, holder_klass); assert(!stopped(), "not a subtype"); - Node* halt = _gvn.transform(new HaltNode(not_subtype_ctrl, frameptr(), "failed receiver subtype check")); - C->root()->add_req(halt); + halt(not_subtype_ctrl, frameptr(), "failed receiver subtype check"); } } #endif // ASSERT diff --git a/src/hotspot/share/opto/rangeinference.hpp b/src/hotspot/share/opto/rangeinference.hpp index ebfd98ca4a6..66ea741a2da 100644 --- a/src/hotspot/share/opto/rangeinference.hpp +++ b/src/hotspot/share/opto/rangeinference.hpp @@ -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 @@ -147,7 +147,7 @@ public: static const Type* int_type_xmeet(const CT* i1, const Type* t2); template - static CTP int_type_union(CTP t1, CTP t2) { + static auto int_type_union(CTP t1, CTP t2) { using CT = std::conditional_t, std::remove_pointer_t, CTP>; using S = std::remove_const_t; using U = std::remove_const_t; @@ -209,7 +209,7 @@ public: KnownBits _bits; int _widen = 0; // dummy field to mimic the same field in TypeInt, useful in testing - static TypeIntMirror make(const TypeIntPrototype& t, int widen) { + static TypeIntMirror make(const TypeIntPrototype& t, int widen = 0) { auto canonicalized_t = t.canonicalize_constraints(); assert(!canonicalized_t.empty(), "must not be empty"); return TypeIntMirror{canonicalized_t._data._srange._lo, canonicalized_t._data._srange._hi, @@ -217,11 +217,15 @@ public: canonicalized_t._data._bits}; } + TypeIntMirror meet(const TypeIntMirror& o) const { + return TypeIntHelper::int_type_union(this, &o); + } + // These allow TypeIntMirror to mimick the behaviors of TypeInt* and TypeLong*, so they can be // passed into RangeInference methods. These are only used in testing, so they are implemented in // the test file. + static TypeIntMirror make(const TypeIntMirror& t, int widen); const TypeIntMirror* operator->() const; - TypeIntMirror meet(const TypeIntMirror& o) const; bool contains(U u) const; bool contains(const TypeIntMirror& o) const; bool operator==(const TypeIntMirror& o) const; @@ -322,7 +326,7 @@ private: // Infer a result given the input types of a binary operation template static CTP infer_binary(CTP t1, CTP t2, Inference infer) { - CTP res; + TypeIntMirror, U> res; bool is_init = false; SimpleIntervalIterable t1_simple_intervals(t1); @@ -330,10 +334,10 @@ private: for (auto& st1 : t1_simple_intervals) { for (auto& st2 : t2_simple_intervals) { - CTP current = infer(st1, st2); + TypeIntMirror, U> current = infer(st1, st2); if (is_init) { - res = res->meet(current)->template cast>(); + res = res.meet(current); } else { is_init = true; res = current; @@ -342,7 +346,22 @@ private: } assert(is_init, "must be initialized"); - return res; + // It is important that widen is computed on the whole result instead of during each step. This + // is because we normalize the widen of small Type instances to 0, so computing the widen value + // for each step and taking the union of them may return a widen value that conflicts with + // other computations, trigerring the monotonicity assert during CCP. + // + // For example, let us consider the operation r = x ^ y: + // - During the first step of CCP, type(x) = {0}, type(y) = [-2, 2], w = 3. + // Since x is a constant that is the identity element of the xor operation, type(r) = type(y) = [-2, 2], w = 3 + // - During the second step, type(x) is widened to [0, 2], w = 0. + // We then compute the range for: + // r1 = x ^ y1, type(x) = [0, 2], w = 0, type(y1) = [0, 2], w = 0 + // r2 = x ^ y2, type(x) = [0, 2], w = 0, type(y2) = [-2, -1], w = 0 + // This results in type(r1) = [0, 3], w = 0, and type(r2) = [-4, -1], w = 0 + // So the union of type(r1) and type(r2) is [-4, 3], w = 0. This widen value is smaller than + // that of the previous step, triggering the monotonicity assert. + return CT::make(res, MAX2(t1->_widen, t2->_widen)); } public: @@ -357,7 +376,7 @@ public: U uhi = MIN2(st1._uhi, st2._uhi); U zeros = st1._bits._zeros | st2._bits._zeros; U ones = st1._bits._ones & st2._bits._ones; - return CT::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}, MAX2(t1->_widen, t2->_widen)); + return TypeIntMirror, U>::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}); }); } @@ -372,7 +391,7 @@ public: U uhi = std::numeric_limits>::max(); U zeros = st1._bits._zeros & st2._bits._zeros; U ones = st1._bits._ones | st2._bits._ones; - return CT::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}, MAX2(t1->_widen, t2->_widen)); + return TypeIntMirror, U>::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}); }); } @@ -385,7 +404,7 @@ public: U uhi = std::numeric_limits>::max(); U zeros = (st1._bits._zeros & st2._bits._zeros) | (st1._bits._ones & st2._bits._ones); U ones = (st1._bits._zeros & st2._bits._ones) | (st1._bits._ones & st2._bits._zeros); - return CT::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}, MAX2(t1->_widen, t2->_widen)); + return TypeIntMirror, U>::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}); }); } }; diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index ecb8c2c1cd8..d3271e79f2f 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.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 @@ -2443,6 +2443,12 @@ const TypeVect* TypeVect::make(BasicType elem_bt, uint length, bool is_mask) { return nullptr; } +// Create a vector mask type with the given element basic type and length. +// - Returns "TypeVectMask" (PVectMask) for platforms that support the predicate +// feature and it is implemented properly in the backend, allowing the mask to +// be stored in a predicate/mask register. +// - Returns a normal vector type "TypeVectA ~ TypeVectZ" (NVectMask) otherwise, +// where the vector mask is stored in a vector register. const TypeVect* TypeVect::makemask(BasicType elem_bt, uint length) { if (Matcher::has_predicated_vectors() && Matcher::match_rule_supported_vector_masked(Op_VectorLoadMask, length, elem_bt)) { diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 73e2ba0045a..7c7ff035a54 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.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 @@ -799,6 +799,9 @@ public: static const TypeInt* make(jint lo, jint hi, int widen); static const Type* make_or_top(const TypeIntPrototype& t, int widen); static const TypeInt* make(const TypeIntPrototype& t, int widen) { return make_or_top(t, widen)->is_int(); } + static const TypeInt* make(const TypeIntMirror& t, int widen) { + return (new TypeInt(TypeIntPrototype{{t._lo, t._hi}, {t._ulo, t._uhi}, t._bits}, widen, false))->hashcons()->is_int(); + } // Check for single integer bool is_con() const { return _lo == _hi; } @@ -881,6 +884,9 @@ public: static const TypeLong* make(jlong lo, jlong hi, int widen); static const Type* make_or_top(const TypeIntPrototype& t, int widen); static const TypeLong* make(const TypeIntPrototype& t, int widen) { return make_or_top(t, widen)->is_long(); } + static const TypeLong* make(const TypeIntMirror& t, int widen) { + return (new TypeLong(TypeIntPrototype{{t._lo, t._hi}, {t._ulo, t._uhi}, t._bits}, widen, false))->hashcons()->is_long(); + } // Check for single integer bool is_con() const { return _lo == _hi; } @@ -1012,7 +1018,7 @@ public: }; //------------------------------TypeVect--------------------------------------- -// Class of Vector Types +// Basic class of vector (mask) types. class TypeVect : public Type { const BasicType _elem_bt; // Vector's element type const uint _length; // Elements in vector (power of 2) @@ -1052,6 +1058,16 @@ public: #endif }; +// TypeVect subclasses representing vectors or vector masks with "BVectMask" or "NVectMask" +// layout (see vectornode.hpp for detailed notes on vector mask representations), mapped +// to vector registers and distinguished by vector register size: +// +// - TypeVectA: Scalable vector type (variable size, e.g., AArch64 SVE, RISC-V RVV) +// - TypeVectS: 32-bit vector type +// - TypeVectD: 64-bit vector type +// - TypeVectX: 128-bit vector type +// - TypeVectY: 256-bit vector type +// - TypeVectZ: 512-bit vector type class TypeVectA : public TypeVect { friend class TypeVect; TypeVectA(BasicType elem_bt, uint length) : TypeVect(VectorA, elem_bt, length) {} @@ -1082,6 +1098,9 @@ class TypeVectZ : public TypeVect { TypeVectZ(BasicType elem_bt, uint length) : TypeVect(VectorZ, elem_bt, length) {} }; +// Class of TypeVectMask, representing vector masks with "PVectMask" layout (see +// vectornode.hpp for detailed notes on vector mask representations), mapped to +// dedicated hardware predicate/mask registers. class TypeVectMask : public TypeVect { public: friend class TypeVect; diff --git a/src/hotspot/share/opto/vectorIntrinsics.cpp b/src/hotspot/share/opto/vectorIntrinsics.cpp index b48b5f2cd05..6dcf4615b10 100644 --- a/src/hotspot/share/opto/vectorIntrinsics.cpp +++ b/src/hotspot/share/opto/vectorIntrinsics.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include "ci/ciSymbols.hpp" #include "classfile/vmSymbols.hpp" #include "opto/library_call.hpp" +#include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "opto/vectornode.hpp" #include "prims/vectorSupport.hpp" @@ -2330,6 +2331,21 @@ bool LibraryCallKit::inline_vector_convert() { Node* op = opd1; if (is_cast) { assert(!is_mask || num_elem_from == num_elem_to, "vector mask cast needs the same elem num"); + + // Make sure the precondition of VectorCastNode::opcode holds: we can only have + // unsigned casts for integral types (excluding long). VectorAPI code is not + // expected to violate this at runtime, but we may compile unreachable code + // where such impossible combinations arise. + if (is_ucast && (!is_integral_type(elem_bt_from) || elem_bt_from == T_LONG)) { + // Halt-and-catch fire here. This condition should never happen at runtime. + stringStream ss; + ss.print("impossible combination: unsigned vector cast from %s", type2name(elem_bt_from)); + halt(control(), frameptr(), ss.as_string(C->comp_arena())); + stop_and_kill_map(); + log_if_needed(" ** impossible combination: unsigned cast from %s", type2name(elem_bt_from)); + return true; + } + int cast_vopc = VectorCastNode::opcode(-1, elem_bt_from, !is_ucast); // Make sure that vector cast is implemented to particular type/size combination if it is diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index dc7aa13cf36..b1976d8f0db 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -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 @@ -33,7 +33,63 @@ #include "opto/opcodes.hpp" #include "prims/vectorSupport.hpp" -//------------------------------VectorNode------------------------------------- +//=======================Notes-for-VectorMask-Representation======================= +// +// There are three distinct representations for vector masks based on platform and +// use scenarios: +// +// - BVectMask: Platform-independent mask stored in a vector register with 8-bit +// lanes containing 1/0 values. The corresponding type is TypeVectA ~ TypeVectZ. +// +// - NVectMask: Platform-specific mask stored in vector registers with N-bit lanes, +// where all bits in each lane are either set (true) or unset (false). Generated +// on architectures without predicate/mask feature, such as AArch64 NEON, x86 +// AVX2, etc. The corresponding type is TypeVectA ~ TypeVectZ. +// +// - PVectMask: Platform-specific mask stored in predicate/mask registers. Generated +// on architectures with predicate/mask feature, such as AArch64 SVE, x86 AVX-512, +// and RISC-V Vector Extension (RVV). The corresponding type is TypeVectMask. +// +// NVectMask and PVectMask encode element data type and vector length information. +// They are the primary mask representations used in most mask and masked vector +// operations. BVectMask primarily represents mask values loaded from or stored to +// Java boolean memory (mask backing storage). VectorLoadMask/VectorStoreMask nodes +// are needed to transform it to/from P/NVectMask. It is also used in certain mask +// operations (e.g. VectorMaskOpNode). +// +//=========================Notes-for-Masked-Vector-Nodes=========================== +// +// Each lane-wise and cross-lane (reduction) ALU node supports both non-masked +// and masked operations. +// +// Currently masked vector nodes are only used to implement the Vector API's masked +// operations (which might also be used for auto-vectorization in future), such as: +// Vector lanewise(VectorOperators.Binary op, Vector v, VectorMask m) +// +// They are generated during intrinsification for Vector API, only on architectures +// that support the relevant predicated instructions. The compiler uses +// "Matcher::match_rule_supported_vector_masked()" to check whether the current +// platform supports the predicated/masked vector instructions for an operation. It +// generates the masked vector node for the operation if supported. Otherwise, it +// generates the unpredicated vector node and implements the masked operation with +// the help of a VectorBlendNode. Please see more details from API intrinsification +// in vectorIntrinsics.cpp. +// +// To differentiate the masked and non-masked nodes, flag "Flag_is_predicated_vector" +// is set for the masked version. Meanwhile, there is an additional mask input for +// the masked nodes. +// +// For example: +// - Non-masked version: +// in1 in2 +// \ / +// AddVBNode +// +// - Masked version (with "Flag_is_predicated_vector" being set): +// in1 in2 mask +// \ | / +// AddVBNode + // Vector Operation class VectorNode : public TypeNode { public: @@ -103,6 +159,8 @@ class VectorNode : public TypeNode { static bool implemented(int opc, uint vlen, BasicType bt); static bool is_shift(Node* n); static bool is_vshift_cnt(Node* n); + // Returns true if the lower vlen bits (bits [0, vlen-1]) of the long value + // are all 1s or all 0s, indicating a "mask all" or "mask none" pattern. static bool is_maskall_type(const TypeLong* type, int vlen); static bool is_muladds2i(const Node* n); static bool is_roundopD(Node* n); @@ -176,7 +234,6 @@ class SaturatingVectorNode : public VectorNode { bool is_unsigned() { return _is_unsigned; } }; -//------------------------------AddVBNode-------------------------------------- // Vector add byte class AddVBNode : public VectorNode { public: @@ -184,7 +241,6 @@ class AddVBNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AddVSNode-------------------------------------- // Vector add char/short class AddVSNode : public VectorNode { public: @@ -192,7 +248,6 @@ class AddVSNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AddVINode-------------------------------------- // Vector add int class AddVINode : public VectorNode { public: @@ -200,7 +255,6 @@ class AddVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AddVLNode-------------------------------------- // Vector add long class AddVLNode : public VectorNode { public: @@ -208,7 +262,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddVHFNode-------------------------------------- // Vector add float class AddVHFNode : public VectorNode { public: @@ -216,7 +269,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddVFNode-------------------------------------- // Vector add float class AddVFNode : public VectorNode { public: @@ -224,7 +276,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddVDNode-------------------------------------- // Vector add double class AddVDNode : public VectorNode { public: @@ -232,7 +283,6 @@ public: virtual int Opcode() const; }; -//------------------------------ReductionNode------------------------------------ // Perform reduction of a vector class ReductionNode : public Node { private: @@ -294,7 +344,6 @@ class ReductionNode : public Node { #endif }; -//------------------------------AddReductionVINode-------------------------------------- // Vector add byte, short and int as a reduction class AddReductionVINode : public ReductionNode { public: @@ -302,7 +351,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddReductionVLNode-------------------------------------- // Vector add long as a reduction class AddReductionVLNode : public ReductionNode { public: @@ -310,7 +358,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddReductionVFNode-------------------------------------- // Vector add float as a reduction class AddReductionVFNode : public ReductionNode { private: @@ -337,7 +384,6 @@ public: virtual uint size_of() const { return sizeof(*this); } }; -//------------------------------AddReductionVDNode-------------------------------------- // Vector add double as a reduction class AddReductionVDNode : public ReductionNode { private: @@ -364,7 +410,6 @@ public: virtual uint size_of() const { return sizeof(*this); } }; -//------------------------------SubVBNode-------------------------------------- // Vector subtract byte class SubVBNode : public VectorNode { public: @@ -372,7 +417,6 @@ class SubVBNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SubVSNode-------------------------------------- // Vector subtract short class SubVSNode : public VectorNode { public: @@ -380,7 +424,6 @@ class SubVSNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SubVINode-------------------------------------- // Vector subtract int class SubVINode : public VectorNode { public: @@ -388,7 +431,6 @@ class SubVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SubVLNode-------------------------------------- // Vector subtract long class SubVLNode : public VectorNode { public: @@ -396,7 +438,6 @@ class SubVLNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SaturatingAddVNode----------------------------- // Vector saturating addition. class SaturatingAddVNode : public SaturatingVectorNode { public: @@ -404,7 +445,6 @@ class SaturatingAddVNode : public SaturatingVectorNode { virtual int Opcode() const; }; -//------------------------------SaturatingSubVNode----------------------------- // Vector saturating subtraction. class SaturatingSubVNode : public SaturatingVectorNode { public: @@ -412,7 +452,6 @@ class SaturatingSubVNode : public SaturatingVectorNode { virtual int Opcode() const; }; -//------------------------------SubVHFNode-------------------------------------- // Vector subtract half float class SubVHFNode : public VectorNode { public: @@ -420,8 +459,6 @@ public: virtual int Opcode() const; }; - -//------------------------------SubVFNode-------------------------------------- // Vector subtract float class SubVFNode : public VectorNode { public: @@ -429,7 +466,6 @@ class SubVFNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SubVDNode-------------------------------------- // Vector subtract double class SubVDNode : public VectorNode { public: @@ -437,7 +473,6 @@ class SubVDNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------MulVBNode-------------------------------------- // Vector multiply byte class MulVBNode : public VectorNode { public: @@ -445,7 +480,6 @@ class MulVBNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------MulVSNode-------------------------------------- // Vector multiply short class MulVSNode : public VectorNode { public: @@ -453,7 +487,6 @@ class MulVSNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------MulVINode-------------------------------------- // Vector multiply int class MulVINode : public VectorNode { public: @@ -461,7 +494,6 @@ class MulVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------MulVLNode-------------------------------------- // Vector multiply long class MulVLNode : public VectorNode { public: @@ -473,7 +505,6 @@ public: bool has_uint_inputs() const; }; -//------------------------------MulVFNode-------------------------------------- // Vector multiply half float class MulVHFNode : public VectorNode { public: @@ -481,7 +512,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulVFNode-------------------------------------- // Vector multiply float class MulVFNode : public VectorNode { public: @@ -489,7 +519,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulVDNode-------------------------------------- // Vector multiply double class MulVDNode : public VectorNode { public: @@ -497,7 +526,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulAddVS2VINode-------------------------------- // Vector multiply shorts to int and add adjacent ints. class MulAddVS2VINode : public VectorNode { public: @@ -505,7 +533,6 @@ class MulAddVS2VINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------FmaVNode-------------------------------------- // Vector fused-multiply-add class FmaVNode : public VectorNode { public: @@ -515,7 +542,6 @@ public: virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; -//------------------------------FmaVDNode-------------------------------------- // Vector fused-multiply-add double class FmaVDNode : public FmaVNode { public: @@ -523,7 +549,6 @@ public: virtual int Opcode() const; }; -//------------------------------FmaVFNode-------------------------------------- // Vector fused-multiply-add float class FmaVFNode : public FmaVNode { public: @@ -531,7 +556,6 @@ public: virtual int Opcode() const; }; -//------------------------------FmaVHFNode------------------------------------- // Vector fused-multiply-add half-precision float class FmaVHFNode : public FmaVNode { public: @@ -539,7 +563,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulReductionVINode-------------------------------------- // Vector multiply byte, short and int as a reduction class MulReductionVINode : public ReductionNode { public: @@ -547,7 +570,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulReductionVLNode-------------------------------------- // Vector multiply int as a reduction class MulReductionVLNode : public ReductionNode { public: @@ -555,7 +577,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulReductionVFNode-------------------------------------- // Vector multiply float as a reduction class MulReductionVFNode : public ReductionNode { // True if mul reduction operation for floats requires strict ordering. @@ -581,7 +602,6 @@ public: virtual uint size_of() const { return sizeof(*this); } }; -//------------------------------MulReductionVDNode-------------------------------------- // Vector multiply double as a reduction class MulReductionVDNode : public ReductionNode { // True if mul reduction operation for doubles requires strict ordering. @@ -607,7 +627,6 @@ public: virtual uint size_of() const { return sizeof(*this); } }; -//------------------------------DivVHFNode------------------------------------- // Vector divide half float class DivVHFNode : public VectorNode { public: @@ -615,7 +634,6 @@ public: virtual int Opcode() const; }; -//------------------------------DivVFNode-------------------------------------- // Vector divide float class DivVFNode : public VectorNode { public: @@ -623,7 +641,6 @@ class DivVFNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------DivVDNode-------------------------------------- // Vector Divide double class DivVDNode : public VectorNode { public: @@ -631,7 +648,6 @@ class DivVDNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AbsVBNode-------------------------------------- // Vector Abs byte class AbsVBNode : public VectorNode { public: @@ -639,7 +655,6 @@ public: virtual int Opcode() const; }; -//------------------------------AbsVSNode-------------------------------------- // Vector Abs short class AbsVSNode : public VectorNode { public: @@ -647,7 +662,6 @@ public: virtual int Opcode() const; }; -//------------------------------MinVNode-------------------------------------- // Vector Min class MinVNode : public VectorNode { public: @@ -655,7 +669,6 @@ public: virtual int Opcode() const; }; -//------------------------------MinVHFNode------------------------------------ // Vector Min for half floats class MinVHFNode : public VectorNode { public: @@ -663,7 +676,6 @@ public: virtual int Opcode() const; }; -//------------------------------MaxVHFNode------------------------------------ // Vector Max for half floats class MaxVHFNode : public VectorNode { public: @@ -671,6 +683,7 @@ public: virtual int Opcode() const; }; +// Vector Unsigned Min class UMinVNode : public VectorNode { public: UMinVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2 ,vt) { @@ -681,8 +694,6 @@ class UMinVNode : public VectorNode { virtual int Opcode() const; }; - -//------------------------------MaxVNode-------------------------------------- // Vector Max class MaxVNode : public VectorNode { public: @@ -690,6 +701,7 @@ class MaxVNode : public VectorNode { virtual int Opcode() const; }; +// Vector Unsigned Max class UMaxVNode : public VectorNode { public: UMaxVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) { @@ -700,7 +712,6 @@ class UMaxVNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AbsVINode-------------------------------------- // Vector Abs int class AbsVINode : public VectorNode { public: @@ -708,7 +719,6 @@ class AbsVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AbsVLNode-------------------------------------- // Vector Abs long class AbsVLNode : public VectorNode { public: @@ -716,7 +726,6 @@ public: virtual int Opcode() const; }; -//------------------------------AbsVFNode-------------------------------------- // Vector Abs float class AbsVFNode : public VectorNode { public: @@ -724,7 +733,6 @@ class AbsVFNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AbsVDNode-------------------------------------- // Vector Abs double class AbsVDNode : public VectorNode { public: @@ -732,7 +740,6 @@ class AbsVDNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------NegVNode--------------------------------------- // Vector Neg parent class (not for code generation). class NegVNode : public VectorNode { public: @@ -746,7 +753,6 @@ class NegVNode : public VectorNode { Node* degenerate_integral_negate(PhaseGVN* phase, bool is_predicated); }; -//------------------------------NegVINode-------------------------------------- // Vector Neg byte/short/int class NegVINode : public NegVNode { public: @@ -754,7 +760,6 @@ class NegVINode : public NegVNode { virtual int Opcode() const; }; -//------------------------------NegVLNode-------------------------------------- // Vector Neg long class NegVLNode : public NegVNode { public: @@ -762,7 +767,6 @@ class NegVLNode : public NegVNode { virtual int Opcode() const; }; -//------------------------------NegVFNode-------------------------------------- // Vector Neg float class NegVFNode : public NegVNode { public: @@ -770,7 +774,6 @@ class NegVFNode : public NegVNode { virtual int Opcode() const; }; -//------------------------------NegVDNode-------------------------------------- // Vector Neg double class NegVDNode : public NegVNode { public: @@ -778,7 +781,6 @@ class NegVDNode : public NegVNode { virtual int Opcode() const; }; -//------------------------------PopCountVINode--------------------------------- // Vector popcount integer bits class PopCountVINode : public VectorNode { public: @@ -786,7 +788,6 @@ class PopCountVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------PopCountVLNode--------------------------------- // Vector popcount long bits class PopCountVLNode : public VectorNode { public: @@ -796,7 +797,6 @@ class PopCountVLNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SqrtVHFNode------------------------------------- // Vector Sqrt half-precision float class SqrtVHFNode : public VectorNode { public: @@ -804,14 +804,13 @@ public: virtual int Opcode() const; }; -//------------------------------SqrtVFNode-------------------------------------- // Vector Sqrt float class SqrtVFNode : public VectorNode { public: SqrtVFNode(Node* in, const TypeVect* vt) : VectorNode(in,vt) {} virtual int Opcode() const; }; -//------------------------------RoundDoubleVNode-------------------------------- + // Vector round double class RoundDoubleModeVNode : public VectorNode { public: @@ -819,7 +818,6 @@ class RoundDoubleModeVNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SqrtVDNode-------------------------------------- // Vector Sqrt double class SqrtVDNode : public VectorNode { public: @@ -827,8 +825,7 @@ class SqrtVDNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------ShiftVNode----------------------------------- -// Class ShiftV functionality. This covers the common behaviors for all kinds +// Class ShiftV functionality. This covers the common behaviors for all kinds // of vector shifts. class ShiftVNode : public VectorNode { private: @@ -848,7 +845,6 @@ class ShiftVNode : public VectorNode { virtual uint size_of() const { return sizeof(ShiftVNode); } }; -//------------------------------LShiftVBNode----------------------------------- // Vector left shift bytes class LShiftVBNode : public ShiftVNode { public: @@ -857,7 +853,6 @@ class LShiftVBNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------LShiftVSNode----------------------------------- // Vector left shift shorts class LShiftVSNode : public ShiftVNode { public: @@ -866,7 +861,6 @@ class LShiftVSNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------LShiftVINode----------------------------------- // Vector left shift ints class LShiftVINode : public ShiftVNode { public: @@ -875,7 +869,6 @@ class LShiftVINode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------LShiftVLNode----------------------------------- // Vector left shift longs class LShiftVLNode : public ShiftVNode { public: @@ -884,7 +877,6 @@ class LShiftVLNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------RShiftVBNode----------------------------------- // Vector right arithmetic (signed) shift bytes class RShiftVBNode : public ShiftVNode { public: @@ -893,7 +885,6 @@ class RShiftVBNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------RShiftVSNode----------------------------------- // Vector right arithmetic (signed) shift shorts class RShiftVSNode : public ShiftVNode { public: @@ -902,7 +893,6 @@ class RShiftVSNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------RShiftVINode----------------------------------- // Vector right arithmetic (signed) shift ints class RShiftVINode : public ShiftVNode { public: @@ -911,7 +901,6 @@ class RShiftVINode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------RShiftVLNode----------------------------------- // Vector right arithmetic (signed) shift longs class RShiftVLNode : public ShiftVNode { public: @@ -920,7 +909,6 @@ class RShiftVLNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------URShiftVBNode---------------------------------- // Vector right logical (unsigned) shift bytes class URShiftVBNode : public ShiftVNode { public: @@ -929,7 +917,6 @@ class URShiftVBNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------URShiftVSNode---------------------------------- // Vector right logical (unsigned) shift shorts class URShiftVSNode : public ShiftVNode { public: @@ -938,7 +925,6 @@ class URShiftVSNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------URShiftVINode---------------------------------- // Vector right logical (unsigned) shift ints class URShiftVINode : public ShiftVNode { public: @@ -947,7 +933,6 @@ class URShiftVINode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------URShiftVLNode---------------------------------- // Vector right logical (unsigned) shift longs class URShiftVLNode : public ShiftVNode { public: @@ -956,7 +941,6 @@ class URShiftVLNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------LShiftCntVNode--------------------------------- // Vector left shift count class LShiftCntVNode : public VectorNode { public: @@ -964,7 +948,6 @@ class LShiftCntVNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------RShiftCntVNode--------------------------------- // Vector right shift count class RShiftCntVNode : public VectorNode { public: @@ -972,7 +955,6 @@ class RShiftCntVNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AndVNode--------------------------------------- // Vector and integer class AndVNode : public VectorNode { public: @@ -981,7 +963,6 @@ class AndVNode : public VectorNode { virtual Node* Identity(PhaseGVN* phase); }; -//------------------------------AndReductionVNode-------------------------------------- // Vector and byte, short, int, long as a reduction class AndReductionVNode : public ReductionNode { public: @@ -989,8 +970,7 @@ class AndReductionVNode : public ReductionNode { virtual int Opcode() const; }; -//------------------------------OrVNode--------------------------------------- -// Vector or byte, short, int, long as a reduction +// Vector or integer class OrVNode : public VectorNode { public: OrVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1,in2,vt) {} @@ -998,15 +978,13 @@ class OrVNode : public VectorNode { virtual Node* Identity(PhaseGVN* phase); }; -//------------------------------OrReductionVNode-------------------------------------- -// Vector xor byte, short, int, long as a reduction +// Vector or byte, short, int, long as a reduction class OrReductionVNode : public ReductionNode { public: OrReductionVNode(Node* ctrl, Node* in1, Node* in2) : ReductionNode(ctrl, in1, in2) {} virtual int Opcode() const; }; -//------------------------------XorVNode--------------------------------------- // Vector xor integer class XorVNode : public VectorNode { public: @@ -1016,15 +994,13 @@ class XorVNode : public VectorNode { Node* Ideal_XorV_VectorMaskCmp(PhaseGVN* phase, bool can_reshape); }; -//------------------------------XorReductionVNode-------------------------------------- -// Vector and int, long as a reduction +// Vector xor byte, short, int, long as a reduction class XorReductionVNode : public ReductionNode { public: XorReductionVNode(Node* ctrl, Node* in1, Node* in2) : ReductionNode(ctrl, in1, in2) {} virtual int Opcode() const; }; -//------------------------------MinReductionVNode-------------------------------------- // Vector min byte, short, int, long, float, double as a reduction class MinReductionVNode : public ReductionNode { public: @@ -1032,15 +1008,13 @@ public: virtual int Opcode() const; }; -//------------------------------MaxReductionVNode-------------------------------------- -// Vector min byte, short, int, long, float, double as a reduction +// Vector max byte, short, int, long, float, double as a reduction class MaxReductionVNode : public ReductionNode { public: MaxReductionVNode(Node* ctrl, Node* in1, Node* in2) : ReductionNode(ctrl, in1, in2) {} virtual int Opcode() const; }; -//------------------------------CompressVNode-------------------------------------- // Vector compress class CompressVNode: public VectorNode { public: @@ -1051,6 +1025,7 @@ class CompressVNode: public VectorNode { virtual int Opcode() const; }; +// Vector mask compress class CompressMNode: public VectorNode { public: CompressMNode(Node* mask, const TypeVect* vt) : @@ -1060,7 +1035,6 @@ class CompressMNode: public VectorNode { virtual int Opcode() const; }; -//------------------------------ExpandVNode-------------------------------------- // Vector expand class ExpandVNode: public VectorNode { public: @@ -1073,7 +1047,6 @@ class ExpandVNode: public VectorNode { //================================= M E M O R Y =============================== -//------------------------------LoadVectorNode--------------------------------- // Load Vector from memory class LoadVectorNode : public LoadNode { private: @@ -1115,7 +1088,6 @@ class LoadVectorNode : public LoadNode { #endif }; -//------------------------------LoadVectorGatherNode------------------------------ // Load Vector from memory via index map class LoadVectorGatherNode : public LoadVectorNode { public: @@ -1138,7 +1110,6 @@ class LoadVectorGatherNode : public LoadVectorNode { } }; -//------------------------------StoreVectorNode-------------------------------- // Store Vector to memory class StoreVectorNode : public StoreNode { private: @@ -1180,9 +1151,7 @@ class StoreVectorNode : public StoreNode { #endif }; -//------------------------------StoreVectorScatterNode------------------------------ // Store Vector into memory via index map - class StoreVectorScatterNode : public StoreVectorNode { public: enum { Indices = 4 }; @@ -1200,7 +1169,6 @@ class StoreVectorNode : public StoreNode { virtual Node* indices() const { return in(Indices); } }; -//------------------------------StoreVectorMaskedNode-------------------------------- // Store Vector to memory under the influence of a predicate register(mask). class StoreVectorMaskedNode : public StoreVectorNode { public: @@ -1221,7 +1189,6 @@ class StoreVectorMaskedNode : public StoreVectorNode { virtual Node* mask() const { return in(Mask); } }; -//------------------------------LoadVectorMaskedNode-------------------------------- // Load Vector from memory under the influence of a predicate register(mask). class LoadVectorMaskedNode : public LoadVectorNode { public: @@ -1245,7 +1212,6 @@ class LoadVectorMaskedNode : public LoadVectorNode { } }; -//-------------------------------LoadVectorGatherMaskedNode--------------------------------- // Load Vector from memory via index map under the influence of a predicate register(mask). class LoadVectorGatherMaskedNode : public LoadVectorNode { public: @@ -1268,7 +1234,6 @@ class LoadVectorGatherMaskedNode : public LoadVectorNode { } }; -//------------------------------StoreVectorScatterMaskedNode-------------------------------- // Store Vector into memory via index map under the influence of a predicate register(mask). class StoreVectorScatterMaskedNode : public StoreVectorNode { public: @@ -1312,7 +1277,6 @@ public: virtual const Type *bottom_type() const { return in(1)->bottom_type(); } }; -//------------------------------VectorCmpMaskedNode-------------------------------- // Vector Comparison under the influence of a predicate register(mask). class VectorCmpMaskedNode : public TypeNode { public: @@ -1325,7 +1289,8 @@ class VectorCmpMaskedNode : public TypeNode { virtual int Opcode() const; }; -//------------------------------VectorMaskGenNode---------------------------------- +// Generate a vector mask based on the given length. Lanes with indices in +// [0, length) are set to true, while the remaining lanes are set to false. class VectorMaskGenNode : public TypeNode { public: VectorMaskGenNode(Node* length, const Type* ty): TypeNode(ty, 2) { @@ -1338,7 +1303,8 @@ class VectorMaskGenNode : public TypeNode { static Node* make(Node* length, BasicType vmask_bt, int vmask_len); }; -//------------------------------VectorMaskOpNode----------------------------------- +// Base class for certain vector mask operations. The supported input mask can +// be either "BVectMask" or "PVectMask" depending on the platform. class VectorMaskOpNode : public TypeNode { private: int _mopc; @@ -1359,6 +1325,7 @@ class VectorMaskOpNode : public TypeNode { static Node* make(Node* mask, const Type* ty, int mopc); }; +// Count the number of true (set) lanes in the vector mask. class VectorMaskTrueCountNode : public VectorMaskOpNode { public: VectorMaskTrueCountNode(Node* mask, const Type* ty): @@ -1366,6 +1333,8 @@ class VectorMaskTrueCountNode : public VectorMaskOpNode { virtual int Opcode() const; }; +// Returns the index of the first true (set) lane in the vector mask. +// If no lanes are set, returns the vector length. class VectorMaskFirstTrueNode : public VectorMaskOpNode { public: VectorMaskFirstTrueNode(Node* mask, const Type* ty): @@ -1373,6 +1342,8 @@ class VectorMaskFirstTrueNode : public VectorMaskOpNode { virtual int Opcode() const; }; +// Returns the index of the last true (set) lane in the vector mask. +// If no lanes are set, returns -1 . class VectorMaskLastTrueNode : public VectorMaskOpNode { public: VectorMaskLastTrueNode(Node* mask, const Type* ty): @@ -1380,6 +1351,10 @@ class VectorMaskLastTrueNode : public VectorMaskOpNode { virtual int Opcode() const; }; +// Pack the mask lane values into a long value, supporting at most the +// first 64 lanes. Each mask lane is packed into one bit in the long +// value, ordered from the least significant bit to the most significant +// bit. class VectorMaskToLongNode : public VectorMaskOpNode { public: VectorMaskToLongNode(Node* mask, const Type* ty): @@ -1391,6 +1366,9 @@ class VectorMaskToLongNode : public VectorMaskOpNode { virtual Node* Identity(PhaseGVN* phase); }; +// Unpack bits from a long value into vector mask lane values. Each bit +// in the long value is unpacked into one mask lane, ordered from the +// least significant bit to the sign bit. class VectorLongToMaskNode : public VectorNode { public: VectorLongToMaskNode(Node* mask, const TypeVect* ty): @@ -1400,36 +1378,40 @@ class VectorLongToMaskNode : public VectorNode { virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; -//-------------------------- Vector mask broadcast ----------------------------------- +// Broadcast a scalar value to all lanes of a vector mask. All lanes are set +// to true if the input value is non-zero, or false if the input value is +// zero. This node is only used to generate a mask with "PVectMask" layout. class MaskAllNode : public VectorNode { public: MaskAllNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) {} virtual int Opcode() const; }; -//--------------------------- Vector mask logical and -------------------------------- +// Perform a bitwise AND operation between two vector masks. This node is +// only used for vector masks with "PVectMask" layout. class AndVMaskNode : public AndVNode { public: AndVMaskNode(Node* in1, Node* in2, const TypeVect* vt) : AndVNode(in1, in2, vt) {} virtual int Opcode() const; }; -//--------------------------- Vector mask logical or --------------------------------- +// Perform a bitwise OR operation between two vector masks. This node is +// only used for vector masks with "PVectMask" layout. class OrVMaskNode : public OrVNode { public: OrVMaskNode(Node* in1, Node* in2, const TypeVect* vt) : OrVNode(in1, in2, vt) {} virtual int Opcode() const; }; -//--------------------------- Vector mask logical xor -------------------------------- +// Perform a bitwise XOR operation between two vector masks. This node is +// only used for vector masks with "PVectMask" layout. class XorVMaskNode : public XorVNode { public: XorVMaskNode(Node* in1, Node* in2, const TypeVect* vt) : XorVNode(in1, in2, vt) {} virtual int Opcode() const; }; -//=========================Promote_Scalar_to_Vector============================ - +// Replicate a scalar value to all lanes of a vector. class ReplicateNode : public VectorNode { public: ReplicateNode(Node* in1, const TypeVect* vt) : VectorNode(in1, vt) { @@ -1439,7 +1421,7 @@ class ReplicateNode : public VectorNode { virtual int Opcode() const; }; -//======================Populate_Indices_into_a_Vector========================= +// Populate indices into a vector. class PopulateIndexNode : public VectorNode { public: PopulateIndexNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {} @@ -1448,7 +1430,6 @@ class PopulateIndexNode : public VectorNode { //========================Pack_Scalars_into_a_Vector=========================== -//------------------------------PackNode--------------------------------------- // Pack parent class (not for code generation). class PackNode : public VectorNode { public: @@ -1466,7 +1447,6 @@ class PackNode : public VectorNode { static PackNode* make(Node* s, uint vlen, BasicType bt); }; -//------------------------------PackBNode-------------------------------------- // Pack byte scalars into vector class PackBNode : public PackNode { public: @@ -1474,7 +1454,6 @@ class PackBNode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackSNode-------------------------------------- // Pack short scalars into a vector class PackSNode : public PackNode { public: @@ -1483,7 +1462,6 @@ class PackSNode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackINode-------------------------------------- // Pack integer scalars into a vector class PackINode : public PackNode { public: @@ -1492,7 +1470,6 @@ class PackINode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackLNode-------------------------------------- // Pack long scalars into a vector class PackLNode : public PackNode { public: @@ -1501,7 +1478,6 @@ class PackLNode : public PackNode { virtual int Opcode() const; }; -//------------------------------Pack2LNode------------------------------------- // Pack 2 long scalars into a vector class Pack2LNode : public PackNode { public: @@ -1509,7 +1485,6 @@ class Pack2LNode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackFNode-------------------------------------- // Pack float scalars into vector class PackFNode : public PackNode { public: @@ -1518,7 +1493,6 @@ class PackFNode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackDNode-------------------------------------- // Pack double scalars into a vector class PackDNode : public PackNode { public: @@ -1527,7 +1501,6 @@ class PackDNode : public PackNode { virtual int Opcode() const; }; -//------------------------------Pack2DNode------------------------------------- // Pack 2 double scalars into a vector class Pack2DNode : public PackNode { public: @@ -1535,7 +1508,10 @@ class Pack2DNode : public PackNode { virtual int Opcode() const; }; - +// Load the IOTA constant vector containing sequential indices starting from 0 +// and incrementing by 1 up to "VLENGTH - 1". So far, the first input is an int +// constant 0. For example, a 128-bit vector with int (32-bit) elements produces +// a vector like "[0, 1, 2, 3]". class VectorLoadConstNode : public VectorNode { public: VectorLoadConstNode(Node* in1, const TypeVect* vt) : VectorNode(in1, vt) {} @@ -1544,8 +1520,7 @@ class VectorLoadConstNode : public VectorNode { //========================Extract_Scalar_from_Vector=========================== -//------------------------------ExtractNode------------------------------------ -// Extract a scalar from a vector at position "pos" +// The base class for all extract nodes. class ExtractNode : public Node { public: ExtractNode(Node* src, Node* pos) : Node(nullptr, src, pos) {} @@ -1554,8 +1529,7 @@ class ExtractNode : public Node { static int opcode(BasicType bt); }; -//------------------------------ExtractBNode----------------------------------- -// Extract a byte from a vector at position "pos" +// Extract a byte from a vector at position "pos". class ExtractBNode : public ExtractNode { public: ExtractBNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1564,8 +1538,7 @@ class ExtractBNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractUBNode---------------------------------- -// Extract a boolean from a vector at position "pos" +// Extract a boolean from a vector at position "pos". class ExtractUBNode : public ExtractNode { public: ExtractUBNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1574,8 +1547,7 @@ class ExtractUBNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractCNode----------------------------------- -// Extract a char from a vector at position "pos" +// Extract a char from a vector at position "pos". class ExtractCNode : public ExtractNode { public: ExtractCNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1584,8 +1556,7 @@ class ExtractCNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractSNode----------------------------------- -// Extract a short from a vector at position "pos" +// Extract a short from a vector at position "pos". class ExtractSNode : public ExtractNode { public: ExtractSNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1594,8 +1565,7 @@ class ExtractSNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractINode----------------------------------- -// Extract an int from a vector at position "pos" +// Extract an int from a vector at position "pos". class ExtractINode : public ExtractNode { public: ExtractINode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1604,8 +1574,7 @@ class ExtractINode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractLNode----------------------------------- -// Extract a long from a vector at position "pos" +// Extract a long from a vector at position "pos". class ExtractLNode : public ExtractNode { public: ExtractLNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1614,8 +1583,7 @@ class ExtractLNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegL; } }; -//------------------------------ExtractFNode----------------------------------- -// Extract a float from a vector at position "pos" +// Extract a float from a vector at position "pos". class ExtractFNode : public ExtractNode { public: ExtractFNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1624,8 +1592,7 @@ class ExtractFNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegF; } }; -//------------------------------ExtractDNode----------------------------------- -// Extract a double from a vector at position "pos" +// Extract a double from a vector at position "pos". class ExtractDNode : public ExtractNode { public: ExtractDNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1634,7 +1601,6 @@ class ExtractDNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegD; } }; -//------------------------------MacroLogicVNode------------------------------- // Vector logical operations packing node. class MacroLogicVNode : public VectorNode { private: @@ -1653,6 +1619,8 @@ public: Node* mask, uint truth_table, const TypeVect* vt); }; +// Compare two vectors lane-wise using the specified predicate and produce a +// vector mask. class VectorMaskCmpNode : public VectorNode { private: BoolTest::mask _predicate; @@ -1697,6 +1665,8 @@ class VectorMaskWrapperNode : public VectorNode { Node* vector_mask() const { return in(2); } }; +// Test whether all or any lanes in the first input vector mask is true, +// based on the specified predicate. class VectorTestNode : public CmpNode { private: BoolTest::mask _predicate; @@ -1719,6 +1689,9 @@ class VectorTestNode : public CmpNode { } }; +// Blend two vectors based on a vector mask. For each lane, select the value +// from the first input vector (vec1) if the corresponding mask lane is set, +// otherwise select from the second input vector (vec2). class VectorBlendNode : public VectorNode { public: VectorBlendNode(Node* vec1, Node* vec2, Node* mask) @@ -1732,11 +1705,14 @@ class VectorBlendNode : public VectorNode { Node* vec_mask() const { return in(3); } }; +// Rearrange lane elements from a source vector under the control of a shuffle +// (indexes) vector. Each lane in the shuffle vector specifies which lane from +// the source vector to select for the corresponding output lane. All indexes +// are in the range [0, VLENGTH). class VectorRearrangeNode : public VectorNode { public: VectorRearrangeNode(Node* vec1, Node* shuffle) : VectorNode(vec1, shuffle, vec1->bottom_type()->is_vect()) { - // assert(mask->is_VectorMask(), "VectorBlendNode requires that third argument be a mask"); } virtual int Opcode() const; @@ -1744,9 +1720,12 @@ class VectorRearrangeNode : public VectorNode { Node* vec_shuffle() const { return in(2); } }; - -// Select elements from two source vectors based on the wrapped indexes held in -// the first vector. +// Select lane elements from two source vectors ("src1" and "src2") under the +// control of an "indexes" vector. The two source vectors are logically concatenated +// to form a table of 2*VLENGTH elements, where src1 occupies indices [0, VLENGTH) +// and src2 occupies indices [VLENGTH, 2*VLENGTH). Each lane in the "indexes" +// vector specifies which element from this table to select for the corresponding +// output lane. class SelectFromTwoVectorNode : public VectorNode { public: SelectFromTwoVectorNode(Node* indexes, Node* src1, Node* src2, const TypeVect* vt) @@ -1758,13 +1737,19 @@ public: virtual int Opcode() const; }; -// The target may not directly support the rearrange operation for an element type. In those cases, -// we can transform the rearrange into a different element type. For example, on x86 before AVX512, -// there is no rearrange instruction for short elements, what we will then do is to transform the -// shuffle vector into one that we can do byte rearrange such that it would provide the same -// result. This could have been done in VectorRearrangeNode during code emission but we eagerly -// expand this out because it is often the case that an index vector is reused in many rearrange -// operations. This allows the index preparation to be GVN-ed as well as hoisted out of loops, etc. +// Transform a shuffle vector when the target does not directly support rearrange +// operations for the original element type. In such cases, the rearrange can be +// transformed to use a different element type. +// +// For example, on x86 before AVX512, there are no rearrange instructions for short +// elements. The shuffle vector is transformed into one suitable for byte rearrange +// that produces the same result. This could have been done in VectorRearrangeNode +// during code emission, but we eagerly expand it out because shuffle vectors are +// often reused in many rearrange operations. This allows the transformation to be +// GVN-ed and hoisted out of loops. +// +// Input: Original shuffle vector (indices for the desired element type) +// Output: Transformed shuffle vector (indices for the supported element type) class VectorLoadShuffleNode : public VectorNode { public: VectorLoadShuffleNode(Node* in, const TypeVect* vt) @@ -1773,6 +1758,8 @@ class VectorLoadShuffleNode : public VectorNode { virtual int Opcode() const; }; +// Convert a "BVectMask" into a platform-specific vector mask (either "NVectMask" +// or "PVectMask"). class VectorLoadMaskNode : public VectorNode { public: VectorLoadMaskNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) { @@ -1784,6 +1771,8 @@ class VectorLoadMaskNode : public VectorNode { Node* Ideal(PhaseGVN* phase, bool can_reshape); }; +// Convert a platform-specific vector mask (either "NVectMask" or "PVectMask") +// into a "BVectMask". class VectorStoreMaskNode : public VectorNode { protected: VectorStoreMaskNode(Node* in1, ConINode* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {} @@ -1795,6 +1784,8 @@ class VectorStoreMaskNode : public VectorNode { static VectorStoreMaskNode* make(PhaseGVN& gvn, Node* in, BasicType in_type, uint num_elem); }; +// Lane-wise type cast a vector mask to the given vector type. The vector length +// of the input and output must be the same. class VectorMaskCastNode : public VectorNode { public: VectorMaskCastNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) { @@ -1831,6 +1822,8 @@ class VectorReinterpretNode : public VectorNode { virtual int Opcode() const; }; +// Lane-wise type cast a vector to the given vector type. This is the base +// class for all vector type cast operations. class VectorCastNode : public VectorNode { public: VectorCastNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) {} @@ -1843,6 +1836,7 @@ class VectorCastNode : public VectorNode { virtual Node* Identity(PhaseGVN* phase); }; +// Cast a byte vector to the given vector type. class VectorCastB2XNode : public VectorCastNode { public: VectorCastB2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1851,6 +1845,7 @@ class VectorCastB2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a short vector to the given vector type. class VectorCastS2XNode : public VectorCastNode { public: VectorCastS2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1859,6 +1854,7 @@ class VectorCastS2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast an int vector to the given vector type. class VectorCastI2XNode : public VectorCastNode { public: VectorCastI2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1867,6 +1863,7 @@ class VectorCastI2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a long vector to the given vector type. class VectorCastL2XNode : public VectorCastNode { public: VectorCastL2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1875,6 +1872,7 @@ class VectorCastL2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a float vector to the given vector type. class VectorCastF2XNode : public VectorCastNode { public: VectorCastF2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1883,6 +1881,7 @@ class VectorCastF2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a double vector to the given vector type. class VectorCastD2XNode : public VectorCastNode { public: VectorCastD2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1891,6 +1890,7 @@ class VectorCastD2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a half float vector to float vector type. class VectorCastHF2FNode : public VectorCastNode { public: VectorCastHF2FNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1899,6 +1899,7 @@ class VectorCastHF2FNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a float vector to a half float vector type. class VectorCastF2HFNode : public VectorCastNode { public: VectorCastF2HFNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1907,8 +1908,12 @@ class VectorCastF2HFNode : public VectorCastNode { virtual int Opcode() const; }; -// So far, VectorUCastNode can only be used in Vector API unsigned extensions -// between integral types. E.g., extending byte to float is not supported now. +// Unsigned vector cast operations can only be used in unsigned (zero) +// extensions between integral types so far. E.g., extending byte to +// float is not supported now. + +// Unsigned cast a byte vector to the given vector type with short, int, +// or long element type. class VectorUCastB2XNode : public VectorCastNode { public: VectorUCastB2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1920,6 +1925,8 @@ class VectorUCastB2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Unsigned cast a short vector to the given vector type with int or long +// element type. class VectorUCastS2XNode : public VectorCastNode { public: VectorUCastS2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1930,6 +1937,7 @@ class VectorUCastS2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Unsigned cast an int vector to the given vector type with long element type. class VectorUCastI2XNode : public VectorCastNode { public: VectorUCastI2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1939,6 +1947,7 @@ class VectorUCastI2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Vector round float to nearest integer. class RoundVFNode : public VectorNode { public: RoundVFNode(Node* in, const TypeVect* vt) :VectorNode(in, vt) { @@ -1947,6 +1956,7 @@ class RoundVFNode : public VectorNode { virtual int Opcode() const; }; +// Vector round double to nearest integer. class RoundVDNode : public VectorNode { public: RoundVDNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) { @@ -1955,6 +1965,7 @@ class RoundVDNode : public VectorNode { virtual int Opcode() const; }; +// Insert a new value into a vector lane at the specified position. class VectorInsertNode : public VectorNode { public: VectorInsertNode(Node* vsrc, Node* new_val, ConINode* pos, const TypeVect* vt) : VectorNode(vsrc, new_val, (Node*)pos, vt) { @@ -1968,6 +1979,9 @@ class VectorInsertNode : public VectorNode { static Node* make(Node* vec, Node* new_val, int position, PhaseGVN& gvn); }; +// Box a vector value into a Vector API object (e.g., IntMaxVector). +// This is a macro node that gets expanded during vector optimization +// phase. class VectorBoxNode : public Node { private: const TypeInstPtr* const _box_type; @@ -1995,6 +2009,8 @@ class VectorBoxNode : public Node { static const TypeFunc* vec_box_type(const TypeInstPtr* box_type); }; +// Allocate storage for boxing a vector value. This is used during vector +// box expansion. class VectorBoxAllocateNode : public CallStaticJavaNode { public: VectorBoxAllocateNode(Compile* C, const TypeInstPtr* vbox_type) @@ -2009,6 +2025,9 @@ class VectorBoxAllocateNode : public CallStaticJavaNode { #endif // !PRODUCT }; +// Unbox a Vector API object (e.g., IntMaxVector) to extract the underlying +// vector value. This is a macro node expanded during vector optimization +// phase. class VectorUnboxNode : public VectorNode { protected: uint size_of() const { return sizeof(*this); } @@ -2027,6 +2046,7 @@ class VectorUnboxNode : public VectorNode { Node* Ideal(PhaseGVN* phase, bool can_reshape); }; +// Lane-wise right rotation of the first input by the second input. class RotateRightVNode : public VectorNode { public: RotateRightVNode(Node* in1, Node* in2, const TypeVect* vt) @@ -2036,6 +2056,7 @@ public: Node* Ideal(PhaseGVN* phase, bool can_reshape); }; +// Lane-wise left rotation of the first input by the second input. class RotateLeftVNode : public VectorNode { public: RotateLeftVNode(Node* in1, Node* in2, const TypeVect* vt) @@ -2045,6 +2066,7 @@ public: Node* Ideal(PhaseGVN* phase, bool can_reshape); }; +// Count the number of leading zeros in each lane of the input. class CountLeadingZerosVNode : public VectorNode { public: CountLeadingZerosVNode(Node* in, const TypeVect* vt) @@ -2056,6 +2078,7 @@ class CountLeadingZerosVNode : public VectorNode { virtual int Opcode() const; }; +// Count the number of trailing zeros in each lane of the input. class CountTrailingZerosVNode : public VectorNode { public: CountTrailingZerosVNode(Node* in, const TypeVect* vt) @@ -2067,6 +2090,7 @@ class CountTrailingZerosVNode : public VectorNode { virtual int Opcode() const; }; +// Reverse the bits within each lane (e.g., 0b10110010 becomes 0b01001101). class ReverseVNode : public VectorNode { public: ReverseVNode(Node* in, const TypeVect* vt) @@ -2076,6 +2100,7 @@ public: virtual int Opcode() const; }; +// Reverse the byte order within each lane (e.g., 0x12345678 becomes 0x78563412). class ReverseBytesVNode : public VectorNode { public: ReverseBytesVNode(Node* in, const TypeVect* vt) @@ -2085,6 +2110,7 @@ public: virtual int Opcode() const; }; +// Vector signum float. class SignumVFNode : public VectorNode { public: SignumVFNode(Node* in1, Node* zero, Node* one, const TypeVect* vt) @@ -2093,6 +2119,7 @@ public: virtual int Opcode() const; }; +// Vector signum double. class SignumVDNode : public VectorNode { public: SignumVDNode(Node* in1, Node* zero, Node* one, const TypeVect* vt) @@ -2101,6 +2128,8 @@ public: virtual int Opcode() const; }; +// Compress (extract and pack) bits in each lane of the first input +// based on the mask input. class CompressBitsVNode : public VectorNode { public: CompressBitsVNode(Node* in, Node* mask, const TypeVect* vt) @@ -2108,6 +2137,8 @@ public: virtual int Opcode() const; }; +// Expand (deposit) bits in each lane of the first input based on the +// mask input. class ExpandBitsVNode : public VectorNode { public: ExpandBitsVNode(Node* in, Node* mask, const TypeVect* vt) diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index ef5aca96a57..6d6937a97e9 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.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 @@ -115,9 +115,6 @@ #if INCLUDE_MANAGEMENT #include "services/finalizerService.hpp" #endif -#ifdef LINUX -#include "osContainer_linux.hpp" -#endif #include @@ -500,11 +497,9 @@ JVM_LEAF(jboolean, JVM_IsUseContainerSupport(void)) JVM_END JVM_LEAF(jboolean, JVM_IsContainerized(void)) -#ifdef LINUX - if (OSContainer::is_containerized()) { + if (os::is_containerized()) { return JNI_TRUE; } -#endif return JNI_FALSE; JVM_END diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 1c2f5527ff9..de5bc9ea58f 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -129,7 +129,6 @@ #ifdef LINUX #include "cgroupSubsystem_linux.hpp" #include "os_linux.hpp" -#include "osContainer_linux.hpp" #endif #define CHECK_JNI_EXCEPTION_(env, value) \ @@ -2582,14 +2581,12 @@ WB_ENTRY(jboolean, WB_CheckLibSpecifiesNoexecstack(JNIEnv* env, jobject o, jstri WB_END WB_ENTRY(jboolean, WB_IsContainerized(JNIEnv* env, jobject o)) - LINUX_ONLY(return OSContainer::is_containerized();) - return false; + return os::is_containerized(); WB_END // Physical memory of the host machine (including containers) WB_ENTRY(jlong, WB_HostPhysicalMemory(JNIEnv* env, jobject o)) - LINUX_ONLY(return static_cast(os::Linux::physical_memory());) - return static_cast(os::physical_memory()); + return static_cast(os::Machine::physical_memory()); WB_END // Available memory of the host machine (container-aware) diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index cf18388c625..1c06bf3c521 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.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 @@ -81,10 +81,6 @@ #include "utilities/permitForbiddenFunctions.hpp" #include "utilities/powerOfTwo.hpp" -#ifdef LINUX -#include "osContainer_linux.hpp" -#endif - #ifndef _WINDOWS # include #endif @@ -2205,11 +2201,14 @@ static void assert_nonempty_range(const char* addr, size_t bytes) { } bool os::used_memory(physical_memory_size_type& value) { -#ifdef LINUX - if (OSContainer::is_containerized()) { - return OSContainer::memory_usage_in_bytes(value); + if (is_containerized()) { + return Container::used_memory(value); } -#endif + + return Machine::used_memory(value); +} + +bool os::Machine::used_memory(physical_memory_size_type& value) { physical_memory_size_type avail_mem = 0; // Return value ignored - defaulting to 0 on failure. (void)os::available_memory(avail_mem); @@ -2218,6 +2217,44 @@ bool os::used_memory(physical_memory_size_type& value) { return true; } +#ifndef LINUX +bool os::is_containerized() { + return false; +} + +bool os::Container::processor_count(double& value) { + return false; +} + +bool os::Container::available_memory(physical_memory_size_type& value) { + return false; +} + +bool os::Container::used_memory(physical_memory_size_type& value) { + return false; +} + +bool os::Container::total_swap_space(physical_memory_size_type& value) { + return false; +} + +bool os::Container::free_swap_space(physical_memory_size_type& value) { + return false; +} + +bool os::Container::memory_limit(physical_memory_size_type& value) { + return false; +} + +bool os::Container::memory_soft_limit(physical_memory_size_type& value) { + return false; +} + +bool os::Container::memory_throttle_limit(physical_memory_size_type& value) { + return false; +} +#endif + bool os::commit_memory(char* addr, size_t bytes, bool executable) { assert_nonempty_range(addr, bytes); diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index d585d3e5fc0..29c872157fd 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.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 @@ -342,6 +342,52 @@ class os: AllStatic { static bool is_server_class_machine(); static size_t rss(); + // On platforms with container support (currently only Linux) we combine machine values with + // potential container values in os:: methods, abstracting which value is actually used. + // The os::Machine and os::Container classes and containing methods are used to get machine + // and container values (when available) separately. + static bool is_containerized(); + + // The os::Machine class reports system resource metrics from the perspective of the operating + // system, without considering container-imposed limits. The values returned by these methods + // reflect the resources visible to the process as reported by the OS, and may already be + // affected by mechanisms such as virtualization, hypervisor limits, or process affinity, + // but do NOT consider further restrictions imposed by container runtimes (e.g., cgroups) + class Machine : AllStatic { + public: + static int active_processor_count(); + + [[nodiscard]] static bool available_memory(physical_memory_size_type& value); + [[nodiscard]] static bool used_memory(physical_memory_size_type& value); + [[nodiscard]] static bool free_memory(physical_memory_size_type& value); + + [[nodiscard]] static bool total_swap_space(physical_memory_size_type& value); + [[nodiscard]] static bool free_swap_space(physical_memory_size_type& value); + + static physical_memory_size_type physical_memory(); + }; + + // The os::Container class reports resource limits as imposed by a supported container runtime + // (currently only cgroup-based Linux runtimes). If the process is running inside a + // containerized environment, methods from this class report the effective limits imposed + // by the container, which may be more restrictive than what os::Machine reports. + // Methods return true and set the out-parameter if a limit is found, + // or false if no limit exists or it cannot be determined. + class Container : AllStatic { + public: + [[nodiscard]] static bool processor_count(double& value); // Returns the core-equivalent CPU quota + + [[nodiscard]] static bool available_memory(physical_memory_size_type& value); + [[nodiscard]] static bool used_memory(physical_memory_size_type& value); + + [[nodiscard]] static bool total_swap_space(physical_memory_size_type& value); + [[nodiscard]] static bool free_swap_space(physical_memory_size_type& value); + + [[nodiscard]] static bool memory_limit(physical_memory_size_type& value); + [[nodiscard]] static bool memory_soft_limit(physical_memory_size_type& value); + [[nodiscard]] static bool memory_throttle_limit(physical_memory_size_type& value); + }; + // Returns the id of the processor on which the calling thread is currently executing. // The returned value is guaranteed to be between 0 and (os::processor_count() - 1). static uint processor_id(); diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index f3c08ae62f7..bfbd4727e9a 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -36,6 +36,7 @@ #include "memory/resourceArea.hpp" #include "nmt/memTracker.hpp" #include "oops/oop.inline.hpp" +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaThread.inline.hpp" @@ -82,7 +83,7 @@ Thread::Thread(MemTag mem_tag) { _threads_hazard_ptr = nullptr; _threads_list_ptr = nullptr; _nested_threads_hazard_ptr_cnt = 0; - _rcu_counter = 0; + _rcu_counter.store_relaxed(0); // the handle mark links itself to last_handle_mark new HandleMark(this); diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index 181dfc46f87..dcd6fb2d3fd 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.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. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -30,6 +30,7 @@ #include "gc/shared/threadLocalAllocBuffer.hpp" #include "jni.h" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "runtime/globals.hpp" #include "runtime/os.hpp" @@ -238,9 +239,9 @@ class Thread: public ThreadShadow { // Support for GlobalCounter private: - volatile uintx _rcu_counter; + Atomic _rcu_counter; public: - volatile uintx* get_rcu_counter() { + Atomic* get_rcu_counter() { return &_rcu_counter; } diff --git a/src/hotspot/share/utilities/globalCounter.cpp b/src/hotspot/share/utilities/globalCounter.cpp index 7019273d937..114d3b5fed1 100644 --- a/src/hotspot/share/utilities/globalCounter.cpp +++ b/src/hotspot/share/utilities/globalCounter.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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ */ #include "memory/iterator.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/javaThread.hpp" #include "runtime/threadSMR.inline.hpp" #include "runtime/vmThread.hpp" @@ -41,7 +41,7 @@ class GlobalCounter::CounterThreadCheck : public ThreadClosure { SpinYield yield; // Loops on this thread until it has exited the critical read section. while(true) { - uintx cnt = AtomicAccess::load_acquire(thread->get_rcu_counter()); + uintx cnt = thread->get_rcu_counter()->load_acquire(); // This checks if the thread's counter is active. And if so is the counter // for a pre-existing reader (belongs to this grace period). A pre-existing // reader will have a lower counter than the global counter version for this @@ -57,9 +57,9 @@ class GlobalCounter::CounterThreadCheck : public ThreadClosure { }; void GlobalCounter::write_synchronize() { - assert((*Thread::current()->get_rcu_counter() & COUNTER_ACTIVE) == 0x0, "must be outside a critcal section"); - // AtomicAccess::add must provide fence since we have storeload dependency. - uintx gbl_cnt = AtomicAccess::add(&_global_counter._counter, COUNTER_INCREMENT); + assert((Thread::current()->get_rcu_counter()->load_relaxed() & COUNTER_ACTIVE) == 0x0, "must be outside a critcal section"); + // Atomic add must provide fence since we have storeload dependency. + uintx gbl_cnt = _global_counter._counter.add_then_fetch(COUNTER_INCREMENT); // Do all RCU threads. CounterThreadCheck ctc(gbl_cnt); diff --git a/src/hotspot/share/utilities/globalCounter.hpp b/src/hotspot/share/utilities/globalCounter.hpp index c78831acfa5..80fc1b3e94e 100644 --- a/src/hotspot/share/utilities/globalCounter.hpp +++ b/src/hotspot/share/utilities/globalCounter.hpp @@ -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 @@ -27,6 +27,7 @@ #include "memory/allStatic.hpp" #include "memory/padded.hpp" +#include "runtime/atomic.hpp" class Thread; @@ -47,7 +48,7 @@ class GlobalCounter : public AllStatic { // counter is on a separate cacheline. struct PaddedCounter { DEFINE_PAD_MINUS_SIZE(0, DEFAULT_PADDING_SIZE, 0); - volatile uintx _counter; + Atomic _counter; DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(volatile uintx)); }; diff --git a/src/hotspot/share/utilities/globalCounter.inline.hpp b/src/hotspot/share/utilities/globalCounter.inline.hpp index 9cc746173b8..ed37b8a878d 100644 --- a/src/hotspot/share/utilities/globalCounter.inline.hpp +++ b/src/hotspot/share/utilities/globalCounter.inline.hpp @@ -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 @@ -27,30 +27,29 @@ #include "utilities/globalCounter.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/javaThread.hpp" inline GlobalCounter::CSContext GlobalCounter::critical_section_begin(Thread *thread) { assert(thread == Thread::current(), "must be current thread"); - uintx old_cnt = AtomicAccess::load(thread->get_rcu_counter()); + uintx old_cnt = thread->get_rcu_counter()->load_relaxed(); // Retain the old counter value if already active, e.g. nested. // Otherwise, set the counter to the current version + active bit. uintx new_cnt = old_cnt; if ((new_cnt & COUNTER_ACTIVE) == 0) { - new_cnt = AtomicAccess::load(&_global_counter._counter) | COUNTER_ACTIVE; + new_cnt = _global_counter._counter.load_relaxed() | COUNTER_ACTIVE; } - AtomicAccess::release_store_fence(thread->get_rcu_counter(), new_cnt); + thread->get_rcu_counter()->release_store_fence(new_cnt); return static_cast(old_cnt); } inline void GlobalCounter::critical_section_end(Thread *thread, CSContext context) { assert(thread == Thread::current(), "must be current thread"); - assert((*thread->get_rcu_counter() & COUNTER_ACTIVE) == COUNTER_ACTIVE, "must be in critical section"); + assert((thread->get_rcu_counter()->load_relaxed() & COUNTER_ACTIVE) == COUNTER_ACTIVE, "must be in critical section"); // Restore the counter value from before the associated begin. - AtomicAccess::release_store(thread->get_rcu_counter(), - static_cast(context)); + thread->get_rcu_counter()->release_store(static_cast(context)); } class GlobalCounter::CriticalSection { diff --git a/src/java.base/share/classes/java/lang/LazyConstant.java b/src/java.base/share/classes/java/lang/LazyConstant.java index 703d67b8abf..85f9d0e82fd 100644 --- a/src/java.base/share/classes/java/lang/LazyConstant.java +++ b/src/java.base/share/classes/java/lang/LazyConstant.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 @@ -87,9 +87,8 @@ import java.util.function.Supplier; * is thrown. Hence, a lazy constant can never hold a {@code null} value. Clients who * want to use a nullable constant can wrap the value into an {@linkplain Optional} holder. *

- * If the computing function recursively invokes itself (directly or indirectly via - * the lazy constant), an {@linkplain IllegalStateException} is thrown, and the lazy - * constant is not initialized. + * If the computing function recursively invokes itself via the lazy constant, an + * {@linkplain IllegalStateException} is thrown, and the lazy constant is not initialized. * *

Composing lazy constants

* A lazy constant can depend on other lazy constants, forming a dependency graph diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java index 43408de292a..5f9a90e1748 100644 --- a/src/java.base/share/classes/java/util/List.java +++ b/src/java.base/share/classes/java/util/List.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 @@ -1224,7 +1224,7 @@ public interface List extends SequencedCollection { * The returned list and its {@link List#subList(int, int) subList()} or * {@link List#reversed()} views implement the {@link RandomAccess} interface. *

- * If the provided computing function recursively calls itself or the returned + * If the provided computing function recursively calls itself via the returned * lazy list for the same index, an {@linkplain IllegalStateException} * will be thrown. *

diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java index 177f0522b1b..fa16fb89050 100644 --- a/src/java.base/share/classes/java/util/Map.java +++ b/src/java.base/share/classes/java/util/Map.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 @@ -1777,7 +1777,7 @@ public interface Map { * The values of any {@link Map#values()} or {@link Map#entrySet()} views of * the returned map are also lazily computed. *

- * If the provided computing function recursively calls itself or + * If the provided computing function recursively calls itself via * the returned lazy map for the same key, an {@linkplain IllegalStateException} * will be thrown. *

diff --git a/src/java.base/share/classes/sun/security/ssl/DHasKEM.java b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java new file mode 100644 index 00000000000..763013f280c --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.NamedCurve; + +import javax.crypto.DecapsulateException; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; +import java.io.IOException; +import java.math.BigInteger; +import java.security.*; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.XECKey; +import java.security.interfaces.XECPublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.NamedParameterSpec; +import java.security.spec.XECPublicKeySpec; +import java.util.Arrays; + +/** + * The DHasKEM class presents a KEM abstraction layer over traditional + * DH-based key exchange, which can be used for either straight + * ECDH/XDH or TLS hybrid key exchanges. + * + * This class can be alongside standard full post-quantum KEMs + * when hybrid implementations are required. + */ +public class DHasKEM implements KEMSpi { + + @Override + public EncapsulatorSpi engineNewEncapsulator( + PublicKey publicKey, AlgorithmParameterSpec spec, + SecureRandom secureRandom) throws InvalidKeyException { + return new Handler(publicKey, null, secureRandom); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, + AlgorithmParameterSpec spec) throws InvalidKeyException { + return new Handler(null, privateKey, null); + } + + private static final class Handler + implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { + private final PublicKey pkR; + private final PrivateKey skR; + private final SecureRandom sr; + private final Params params; + + Handler(PublicKey pk, PrivateKey sk, SecureRandom sr) + throws InvalidKeyException { + this.pkR = pk; + this.skR = sk; + this.sr = sr; + this.params = paramsFromKey(pk == null ? sk : pk); + } + + @Override + public KEM.Encapsulated engineEncapsulate(int from, int to, + String algorithm) { + KeyPair kpE = params.generateKeyPair(sr); + PrivateKey skE = kpE.getPrivate(); + PublicKey pkE = kpE.getPublic(); + byte[] pkEm = params.SerializePublicKey(pkE); + try { + SecretKey dh = params.DH(algorithm, skE, pkR); + return new KEM.Encapsulated( + sub(dh, from, to), + pkEm, null); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + @Override + public int engineSecretSize() { + return params.secretLen; + } + + @Override + public int engineEncapsulationSize() { + return params.publicKeyLen; + } + + @Override + public SecretKey engineDecapsulate(byte[] encapsulation, int from, + int to, String algorithm) throws DecapsulateException { + if (encapsulation.length != params.publicKeyLen) { + throw new DecapsulateException("incorrect encapsulation size"); + } + try { + PublicKey pkE = params.DeserializePublicKey(encapsulation); + SecretKey dh = params.DH(algorithm, skR, pkE); + return sub(dh, from, to); + } catch (IOException | InvalidKeyException e) { + throw new DecapsulateException("Cannot decapsulate", e); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + private SecretKey sub(SecretKey key, int from, int to) { + if (from == 0 && to == params.secretLen) { + return key; + } + + // Key slicing should never happen. Otherwise, there might be + // a programming error. + throw new AssertionError( + "Unexpected key slicing: from=" + from + ", to=" + to); + } + + // This KEM is designed to be able to represent every ECDH and XDH + private Params paramsFromKey(Key k) throws InvalidKeyException { + if (k instanceof ECKey eckey) { + if (ECUtil.equals(eckey.getParams(), CurveDB.P_256)) { + return Params.P256; + } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_384)) { + return Params.P384; + } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_521)) { + return Params.P521; + } + } else if (k instanceof XECKey xkey + && xkey.getParams() instanceof NamedParameterSpec ns) { + if (ns.getName().equalsIgnoreCase( + NamedParameterSpec.X25519.getName())) { + return Params.X25519; + } else if (ns.getName().equalsIgnoreCase( + NamedParameterSpec.X448.getName())) { + return Params.X448; + } + } + throw new InvalidKeyException("Unsupported key"); + } + } + + private enum Params { + + P256(32, 2 * 32 + 1, + "ECDH", "EC", CurveDB.P_256), + + P384(48, 2 * 48 + 1, + "ECDH", "EC", CurveDB.P_384), + + P521(66, 2 * 66 + 1, + "ECDH", "EC", CurveDB.P_521), + + X25519(32, 32, + "XDH", "XDH", NamedParameterSpec.X25519), + + X448(56, 56, + "XDH", "XDH", NamedParameterSpec.X448); + + private final int secretLen; + private final int publicKeyLen; + private final String kaAlgorithm; + private final String keyAlgorithm; + private final AlgorithmParameterSpec spec; + + Params(int secretLen, int publicKeyLen, String kaAlgorithm, + String keyAlgorithm, AlgorithmParameterSpec spec) { + this.spec = spec; + this.secretLen = secretLen; + this.publicKeyLen = publicKeyLen; + this.kaAlgorithm = kaAlgorithm; + this.keyAlgorithm = keyAlgorithm; + } + + private boolean isEC() { + return this == P256 || this == P384 || this == P521; + } + + private KeyPair generateKeyPair(SecureRandom sr) { + try { + KeyPairGenerator g = KeyPairGenerator.getInstance(keyAlgorithm); + g.initialize(spec, sr); + return g.generateKeyPair(); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + private byte[] SerializePublicKey(PublicKey k) { + if (isEC()) { + ECPoint w = ((ECPublicKey) k).getW(); + return ECUtil.encodePoint(w, ((NamedCurve) spec).getCurve()); + } else { + byte[] uArray = ((XECPublicKey) k).getU().toByteArray(); + ArrayUtil.reverse(uArray); + return Arrays.copyOf(uArray, publicKeyLen); + } + } + + private PublicKey DeserializePublicKey(byte[] data) throws + IOException, NoSuchAlgorithmException, + InvalidKeySpecException { + KeySpec keySpec; + if (isEC()) { + NamedCurve curve = (NamedCurve) this.spec; + keySpec = new ECPublicKeySpec( + ECUtil.decodePoint(data, curve.getCurve()), curve); + } else { + data = data.clone(); + ArrayUtil.reverse(data); + keySpec = new XECPublicKeySpec( + this.spec, new BigInteger(1, data)); + } + return KeyFactory.getInstance(keyAlgorithm). + generatePublic(keySpec); + } + + private SecretKey DH(String alg, PrivateKey skE, PublicKey pkR) + throws NoSuchAlgorithmException, InvalidKeyException { + KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); + ka.init(skE); + ka.doPhase(pkR, true); + return ka.generateSecret(alg); + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/Hybrid.java b/src/java.base/share/classes/sun/security/ssl/Hybrid.java new file mode 100644 index 00000000000..e3e2cfa0b23 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/Hybrid.java @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.RawKeySpec; +import sun.security.x509.X509Key; + +import javax.crypto.DecapsulateException; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.SecretKey; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyFactorySpi; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyPairGeneratorSpi; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.*; +import java.util.Arrays; +import java.util.Locale; + +// The Hybrid class wraps two underlying algorithms (left and right sides) +// in a single TLS hybrid named group. +// It implements: +// - Hybrid KeyPair generation +// - Hybrid KeyFactory for decoding concatenated hybrid public keys +// - Hybrid KEM implementation for performing encapsulation and +// decapsulation over two underlying algorithms (traditional +// algorithm and post-quantum KEM algorithm) + +public class Hybrid { + + public static final NamedParameterSpec X25519_MLKEM768 = + new NamedParameterSpec("X25519MLKEM768"); + + public static final NamedParameterSpec SECP256R1_MLKEM768 = + new NamedParameterSpec("SecP256r1MLKEM768"); + + public static final NamedParameterSpec SECP384R1_MLKEM1024 = + new NamedParameterSpec("SecP384r1MLKEM1024"); + + private static AlgorithmParameterSpec getSpec(String name) { + if (name.startsWith("secp")) { + return new ECGenParameterSpec(name); + } else { + return new NamedParameterSpec(name); + } + } + + private static KeyPairGenerator getKeyPairGenerator(String name) throws + NoSuchAlgorithmException { + if (name.startsWith("secp")) { + name = "EC"; + } + return KeyPairGenerator.getInstance(name); + } + + private static KeyFactory getKeyFactory(String name) throws + NoSuchAlgorithmException { + if (name.startsWith("secp")) { + name = "EC"; + } + return KeyFactory.getInstance(name); + } + + /** + * Returns a KEM instance for each side of the hybrid algorithm. + * For traditional key exchange algorithms, we use the DH-based KEM + * implementation provided by DHasKEM class. + * For ML-KEM post-quantum algorithms, we obtain a KEM instance + * with "ML-KEM". This is done to work with 3rd-party providers that + * only have "ML-KEM" KEM algorithm. + */ + private static KEM getKEM(String name) throws NoSuchAlgorithmException { + if (name.startsWith("secp") || name.equals("X25519")) { + return KEM.getInstance("DH", HybridProvider.PROVIDER); + } else { + return KEM.getInstance("ML-KEM"); + } + } + + public static class KeyPairGeneratorImpl extends KeyPairGeneratorSpi { + private final KeyPairGenerator left; + private final KeyPairGenerator right; + private final AlgorithmParameterSpec leftSpec; + private final AlgorithmParameterSpec rightSpec; + + public KeyPairGeneratorImpl(String leftAlg, String rightAlg) + throws NoSuchAlgorithmException { + left = getKeyPairGenerator(leftAlg); + right = getKeyPairGenerator(rightAlg); + leftSpec = getSpec(leftAlg); + rightSpec = getSpec(rightAlg); + } + + @Override + public void initialize(AlgorithmParameterSpec params, + SecureRandom random) + throws InvalidAlgorithmParameterException { + left.initialize(leftSpec, random); + right.initialize(rightSpec, random); + } + + @Override + public void initialize(int keysize, SecureRandom random) { + // NO-OP (do nothing) + } + + @Override + public KeyPair generateKeyPair() { + var kp1 = left.generateKeyPair(); + var kp2 = right.generateKeyPair(); + return new KeyPair( + new PublicKeyImpl("Hybrid", kp1.getPublic(), + kp2.getPublic()), + new PrivateKeyImpl("Hybrid", kp1.getPrivate(), + kp2.getPrivate())); + } + } + + public static class KeyFactoryImpl extends KeyFactorySpi { + private final KeyFactory left; + private final KeyFactory right; + private final int leftlen; + private final String leftname; + private final String rightname; + + public KeyFactoryImpl(String left, String right) + throws NoSuchAlgorithmException { + this.left = getKeyFactory(left); + this.right = getKeyFactory(right); + this.leftlen = leftPublicLength(left); + this.leftname = left; + this.rightname = right; + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) + throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec must not be null"); + } + + if (keySpec instanceof RawKeySpec rks) { + byte[] key = rks.getKeyArr(); + if (key == null) { + throw new InvalidKeySpecException( + "RawkeySpec contains null key data"); + } + if (key.length <= leftlen) { + throw new InvalidKeySpecException( + "Hybrid key length " + key.length + + " is too short and its left key length is " + + leftlen); + } + + byte[] leftKeyBytes = Arrays.copyOfRange(key, 0, leftlen); + byte[] rightKeyBytes = Arrays.copyOfRange(key, leftlen, + key.length); + PublicKey leftKey, rightKey; + + try { + if (leftname.startsWith("secp")) { + var curve = CurveDB.lookup(leftname); + var ecSpec = new ECPublicKeySpec( + ECUtil.decodePoint(leftKeyBytes, + curve.getCurve()), curve); + leftKey = left.generatePublic(ecSpec); + } else if (leftname.startsWith("ML-KEM")) { + leftKey = left.generatePublic(new RawKeySpec( + leftKeyBytes)); + } else { + throw new InvalidKeySpecException("Unsupported left" + + " algorithm" + leftname); + } + + if (rightname.equals("X25519")) { + ArrayUtil.reverse(rightKeyBytes); + var xecSpec = new XECPublicKeySpec( + new NamedParameterSpec(rightname), + new BigInteger(1, rightKeyBytes)); + rightKey = right.generatePublic(xecSpec); + } else if (rightname.startsWith("ML-KEM")) { + rightKey = right.generatePublic(new RawKeySpec( + rightKeyBytes)); + } else { + throw new InvalidKeySpecException("Unsupported right" + + " algorithm: " + rightname); + } + + return new PublicKeyImpl("Hybrid", leftKey, rightKey); + } catch (Exception e) { + throw new InvalidKeySpecException("Failed to decode " + + "hybrid key", e); + } + } + + throw new InvalidKeySpecException( + "KeySpec type:" + + keySpec.getClass().getName() + " not supported"); + } + + private static int leftPublicLength(String name) { + return switch (name.toLowerCase(Locale.ROOT)) { + case "secp256r1" -> 65; + case "secp384r1" -> 97; + case "ml-kem-768" -> 1184; + default -> throw new IllegalArgumentException( + "Unknown named group: " + name); + }; + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws + InvalidKeySpecException { + throw new UnsupportedOperationException(); + } + + @Override + protected T engineGetKeySpec(Key key, + Class keySpec) throws InvalidKeySpecException { + throw new UnsupportedOperationException(); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + throw new UnsupportedOperationException(); + } + } + + public static class KEMImpl implements KEMSpi { + private final KEM left; + private final KEM right; + + public KEMImpl(String left, String right) + throws NoSuchAlgorithmException { + this.left = getKEM(left); + this.right = getKEM(right); + } + + @Override + public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, + AlgorithmParameterSpec spec, SecureRandom secureRandom) throws + InvalidAlgorithmParameterException, InvalidKeyException { + if (publicKey instanceof PublicKeyImpl pk) { + return new Handler(left.newEncapsulator(pk.left, secureRandom), + right.newEncapsulator(pk.right, secureRandom), + null, null); + } + throw new InvalidKeyException(); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, + AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (privateKey instanceof PrivateKeyImpl pk) { + return new Handler(null, null, left.newDecapsulator(pk.left), + right.newDecapsulator(pk.right)); + } + throw new InvalidKeyException(); + } + } + + private static byte[] concat(byte[]... inputs) { + int outLen = 0; + for (byte[] in : inputs) { + outLen += in.length; + } + byte[] out = new byte[outLen]; + int pos = 0; + for (byte[] in : inputs) { + System.arraycopy(in, 0, out, pos, in.length); + pos += in.length; + } + return out; + } + + private record Handler(KEM.Encapsulator le, KEM.Encapsulator re, + KEM.Decapsulator ld, KEM.Decapsulator rd) + implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { + @Override + public KEM.Encapsulated engineEncapsulate(int from, int to, + String algorithm) { + int expectedSecretSize = engineSecretSize(); + if (!(from == 0 && to == expectedSecretSize)) { + throw new IllegalArgumentException( + "Invalid range for encapsulation: from = " + from + + " to = " + to + ", expected total secret size = " + + expectedSecretSize); + } + + var left = le.encapsulate(); + var right = re.encapsulate(); + return new KEM.Encapsulated( + new SecretKeyImpl(left.key(), right.key()), + concat(left.encapsulation(), right.encapsulation()), + null); + } + + @Override + public int engineSecretSize() { + if (le != null) { + return le.secretSize() + re.secretSize(); + } else { + return ld.secretSize() + rd.secretSize(); + } + } + + @Override + public int engineEncapsulationSize() { + if (le != null) { + return le.encapsulationSize() + re.encapsulationSize(); + } else { + return ld.encapsulationSize() + rd.encapsulationSize(); + } + } + + @Override + public SecretKey engineDecapsulate(byte[] encapsulation, int from, + int to, String algorithm) throws DecapsulateException { + int expectedEncSize = engineEncapsulationSize(); + if (encapsulation.length != expectedEncSize) { + throw new IllegalArgumentException( + "Invalid key encapsulation message length: " + + encapsulation.length + + ", expected = " + expectedEncSize); + } + + int expectedSecretSize = engineSecretSize(); + if (!(from == 0 && to == expectedSecretSize)) { + throw new IllegalArgumentException( + "Invalid range for decapsulation: from = " + from + + " to = " + to + ", expected total secret size = " + + expectedSecretSize); + } + + var left = Arrays.copyOf(encapsulation, ld.encapsulationSize()); + var right = Arrays.copyOfRange(encapsulation, + ld.encapsulationSize(), encapsulation.length); + return new SecretKeyImpl( + ld.decapsulate(left), + rd.decapsulate(right) + ); + } + } + + // Package-private + record SecretKeyImpl(SecretKey k1, SecretKey k2) + implements SecretKey { + @Override + public String getAlgorithm() { + return "Generic"; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return null; + } + } + + /** + * Hybrid public key combines two underlying public keys (left and right). + * Public keys can be transmitted/encoded because the hybrid protocol + * requires the public component to be sent. + */ + // Package-private + record PublicKeyImpl(String algorithm, PublicKey left, + PublicKey right) implements PublicKey { + @Override + public String getAlgorithm() { + return algorithm; + } + + // getFormat() returns "RAW" as hybrid key uses RAW concatenation + // of underlying encodings. + @Override + public String getFormat() { + return "RAW"; + } + + // getEncoded() returns the concatenation of the encoded bytes of the + // left and right public keys. + @Override + public byte[] getEncoded() { + return concat(onlyKey(left), onlyKey(right)); + } + + static byte[] onlyKey(PublicKey key) { + if (key instanceof X509Key xk) { + return xk.getKeyAsBytes(); + } + + // Fallback for 3rd-party providers + if (!"X.509".equalsIgnoreCase(key.getFormat())) { + throw new ProviderException("Invalid public key encoding " + + "format"); + } + var xk = new X509Key(); + try { + xk.decode(key.getEncoded()); + } catch (InvalidKeyException e) { + throw new ProviderException("Invalid public key encoding", e); + } + return xk.getKeyAsBytes(); + } + } + + /** + * Hybrid private key combines two underlying private keys (left and right). + * It is for internal use only. The private keys should never be exported. + */ + private record PrivateKeyImpl(String algorithm, PrivateKey left, + PrivateKey right) implements PrivateKey { + + @Override + public String getAlgorithm() { + return algorithm; + } + + // getFormat() returns null because there is no standard + // format for a hybrid private key. + @Override + public String getFormat() { + return null; + } + + // getEncoded() returns an empty byte array because there is no + // standard encoding format for a hybrid private key. + @Override + public byte[] getEncoded() { + return null; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/HybridProvider.java b/src/java.base/share/classes/sun/security/ssl/HybridProvider.java new file mode 100644 index 00000000000..c77d6f66273 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/HybridProvider.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package sun.security.ssl; + +import java.security.Provider; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Map; + +import static sun.security.util.SecurityConstants.PROVIDER_VER; + +// This is an internal provider used in the JSSE code for DH-as-KEM +// and Hybrid KEM support. It doesn't actually get installed in the +// system's list of security providers that is searched at runtime. +// JSSE loads this provider internally. +// It registers Hybrid KeyPairGenerator, KeyFactory, and KEM +// implementations for hybrid named groups as Provider services. + +public class HybridProvider { + + public static final Provider PROVIDER = new ProviderImpl(); + + private static final class ProviderImpl extends Provider { + @java.io.Serial + private static final long serialVersionUID = 0L; + + ProviderImpl() { + super("HybridAndDHAsKEM", PROVIDER_VER, + "Hybrid and DHAsKEM provider"); + put("KEM.DH", DHasKEM.class.getName()); + + // Hybrid KeyPairGenerator/KeyFactory/KEM + + // The order of shares in the concatenation for group name + // X25519MLKEM768 has been reversed as per the current + // draft RFC. + var attrs = Map.of("name", "X25519MLKEM768", "left", "ML-KEM-768", + "right", "X25519"); + putService(new HybridService(this, "KeyPairGenerator", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + + attrs = Map.of("name", "SecP256r1MLKEM768", "left", "secp256r1", + "right", "ML-KEM-768"); + putService(new HybridService(this, "KeyPairGenerator", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + + attrs = Map.of("name", "SecP384r1MLKEM1024", "left", "secp384r1", + "right", "ML-KEM-1024"); + putService(new HybridService(this, "KeyPairGenerator", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + } + } + + private static class HybridService extends Provider.Service { + + HybridService(Provider p, String type, String algo, String cn, + List aliases, Map attrs) { + super(p, type, algo, cn, aliases, attrs); + } + + @Override + public Object newInstance(Object ctrParamObj) + throws NoSuchAlgorithmException { + String type = getType(); + return switch (type) { + case "KeyPairGenerator" -> new Hybrid.KeyPairGeneratorImpl( + getAttribute("left"), getAttribute("right")); + case "KeyFactory" -> new Hybrid.KeyFactoryImpl( + getAttribute("left"), getAttribute("right")); + case "KEM" -> new Hybrid.KEMImpl( + getAttribute("left"), getAttribute("right")); + default -> throw new NoSuchAlgorithmException( + "Unexpected value: " + type); + }; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java index 623f83f547a..39e82b50435 100644 --- a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java @@ -24,7 +24,10 @@ */ package sun.security.ssl; +import sun.security.util.RawKeySpec; + import javax.crypto.KDF; +import javax.crypto.KEM; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; @@ -32,9 +35,11 @@ import javax.net.ssl.SSLHandshakeException; import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.Provider; import java.security.PublicKey; -import java.security.spec.AlgorithmParameterSpec; +import java.security.SecureRandom; import sun.security.util.KeyUtil; /** @@ -46,15 +51,32 @@ public class KAKeyDerivation implements SSLKeyDerivation { private final HandshakeContext context; private final PrivateKey localPrivateKey; private final PublicKey peerPublicKey; + private final byte[] keyshare; + private final Provider provider; + // Constructor called by Key Agreement KAKeyDerivation(String algorithmName, HandshakeContext context, PrivateKey localPrivateKey, PublicKey peerPublicKey) { + this(algorithmName, null, context, localPrivateKey, + peerPublicKey, null); + } + + // When the constructor called by KEM: store the client's public key or the + // encapsulated message in keyshare. + KAKeyDerivation(String algorithmName, + NamedGroup namedGroup, + HandshakeContext context, + PrivateKey localPrivateKey, + PublicKey peerPublicKey, + byte[] keyshare) { this.algorithmName = algorithmName; this.context = context; this.localPrivateKey = localPrivateKey; this.peerPublicKey = peerPublicKey; + this.keyshare = keyshare; + this.provider = (namedGroup != null) ? namedGroup.getProvider() : null; } @Override @@ -94,22 +116,15 @@ public class KAKeyDerivation implements SSLKeyDerivation { } } - /** - * Handle the TLSv1.3 objects, which use the HKDF algorithms. - */ - private SecretKey t13DeriveKey(String type) - throws IOException { - SecretKey sharedSecret = null; + private SecretKey deriveHandshakeSecret(String label, + SecretKey sharedSecret) + throws GeneralSecurityException, IOException { SecretKey earlySecret = null; SecretKey saltSecret = null; - try { - KeyAgreement ka = KeyAgreement.getInstance(algorithmName); - ka.init(localPrivateKey); - ka.doPhase(peerPublicKey, true); - sharedSecret = ka.generateSecret("TlsPremasterSecret"); - CipherSuite.HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; - SSLKeyDerivation kd = context.handshakeKeyDerivation; + CipherSuite.HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; + SSLKeyDerivation kd = context.handshakeKeyDerivation; + try { if (kd == null) { // No PSK is in use. // If PSK is not in use, Early Secret will still be // HKDF-Extract(0, 0). @@ -129,12 +144,90 @@ public class KAKeyDerivation implements SSLKeyDerivation { // the handshake secret key derivation (below) as it may not // work with the "sharedSecret" obj. KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm); - return hkdf.deriveKey(type, HKDFParameterSpec.ofExtract() - .addSalt(saltSecret).addIKM(sharedSecret).extractOnly()); + var spec = HKDFParameterSpec.ofExtract().addSalt(saltSecret); + if (sharedSecret instanceof Hybrid.SecretKeyImpl hsk) { + spec = spec.addIKM(hsk.k1()).addIKM(hsk.k2()); + } else { + spec = spec.addIKM(sharedSecret); + } + + return hkdf.deriveKey(label, spec.extractOnly()); + } finally { + KeyUtil.destroySecretKeys(earlySecret, saltSecret); + } + } + /** + * This method is called by the server to perform KEM encapsulation. + * It uses the client's public key (sent by the client as a keyshare) + * to encapsulate a shared secret and returns the encapsulated message. + * + * Package-private, used from KeyShareExtension.SHKeyShareProducer:: + * produce(). + */ + KEM.Encapsulated encapsulate(String algorithm, SecureRandom random) + throws IOException { + SecretKey sharedSecret = null; + + if (keyshare == null) { + throw new IOException("No keyshare available for KEM " + + "encapsulation"); + } + + try { + KeyFactory kf = (provider != null) ? + KeyFactory.getInstance(algorithmName, provider) : + KeyFactory.getInstance(algorithmName); + var pk = kf.generatePublic(new RawKeySpec(keyshare)); + + KEM kem = (provider != null) ? + KEM.getInstance(algorithmName, provider) : + KEM.getInstance(algorithmName); + KEM.Encapsulator e = kem.newEncapsulator(pk, random); + KEM.Encapsulated enc = e.encapsulate(); + sharedSecret = enc.key(); + + SecretKey derived = deriveHandshakeSecret(algorithm, sharedSecret); + + return new KEM.Encapsulated(derived, enc.encapsulation(), null); } catch (GeneralSecurityException gse) { throw new SSLHandshakeException("Could not generate secret", gse); } finally { - KeyUtil.destroySecretKeys(sharedSecret, earlySecret, saltSecret); + KeyUtil.destroySecretKeys(sharedSecret); + } + } + + /** + * Handle the TLSv1.3 objects, which use the HKDF algorithms. + */ + private SecretKey t13DeriveKey(String type) + throws IOException { + SecretKey sharedSecret = null; + + try { + if (keyshare != null) { + // Using KEM: called by the client after receiving the KEM + // ciphertext (keyshare) from the server in ServerHello. + // The client decapsulates it using its private key. + KEM kem = (provider != null) + ? KEM.getInstance(algorithmName, provider) + : KEM.getInstance(algorithmName); + var decapsulator = kem.newDecapsulator(localPrivateKey); + sharedSecret = decapsulator.decapsulate( + keyshare, 0, decapsulator.secretSize(), + "TlsPremasterSecret"); + } else { + // Using traditional DH-style Key Agreement + KeyAgreement ka = KeyAgreement.getInstance(algorithmName); + ka.init(localPrivateKey); + ka.doPhase(peerPublicKey, true); + sharedSecret = ka.generateSecret("TlsPremasterSecret"); + } + + return deriveHandshakeSecret(type, sharedSecret); + } catch (GeneralSecurityException gse) { + throw new SSLHandshakeException("Could not generate secret", gse); + } finally { + KeyUtil.destroySecretKeys(sharedSecret); } } } diff --git a/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java new file mode 100644 index 00000000000..fb8de6cb104 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package sun.security.ssl; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.NamedParameterSpec; +import javax.crypto.SecretKey; + +import sun.security.ssl.NamedGroup.NamedGroupSpec; +import sun.security.x509.X509Key; + +/** + * Specifics for single or hybrid Key exchanges based on KEM + */ +final class KEMKeyExchange { + + static final SSLKeyAgreementGenerator kemKAGenerator + = new KEMKAGenerator(); + + static final class KEMCredentials implements NamedGroupCredentials { + + final NamedGroup namedGroup; + // Unlike other credentials, we directly store the key share + // value here, no need to convert to a key + private final byte[] keyshare; + + KEMCredentials(byte[] keyshare, NamedGroup namedGroup) { + this.keyshare = keyshare; + this.namedGroup = namedGroup; + } + + // For KEM, server performs encapsulation and the resulting + // encapsulated message becomes the key_share value sent to + // the client. It is not a public key, so no PublicKey object + // to return. + @Override + public PublicKey getPublicKey() { + throw new UnsupportedOperationException( + "KEMCredentials stores raw keyshare, not a PublicKey"); + } + + public byte[] getKeyShare() { + return keyshare; + } + + @Override + public NamedGroup getNamedGroup() { + return namedGroup; + } + + /** + * Instantiates a KEMCredentials object + */ + static KEMCredentials valueOf(NamedGroup namedGroup, + byte[] encodedPoint) { + + if (namedGroup.spec != NamedGroupSpec.NAMED_GROUP_KEM) { + throw new RuntimeException( + "Credentials decoding: Not KEM named group"); + } + + if (encodedPoint == null || encodedPoint.length == 0) { + return null; + } + + return new KEMCredentials(encodedPoint, namedGroup); + } + } + + private static class KEMPossession implements SSLPossession { + private final NamedGroup namedGroup; + + public KEMPossession(NamedGroup ng) { + this.namedGroup = ng; + } + public NamedGroup getNamedGroup() { + return namedGroup; + } + } + + static final class KEMReceiverPossession extends KEMPossession { + + private final PrivateKey privateKey; + private final PublicKey publicKey; + + KEMReceiverPossession(NamedGroup namedGroup, SecureRandom random) { + super(namedGroup); + String algName = null; + try { + // For KEM: This receiver side (client) generates a key pair. + algName = ((NamedParameterSpec)namedGroup.keAlgParamSpec). + getName(); + Provider provider = namedGroup.getProvider(); + KeyPairGenerator kpg = (provider != null) ? + KeyPairGenerator.getInstance(algName, provider) : + KeyPairGenerator.getInstance(algName); + + kpg.initialize(namedGroup.keAlgParamSpec, random); + KeyPair kp = kpg.generateKeyPair(); + privateKey = kp.getPrivate(); + publicKey = kp.getPublic(); + } catch (GeneralSecurityException e) { + throw new RuntimeException( + "Could not generate keypair for algorithm: " + + algName, e); + } + } + + @Override + public byte[] encode() { + if (publicKey instanceof X509Key xk) { + return xk.getKeyAsBytes(); + } else if (publicKey instanceof Hybrid.PublicKeyImpl hk) { + return hk.getEncoded(); + } + throw new ProviderException("Unsupported key type: " + publicKey); + } + + // Package-private + PublicKey getPublicKey() { + return publicKey; + } + + // Package-private + PrivateKey getPrivateKey() { + return privateKey; + } + } + + static final class KEMSenderPossession extends KEMPossession { + + private SecretKey key; + private final SecureRandom random; + + KEMSenderPossession(NamedGroup namedGroup, SecureRandom random) { + super(namedGroup); + this.random = random; + } + + // Package-private + SecureRandom getRandom() { + return random; + } + + // Package-private + SecretKey getKey() { + return key; + } + + // Package-private + void setKey(SecretKey key) { + this.key = key; + } + + @Override + public byte[] encode() { + throw new UnsupportedOperationException("encode() not supported"); + } + } + + private static final class KEMKAGenerator + implements SSLKeyAgreementGenerator { + + // Prevent instantiation of this class. + private KEMKAGenerator() { + // blank + } + + @Override + public SSLKeyDerivation createKeyDerivation( + HandshakeContext context) throws IOException { + for (SSLPossession poss : context.handshakePossessions) { + if (poss instanceof KEMReceiverPossession kposs) { + NamedGroup ng = kposs.getNamedGroup(); + for (SSLCredentials cred : context.handshakeCredentials) { + if (cred instanceof KEMCredentials kcred && + ng.equals(kcred.namedGroup)) { + String name = ((NamedParameterSpec) + ng.keAlgParamSpec).getName(); + return new KAKeyDerivation(name, ng, context, + kposs.getPrivateKey(), null, + kcred.getKeyShare()); + } + } + } + } + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No suitable KEM key agreement " + + "parameters negotiated"); + return null; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java index 8d785f7515a..0d2cbb8f529 100644 --- a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java @@ -27,8 +27,11 @@ package sun.security.ssl; import java.io.IOException; import java.nio.ByteBuffer; +import java.security.AlgorithmConstraints; import java.security.CryptoPrimitive; import java.security.GeneralSecurityException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.NamedParameterSpec; import java.text.MessageFormat; import java.util.*; import javax.net.ssl.SSLProtocolException; @@ -297,7 +300,9 @@ final class KeyShareExtension { // update the context chc.handshakePossessions.add(pos); // May need more possession types in the future. - if (pos instanceof NamedGroupPossession) { + if (pos instanceof NamedGroupPossession || + pos instanceof + KEMKeyExchange.KEMReceiverPossession) { return pos.encode(); } } @@ -358,24 +363,16 @@ final class KeyShareExtension { try { SSLCredentials kaCred = ng.decodeCredentials(entry.keyExchange); - if (shc.algorithmConstraints != null && - kaCred instanceof - NamedGroupCredentials namedGroupCredentials) { - if (!shc.algorithmConstraints.permits( - EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), - namedGroupCredentials.getPublicKey())) { - if (SSLLogger.isOn() && - SSLLogger.isOn("ssl,handshake")) { - SSLLogger.warning( + + if (!isCredentialPermitted(shc.algorithmConstraints, + kaCred)) { + if (SSLLogger.isOn() && + SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( "key share entry of " + ng + " does not " + - " comply with algorithm constraints"); - } - - kaCred = null; + "comply with algorithm constraints"); } - } - - if (kaCred != null) { + } else { credentials.add(kaCred); } } catch (GeneralSecurityException ex) { @@ -513,7 +510,8 @@ final class KeyShareExtension { @Override public byte[] produce(ConnectionContext context, HandshakeMessage message) throws IOException { - // The producing happens in client side only. + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; // In response to key_share request only @@ -571,7 +569,9 @@ final class KeyShareExtension { SSLPossession[] poses = ke.createPossessions(shc); for (SSLPossession pos : poses) { - if (!(pos instanceof NamedGroupPossession)) { + if (!(pos instanceof NamedGroupPossession || + pos instanceof + KEMKeyExchange.KEMSenderPossession)) { // May need more possession types in the future. continue; } @@ -579,7 +579,34 @@ final class KeyShareExtension { // update the context shc.handshakeKeyExchange = ke; shc.handshakePossessions.add(pos); - keyShare = new KeyShareEntry(ng.id, pos.encode()); + + // For KEM, perform encapsulation using the clientโ€™s public + // key (KEMCredentials). The resulting encapsulated message + // becomes the key_share value sent to the client. The + // shared secret derived from encapsulation is stored in + // the KEMSenderPossession for later use in the TLS key + // schedule. + + // SSLKeyExchange.createPossessions() returns at most one + // key-agreement possession or one KEMSenderPossession + // per handshake. + if (pos instanceof KEMKeyExchange.KEMSenderPossession xp) { + if (cd instanceof KEMKeyExchange.KEMCredentials kcred + && ng.equals(kcred.namedGroup)) { + String name = ((NamedParameterSpec) + ng.keAlgParamSpec).getName(); + KAKeyDerivation handshakeKD = new KAKeyDerivation( + name, ng, shc, null, null, + kcred.getKeyShare()); + var encaped = handshakeKD.encapsulate( + "TlsHandshakeSecret", xp.getRandom()); + xp.setKey(encaped.key()); + keyShare = new KeyShareEntry(ng.id, + encaped.encapsulation()); + } + } else { + keyShare = new KeyShareEntry(ng.id, pos.encode()); + } break; } @@ -663,19 +690,13 @@ final class KeyShareExtension { try { SSLCredentials kaCred = ng.decodeCredentials(keyShare.keyExchange); - if (chc.algorithmConstraints != null && - kaCred instanceof - NamedGroupCredentials namedGroupCredentials) { - if (!chc.algorithmConstraints.permits( - EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), - namedGroupCredentials.getPublicKey())) { - chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY, - "key share entry of " + ng + " does not " + - " comply with algorithm constraints"); - } - } - if (kaCred != null) { + if (!isCredentialPermitted(chc.algorithmConstraints, + kaCred)) { + chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY, + "key share entry of " + ng + " does not " + + "comply with algorithm constraints"); + } else { credentials = kaCred; } } catch (GeneralSecurityException ex) { @@ -696,6 +717,34 @@ final class KeyShareExtension { } } + private static boolean isCredentialPermitted( + AlgorithmConstraints constraints, + SSLCredentials cred) { + + if (constraints == null) return true; + if (cred == null) return false; + + if (cred instanceof NamedGroupCredentials namedGroupCred) { + if (namedGroupCred instanceof KEMKeyExchange.KEMCredentials + kemCred) { + AlgorithmParameterSpec paramSpec = kemCred.getNamedGroup(). + keAlgParamSpec; + String algName = (paramSpec instanceof NamedParameterSpec nps) ? + nps.getName() : null; + return algName != null && constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + algName, + null); + } else { + return constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + namedGroupCred.getPublicKey()); + } + } + + return true; + } + /** * The absence processing if the extension is not present in * the ServerHello handshake message. diff --git a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java index 877236ebfad..abf973727f3 100644 --- a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java +++ b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java @@ -214,6 +214,39 @@ enum NamedGroup { ProtocolVersion.PROTOCOLS_TO_13, PredefinedDHParameterSpecs.ffdheParams.get(8192)), + ML_KEM_512(0x0200, "MLKEM512", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + ML_KEM_768(0x0201, "MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + ML_KEM_1024(0x0202, "MLKEM1024", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + X25519MLKEM768(0x11ec, "X25519MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.X25519_MLKEM768, + HybridProvider.PROVIDER), + + SECP256R1MLKEM768(0x11eb, "SecP256r1MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.SECP256R1_MLKEM768, + HybridProvider.PROVIDER), + + SECP384R1MLKEM1024(0x11ed, "SecP384r1MLKEM1024", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.SECP384R1_MLKEM1024, + HybridProvider.PROVIDER), + // Elliptic Curves (RFC 4492) // // arbitrary prime and characteristic-2 curves @@ -234,22 +267,33 @@ enum NamedGroup { final AlgorithmParameterSpec keAlgParamSpec; final AlgorithmParameters keAlgParams; final boolean isAvailable; + final Provider defaultProvider; // performance optimization private static final Set KEY_AGREEMENT_PRIMITIVE_SET = Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT)); - // Constructor used for all NamedGroup types NamedGroup(int id, String name, NamedGroupSpec namedGroupSpec, ProtocolVersion[] supportedProtocols, AlgorithmParameterSpec keAlgParamSpec) { + this(id, name, namedGroupSpec, supportedProtocols, keAlgParamSpec, + null); + } + + // Constructor used for all NamedGroup types + NamedGroup(int id, String name, + NamedGroupSpec namedGroupSpec, + ProtocolVersion[] supportedProtocols, + AlgorithmParameterSpec keAlgParamSpec, + Provider defaultProvider) { this.id = id; this.name = name; this.spec = namedGroupSpec; this.algorithm = namedGroupSpec.algorithm; this.supportedProtocols = supportedProtocols; this.keAlgParamSpec = keAlgParamSpec; + this.defaultProvider = defaultProvider; // Check if it is a supported named group. AlgorithmParameters algParams = null; @@ -266,16 +310,28 @@ enum NamedGroup { // Check the specific algorithm parameters. if (mediator) { try { - algParams = - AlgorithmParameters.getInstance(namedGroupSpec.algorithm); - algParams.init(keAlgParamSpec); + // Skip AlgorithmParameters for KEMs (not supported) + // Check KEM's availability via KeyFactory + if (namedGroupSpec == NamedGroupSpec.NAMED_GROUP_KEM) { + if (defaultProvider == null) { + KeyFactory.getInstance(name); + } else { + KeyFactory.getInstance(name, defaultProvider); + } + } else { + // ECDHE or others: use AlgorithmParameters as before + algParams = AlgorithmParameters.getInstance( + namedGroupSpec.algorithm); + algParams.init(keAlgParamSpec); + } } catch (InvalidParameterSpecException | NoSuchAlgorithmException exp) { if (namedGroupSpec != NamedGroupSpec.NAMED_GROUP_XDH) { mediator = false; if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( - "No AlgorithmParameters for " + name, exp); + "No AlgorithmParameters or KeyFactory for " + name, + exp); } } else { // Please remove the following code if the XDH/X25519/X448 @@ -307,6 +363,10 @@ enum NamedGroup { this.keAlgParams = mediator ? algParams : null; } + Provider getProvider() { + return defaultProvider; + } + // // The next set of methods search & retrieve NamedGroups. // @@ -545,6 +605,10 @@ enum NamedGroup { return spec.decodeCredentials(this, encoded); } + SSLPossession createPossession(boolean isClient, SecureRandom random) { + return spec.createPossession(this, isClient, random); + } + SSLPossession createPossession(SecureRandom random) { return spec.createPossession(this, random); } @@ -566,6 +630,11 @@ enum NamedGroup { SSLKeyDerivation createKeyDerivation( HandshakeContext hc) throws IOException; + + default SSLPossession createPossession(NamedGroup ng, boolean isClient, + SecureRandom random) { + return createPossession(ng, random); + } } enum NamedGroupSpec implements NamedGroupScheme { @@ -578,6 +647,10 @@ enum NamedGroup { // Finite Field Groups (XDH) NAMED_GROUP_XDH("XDH", XDHScheme.instance), + // Post-Quantum Cryptography (PQC) KEM groups + // Currently used for hybrid named groups + NAMED_GROUP_KEM("KEM", KEMScheme.instance), + // arbitrary prime and curves (ECDHE) NAMED_GROUP_ARBITRARY("EC", null), @@ -634,6 +707,15 @@ enum NamedGroup { return null; } + public SSLPossession createPossession( + NamedGroup ng, boolean isClient, SecureRandom random) { + if (scheme != null) { + return scheme.createPossession(ng, isClient, random); + } + + return null; + } + @Override public SSLPossession createPossession( NamedGroup ng, SecureRandom random) { @@ -739,6 +821,42 @@ enum NamedGroup { } } + private static class KEMScheme implements NamedGroupScheme { + private static final KEMScheme instance = new KEMScheme(); + + @Override + public byte[] encodePossessionPublicKey(NamedGroupPossession poss) { + return poss.encode(); + } + + @Override + public SSLCredentials decodeCredentials(NamedGroup ng, + byte[] encoded) throws IOException, GeneralSecurityException { + return KEMKeyExchange.KEMCredentials.valueOf(ng, encoded); + } + + @Override + public SSLPossession createPossession(NamedGroup ng, + SecureRandom random) { + // Must call createPossession with isClient + throw new UnsupportedOperationException(); + } + + @Override + public SSLPossession createPossession( + NamedGroup ng, boolean isClient, SecureRandom random) { + return isClient + ? new KEMKeyExchange.KEMReceiverPossession(ng, random) + : new KEMKeyExchange.KEMSenderPossession(ng, random); + } + + @Override + public SSLKeyDerivation createKeyDerivation( + HandshakeContext hc) throws IOException { + return KEMKeyExchange.kemKAGenerator.createKeyDerivation(hc); + } + } + static final class SupportedGroups { // the supported named groups, non-null immutable list static final String[] namedGroups; @@ -784,6 +902,9 @@ enum NamedGroup { } else { // default groups NamedGroup[] groups = new NamedGroup[] { + // Hybrid key agreement + X25519MLKEM768, + // Primary XDH (RFC 7748) curves X25519, diff --git a/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java index 22a44590ce3..263308f0659 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -570,7 +570,9 @@ final class SSLKeyExchange implements SSLKeyAgreementGenerator, @Override public SSLPossession createPossession(HandshakeContext hc) { - return namedGroup.createPossession(hc.sslContext.getSecureRandom()); + return namedGroup.createPossession( + hc instanceof ClientHandshakeContext, + hc.sslContext.getSecureRandom()); } @Override diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHello.java b/src/java.base/share/classes/sun/security/ssl/ServerHello.java index 903f43fa7eb..ad96c26e8c4 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java @@ -565,6 +565,34 @@ final class ServerHello { clientHello); shc.serverHelloRandom = shm.serverRandom; + // For key derivation, we will either use the traditional Key + // Agreement (KA) model or the Key Encapsulation Mechanism (KEM) + // model, depending on what key exchange group is used. + // + // For KA flows, the server first receives the client's share, + // then generates its key share, and finally comes here. + // However, this is changed for KEM: the server + // must perform both actions โ€” derive the secret and generate + // the key encapsulation message at the same time during + // encapsulation in SHKeyShareProducer. + // + // Traditional Key Agreement (KA): + // - Both peers generate a key share and exchange it. + // - Each peer computes a shared secret sometime after + // receiving the other's key share. + // + // Key Encapsulation Mechanism (KEM): + // The client publishes a public key via a KeyShareExtension, + // which the server uses to: + // + // - generate the shared secret + // - encapsulate the message which is sent to the client in + // another KeyShareExtension + // + // The derived shared secret must be stored in a + // KEMSenderPossession so it can be retrieved for handshake + // traffic secret derivation later. + // Produce extensions for ServerHello handshake message. SSLExtension[] serverHelloExtensions = shc.sslConfig.getEnabledExtensions( @@ -590,9 +618,26 @@ final class ServerHello { "Not negotiated key shares"); } - SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc); - SecretKey handshakeSecret = handshakeKD.deriveKey( - "TlsHandshakeSecret"); + SecretKey handshakeSecret = null; + + // For KEM, the shared secret has already been generated and + // stored in the serverโ€™s possession (KEMSenderPossession) + // during encapsulation in SHKeyShareProducer. + // + // Only one key share is selected by the server, so at most one + // possession will contain the pre-derived shared secret. + for (var pos : shc.handshakePossessions) { + if (pos instanceof KEMKeyExchange.KEMSenderPossession xp) { + handshakeSecret = xp.getKey(); + break; + } + } + + if (handshakeSecret == null) { + SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc); + handshakeSecret = handshakeKD.deriveKey( + "TlsHandshakeSecret"); + } SSLTrafficKeyDerivation kdg = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); diff --git a/src/java.base/share/classes/sun/security/x509/X509Key.java b/src/java.base/share/classes/sun/security/x509/X509Key.java index c83e06f651e..1cfe3f9d95d 100644 --- a/src/java.base/share/classes/sun/security/x509/X509Key.java +++ b/src/java.base/share/classes/sun/security/x509/X509Key.java @@ -104,6 +104,10 @@ public class X509Key implements PublicKey, DerEncoder { return (BitArray)bitStringKey.clone(); } + public byte[] getKeyAsBytes() { + return bitStringKey.toByteArray(); + } + /** * Construct X.509 subject public key from a DER value. If * the runtime environment is configured with a specific class for diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java index 979eeb36239..508b1a843ef 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -355,20 +355,9 @@ public final class CPrinterJob extends RasterPrinterJob { validateDestination(destinationAttr); } - /* Get the range of pages we are to print. If the - * last page to print is unknown, then we print to - * the end of the document. Note that firstPage - * and lastPage are 0 based page indices. - */ - + // Note that firstPage is 0 based page index. int firstPage = getFirstPage(); - int lastPage = getLastPage(); - if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) { - int totalPages = mDocument.getNumberOfPages(); - if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) { - lastPage = mDocument.getNumberOfPages() - 1; - } - } + int totalPages = mDocument.getNumberOfPages(); try { synchronized (this) { @@ -393,7 +382,7 @@ public final class CPrinterJob extends RasterPrinterJob { try { // Fire off the print rendering loop on the AppKit thread, and don't have // it wait and block this thread. - if (printLoop(false, firstPage, lastPage)) { + if (printLoop(false, firstPage, totalPages)) { // Start a secondary loop on EDT until printing operation is finished or cancelled printingLoop.enter(); } @@ -407,7 +396,7 @@ public final class CPrinterJob extends RasterPrinterJob { onEventThread = false; try { - printLoop(true, firstPage, lastPage); + printLoop(true, firstPage, totalPages); } catch (Exception e) { e.printStackTrace(); } @@ -417,7 +406,6 @@ public final class CPrinterJob extends RasterPrinterJob { } if (++loopi < prMembers.length) { firstPage = prMembers[loopi][0]-1; - lastPage = prMembers[loopi][1] -1; } } while (loopi < prMembers.length); } finally { @@ -693,7 +681,7 @@ public final class CPrinterJob extends RasterPrinterJob { } } - private native boolean printLoop(boolean waitUntilDone, int firstPage, int lastPage) throws PrinterException; + private native boolean printLoop(boolean waitUntilDone, int firstPage, int totalPages) throws PrinterException; private PageFormat getPageFormat(int pageIndex) { // This is called from the native side. diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m index 9cc0a18564f..555a2746f43 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -656,7 +656,7 @@ JNI_COCOA_EXIT(env); * Signature: ()V */ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CPrinterJob_printLoop - (JNIEnv *env, jobject jthis, jboolean blocks, jint firstPage, jint lastPage) + (JNIEnv *env, jobject jthis, jboolean blocks, jint firstPage, jint totalPages) { AWT_ASSERT_NOT_APPKIT_THREAD; @@ -672,14 +672,14 @@ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CPrinterJob_printLoop JNI_COCOA_ENTER(env); // Get the first page's PageFormat for setting things up (This introduces // and is a facet of the same problem in Radar 2818593/2708932). - jobject page = (*env)->CallObjectMethod(env, jthis, jm_getPageFormat, 0); // AWT_THREADING Safe (!appKit) + jobject page = (*env)->CallObjectMethod(env, jthis, jm_getPageFormat, firstPage); // AWT_THREADING Safe (!appKit) CHECK_EXCEPTION(); if (page != NULL) { jobject pageFormatArea = (*env)->CallObjectMethod(env, jthis, jm_getPageFormatArea, page); // AWT_THREADING Safe (!appKit) CHECK_EXCEPTION(); PrinterView* printerView = [[PrinterView alloc] initWithFrame:JavaToNSRect(env, pageFormatArea) withEnv:env withPrinterJob:jthis]; - [printerView setFirstPage:firstPage lastPage:lastPage]; + [printerView setTotalPages:totalPages]; GET_NSPRINTINFO_METHOD_RETURN(NO) NSPrintInfo* printInfo = (NSPrintInfo*)jlong_to_ptr((*env)->CallLongMethod(env, jthis, sjm_getNSPrintInfo)); // AWT_THREADING Safe (known object) diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h index 43472bee920..95a8055cdb0 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,12 +32,12 @@ jobject fCurPainter; jobject fCurPeekGraphics; - jint fFirstPage, fLastPage; + jint fTotalPages; } - (id)initWithFrame:(NSRect)aRect withEnv:(JNIEnv*)env withPrinterJob:(jobject)printerJob; -- (void)setFirstPage:(jint)firstPage lastPage:(jint)lastPage; +- (void)setTotalPages:(jint)totalPages; - (void)releaseReferences:(JNIEnv*)env; diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m index d19948d9f0f..f219e8082b4 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -72,9 +72,8 @@ static jclass sjc_PAbortEx = NULL; } } -- (void)setFirstPage:(jint)firstPage lastPage:(jint)lastPage { - fFirstPage = firstPage; - fLastPage = lastPage; +- (void)setTotalPages:(jint)totalPages { + fTotalPages = totalPages; } - (void)drawRect:(NSRect)aRect @@ -156,15 +155,15 @@ static jclass sjc_PAbortEx = NULL; return NO; } - aRange->location = fFirstPage + 1; + aRange->location = 1; - if (fLastPage == java_awt_print_Pageable_UNKNOWN_NUMBER_OF_PAGES) + if (fTotalPages == java_awt_print_Pageable_UNKNOWN_NUMBER_OF_PAGES) { aRange->length = NSIntegerMax; } else { - aRange->length = (fLastPage + 1) - fFirstPage; + aRange->length = fTotalPages; } return YES; diff --git a/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c b/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c index 0ea86e3e9b3..ebb3e32890e 100644 --- a/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c +++ b/src/java.desktop/unix/native/libawt_xawt/java2d/x11/XRBackendNative.c @@ -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 @@ -339,7 +339,7 @@ Java_sun_java2d_xr_XRBackendNative_createPixmap(JNIEnv *env, jobject this, JNIEXPORT jint JNICALL Java_sun_java2d_xr_XRBackendNative_createPictureNative (JNIEnv *env, jclass cls, jint drawable, jlong formatPtr) { - XRenderPictureAttributes pict_attr; + XRenderPictureAttributes pict_attr = {0}; return XRenderCreatePicture(awt_display, (Drawable) drawable, (XRenderPictFormat *) jlong_to_ptr(formatPtr), 0, &pict_attr); diff --git a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsIconFactory.java b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsIconFactory.java index 915a361a3a1..91c2cbcd61d 100644 --- a/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsIconFactory.java +++ b/src/java.desktop/windows/classes/com/sun/java/swing/plaf/windows/WindowsIconFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -905,10 +905,46 @@ public final class WindowsIconFactory implements Serializable XPStyle xp = XPStyle.getXP(); if (xp != null) { Skin skin = xp.getSkin(c, part); - if (icon == null || icon.getIconHeight() <= 16) { - skin.paintSkin(g, x + OFFSET, y + OFFSET, state); + if (WindowsGraphicsUtils.isLeftToRight(c)) { + if (icon == null || icon.getIconHeight() <= 16) { + skin.paintSkin(g, x + OFFSET, y + OFFSET, state); + } else { + skin.paintSkin(g, x + OFFSET, y + icon.getIconHeight() / 2, state); + } } else { - skin.paintSkin(g, x + OFFSET, y + icon.getIconHeight() / 2, state); + if (icon == null) { + skin.paintSkin(g, x + 4 * OFFSET, y + OFFSET, state); + } else { + int ycoord = (icon.getIconHeight() <= 16) + ? y + OFFSET + : (y + icon.getIconHeight() / 2); + if (icon.getIconWidth() <= 8) { + skin.paintSkin(g, x + OFFSET, ycoord, state); + } else if (icon.getIconWidth() <= 16) { + if (menuItem.getText().isEmpty()) { + skin.paintSkin(g, + (menuItem.getAccelerator() != null) + ? (x + 2 * OFFSET) : (x + 3 * OFFSET), + ycoord, state); + } else { + skin.paintSkin(g, + (type == JRadioButtonMenuItem.class) + ? (x + 4 * OFFSET) : (x + 3 * OFFSET), + ycoord, state); + } + } else { + if (menuItem.getText().isEmpty() + || menuItem.getAccelerator() != null) { + skin.paintSkin(g, + (type == JRadioButtonMenuItem.class) + ? (x + 3 * OFFSET) : (x + 4 * OFFSET), + ycoord, state); + } else { + skin.paintSkin(g, x + 7 * OFFSET, + ycoord, state); + } + } + } } } } diff --git a/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java b/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java index af8870ecf64..998e8d037b4 100644 --- a/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.java +++ b/src/jdk.attach/linux/classes/sun/tools/attach/VirtualMachineImpl.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 @@ -41,6 +41,8 @@ import java.util.regex.Pattern; import static java.nio.charset.StandardCharsets.UTF_8; +import sun.jvmstat.monitor.MonitoredHost; + /* * Linux implementation of HotSpotVirtualMachine */ @@ -228,7 +230,7 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { // Return the socket file for the given process. private File findSocketFile(long pid, long ns_pid) throws AttachNotSupportedException, IOException { - return new File(findTargetProcessTmpDirectory(pid, ns_pid), ".java_pid" + ns_pid); + return new File(findTargetProcessTmpDirectory(pid), ".java_pid" + ns_pid); } // On Linux a simple handshake is used to start the attach mechanism @@ -243,14 +245,14 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { // Do not canonicalize the file path, or we will fail to attach to a VM in a container. f.createNewFile(); } catch (IOException _) { - f = new File(findTargetProcessTmpDirectory(pid, ns_pid), fn.toString()); + f = new File(findTargetProcessTmpDirectory(pid), fn.toString()); f.createNewFile(); } return f; } - private String findTargetProcessTmpDirectory(long pid, long ns_pid) throws AttachNotSupportedException, IOException { - final var procPidRoot = PROC.resolve(Long.toString(pid)).resolve(ROOT_TMP); + private String findTargetProcessTmpDirectory(long pid) throws AttachNotSupportedException { + final var tmpOnProcPidRoot = PROC.resolve(Long.toString(pid)).resolve(ROOT_TMP); /* We need to handle at least 4 different cases: * 1. Caller and target processes share PID namespace and root filesystem (host to host or container to @@ -261,21 +263,44 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { * 4. Caller and target processes share neither PID namespace nor root filesystem (host to container) * * if target is elevated, we cant use /proc//... so we have to fallback to /tmp, but that may not be shared - * with the target/attachee process, we can try, except in the case where the ns_pid also exists in this pid ns - * which is ambiguous, if we share /tmp with the intended target, the attach will succeed, if we do not, - * then we will potentially attempt to attach to some arbitrary process with the same pid (in this pid ns) - * as that of the intended target (in its * pid ns). + * with the target/attachee process, so we should check whether /tmp on both is same. This method would throw + * AttachNotSupportedException if they are different because we cannot make a connection with target VM. * - * so in that case we should prehaps throw - or risk sending SIGQUIT to some arbitrary process... which could kill it - * - * however we can also check the target pid's signal masks to see if it catches SIGQUIT and only do so if in + * In addition, we can also check the target pid's signal masks to see if it catches SIGQUIT and only do so if in * fact it does ... this reduces the risk of killing an innocent process in the current ns as opposed to * attaching to the actual target JVM ... c.f: checkCatchesAndSendQuitTo() below. - * - * note that if pid == ns_pid we are in a shared pid ns with the target and may (potentially) share /tmp */ - return (Files.isWritable(procPidRoot) ? procPidRoot : TMPDIR).toString(); + try { + if (Files.isWritable(tmpOnProcPidRoot)) { + return tmpOnProcPidRoot.toString(); + } else if (Files.isSameFile(tmpOnProcPidRoot, TMPDIR)) { + return TMPDIR.toString(); + } else { + throw new AttachNotSupportedException("Unable to access the filesystem of the target process"); + } + } catch (IOException ioe) { + try { + boolean found = MonitoredHost.getMonitoredHost("//localhost") + .activeVms() + .stream() + .anyMatch(i -> pid == i.intValue()); + if (found) { + // We can use /tmp because target process is on same host + // even if we cannot access /proc//root. + // The process with capsh/setcap would fall this pattern. + return TMPDIR.toString(); + } else { + throw new AttachNotSupportedException("Unable to access the filesystem of the target process", ioe); + } + } catch (AttachNotSupportedException e) { + // AttachNotSupportedException happened in above should go through + throw e; + } catch (Exception e) { + // Other exceptions would be wrapped with AttachNotSupportedException + throw new AttachNotSupportedException("Unable to access the filesystem of the target process", e); + } + } } // Return the inner most namespaced PID if there is one, diff --git a/src/jdk.attach/share/classes/com/sun/tools/attach/AttachNotSupportedException.java b/src/jdk.attach/share/classes/com/sun/tools/attach/AttachNotSupportedException.java index 382c8a57b26..725db3e7732 100644 --- a/src/jdk.attach/share/classes/com/sun/tools/attach/AttachNotSupportedException.java +++ b/src/jdk.attach/share/classes/com/sun/tools/attach/AttachNotSupportedException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, 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 @@ -62,4 +62,17 @@ public class AttachNotSupportedException extends Exception { super(s); } + /** + * Constructs an AttachNotSupportedException with + * the specified cause. + * + * @param message the detail message. + * @param cause the cause of this exception. + * + * @since 27 + */ + public AttachNotSupportedException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/test/hotspot/gtest/opto/test_rangeinference.cpp b/test/hotspot/gtest/opto/test_rangeinference.cpp index 42767e9fcab..fd49050d022 100644 --- a/test/hotspot/gtest/opto/test_rangeinference.cpp +++ b/test/hotspot/gtest/opto/test_rangeinference.cpp @@ -216,13 +216,13 @@ TEST_VM(opto, canonicalize_constraints) { // Implementations of TypeIntMirror methods for testing purposes template -const TypeIntMirror* TypeIntMirror::operator->() const { - return this; +TypeIntMirror TypeIntMirror::make(const TypeIntMirror& t, int widen) { + return t; } template -TypeIntMirror TypeIntMirror::meet(const TypeIntMirror& o) const { - return TypeIntHelper::int_type_union(*this, o); +const TypeIntMirror* TypeIntMirror::operator->() const { + return this; } template diff --git a/test/hotspot/jtreg/compiler/ccp/TestWrongXorIWiden.java b/test/hotspot/jtreg/compiler/ccp/TestWrongXorIWiden.java new file mode 100644 index 00000000000..3b058758beb --- /dev/null +++ b/test/hotspot/jtreg/compiler/ccp/TestWrongXorIWiden.java @@ -0,0 +1,54 @@ +/* + * 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.ccp; + +/* + * @test + * @bug 8374180 + * @summary Test that _widen is set correctly in XorI::add_ring() to ensure monotonicity. + * @run main/othervm -XX:CompileCommand=compileonly,${test.main.class}::* -Xcomp ${test.main.class} + */ +public class TestWrongXorIWiden { + static byte byFld; + + public static void main(String[] strArr) { + test(); + } + + static void test() { + int k, i17 = 0; + long lArr[] = new long[400]; + for (int i = 9; i < 54; ++i) { + for (int j = 7; j > 1; j--) { + for (k = 1; k < 2; k++) { + i17 >>= i; + } + byFld += j ^ i17; + for (int a = 1; a < 2; a++) { + i17 = k; + } + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/AbstractInfo.java b/test/hotspot/jtreg/compiler/lib/ir_framework/AbstractInfo.java index 20a865b1337..b57b209112c 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/AbstractInfo.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/AbstractInfo.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 @@ -103,7 +103,7 @@ abstract public class AbstractInfo { } /** - * Returns a boolean indicating if the test VM runs with flags that allow C2 compilations. + * Returns a boolean indicating if the Test VM runs with flags that allow C2 compilations. * * @return {@code true} if C2 compilations are allowed; * {@code false} otherwise (run with {@code -XX:TieredStopAtLevel={1,2,3}, -XX:-UseCompiler}). diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/CompLevel.java b/test/hotspot/jtreg/compiler/lib/ir_framework/CompLevel.java index b6e55c0f617..f8760b4dfad 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/CompLevel.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/CompLevel.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 @@ -51,7 +51,7 @@ public enum CompLevel { * Can only be used at {@link Test#compLevel()}. After the warm-up, the framework keeps invoking the test over a span * of 10s (configurable by setting the property flag {@code -DWaitForCompilationTimeout}) until HotSpot compiles the * {@link Test} method. If the method was not compiled after 10s, an exception is thrown. The framework does not wait - * for the compilation if the test VM is run with {@code -Xcomp}, {@code -XX:-UseCompiler}, or + * for the compilation if the Test VM is run with {@code -Xcomp}, {@code -XX:-UseCompiler}, or * {@code -DExcludeRandom=true}. */ WAIT_FOR_COMPILATION(-4), diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IR.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IR.java index fd2cd69056a..96df71dacd7 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IR.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IR.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, 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 @@ -55,7 +55,7 @@ import java.lang.annotation.RetentionPolicy; * For any other flag specified either by user code (e.g. {@link Scenario#Scenario(int, String...)}, * {@link TestFramework#runWithFlags(String...) etc.} or as part of the JTreg whitelist, IR verification is applied. * To restrict the application of IR rules when certain flags are present that could change the IR, each {@code @IR} - * annotation can specify additional preconditions on the allowed test VM flags that must hold when an IR rule is applied. + * annotation can specify additional preconditions on the allowed Test VM flags that must hold when an IR rule is applied. * If the specified preconditions fail, then the framework does not apply the IR rule. These preconditions can be * set with {@link #applyIf()}, {@link #applyIfNot()}, {@link #applyIfAnd()}, or {@link #applyIfOr()}. *

diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md index c0df8b54658..da59bd61b13 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md @@ -115,7 +115,7 @@ The [@IR](./IR.java) annotation provides two kinds of checks: - `counts`: A list of one or more "IR node/user-defined regex - counter" pairs which specify how often each IR node/user-defined regex should be matched on the compilation output of each compile phase. #### Disable/Enable IR Rules based on VM Flags -One might also want to restrict the application of certain `@IR` rules depending on the used flags in the test VM. These could be flags defined by the user or by JTreg. In the latter case, the flags must be whitelisted in `JTREG_WHITELIST_FLAGS` in [TestFramework](./TestFramework.java) (i.e. have no unexpected impact on the IR except if the flag simulates a specific machine setup like `UseAVX={1,2,3}` etc.) to enable an IR verification by the framework. The `@IR` rules thus have an option to restrict their application: +One might also want to restrict the application of certain `@IR` rules depending on the used flags in the Test VM. These could be flags defined by the user or by JTreg. In the latter case, the flags must be whitelisted in `JTREG_WHITELIST_FLAGS` in [TestFramework](./TestFramework.java) (i.e. have no unexpected impact on the IR except if the flag simulates a specific machine setup like `UseAVX={1,2,3}` etc.) to enable an IR verification by the framework. The `@IR` rules thus have an option to restrict their application: - `applyIf`: Only apply a rule if a flag has the specified value/range of values. - `applyIfNot`: Only apply a rule if a flag has **not** a specified value/range of values @@ -144,7 +144,7 @@ An IR verification cannot always be performed. Certain VM flags explicitly disab More information about IR matching can be found in the Javadocs of [IR](./IR.java). Concrete examples on how to specify IR constraint/rules can be found in [IRExample](../../../testlibrary_tests/ir_framework/examples/IRExample.java), [TestIRMatching](../../../testlibrary_tests/ir_framework/tests/TestIRMatching.java) (internal framework test), and [TestPhaseIRMatching](../../../testlibrary_tests/ir_framework/tests/TestPhaseIRMatching.java) (internal framework test). ### 2.3 Test VM Flags and Scenarios -The recommended way to use the framework is by defining a single `@run driver` statement in the JTreg header which, however, does not allow the specification of additional test VM flags. Instead, the user has the possibility to provide VM flags by calling `TestFramework.runWithFlags()` or by creating a `TestFramework` builder object on which `addFlags()` can be called. +The recommended way to use the framework is by defining a single `@run driver` statement in the JTreg header which, however, does not allow the specification of additional Test VM flags. Instead, the user has the possibility to provide VM flags by calling `TestFramework.runWithFlags()` or by creating a `TestFramework` builder object on which `addFlags()` can be called. If a user wants to provide multiple flag combinations for a single test, he or she has the option to provide different scenarios. A scenario based flag will always have precedence over other user defined flags. More information about scenarios can be found in the Javadocs of [Scenario](./Scenario.java). If a user wants to test all combinations of multiple sets of flags, they can use `TestFramework.addCrossProductScenarios()`. @@ -174,16 +174,16 @@ The framework provides various stress and debug flags. They should mainly be use - `-DExclude=test3`: Provide a list of `@Test` method names which should be excluded from execution. - `-DScenarios=1,2`: Provide a list of scenario indexes to specify which scenarios should be executed. - `-DWarmup=200`: Provide a new default value of the number of warm-up iterations (framework default is 2000). This might have an influence on the resulting IR and could lead to matching failures (the user can also set a fixed default warm-up value in a test with `testFrameworkObject.setDefaultWarmup(200)`). -- `-DReportStdout=true`: Print the standard output of the test VM. +- `-DReportStdout=true`: Print the standard output of the Test VM. - `-DVerbose=true`: Enable more fine-grained logging (slows the execution down). -- `-DReproduce=true`: Flag to use when directly running a test VM to bypass dependencies to the driver VM state (for example, when reproducing an issue). +- `-DReproduce=true`: Flag to use when directly running a Test VM to bypass dependencies to the Driver VM state (for example, when reproducing an issue). - `-DPrintTimes=true`: Print the execution time measurements of each executed test. - `-DPrintRuleMatchingTime=true`: Print the time of matching IR rules per method. Slows down the execution as the rules are warmed up before measurement. -- `-DVerifyVM=true`: The framework runs the test VM with additional verification flags (slows the execution down). +- `-DVerifyVM=true`: The framework runs the Test VM with additional verification flags (slows the execution down). - `-DExcludeRandom=true`: The framework randomly excludes some methods from compilation. IR verification is disabled completely with this flag. - `-DFlipC1C2=true`: The framework compiles all `@Test` annotated method with C1 if a C2 compilation would have been applied and vice versa. IR verification is disabled completely with this flag. - `-DShuffleTests=false`: Disables the random execution order of all tests (such a shuffling is always done by default). -- `-DDumpReplay=true`: Add the `DumpReplay` directive to the test VM. +- `-DDumpReplay=true`: Add the `DumpReplay` directive to the Test VM. - `-DGCAfter=true`: Perform `System.gc()` after each test (slows the execution down). - `-DTestCompilationTimeout=20`: Change the default waiting time (default: 10s) for a compilation of a normal `@Test` annotated method. - `-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). @@ -193,12 +193,12 @@ The framework provides various stress and debug flags. They should mainly be use ## 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. -The framework will spawn a new "test VM" to execute the user defined tests. The test VM collects all tests of the test class specified by the user code in `main()` and ensures that there is no violation of the required format by the framework. In a next step, the framework does the following for each test in general: +The framework will spawn a new "Test VM" to execute the user defined tests. The Test VM collects all tests of the test class specified by the user code in `main()` and ensures that there is no violation of the required format by the framework. In a next step, the framework does the following for each test in general: 1. Warm the test up for a predefined number of times (default 2000). This can also be adapted for all tests by using `testFrameworkobject.setDefaultWarmup(100)` or for individual tests with an additional [@Warmup](./Warmup.java) annotation. 2. After the warm-up is finished, the framework compiles the associated `@Test` annotated method at the specified compilation level (default: C2). 3. After the compilation, the test is invoked one more time. -Once the test VM terminates, IR verification (if possible) is performed on the output of the test VM. If any test throws an exception during its execution or if IR matching fails, the failures are collected and reported in a pretty format. Check the standard error and output for more information and how to reproduce these failures. +Once the Test VM terminates, IR verification (if possible) is performed on the output of the Test VM. If any test throws an exception during its execution or if IR matching fails, the failures are collected and reported in a pretty format. Check the standard error and output for more information and how to reproduce these failures. Some of the steps above can be different due to the kind of the test or due to using non-default annotation properties. These details and differences are described in the Javadocs for the three tests (see section 2.1 Different Tests). @@ -212,10 +212,10 @@ Additional testing was performed by converting all compiler Inline Types tests t ## 5. Framework Package Structure A user only needs to import classes from the package `compiler.lib.ir_framework` (e.g. `import compiler.lib.ir_framework.*;`) which represents the interface classes to the framework. The remaining framework internal classes are kept in separate subpackages and should not directly be imported: -- `compiler.lib.ir_framework.driver`: These classes are used while running the driver VM (same VM as the one running the user code's `main()` method of a JTreg test). -- `compiler.lib.ir_framework.flag`: These classes are used while running the flag VM to determine additional flags for the test VM which are required for IR verification. -- `compiler.lib.ir_framework.test`: These classes are used while running the test VM (i.e. the actual execution of the user tests as described in section 3). -- `compiler.lib.ir_framework.shared`: These classes can be called from either the driver, flag, or test VM. +- `compiler.lib.ir_framework.driver`: These classes are used while running the Driver VM (same VM as the one running the user code's `main()` method of a JTreg test). +- `compiler.lib.ir_framework.flag`: These classes are used while running the Flag VM to determine additional flags for the Test VM which are required for IR verification. +- `compiler.lib.ir_framework.test`: These classes are used while running the Test VM (i.e. the actual execution of the user tests as described in section 3). +- `compiler.lib.ir_framework.shared`: These classes can be called from either the driver, flag, or Test VM. ## 6. Summary The initial design and feature set was kept simple and straight forward and serves well for small to medium sized tests. There are a lot of possibilities to further enhance the framework and make it more powerful. This can be tackled in additional RFEs. A few ideas can be found as subtasks of the [initial RFE](https://bugs.openjdk.org/browse/JDK-8254129) for this framework. diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/Scenario.java b/test/hotspot/jtreg/compiler/lib/ir_framework/Scenario.java index 17776f7285c..65f61173e2a 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/Scenario.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/Scenario.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 @@ -121,16 +121,16 @@ public class Scenario { } /** - * Get the test VM output (stdout + stderr) of this scenario from the last execution of the framework. + * Get the Test VM output (stdout + stderr) of this scenario from the last execution of the framework. * - * @return the test VM output. + * @return the Test VM output. */ public String getTestVMOutput() { return testVMOutput; } /** - * Set the test VM output, called by the framework. + * Set the Test VM output, called by the framework. */ void setTestVMOutput(String testVMOutput) { this.testVMOutput = testVMOutput; diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java index 09e291ce5a4..1fea5da52ef 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.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 @@ -85,19 +85,19 @@ import java.util.stream.Stream; * {@code runXX()} methods of {@link TestFramework}. The second way, which gives more control, is to create a new * {@code TestFramework} builder object on which {@link #start()} needs to be eventually called to start the testing. *

- * The framework is called from the driver VM in which the JTreg test is initially run by specifying {@code + * The framework is called from the Driver VM in which the JTreg test is initially run by specifying {@code * @run driver} in the JTreg header. This strips all additionally specified JTreg VM and Javaoptions. - * The framework creates a new flag VM with all these flags added again in order to figure out which flags are + * The framework creates a new Flag VM with all these flags added again in order to figure out which flags are * required to run the tests specified in the test class (e.g. {@code -XX:+PrintIdeal} and {@code -XX:+PrintOptoAssembly} * for IR matching). *

- * After the flag VM terminates, it starts a new test VM which performs the execution of the specified + * After the Flag VM terminates, it starts a new Test VM which performs the execution of the specified * tests in the test class as described in {@link Test}, {@link Check}, and {@link Run}. *

- * In a last step, once the test VM has terminated without exceptions, IR matching is performed if there are any IR + * In a last step, once the Test VM has terminated without exceptions, IR matching is performed if there are any IR * rules and if no VM flags disable it (e.g. not running with {@code -Xint}, see {@link IR} for more details). * The IR regex matching is done on the output of {@code -XX:+PrintIdeal} and {@code -XX:+PrintOptoAssembly} by parsing - * the hotspot_pid file of the test VM. Failing IR rules are reported by throwing a {@link IRViolationException}. + * the hotspot_pid file of the Test VM. Failing IR rules are reported by throwing a {@link IRViolationException}. * * @see Test * @see Check @@ -166,7 +166,7 @@ public class TestFramework { ############################################################# - To only run the failed tests use -DTest, -DExclude, and/or -DScenarios. - - To also get the standard output of the test VM run with + - To also get the standard output of the Test VM run with -DReportStdout=true or for even more fine-grained logging use -DVerbose=true. ############################################################# @@ -236,10 +236,10 @@ public class TestFramework { } /** - * Tests the class from which this method was invoked from. The test VM is called with the specified {@code flags}. + * Tests the class from which this method was invoked from. The Test VM is called with the specified {@code flags}. *

    *
  • The {@code flags} override any set VM or Javaoptions flags by JTreg by default.

    - * Use {@code -DPreferCommandLineFlags=true} if you want to prefer the JTreg VM and Javaoptions flags over + * Use {@code -DPreferCommandLineFlags=true} if you want to prefer the JTreg VM and Javaoptions flags over * the specified {@code flags} of this method.

  • *
  • If you want to run your entire JTreg test with additional flags, use this method.

  • *
  • If you want to run your entire JTreg test with additional flags but for another test class then the one @@ -248,7 +248,7 @@ public class TestFramework { * {@link #addScenarios(Scenario...)}

  • *
* - * @param flags VM flags to be used for the test VM. + * @param flags VM flags to be used for the Test VM. */ public static void runWithFlags(String... flags) { StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); @@ -258,13 +258,13 @@ public class TestFramework { } /** - * Add VM flags to be used for the test VM. These flags override any VM or Javaoptions set by JTreg by default.

+ * Add VM flags to be used for the Test VM. These flags override any VM or Javaoptions set by JTreg by default.

* Use {@code -DPreferCommandLineFlags=true} if you want to prefer the VM or Javaoptions over the scenario flags. * *

* The testing can be started by invoking {@link #start()} * - * @param flags VM options to be applied to the test VM. + * @param flags VM options to be applied to the Test VM. * @return the same framework instance. */ public TestFramework addFlags(String... flags) { @@ -306,7 +306,7 @@ public class TestFramework { } /** - * Add scenarios to be used for the test VM. A test VM is called for each scenario in {@code scenarios} by using the + * Add scenarios to be used for the Test VM. A Test VM is called for each scenario in {@code scenarios} by using the * specified VM flags in the scenario. The scenario flags override any flags set by {@link #addFlags(String...)} * and thus also override any VM or Javaoptions set by JTreg by default.

* Use {@code -DPreferCommandLineFlags=true} if you want to prefer the VM and Javaoptions over the scenario flags. @@ -314,7 +314,7 @@ public class TestFramework { *

* The testing can be started by invoking {@link #start()} * - * @param scenarios scenarios which specify specific flags for the test VM. + * @param scenarios scenarios which specify specific flags for the Test VM. * @return the same framework instance. */ public TestFramework addScenarios(Scenario... scenarios) { @@ -503,10 +503,10 @@ public class TestFramework { } /** - * Get the VM output of the test VM. Use {@code -DVerbose=true} to enable more debug information. If scenarios + * Get the VM output of the Test VM. Use {@code -DVerbose=true} to enable more debug information. If scenarios * were run, use {@link Scenario#getTestVMOutput()}. * - * @return the last test VM output. + * @return the last Test VM output. */ public static String getLastTestVMOutput() { return TestVMProcess.getLastTestVMOutput(); @@ -796,9 +796,9 @@ public class TestFramework { } /** - * Execute a separate "flag" VM with White Box access to determine all test VM flags. The flag VM sends an encoding of - * all required flags for the test VM to the driver VM over a socket. Once the flag VM exits, this driver VM parses the - * test VM flags, which also determine if IR matching should be done, and then starts the test VM to execute all tests. + * Execute a separate Flag VM with White Box access to determine all Test VM flags. The Flag VM sends an encoding of + * all required flags for the Test VM to the Driver VM over a socket. Once the Flag VM exits, this Driver VM parses the + * Test VM flags, which also determine if IR matching should be done, and then starts the Test VM to execute all tests. */ private void start(Scenario scenario) { if (scenario != null && !scenario.isEnabled()) { @@ -823,12 +823,12 @@ public class TestFramework { "" : " - [" + String.join(", ", additionalFlags) + "]"; if (shouldVerifyIR) { - // Only need to use flag VM if an IR verification is possibly done. + // Only need to use Flag VM if an IR verification is possibly done. System.out.println("Run Flag VM:"); FlagVMProcess flagVMProcess = new FlagVMProcess(testClass, additionalFlags); shouldVerifyIR = flagVMProcess.shouldVerifyIR(); if (shouldVerifyIR) { - // Add more flags for the test VM which are required to do IR verification. + // Add more flags for the Test VM which are required to do IR verification. additionalFlags.addAll(flagVMProcess.getTestVMFlags()); } // else: Flag VM found a reason to not do IR verification. } else { @@ -882,7 +882,7 @@ public class TestFramework { try { TestClassParser testClassParser = new TestClassParser(testClass, isAllowNotCompilable); Matchable testClassMatchable = testClassParser.parse(testVMProcess.getHotspotPidFileName(), - testVMProcess.getIrEncoding()); + testVMProcess.getApplicableIRRules()); IRMatcher matcher = new IRMatcher(testClassMatchable); matcher.match(); } catch (IRViolationException e) { @@ -892,7 +892,7 @@ public class TestFramework { } else { System.out.println("IR verification disabled either due to no @IR annotations, through explicitly setting " + "-DVerify=false, due to not running a debug build, using a non-whitelisted JTreg VM or " + - "Javaopts flag like -Xint, or running the test VM with other VM flags added by user code " + + "Javaopts flag like -Xint, or running the Test VM with other VM flags added by user code " + "that make the IR verification impossible (e.g. -XX:-UseCompile, " + "-XX:TieredStopAtLevel=[1,2,3], etc.)."); } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/FlagVMProcess.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/FlagVMProcess.java index 0b7d1db125c..86eb1935207 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/FlagVMProcess.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/FlagVMProcess.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 @@ -41,9 +41,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * This class prepares, creates, and runs the "flag" VM with verification of proper termination. The flag VM determines - * the flags required for the "test" VM. The flag VM writes these flags to a dedicated file which is then parsed by this - * class after the termination of the flag VM. + * This class prepares, creates, and runs the Flag VM with verification of proper termination. The Flag VM determines + * the flags required for the Test VM. The Flag VM writes these flags to a dedicated file which is then parsed by this + * class after the termination of the Flag VM. * * @see FlagVM */ @@ -73,7 +73,7 @@ public class FlagVMProcess { String patternString = "(.*DShouldDoIRVerification=(true|false).*)"; Pattern pattern = Pattern.compile(patternString); Matcher matcher = pattern.matcher(flags); - TestFramework.check(matcher.find(), "Invalid flag encoding emitted by flag VM"); + TestFramework.check(matcher.find(), "Invalid flag encoding emitted by Flag VM"); // Maybe we run with flags that make IR verification impossible shouldVerifyIR = Boolean.parseBoolean(matcher.group(2)); testVMFlags.addAll(Arrays.asList(matcher.group(1).split(FlagVM.TEST_VM_FLAGS_DELIMITER))); @@ -91,8 +91,8 @@ public class FlagVMProcess { } /** - * The flag VM needs White Box access to prepare all test VM flags. The flag VM will write the test VM flags to - * a dedicated file which is afterwards parsed by the driver VM and added as flags to the test VM. + * The Flag VM needs White Box access to prepare all Test VM flags. The Flag VM will write the Test VM flags to + * a dedicated file which is afterwards parsed by the Driver VM and added as flags to the Test VM. */ private void prepareVMFlags(Class testClass, List additionalFlags) { cmds.add("-Dtest.jdk=" + Utils.TEST_JDK); @@ -103,7 +103,7 @@ public class FlagVMProcess { cmds.add("-Xbootclasspath/a:."); cmds.add("-XX:+UnlockDiagnosticVMOptions"); cmds.add("-XX:+WhiteBoxAPI"); - // TestFramework and scenario flags might have an influence on the later used test VM flags. Add them as well. + // TestFramework and scenario flags might have an influence on the later used Test VM flags. Add them as well. cmds.addAll(additionalFlags); cmds.add(FlagVM.class.getCanonicalName()); cmds.add(testClass.getCanonicalName()); @@ -111,10 +111,10 @@ public class FlagVMProcess { private void start() { try { - // Run "flag" VM with White Box access to determine the test VM flags and if IR verification should be done. + // Run Flag VM with White Box access to determine the Test VM flags and if IR verification should be done. oa = ProcessTools.executeTestJava(cmds); } catch (Exception e) { - throw new TestRunException("Failed to execute TestFramework flag VM", e); + throw new TestRunException("Failed to execute TestFramework Flag VM", e); } testVMFlagsFile = FlagVM.TEST_VM_FLAGS_FILE_PREFIX + oa.pid() + FlagVM.FILE_POSTFIX; @@ -125,14 +125,14 @@ public class FlagVMProcess { String flagVMOutput = oa.getOutput(); int exitCode = oa.getExitValue(); if (VERBOSE && exitCode == 0) { - System.out.println("--- OUTPUT TestFramework flag VM ---"); + System.out.println("--- OUTPUT TestFramework Flag VM ---"); System.out.println(flagVMOutput); } if (exitCode != 0) { - System.err.println("--- OUTPUT TestFramework flag VM ---"); + System.err.println("--- OUTPUT TestFramework Flag VM ---"); System.err.println(flagVMOutput); - throw new RuntimeException("TestFramework flag VM exited with " + exitCode); + throw new RuntimeException("TestFramework Flag VM exited with " + exitCode); } } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/TestVMException.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/TestVMException.java index 63b3d522b86..8de9ac8e348 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/TestVMException.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/TestVMException.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 @@ -26,7 +26,7 @@ package compiler.lib.ir_framework.driver; import compiler.lib.ir_framework.shared.TestFormatException; /** - * Exception that is thrown if the test VM has thrown any kind of exception (except for {@link TestFormatException}). + * Exception that is thrown if the Test VM has thrown any kind of exception (except for {@link TestFormatException}). */ public class TestVMException extends RuntimeException { private final String exceptionInfo; @@ -37,9 +37,9 @@ public class TestVMException extends RuntimeException { } /** - * Get some more detailed information about the exception thrown in the test VM and how to reproduce it. + * Get some more detailed information about the exception thrown in the Test VM and how to reproduce it. * - * @return a formatted string containing information about the exception of the test VM and how to reproduce it. + * @return a formatted string containing information about the exception of the Test VM and how to reproduce it. */ public String getExceptionInfo() { return exceptionInfo; diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/TestVMProcess.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/TestVMProcess.java index 2b7fd2a1eea..a172dce1991 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/TestVMProcess.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/TestVMProcess.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 @@ -41,9 +41,9 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; /** - * This class prepares, creates, and runs the "test" VM with verification of proper termination. The class also stores - * information about the test VM which is later queried for IR matching. The communication between this driver VM - * and the test VM is done over a dedicated socket. + * This class prepares, creates, and runs the Test VM with verification of proper termination. The class also stores + * information about the Test VM which is later queried for IR matching. The communication between this Driver VM + * and the Test VM is done over a dedicated socket. * * @see TestVM * @see TestFrameworkSocket @@ -62,7 +62,7 @@ public class TestVMProcess { private String hotspotPidFileName; private String commandLine; private OutputAnalyzer oa; - private String irEncoding; + private String applicableIRRules; public TestVMProcess(List additionalFlags, Class testClass, Set> helperClasses, int defaultWarmup, boolean allowNotCompilable, boolean testClassesOnBootClassPath) { @@ -81,8 +81,8 @@ public class TestVMProcess { return commandLine; } - public String getIrEncoding() { - return irEncoding; + public String getApplicableIRRules() { + return applicableIRRules; } public String getHotspotPidFileName() { @@ -98,7 +98,7 @@ public class TestVMProcess { boolean testClassesOnBootClassPath) { // Set java.library.path so JNI tests which rely on jtreg nativepath setting work cmds.add("-Djava.library.path=" + Utils.TEST_NATIVE_PATH); - // Need White Box access in test VM. + // Need White Box access in Test VM. String bootClassPath = "-Xbootclasspath/a:."; if (testClassesOnBootClassPath) { // Add test classes themselves to boot classpath to make them privileged. @@ -112,7 +112,8 @@ public class TestVMProcess { if (!PREFER_COMMAND_LINE_FLAGS) { cmds.addAll(jtregVMFlags); } - // Add server property flag that enables test VM to print encoding for IR verification last and debug messages. + // Add server property flag that enables the Test VM to print the Applicable IR Rules for IR verification and + // debug messages. cmds.add(socket.getPortPropertyFlag()); cmds.addAll(additionalFlags); cmds.addAll(Arrays.asList(getDefaultFlags())); @@ -142,7 +143,7 @@ public class TestVMProcess { } /** - * Default flags that are added used for the test VM. + * Default flags that are added used for the Test VM. */ private static String[] getDefaultFlags() { return new String[] {"-XX:-BackgroundCompilation", "-XX:CompileCommand=quiet"}; @@ -169,7 +170,7 @@ public class TestVMProcess { throw new TestFrameworkException("Error while executing Test VM", e); } - process.command().add(1, "-DReproduce=true"); // Add after "/path/to/bin/java" in order to rerun the test VM directly + process.command().add(1, "-DReproduce=true"); // Add after "/path/to/bin/java" in order to rerun the Test VM directly commandLine = "Command Line:" + System.lineSeparator() + String.join(" ", process.command()) + System.lineSeparator(); hotspotPidFileName = String.format("hotspot_pid%d.log", oa.pid()); @@ -178,7 +179,7 @@ public class TestVMProcess { /** * Process the socket output: All prefixed lines are dumped to the standard output while the remaining lines - * represent the IR encoding used for IR matching later. + * represent the Applicable IR Rules used for IR matching later. */ private void processSocketOutput(TestFrameworkSocket socket) { String output = socket.getOutput(); @@ -215,16 +216,16 @@ public class TestVMProcess { System.out.println("---------------------"); System.out.println(messagesBuilder); } - irEncoding = nonStdOutBuilder.toString(); + applicableIRRules = nonStdOutBuilder.toString(); } else { - irEncoding = output; + applicableIRRules = output; } } private void checkTestVMExitCode() { final int exitCode = oa.getExitValue(); if (EXCLUDE_RANDOM || REPORT_STDOUT || (VERBOSE && exitCode == 0)) { - System.out.println("--- OUTPUT TestFramework test VM ---"); + System.out.println("--- OUTPUT TestFramework Test VM ---"); System.out.println(oa.getOutput()); } @@ -234,7 +235,7 @@ public class TestVMProcess { } /** - * Exit code was non-zero of test VM. Check the stderr to determine what kind of exception that should be thrown to + * Exit code was non-zero of Test VM. Check the stderr to determine what kind of exception that should be thrown to * react accordingly later. */ private void throwTestVMException() { @@ -266,7 +267,7 @@ public class TestVMProcess { stdOut = System.lineSeparator() + System.lineSeparator() + "Standard Output" + System.lineSeparator() + "---------------" + System.lineSeparator() + oa.getOutput(); } - return "TestFramework test VM exited with code " + exitCode + System.lineSeparator() + stdOut + return "TestFramework Test VM exited with code " + exitCode + System.lineSeparator() + stdOut + System.lineSeparator() + commandLine + System.lineSeparator() + System.lineSeparator() + "Error Output" + System.lineSeparator() + "------------" + System.lineSeparator() + stdErr + System.lineSeparator() + System.lineSeparator(); diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/IREncodingParser.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/ApplicableIRRulesParser.java similarity index 60% rename from test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/IREncodingParser.java rename to test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/ApplicableIRRulesParser.java index 28c4213f82d..7aaf0c2b285 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/IREncodingParser.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/ApplicableIRRulesParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, 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 @@ -28,7 +28,7 @@ import compiler.lib.ir_framework.TestFramework; import compiler.lib.ir_framework.driver.irmatching.parser.hotspot.HotSpotPidFileParser; import compiler.lib.ir_framework.shared.TestFormat; import compiler.lib.ir_framework.shared.TestFrameworkException; -import compiler.lib.ir_framework.test.IREncodingPrinter; +import compiler.lib.ir_framework.test.ApplicableIRRulesPrinter; import java.lang.reflect.Method; import java.util.HashMap; @@ -37,34 +37,34 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Class to parse the IR encoding emitted by the test VM and creating {@link TestMethod} objects for each entry. + * Class to parse the Applicable IR Rules emitted by the Test VM and creating {@link TestMethod} objects for each entry. * * @see TestMethod */ -public class IREncodingParser { +public class ApplicableIRRulesParser { - private static final boolean PRINT_IR_ENCODING = Boolean.parseBoolean(System.getProperty("PrintIREncoding", "false")); - private static final Pattern IR_ENCODING_PATTERN = - Pattern.compile("(?<=" + IREncodingPrinter.START + "\r?\n).*\\R([\\s\\S]*)(?=" + IREncodingPrinter.END + ")"); + private static final boolean PRINT_APPLICABLE_IR_RULES = Boolean.parseBoolean(System.getProperty("PrintApplicableIRRules", "false")); + private static final Pattern APPLICABLE_IR_RULES_PATTERN = + Pattern.compile("(?<=" + ApplicableIRRulesPrinter.START + "\r?\n).*\\R([\\s\\S]*)(?=" + ApplicableIRRulesPrinter.END + ")"); private final Map testMethods; private final Class testClass; - public IREncodingParser(Class testClass) { + public ApplicableIRRulesParser(Class testClass) { this.testClass = testClass; this.testMethods = new HashMap<>(); } /** - * Parse the IR encoding passed as parameter and return a "test name" -> TestMethod map that contains an entry - * for each method that needs to be IR matched on. + * Parse the Applicable IR rules passed as parameter and return a "test name" -> TestMethod map that contains an + * entry for each method that needs to be IR matched on. */ - public TestMethods parse(String irEncoding) { - if (TestFramework.VERBOSE || PRINT_IR_ENCODING) { - System.out.println("Read IR encoding from test VM:"); - System.out.println(irEncoding); + public TestMethods parse(String applicableIRRules) { + if (TestFramework.VERBOSE || PRINT_APPLICABLE_IR_RULES) { + System.out.println("Read Applicable IR Rules from Test VM:"); + System.out.println(applicableIRRules); } - createTestMethodMap(irEncoding, testClass); + createTestMethodMap(applicableIRRules, testClass); // We could have found format errors in @IR annotations. Report them now with an exception. TestFormat.throwIfAnyFailures(); return new TestMethods(testMethods); @@ -74,22 +74,22 @@ public class IREncodingParser { * Sets up a map testname -> TestMethod map. The TestMethod object will later be filled with the ideal and opto * assembly output in {@link HotSpotPidFileParser}. */ - private void createTestMethodMap(String irEncoding, Class testClass) { - Map irRulesMap = parseIREncoding(irEncoding); - createTestMethodsWithEncoding(testClass, irRulesMap); + private void createTestMethodMap(String applicableIRRules, Class testClass) { + Map irRulesMap = parseApplicableIRRules(applicableIRRules); + createTestMethodsWithApplicableIRRules(testClass, irRulesMap); } /** - * Read the IR encoding emitted by the test VM to decide if an @IR rule must be checked for a method. + * Read the Applicable IR Rules emitted by the Test VM to decide if an @IR rule must be checked for a method. */ - private Map parseIREncoding(String irEncoding) { + private Map parseApplicableIRRules(String applicableIRRules) { Map irRulesMap = new HashMap<>(); - String[] irEncodingLines = getIREncodingLines(irEncoding); - for (String s : irEncodingLines) { + String[] applicableIRRulesLines = getApplicableIRRulesLines(applicableIRRules); + for (String s : applicableIRRulesLines) { String line = s.trim(); String[] splitLine = line.split(","); if (splitLine.length < 2) { - throw new TestFrameworkException("Invalid IR match rule encoding. No comma found: " + splitLine[0]); + throw new TestFrameworkException("Invalid Applicable IR Rules format. No comma found: " + splitLine[0]); } String testName = splitLine[0]; int[] irRulesIdx = getRuleIndexes(splitLine); @@ -99,11 +99,12 @@ public class IREncodingParser { } /** - * Parse the IR encoding lines without header, explanation line and footer and return them in an array. + * Parse the Applicable IR Rules lines without header, explanation line and footer and return them in an array. */ - private String[] getIREncodingLines(String irEncoding) { - Matcher matcher = IR_ENCODING_PATTERN.matcher(irEncoding); - TestFramework.check(matcher.find(), "Did not find IR encoding in:" + System.lineSeparator() + irEncoding); + private String[] getApplicableIRRulesLines(String applicableIRRules) { + Matcher matcher = APPLICABLE_IR_RULES_PATTERN.matcher(applicableIRRules); + TestFramework.check(matcher.find(), "Did not find Applicable IR Rules in:" + + System.lineSeparator() + applicableIRRules); String lines = matcher.group(1).trim(); if (lines.isEmpty()) { // Nothing to IR match. @@ -113,7 +114,7 @@ public class IREncodingParser { } /** - * Parse rule indexes from IR encoding line of the format: + * Parse rule indexes from a single line of the Applicable IR Rules in the format: */ private int[] getRuleIndexes(String[] splitLine) { int[] irRulesIdx = new int[splitLine.length - 1]; @@ -121,13 +122,13 @@ public class IREncodingParser { try { irRulesIdx[i - 1] = Integer.parseInt(splitLine[i]); } catch (NumberFormatException e) { - throw new TestFrameworkException("Invalid IR match rule encoding. No number found: " + splitLine[i]); + throw new TestFrameworkException("Invalid Applicable IR Rules format. No number found: " + splitLine[i]); } } return irRulesIdx; } - private void createTestMethodsWithEncoding(Class testClass, Map irRulesMap) { + private void createTestMethodsWithApplicableIRRules(Class testClass, Map irRulesMap) { for (Method m : testClass.getDeclaredMethods()) { IR[] irAnnos = m.getAnnotationsByType(IR.class); if (irAnnos.length > 0) { @@ -144,7 +145,7 @@ public class IREncodingParser { private void validateIRRuleIds(Method m, IR[] irAnnos, int[] ids) { TestFramework.check(ids != null, "Should find method name in validIrRulesMap for " + m); TestFramework.check(ids.length > 0, "Did not find any rule indices for " + m); - TestFramework.check((ids[0] >= 1 || ids[0] == IREncodingPrinter.NO_RULE_APPLIED) + TestFramework.check((ids[0] >= 1 || ids[0] == ApplicableIRRulesPrinter.NO_RULE_APPLIED) && ids[ids.length - 1] <= irAnnos.length, "Invalid IR rule index found in validIrRulesMap for " + m); } @@ -153,6 +154,6 @@ public class IREncodingParser { * Does the list of IR rules contain any applicable IR rules for the given conditions? */ private boolean hasAnyApplicableIRRules(int[] irRuleIds) { - return irRuleIds[0] != IREncodingPrinter.NO_RULE_APPLIED; + return irRuleIds[0] != ApplicableIRRulesPrinter.NO_RULE_APPLIED; } } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/IRMethodBuilder.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/IRMethodBuilder.java index 538680496e7..d7a10acd1cd 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/IRMethodBuilder.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/IRMethodBuilder.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 @@ -24,7 +24,6 @@ package compiler.lib.ir_framework.driver.irmatching.parser; import compiler.lib.ir_framework.Test; -import compiler.lib.ir_framework.TestFramework; import compiler.lib.ir_framework.driver.irmatching.Compilation; import compiler.lib.ir_framework.driver.irmatching.irmethod.IRMethod; import compiler.lib.ir_framework.driver.irmatching.irmethod.IRMethodMatchable; @@ -53,7 +52,7 @@ class IRMethodBuilder { } /** - * Create IR methods for all test methods identified by {@link IREncodingParser} by combining them with the parsed + * Create IR methods for all test methods identified by {@link ApplicableIRRulesParser} by combining them with the parsed * compilation output from {@link HotSpotPidFileParser}. */ public SortedSet build(VMInfo vmInfo) { diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestClassParser.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestClassParser.java index b6c9920c2d1..2329b41afbe 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestClassParser.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestClassParser.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 @@ -50,13 +50,13 @@ public class TestClassParser { } /** - * Parse the IR encoding and hotspot_pid* file to create a collection of {@link IRMethod} objects. + * Parse the Applicable IR Rules and hotspot_pid* file to create a collection of {@link IRMethod} objects. * Return a default/empty TestClass object if there are no applicable @IR rules in any method of the test class. */ - public Matchable parse(String hotspotPidFileName, String irEncoding) { - IREncodingParser irEncodingParser = new IREncodingParser(testClass); - TestMethods testMethods = irEncodingParser.parse(irEncoding); - VMInfo vmInfo = VMInfoParser.parseVMInfo(irEncoding); + public Matchable parse(String hotspotPidFileName, String applicableIRRules) { + ApplicableIRRulesParser applicableIRRulesParser = new ApplicableIRRulesParser(testClass); + TestMethods testMethods = applicableIRRulesParser.parse(applicableIRRules); + VMInfo vmInfo = VMInfoParser.parseVMInfo(applicableIRRules); if (testMethods.hasTestMethods()) { HotSpotPidFileParser hotSpotPidFileParser = new HotSpotPidFileParser(testClass.getName(), testMethods); LoggedMethods loggedMethods = hotSpotPidFileParser.parse(hotspotPidFileName); @@ -66,7 +66,7 @@ public class TestClassParser { } /** - * Create test class with IR methods for all test methods identified by {@link IREncodingParser} by combining them + * Create test class with IR methods for all test methods identified by {@link ApplicableIRRulesParser} by combining them * with the parsed compilation output from {@link HotSpotPidFileParser}. */ private Matchable createTestClass(TestMethods testMethods, LoggedMethods loggedMethods, VMInfo vmInfo) { diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestMethod.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestMethod.java index 8491b8d61eb..43f27c80ac6 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestMethod.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestMethod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, 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 @@ -30,10 +30,10 @@ import compiler.lib.ir_framework.driver.irmatching.parser.hotspot.LoggedMethod; import java.lang.reflect.Method; /** - * This class represents a test method parsed by {@link IREncodingParser}. In combination with the associated + * This class represents a test method parsed by {@link ApplicableIRRulesParser}. In combination with the associated * {@link LoggedMethod}, a new {@link IRMethod} is created to IR match on later. * - * @see IREncodingParser + * @see ApplicableIRRulesParser * @see LoggedMethod * @see IRMethod */ diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestMethods.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestMethods.java index ecc8fb4e0b9..d1edfb08fdf 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestMethods.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/TestMethods.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,15 +29,15 @@ import compiler.lib.ir_framework.driver.irmatching.parser.hotspot.HotSpotPidFile import java.util.Map; /** - * This class stores all test methods that need to be IR matched as identified by {@link IREncodingParser}. + * This class stores all test methods that need to be IR matched as identified by {@link ApplicableIRRulesParser}. * - * @see IREncodingParser + * @see ApplicableIRRulesParser * @see HotSpotPidFileParser * @see IRMethod */ public class TestMethods { /** - * "Method name" -> TestMethod map created by {@link IREncodingParser} which contains an entry for each method that + * "Method name" -> TestMethod map created by {@link ApplicableIRRulesParser} which contains an entry for each method that * needs to be IR matched on. */ private final Map testMethods; diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/VMInfo.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/VMInfo.java index db70e7892a2..89b6d610496 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/VMInfo.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/VMInfo.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 @@ -33,7 +33,7 @@ import java.util.regex.Pattern; /** * This class stores the key value mapping from the VMInfo. * - * @see IREncodingParser + * @see ApplicableIRRulesParser */ public class VMInfo { /** diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/VMInfoParser.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/VMInfoParser.java index a8e6782ab12..2b17303f1a7 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/VMInfoParser.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/VMInfoParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; /** - * Class to parse the VMInfo emitted by the test VM and creating {@link VMInfo} objects for each entry. + * Class to parse the VMInfo emitted by the Test VM and creating {@link VMInfo} objects for each entry. * * @see VMInfo */ @@ -43,11 +43,11 @@ public class VMInfoParser { Pattern.compile("(?<=" + VMInfoPrinter.START_VM_INFO + "\r?\n).*\\R([\\s\\S]*)(?=" + VMInfoPrinter.END_VM_INFO + ")"); /** - * Extract VMInfo from the irEncoding. + * Extract VMInfo from the applicableIRRules. */ - public static VMInfo parseVMInfo(String irEncoding) { + public static VMInfo parseVMInfo(String applicableIRRules) { Map map = new HashMap<>(); - String[] lines = getVMInfoLines(irEncoding); + String[] lines = getVMInfoLines(applicableIRRules); for (String s : lines) { String line = s.trim(); String[] splitLine = line.split(":", 2); @@ -62,11 +62,11 @@ public class VMInfoParser { } /** - * Extract the VMInfo from the irEncoding string, strip away the header and return the individual key-value lines. + * Extract the VMInfo from the applicableIRRules string, strip away the header and return the individual key-value lines. */ - private static String[] getVMInfoLines(String irEncoding) { - Matcher matcher = VM_INFO_PATTERN.matcher(irEncoding); - TestFramework.check(matcher.find(), "Did not find VMInfo in:" + System.lineSeparator() + irEncoding); + private static String[] getVMInfoLines(String applicableIRRules) { + Matcher matcher = VM_INFO_PATTERN.matcher(applicableIRRules); + TestFramework.check(matcher.find(), "Did not find VMInfo in:" + System.lineSeparator() + applicableIRRules); String lines = matcher.group(1).trim(); if (lines.isEmpty()) { // Nothing to IR match. diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/hotspot/CompileQueueMessages.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/hotspot/CompileQueueMessages.java index 9ccb78ea892..f9fa0d1433e 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/hotspot/CompileQueueMessages.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/hotspot/CompileQueueMessages.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,7 @@ package compiler.lib.ir_framework.driver.irmatching.parser.hotspot; import compiler.lib.ir_framework.TestFramework; -import compiler.lib.ir_framework.driver.irmatching.parser.IREncodingParser; +import compiler.lib.ir_framework.driver.irmatching.parser.ApplicableIRRulesParser; import compiler.lib.ir_framework.driver.irmatching.parser.TestMethods; import java.util.HashMap; @@ -34,9 +34,9 @@ import java.util.regex.Pattern; /** * This class parses compile queue messages found in the hotspot_pid* files and keeps track of those that need to be - * IR matched (i.e. identified by {@link IREncodingParser}. + * IR matched (i.e. identified by {@link ApplicableIRRulesParser}. * - * @see IREncodingParser + * @see ApplicableIRRulesParser */ class CompileQueueMessages { private static final Pattern COMPILE_ID_PATTERN = Pattern.compile("compile_id='(\\d+)'"); diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/hotspot/HotSpotPidFileParser.java b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/hotspot/HotSpotPidFileParser.java index a5a5a0b20a9..f39f7cc9a83 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/hotspot/HotSpotPidFileParser.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/driver/irmatching/parser/hotspot/HotSpotPidFileParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, 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 @@ -24,7 +24,7 @@ package compiler.lib.ir_framework.driver.irmatching.parser.hotspot; import compiler.lib.ir_framework.driver.irmatching.irmethod.IRMethod; -import compiler.lib.ir_framework.driver.irmatching.parser.IREncodingParser; +import compiler.lib.ir_framework.driver.irmatching.parser.ApplicableIRRulesParser; import compiler.lib.ir_framework.driver.irmatching.parser.TestMethods; import compiler.lib.ir_framework.shared.TestFrameworkException; @@ -34,10 +34,10 @@ import java.nio.file.Paths; /** * Class to parse the ideal compile phases and PrintOptoAssembly outputs of the test class from the hotspot_pid* file - * of all methods identified by {@link IREncodingParser}. + * of all methods identified by {@link ApplicableIRRulesParser}. * * @see IRMethod - * @see IREncodingParser + * @see ApplicableIRRulesParser */ public class HotSpotPidFileParser { private final State state; @@ -47,8 +47,8 @@ public class HotSpotPidFileParser { } /** - * Parse the hotspot_pid*.log file from the test VM. Read the ideal compile phase and PrintOptoAssembly outputs for - * all methods defined by the IR encoding. + * Parse the hotspot_pid*.log file from the Test VM. Read the ideal compile phase and PrintOptoAssembly outputs for + * all methods defined by the Applicable IR Rules. */ public LoggedMethods parse(String hotspotPidFileName) { try (var reader = Files.newBufferedReader(Paths.get(hotspotPidFileName))) { diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/flag/CompilePhaseCollector.java b/test/hotspot/jtreg/compiler/lib/ir_framework/flag/CompilePhaseCollector.java index 86ef31c6277..0488b3f58e6 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/flag/CompilePhaseCollector.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/flag/CompilePhaseCollector.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 @@ -53,7 +53,7 @@ class CompilePhaseCollector { collectCompilePhases(method)); } } catch (TestFormatException e) { - // Create default map and let the IR matcher report the format failures later in the driver VM. + // Create default map and let the IR matcher report the format failures later in the Driver VM. return createDefaultMap(testClass); } return methodNameToCompilePhasesMap; diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/flag/FlagVM.java b/test/hotspot/jtreg/compiler/lib/ir_framework/flag/FlagVM.java index 42e4c85a499..4b033eb2c0e 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/flag/FlagVM.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/flag/FlagVM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,8 +38,8 @@ import java.util.ArrayList; import java.util.Arrays; /** - * This class' main method is called from {@link TestFramework} and represents the so-called "flag VM". It uses the - * Whitebox API to determine the necessary additional flags to run the test VM (e.g. to do IR matching). It returns + * This class' main method is called from {@link TestFramework} and represents the so-called "Flag VM". It uses the + * Whitebox API to determine the necessary additional flags to run the Test VM (e.g. to do IR matching). It returns * the flags over the dedicated TestFramework socket. */ public class FlagVM { @@ -79,12 +79,12 @@ public class FlagVM { private static final boolean VERIFY_IR = REQUESTED_VERIFY_IR && USE_COMPILER && !EXCLUDE_RANDOM && !FLIP_C1_C2 && !TEST_C1 && Platform.isServer(); /** - * Main entry point of the flag VM. + * Main entry point of the Flag VM. */ public static void main(String[] args) { String testClassName = args[0]; if (VERBOSE) { - System.out.println("FlagVM main() called. Prepare test VM flags to run class " + testClassName); + System.out.println("FlagVM main() called. Prepare Test VM flags to run class " + testClassName); } Class testClass; try { @@ -96,8 +96,8 @@ public class FlagVM { } /** - * Emit test VM flags to the dedicated test VM flags file to parse them from the TestFramework "driver" VM again - * which adds them to the test VM. + * Emit Test VM flags to the dedicated Test VM flags file to parse them from the TestFramework Driver VM again + * which adds them to the Test VM. */ private static void emitTestVMFlags(ArrayList flags) { try (var bw = Files.newBufferedWriter(Paths.get(TEST_VM_FLAGS_FILE))) { diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/shared/NoTestsRunException.java b/test/hotspot/jtreg/compiler/lib/ir_framework/shared/NoTestsRunException.java index 02afdd4ee4f..4b616f53e4d 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/shared/NoTestsRunException.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/shared/NoTestsRunException.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 @@ -24,17 +24,17 @@ package compiler.lib.ir_framework.shared; /** - * Exception that is thrown by the test VM if no tests are run as a result of specifying {@code -DTest} and/or - * {@code -DExclude} defining an empty set with the used test VM flags. + * Exception that is thrown by the Test VM if no tests are run as a result of specifying {@code -DTest} and/or + * {@code -DExclude} defining an empty set with the used Test VM flags. */ public class NoTestsRunException extends RuntimeException { /** - * Default constructor used by test VM + * Default constructor used by Test VM */ public NoTestsRunException() {} /** - * Constructor used to eventually throw the exception in the driver VM. + * Constructor used to eventually throw the exception in the Driver VM. */ public NoTestsRunException(String message) { super(message); diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/shared/TestFrameworkSocket.java b/test/hotspot/jtreg/compiler/lib/ir_framework/shared/TestFrameworkSocket.java index 1fdcb34a6b8..f10540ebc5b 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/shared/TestFrameworkSocket.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/shared/TestFrameworkSocket.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 @@ -37,7 +37,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** - * Dedicated socket to send data from the flag and test VM back to the driver VM. + * Dedicated socket to send data from the flag and Test VM back to the Driver VM. */ public class TestFrameworkSocket implements AutoCloseable { public static final String STDOUT_PREFIX = "[STDOUT]"; @@ -46,7 +46,7 @@ public class TestFrameworkSocket implements AutoCloseable { public static final String PRINT_TIMES_TAG = "[PRINT_TIMES]"; public static final String NOT_COMPILABLE_TAG = "[NOT_COMPILABLE]"; - // Static fields used for test VM only. + // Static fields used for Test VM only. private static final String SERVER_PORT_PROPERTY = "ir.framework.server.port"; private static final int SERVER_PORT = Integer.getInteger(SERVER_PORT_PROPERTY, -1); @@ -85,7 +85,7 @@ public class TestFrameworkSocket implements AutoCloseable { } /** - * Waits for a client (created by flag or test VM) to connect. Return the messages received from the client. + * Waits for a client (created by flag or Test VM) to connect. Return the messages received from the client. */ private FutureTask initSocketTask() { return new FutureTask<>(() -> { @@ -117,18 +117,18 @@ public class TestFrameworkSocket implements AutoCloseable { } /** - * Only called by test VM to write to server socket. + * Only called by Test VM to write to server socket. */ public static void write(String msg, String tag) { write(msg, tag, false); } /** - * Only called by test VM to write to server socket. + * Only called by Test VM to write to server socket. *

- * The test VM is spawned by the main jtreg VM. The stdout of the test VM is hidden + * The Test VM is spawned by the main jtreg VM. The stdout of the Test VM is hidden * unless the Verbose or ReportStdout flag is used. TestFrameworkSocket is used by the parent jtreg - * VM and the test VM to communicate. By sending the prints through the TestFrameworkSocket with the + * VM and the Test VM to communicate. By sending the prints through the TestFrameworkSocket with the * parameter stdout set to true, the parent VM will print the received messages to its stdout, making it * visible to the user. */ @@ -137,10 +137,10 @@ public class TestFrameworkSocket implements AutoCloseable { System.out.println("Debugging Test VM: Skip writing due to -DReproduce"); return; } - TestFramework.check(SERVER_PORT != -1, "Server port was not set correctly for flag and/or test VM " - + "or method not called from flag or test VM"); + TestFramework.check(SERVER_PORT != -1, "Server port was not set correctly for flag and/or Test VM " + + "or method not called from flag or Test VM"); try { - // Keep the client socket open until the test VM terminates (calls closeClientSocket before exiting main()). + // Keep the client socket open until the Test VM terminates (calls closeClientSocket before exiting main()). if (clientSocket == null) { clientSocket = new Socket(InetAddress.getLoopbackAddress(), SERVER_PORT); clientWriter = new PrintWriter(clientSocket.getOutputStream(), true); @@ -150,11 +150,11 @@ public class TestFrameworkSocket implements AutoCloseable { } clientWriter.println(msg); } catch (Exception e) { - // When the test VM is directly run, we should ignore all messages that would normally be sent to the - // driver VM. + // When the Test VM is directly run, we should ignore all messages that would normally be sent to the + // Driver VM. String failMsg = System.lineSeparator() + System.lineSeparator() + """ ########################################################### - Did you directly run the test VM (TestVM class) + Did you directly run the Test VM (TestVM class) to reproduce a bug? => Append the flag -DReproduce=true and try again! ########################################################### @@ -169,7 +169,7 @@ public class TestFrameworkSocket implements AutoCloseable { /** * Closes (and flushes) the printer to the socket and the socket itself. Is called as last thing before exiting - * the main() method of the flag and the test VM. + * the main() method of the flag and the Test VM. */ public static void closeClientSocket() { if (clientSocket != null) { @@ -183,7 +183,7 @@ public class TestFrameworkSocket implements AutoCloseable { } /** - * Get the socket output of the flag VM. + * Get the socket output of the Flag VM. */ public String getOutput() { try { @@ -197,7 +197,7 @@ public class TestFrameworkSocket implements AutoCloseable { } /** - * Return whether test VM sent messages to be put on stdout (starting with {@link ::STDOUT_PREFIX}). + * Return whether Test VM sent messages to be put on stdout (starting with {@link ::STDOUT_PREFIX}). */ public boolean hasStdOut() { return receivedStdOut; diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java similarity index 96% rename from test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java rename to test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.java index a24cfbd3e37..4fa1f8f3fe5 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/ApplicableIRRulesPrinter.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 @@ -39,12 +39,13 @@ import java.util.Objects; import java.util.function.Function; /** - * Prints an encoding to the dedicated test framework socket whether @IR rules of @Test methods should be applied or not. - * This is done during the execution of the test VM by checking the active VM flags. This encoding is eventually parsed - * and checked by the IRMatcher class in the driver VM after the termination of the test VM. IR rule indices start at 1. + * Prints all applicable IR rules to the dedicated test framework socket whether @IR rules of @Test methods should be + * applied or not. This is done during the execution of the Test VM by checking the active VM flags. This + * Applicable IR Rules message is eventually parsed and checked by the IRMatcher class in the Driver VM after the + * termination of the Test VM. IR rule indices start at 1. */ -public class IREncodingPrinter { - public static final String START = "##### IRMatchRulesEncoding - used by TestFramework #####"; +public class ApplicableIRRulesPrinter { + public static final String START = "##### ApplicableIRRules - used by TestFramework #####"; public static final String END = "----- END -----"; public static final int NO_RULE_APPLIED = -1; @@ -60,7 +61,7 @@ public class IREncodingPrinter { // Platforms for use in IR preconditions. Please verify that e.g. there is // a corresponding use in a jtreg @requires annotation before adding new platforms, // as adding non-existent platforms can lead to skipped tests. - private static final List irTestingPlatforms = new ArrayList(Arrays.asList( + private static final List irTestingPlatforms = new ArrayList<>(Arrays.asList( // os.family "aix", "linux", @@ -85,7 +86,7 @@ public class IREncodingPrinter { // Please verify new CPU features before adding them. If we allow non-existent features // on this list, we will ignore tests and never execute them. Consult CPU_FEATURE_FLAGS // in corresponding vm_version_.hpp file to find correct cpu feature's name. - private static final List verifiedCPUFeatures = new ArrayList( Arrays.asList( + private static final List verifiedCPUFeatures = new ArrayList<>( Arrays.asList( // x86 "fma", "f16c", @@ -126,7 +127,7 @@ public class IREncodingPrinter { "zvkn" )); - public IREncodingPrinter() { + public ApplicableIRRulesPrinter() { output.append(START).append(System.lineSeparator()); output.append(",{comma separated applied @IR rule ids}").append(System.lineSeparator()); } @@ -136,7 +137,7 @@ public class IREncodingPrinter { * - indices of all @IR rules that should be applied, separated by a comma * - "-1" if no @IR rule should not be applied */ - public void emitRuleEncoding(Method m, boolean skipped) { + public void emitApplicableIRRules(Method m, boolean skipped) { method = m; int i = 0; ArrayList validRules = new ArrayList<>(); @@ -170,7 +171,7 @@ public class IREncodingPrinter { private void printDisableReason(String method, String reason, String[] apply, int ruleIndex, int ruleMax) { TestFrameworkSocket.write("Disabling IR matching for rule " + ruleIndex + " of " + ruleMax + " in " + method + ": " + reason + ": " + String.join(", ", apply), - "[IREncodingPrinter]", true); + "[ApplicableIRRules]", true); } private boolean shouldApplyIrRule(IR irAnno, String m, int ruleIndex, int ruleMax) { @@ -521,7 +522,7 @@ public class IREncodingPrinter { public void emit() { output.append(END); - TestFrameworkSocket.write(output.toString(), "IR rule application encoding"); + TestFrameworkSocket.write(output.toString(), "ApplicableIRRules"); } } 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 8ccd0faa013..c5ab318d67d 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.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 @@ -41,7 +41,7 @@ import java.util.stream.Stream; import static compiler.lib.ir_framework.shared.TestFrameworkSocket.PRINT_TIMES_TAG; /** - * This class' main method is called from {@link TestFramework} and represents the so-called "test VM". The class is + * This class' main method is called from {@link TestFramework} and represents the so-called "Test VM". The class is * the heart of the framework and is responsible for executing all the specified tests in the test class. It uses the * Whitebox API and reflection to achieve this task. */ @@ -58,7 +58,7 @@ public class TestVM { TestFramework in main() of your test? Make sure to only call setup/run methods and no checks or assertions from main() of your test! - - Are you rerunning the test VM (TestVM class) + - Are you rerunning the Test VM (TestVM class) directly after a JTreg run? Make sure to start it from within JTwork/scratch and with the flag -DReproduce=true! @@ -100,7 +100,7 @@ public class TestVM { 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")); - // Use separate flag as VERIFY_IR could have been set by user but due to other flags it was disabled by flag VM. + // Use separate flag as VERIFY_IR could have been set by user but due to other flags it was disabled by Flag VM. private static final boolean PRINT_VALID_IR_RULES = Boolean.getBoolean("ShouldDoIRVerification"); protected static final long PER_METHOD_TRAP_LIMIT = (Long)WHITE_BOX.getVMFlag("PerMethodTrapLimit"); protected static final boolean PROFILE_INTERPRETER = (Boolean)WHITE_BOX.getVMFlag("ProfileInterpreter"); @@ -114,7 +114,7 @@ public class TestVM { private final List excludeList; private final List testList; private Set> helperClasses = null; // Helper classes that contain framework annotations to be processed. - private final IREncodingPrinter irMatchRulePrinter; + private final ApplicableIRRulesPrinter irMatchRulePrinter; private final Class testClass; private final Map forceCompileMap = new HashMap<>(); @@ -125,7 +125,7 @@ public class TestVM { this.excludeList = createTestFilterList(EXCLUDELIST, testClass); if (PRINT_VALID_IR_RULES) { - irMatchRulePrinter = new IREncodingPrinter(); + irMatchRulePrinter = new ApplicableIRRulesPrinter(); } else { irMatchRulePrinter = null; } @@ -155,7 +155,7 @@ public class TestVM { } /** - * Main entry point of the test VM. + * Main entry point of the Test VM. */ public static void main(String[] args) { try { @@ -293,7 +293,7 @@ public class TestVM { BaseTest baseTest = new BaseTest(test, shouldExcludeTest(m.getName())); allTests.add(baseTest); if (PRINT_VALID_IR_RULES) { - irMatchRulePrinter.emitRuleEncoding(m, baseTest.isSkipped()); + irMatchRulePrinter.emitApplicableIRRules(m, baseTest.isSkipped()); } } catch (TestFormatException e) { // Failure logged. Continue and report later. @@ -687,7 +687,7 @@ public class TestVM { allTests.add(checkedTest); if (PRINT_VALID_IR_RULES) { // Only need to emit IR verification information if IR verification is actually performed. - irMatchRulePrinter.emitRuleEncoding(testMethod, checkedTest.isSkipped()); + irMatchRulePrinter.emitApplicableIRRules(testMethod, checkedTest.isSkipped()); } } @@ -755,7 +755,7 @@ public class TestVM { CustomRunTest customRunTest = new CustomRunTest(m, getAnnotation(m, Warmup.class), runAnno, tests, shouldExcludeTest); allTests.add(customRunTest); if (PRINT_VALID_IR_RULES) { - tests.forEach(test -> irMatchRulePrinter.emitRuleEncoding(test.getTestMethod(), customRunTest.isSkipped())); + tests.forEach(test -> irMatchRulePrinter.emitApplicableIRRules(test.getTestMethod(), customRunTest.isSkipped())); } } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/VMInfoPrinter.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/VMInfoPrinter.java index 14636da4cbe..470569122dd 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/VMInfoPrinter.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/VMInfoPrinter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ import compiler.lib.ir_framework.shared.TestFrameworkSocket; import jdk.test.whitebox.WhiteBox; /** - * Prints some test VM info to the socket. + * Prints some Test VM info to the socket. */ public class VMInfoPrinter { public static final String START_VM_INFO = "##### IRMatchingVMInfo - used by TestFramework #####"; diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestCastShapeBadOpc.java b/test/hotspot/jtreg/compiler/vectorapi/TestCastShapeBadOpc.java new file mode 100644 index 00000000000..4c20c84bc50 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestCastShapeBadOpc.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=all-flags + * @bug 8374889 + * @summary Test case that can compile unexpected code paths in VectorAPI cast intrinsification. + * @modules jdk.incubator.vector + * @library /test/lib / + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-TieredCompilation -Xbatch + * -XX:StressSeed=1462975402 + * -XX:+StressIncrementalInlining + * -XX:CompileCommand=compileonly,${test.main.class}::test2 + * ${test.main.class} + */ + +/* + * @test id=no-stress-seed + * @bug 8374889 + * @modules jdk.incubator.vector + * @library /test/lib / + * @run main/othervm + * -XX:+IgnoreUnrecognizedVMOptions + * -XX:-TieredCompilation -Xbatch + * -XX:+StressIncrementalInlining + * -XX:CompileCommand=compileonly,${test.main.class}::test2 + * ${test.main.class} + */ + +/* + * @test id=vanilla + * @bug 8374889 + * @modules jdk.incubator.vector + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.vectorapi; + +import jdk.incubator.vector.*; + +public class TestCastShapeBadOpc { + public static void main(String[] args) { + for (int i = 0; i < 100_000; ++i) { + test1(); + test2(); + } + } + + // This code does not trigger the bug itself, but seems to be important for profiling, + // so that test2 fails. + public static Object test1() { + LongVector v0 = LongVector.broadcast(LongVector.SPECIES_512, -15L); + var v1 = (ByteVector)v0.convertShape(VectorOperators.Conversion.ofReinterpret(long.class, byte.class), ByteVector.SPECIES_128, 0); + var v2 = (ByteVector)v1.castShape(ByteVector.SPECIES_256, 0); + return v2; + } + + public static Object test2() { + var v0 = ShortVector.broadcast(ShortVector.SPECIES_64, (short)7729); + var v1 = (FloatVector)v0.reinterpretShape(FloatVector.SPECIES_64, 0); + // The castShape below should take the "C" path in AbstractVector::convert0, but sometimes + // we also compile the "Z" case because of profiling. This means we attempt to create + // a vector cast from float -> long, but unfortunately with a UCAST (float -> long is signed). + // This triggered an assert in VectorCastNode::opcode. + var v2 = (LongVector)v1.castShape(LongVector.SPECIES_256, 0); + return v2; + } +} diff --git a/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java b/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java index 6b0a536e4d4..3064b320c0d 100644 --- a/test/hotspot/jtreg/containers/docker/TestCPUAwareness.java +++ b/test/hotspot/jtreg/containers/docker/TestCPUAwareness.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 @@ -143,9 +143,9 @@ public class TestCPUAwareness { // Expected active processor count can not exceed available CPU count - private static int adjustExpectedAPCForAvailableCPUs(int expectedAPC) { - if (expectedAPC > availableCPUs) { - expectedAPC = availableCPUs; + private static double adjustExpectedAPCForAvailableCPUs(double expectedAPC) { + if (expectedAPC > (double)availableCPUs) { + expectedAPC = (double)availableCPUs; System.out.println("Adjusted expectedAPC = " + expectedAPC); } return expectedAPC; @@ -158,7 +158,7 @@ public class TestCPUAwareness { System.out.println("quota = " + quota); System.out.println("period = " + period); - int expectedAPC = (int) Math.ceil((float) quota / (float) period); + double expectedAPC = (double) quota / (double) period; System.out.println("expectedAPC = " + expectedAPC); expectedAPC = adjustExpectedAPCForAvailableCPUs(expectedAPC); @@ -178,7 +178,7 @@ public class TestCPUAwareness { private static void testAPCCombo(String cpuset, int quota, int period, int shares, - int expectedAPC) throws Exception { + double expectedAPC) throws Exception { Common.logNewTestCase("test APC Combo"); System.out.println("cpuset = " + cpuset); System.out.println("quota = " + quota); diff --git a/test/hotspot/jtreg/gc/stress/TestMultiThreadStressRSet.java b/test/hotspot/jtreg/gc/stress/TestMultiThreadStressRSet.java index a323ebd945a..ffd5203dfb8 100644 --- a/test/hotspot/jtreg/gc/stress/TestMultiThreadStressRSet.java +++ b/test/hotspot/jtreg/gc/stress/TestMultiThreadStressRSet.java @@ -44,15 +44,15 @@ import jdk.test.lib.Utils; * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=1 -Xlog:gc + * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=1 -Xlog:gc -XX:-UseGCOverheadLimit * -Xmx500m -XX:G1HeapRegionSize=1m -XX:MaxGCPauseMillis=1000 gc.stress.TestMultiThreadStressRSet 10 4 * * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=100 -Xlog:gc + * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=100 -Xlog:gc -XX:-UseGCOverheadLimit * -Xmx1G -XX:G1HeapRegionSize=8m -XX:MaxGCPauseMillis=1000 gc.stress.TestMultiThreadStressRSet 60 16 * * @run main/othervm/timeout=1200 -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=100 -Xlog:gc + * -XX:+UseG1GC -XX:G1SummarizeRSetStatsPeriod=100 -Xlog:gc -XX:-UseGCOverheadLimit * -Xmx500m -XX:G1HeapRegionSize=1m -XX:MaxGCPauseMillis=1000 gc.stress.TestMultiThreadStressRSet 600 32 */ public class TestMultiThreadStressRSet { diff --git a/test/hotspot/jtreg/runtime/os/TestTrimNativeHeapIntervalTablesCleanup.java b/test/hotspot/jtreg/runtime/os/TestTrimNativeHeapIntervalTablesCleanup.java new file mode 100644 index 00000000000..3ced98b616d --- /dev/null +++ b/test/hotspot/jtreg/runtime/os/TestTrimNativeHeapIntervalTablesCleanup.java @@ -0,0 +1,107 @@ +/* + * 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 8375125 + * @summary Trigger StringTable::clean_dead_entries or SymbolTable::clean_dead_entries + * with -XX:TrimNativeHeapInterval enabled,should not violate lock ordering. + * @requires vm.debug + * @requires vm.gc != "Epsilon" + * @library /test/lib + * @modules java.compiler + * @run main/othervm -Xms128m -Xmx128m + * -XX:TrimNativeHeapInterval=300000 + * TestTrimNativeHeapIntervalTablesCleanup string + * @run main/othervm -Xms128m -Xmx128m + * -XX:TrimNativeHeapInterval=300000 + * TestTrimNativeHeapIntervalTablesCleanup symbol + */ + +import java.util.LinkedList; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +public class TestTrimNativeHeapIntervalTablesCleanup { + + public static void main(String[] args) throws Exception{ + if (args.length != 1) { + throw new IllegalArgumentException("Expected 1 argument: string|symbol"); + } + switch (args[0]) { + case "string": + testStringTableCleanup(); + break; + case "symbol": + testSymbolTableCleanup(); + break; + default: + throw new IllegalArgumentException("Unknown mode: " + args[0]); + } + System.out.println("passed: " + args[0]); + } + + static void testStringTableCleanup() throws Exception{ + final int rounds = 30; + final int maxSize = 200_000; + final int pruneEvery = 50_000; + final int pruneCount = 25_000; + long stringNum = 0; + + for (int round = 0; round < rounds; round++) { + LinkedList list = new LinkedList<>(); + for (int i = 0; i < maxSize; i++, stringNum++) { + if (i != 0 && (i % pruneEvery) == 0) { + int toRemove = Math.min(pruneCount, list.size()); + list.subList(0, toRemove).clear(); + } + list.push(Long.toString(stringNum).intern()); + } + System.gc(); + Thread.sleep(1000); + } + } + + static void testSymbolTableCleanup() throws Exception { + final int rounds = 10; + final int classesPerRound = 100; + + for (int r = 0; r < rounds; r++) { + for (int i = 0; i < classesPerRound; i++) { + String cn = "C" + r + "_" + i; + byte[] bytes = InMemoryJavaCompiler.compile( + cn, + "public class " + cn + " { int m" + i + "() { return " + i + "; } }" + ); + new ClassLoader(null) { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + if (!name.equals(cn)) throw new ClassNotFoundException(name); + return defineClass(name, bytes, 0, bytes.length); + } + }.loadClass(cn).getDeclaredConstructor().newInstance(); + } + System.gc(); + Thread.sleep(1000); + } + } +} \ No newline at end of file diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestIRMatching.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestIRMatching.java index 142a30f34a9..07a6a03f2a6 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestIRMatching.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestIRMatching.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,13 +38,13 @@ import java.util.regex.Pattern; /* * @test * @requires vm.debug == true & vm.compMode != "Xint" & vm.compiler1.enabled & vm.compiler2.enabled & vm.flagless - * @summary Test IR matcher with different default IR node regexes. Use -DPrintIREncoding. + * @summary Test IR matcher with different default IR node regexes. Use -DPrintApplicableIRRules. * Normally, the framework should be called with driver. * @library /test/lib /testlibrary_tests / * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm/timeout=240 -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI -DPrintIREncoding=true ir_framework.tests.TestIRMatching + * -XX:+WhiteBoxAPI -DPrintApplicableIRRules=true ir_framework.tests.TestIRMatching */ public class TestIRMatching { @@ -440,7 +440,8 @@ public class TestIRMatching { } } if (!output.contains(builder.toString())) { - addException(new RuntimeException("Could not find encoding: \"" + builder + System.lineSeparator())); + addException(new RuntimeException("Could not find line in Applicable IR Rules: \"" + builder + + System.lineSeparator())); } } } diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestPhaseIRMatching.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestPhaseIRMatching.java index fe21dce73b5..70e4c463c55 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestPhaseIRMatching.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestPhaseIRMatching.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 @@ -70,7 +70,7 @@ public class TestPhaseIRMatching { TestVMProcess testVMProcess = new TestVMProcess(testVMFlags, testClass, null, -1, false, false); TestClassParser testClassParser = new TestClassParser(testClass, false); Matchable testClassMatchable = testClassParser.parse(testVMProcess.getHotspotPidFileName(), - testVMProcess.getIrEncoding()); + testVMProcess.getApplicableIRRules()); MatchResult result = testClassMatchable.match(); List expectedFails = new ExpectedFailsBuilder().build(testClass); List foundFailures = new FailureBuilder().build(result); diff --git a/test/jdk/com/sun/tools/attach/TestWithoutDumpableProcess.java b/test/jdk/com/sun/tools/attach/TestWithoutDumpableProcess.java new file mode 100644 index 00000000000..be9d0c8790c --- /dev/null +++ b/test/jdk/com/sun/tools/attach/TestWithoutDumpableProcess.java @@ -0,0 +1,102 @@ +/* + * 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. + */ + +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; + +import com.sun.tools.attach.VirtualMachine; + +import jdk.test.lib.Asserts; +import jdk.test.lib.thread.ProcessThread; +import jdk.test.lib.process.ProcessTools; + +/* + * @test + * @bug 8226919 8373867 + * @summary Test to make sure attach target process which is not dumpable. + * @library /test/lib + * @modules jdk.attach + * @requires os.family == "linux" + * + * @run main/timeout=200 TestWithoutDumpableProcess + */ +public class TestWithoutDumpableProcess { + + private static final String EXPECTED_PROP_KEY = "attach.test"; + private static final String EXPECTED_PROP_VALUE = "true"; + + public static class Debuggee { + + // Disable dumpable attribute via prctl(2) + private static void disableDumpable() throws Throwable { + var linker = Linker.nativeLinker(); + var prctl = linker.downcallHandle(linker.defaultLookup().findOrThrow("prctl"), + FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG), + Linker.Option.firstVariadicArg(1), Linker.Option.captureCallState("errno")); + var errnoSeg = Arena.global().allocate(Linker.Option.captureStateLayout()); + final int PR_SET_DUMPABLE = 4; // from linux/prctl.h + + int ret = (int)prctl.invoke(errnoSeg, PR_SET_DUMPABLE, 0L); + if (ret == -1){ + var hndErrno = Linker.Option + .captureStateLayout() + .varHandle(MemoryLayout.PathElement.groupElement("errno")); + int errno = (int)hndErrno.get(errnoSeg, 0L); + throw new RuntimeException("prctl: errno=" + errno); + } + } + + public static void main(String[] args) throws Throwable { + disableDumpable(); + IO.println(Application.READY_MSG); + + while (IO.readln().equals(Application.SHUTDOWN_MSG)); + } + + public static ProcessThread start() { + var args = new String[]{ + "--enable-native-access=ALL-UNNAMED", + String.format("-D%s=%s", EXPECTED_PROP_KEY, EXPECTED_PROP_VALUE), Debuggee.class.getName() + }; + var pb = ProcessTools.createLimitedTestJavaProcessBuilder(args); + var pt = new ProcessThread("runApplication", Application.READY_MSG::equals, pb); + pt.start(); + return pt; + } + + } + + public static void main(String[] args) throws Exception { + var pt = Debuggee.start(); + var vm = VirtualMachine.attach(Long.toString(pt.getPid())); + var val = vm.getSystemProperties().getProperty(EXPECTED_PROP_KEY); + + Asserts.assertNotNull(val, "Expected sysprop not found"); + Asserts.assertEquals(val, "true", "Unexpected sysprop value"); + } + +} diff --git a/test/jdk/java/awt/print/PrinterJob/PageRanges.java b/test/jdk/java/awt/print/PrinterJob/PageRanges.java index e80330bae6c..aea60516f78 100644 --- a/test/jdk/java/awt/print/PrinterJob/PageRanges.java +++ b/test/jdk/java/awt/print/PrinterJob/PageRanges.java @@ -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 @@ -23,7 +23,7 @@ /* * @test - * @bug 6575331 + * @bug 6575331 8297191 * @key printer * @summary The specified pages should be printed. * @library /java/awt/regtesthelpers diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/Driver.java b/test/jdk/java/lang/invoke/MethodHandleProxies/Driver.java index 6acd4fb30e1..0e9c708e8e9 100644 --- a/test/jdk/java/lang/invoke/MethodHandleProxies/Driver.java +++ b/test/jdk/java/lang/invoke/MethodHandleProxies/Driver.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,12 +21,12 @@ * questions. */ -/** +/* * @test * @bug 8280377 * @build m1/* m2/* Unnamed * @run testng/othervm m1/p1.Main - * @run testng/othervm Unnamed + * @run main/othervm Unnamed * @summary Test MethodHandleProxies::asInterfaceInstance with a default * method with varargs */ diff --git a/test/jdk/java/lang/invoke/MethodHandleProxies/Unnamed.java b/test/jdk/java/lang/invoke/MethodHandleProxies/Unnamed.java index f42071f0427..f60f36ca9de 100644 --- a/test/jdk/java/lang/invoke/MethodHandleProxies/Unnamed.java +++ b/test/jdk/java/lang/invoke/MethodHandleProxies/Unnamed.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 @@ -39,7 +39,7 @@ public class Unnamed { // verify that the caller has no access to the proxy created on an // inaccessible interface - Method m = intf.getMethod("test", Object[].class); - assertFalse(m.canAccess(null)); + Method m = intf.getMethod("test"); + assertFalse(m.canAccess(t)); } } diff --git a/test/jdk/java/net/DatagramSocket/SendReceiveMaxSize.java b/test/jdk/java/net/DatagramSocket/SendReceiveMaxSize.java index 3cea9073199..ded087d35e8 100644 --- a/test/jdk/java/net/DatagramSocket/SendReceiveMaxSize.java +++ b/test/jdk/java/net/DatagramSocket/SendReceiveMaxSize.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, 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 @@ -55,6 +55,8 @@ import java.net.MulticastSocket; import java.nio.channels.DatagramChannel; import java.util.Random; +import static java.net.StandardSocketOptions.SO_RCVBUF; +import static org.testng.Assert.assertTrue; import static org.testng.Assert.assertEquals; import static org.testng.Assert.expectThrows; @@ -102,6 +104,10 @@ public class SendReceiveMaxSize { DatagramSocketSupplier supplier, Class exception) throws IOException { try (var receiver = new DatagramSocket(new InetSocketAddress(HOST_ADDR, 0))) { + assertTrue(receiver.getOption(SO_RCVBUF) >= capacity, + receiver.getOption(SO_RCVBUF) + + " for UDP receive buffer too small to hold capacity " + + capacity); var port = receiver.getLocalPort(); var addr = new InetSocketAddress(HOST_ADDR, port); try (var sender = supplier.open()) { diff --git a/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java b/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java index 25f73606b96..786b907b79a 100644 --- a/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java +++ b/test/jdk/javax/net/ssl/SSLParameters/NamedGroups.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * Copyright (C) 2022, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,7 +27,7 @@ /* * @test - * @bug 8281236 + * @bug 8281236 8314323 * @summary Check TLS connection behaviors for named groups configuration * @library /javax/net/ssl/templates * @run main/othervm NamedGroups @@ -136,6 +137,60 @@ public class NamedGroups extends SSLSocketTemplate { "secp256r1" }, true); + + runTest(new String[] { + "X25519MLKEM768" + }, + new String[] { + "X25519MLKEM768" + }, + false); + + runTest(new String[] { + "SecP256r1MLKEM768" + }, + new String[] { + "SecP256r1MLKEM768" + }, + false); + + runTest(new String[] { + "SecP384r1MLKEM1024" + }, + new String[] { + "SecP384r1MLKEM1024" + }, + false); + + runTest(new String[] { + "X25519MLKEM768" + }, + new String[] { + "SecP256r1MLKEM768" + }, + true); + + runTest(new String[] { + "X25519MLKEM768" + }, + new String[0], + true); + + runTest(new String[] { + "SecP256r1MLKEM768" + }, + null, + true); + + runTest(new String[] { + "X25519MLKEM768", + "x25519" + }, + new String[] { + "X25519MLKEM768", + "x25519" + }, + false); } private static void runTest(String[] serverNamedGroups, diff --git a/test/jdk/javax/net/ssl/TLSCommon/NamedGroup.java b/test/jdk/javax/net/ssl/TLSCommon/NamedGroup.java index ec89fe0d5b5..432a2bd1b0d 100644 --- a/test/jdk/javax/net/ssl/TLSCommon/NamedGroup.java +++ b/test/jdk/javax/net/ssl/TLSCommon/NamedGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,7 +37,11 @@ public enum NamedGroup { FFDHE3072("ffdhe3072"), FFDHE4096("ffdhe4096"), FFDHE6144("ffdhe6144"), - FFDHE8192("ffdhe8192"); + FFDHE8192("ffdhe8192"), + + X25519MLKEM768("X25519MLKEM768"), + SECP256R1MLKEM768("SecP256r1MLKEM768"), + SECP384R1MLKEM1024("SecP384r1MLKEM1024"); public final String name; diff --git a/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java b/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java index efb9895b33c..ed26cf90a8c 100644 --- a/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java +++ b/test/jdk/javax/net/ssl/TLSv13/ClientHelloKeyShares.java @@ -26,16 +26,21 @@ /* * @test - * @bug 8247630 + * @bug 8247630 8314323 * @summary Use two key share entries - * @run main/othervm ClientHelloKeyShares 29 23 + * @run main/othervm ClientHelloKeyShares 4588 29 * @run main/othervm -Djdk.tls.namedGroups=secp384r1,secp521r1,x448,ffdhe2048 ClientHelloKeyShares 24 30 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,x25519 ClientHelloKeyShares 29 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,secp256r1 ClientHelloKeyShares 23 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,sect163r1,ffdhe2048,ffdhe3072,ffdhe4096 ClientHelloKeyShares 256 * @run main/othervm -Djdk.tls.namedGroups=sect163k1,ffdhe2048,x25519,secp256r1 ClientHelloKeyShares 256 29 * @run main/othervm -Djdk.tls.namedGroups=secp256r1,secp384r1,ffdhe2048,x25519 ClientHelloKeyShares 23 256 - */ + * @run main/othervm -Djdk.tls.namedGroups=X25519MLKEM768 ClientHelloKeyShares 4588 + * @run main/othervm -Djdk.tls.namedGroups=x25519,X25519MLKEM768 ClientHelloKeyShares 29 4588 + * @run main/othervm -Djdk.tls.namedGroups=SecP256r1MLKEM768,x25519 ClientHelloKeyShares 4587 29 + * @run main/othervm -Djdk.tls.namedGroups=SecP384r1MLKEM1024,secp256r1 ClientHelloKeyShares 4589 23 + * @run main/othervm -Djdk.tls.namedGroups=X25519MLKEM768,SecP256r1MLKEM768,X25519,secp256r1 ClientHelloKeyShares 4588 29 +*/ import javax.net.ssl.*; import javax.net.ssl.SSLEngineResult.*; @@ -62,10 +67,6 @@ public class ClientHelloKeyShares { private static final int HELLO_EXT_SUPP_VERS = 43; private static final int HELLO_EXT_KEY_SHARE = 51; private static final int TLS_PROT_VER_13 = 0x0304; - private static final int NG_SECP256R1 = 0x0017; - private static final int NG_SECP384R1 = 0x0018; - private static final int NG_X25519 = 0x001D; - private static final int NG_X448 = 0x001E; public static void main(String args[]) throws Exception { if (debug) { diff --git a/test/jdk/javax/net/ssl/TLSv13/HRRKeyShares.java b/test/jdk/javax/net/ssl/TLSv13/HRRKeyShares.java index 560faf87049..bd14e465e65 100644 --- a/test/jdk/javax/net/ssl/TLSv13/HRRKeyShares.java +++ b/test/jdk/javax/net/ssl/TLSv13/HRRKeyShares.java @@ -26,10 +26,12 @@ /* * @test - * @bug 8247630 + * @bug 8247630 8314323 * @summary Use two key share entries * @library /test/lib - * @run main/othervm -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1 HRRKeyShares + * @run main/othervm + * -Djdk.tls.namedGroups=x25519,secp256r1,secp384r1,X25519MLKEM768,SecP256r1MLKEM768,SecP384r1MLKEM1024 + * HRRKeyShares */ import java.io.ByteArrayOutputStream; @@ -72,6 +74,10 @@ public class HRRKeyShares { private static final int NG_SECP384R1 = 0x0018; private static final int NG_X25519 = 0x001D; private static final int NG_X448 = 0x001E; + private static final int NG_X25519_MLKEM768 = 0x11EC; + private static final int NG_SECP256R1_MLKEM768 = 0x11EB; + private static final int NG_SECP384R1_MLKEM1024 = 0x11ED; + private static final int NG_GC512A = 0x0026; private static final int COMP_NONE = 0; private static final int ALERT_TYPE_FATAL = 2; @@ -238,6 +244,18 @@ public class HRRKeyShares { System.out.println("Test 4: Bad HRR using known / unasserted x448"); hrrKeyShareTest(NG_X448, false); System.out.println(); + + System.out.println("Test 5: Good HRR exchange using X25519MLKEM768"); + hrrKeyShareTest(NG_X25519_MLKEM768, true); + System.out.println(); + + System.out.println("Test 6: Good HRR exchange using SecP256r1MLKEM768"); + hrrKeyShareTest(NG_SECP256R1_MLKEM768, true); + System.out.println(); + + System.out.println("Test 7: Good HRR exchange using SecP384r1MLKEM1024"); + hrrKeyShareTest(NG_SECP384R1_MLKEM1024, true); + System.out.println(); } private static void logResult(String str, SSLEngineResult result) { @@ -348,7 +366,8 @@ public class HRRKeyShares { try { // Now we're expecting to reissue the ClientHello, this time - // with a secp384r1 share. + // with a key share for the HRR requested named + // group (hrrNamedGroup). cTOs.compact(); clientResult = engine.wrap(clientOut, cTOs); logResult("client wrap: ", clientResult); diff --git a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java index 8799f2305bf..a54bb501f78 100644 --- a/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java +++ b/test/jdk/sun/security/pkcs11/tls/fips/FipsModeTLS.java @@ -34,8 +34,12 @@ * -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 - * @run main/othervm/timeout=120 -Djdk.tls.client.protocols=TLSv1.3 FipsModeTLS + * 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 + * FipsModeTLS */ import java.io.File; diff --git a/test/jdk/sun/security/ssl/CipherSuite/DisabledCurve.java b/test/jdk/sun/security/ssl/CipherSuite/DisabledCurve.java index 26304c5df95..a13f8570f14 100644 --- a/test/jdk/sun/security/ssl/CipherSuite/DisabledCurve.java +++ b/test/jdk/sun/security/ssl/CipherSuite/DisabledCurve.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,12 +23,24 @@ /* * @test - * @bug 8246330 + * @bug 8246330 8314323 * @library /javax/net/ssl/templates /test/lib * @run main/othervm -Djdk.tls.namedGroups="secp384r1" DisabledCurve DISABLE_NONE PASS * @run main/othervm -Djdk.tls.namedGroups="secp384r1" DisabledCurve secp384r1 FAIL + * @run main/othervm -Djdk.tls.namedGroups="X25519MLKEM768" + DisabledCurve DISABLE_NONE PASS + * @run main/othervm -Djdk.tls.namedGroups="X25519MLKEM768" + DisabledCurve X25519MLKEM768 FAIL + * @run main/othervm -Djdk.tls.namedGroups="SecP256r1MLKEM768" + DisabledCurve DISABLE_NONE PASS + * @run main/othervm -Djdk.tls.namedGroups="SecP256r1MLKEM768" + DisabledCurve SecP256r1MLKEM768 FAIL + * @run main/othervm -Djdk.tls.namedGroups="SecP384r1MLKEM1024" + DisabledCurve DISABLE_NONE PASS + * @run main/othervm -Djdk.tls.namedGroups="SecP384r1MLKEM1024" + DisabledCurve SecP384r1MLKEM1024 FAIL */ import java.security.Security; import java.util.Arrays; @@ -45,8 +57,10 @@ public class DisabledCurve extends SSLSocketTemplate { private static final String[][][] protocols = { { { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" }, { "TLSv1.2" } }, { { "TLSv1.2" }, { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" } }, - { { "TLSv1.2" }, { "TLSv1.2" } }, { { "TLSv1.1" }, { "TLSv1.1" } }, - { { "TLSv1" }, { "TLSv1" } } }; + { { "TLSv1.2" }, { "TLSv1.2" } }, + { { "TLSv1.1" }, { "TLSv1.1" } }, + { { "TLSv1" }, { "TLSv1" } }, + { { "TLSv1.3" }, { "TLSv1.3" } } }; @Override protected SSLContext createClientSSLContext() throws Exception { @@ -94,17 +108,36 @@ public class DisabledCurve extends SSLSocketTemplate { String expected = args[1]; String disabledName = ("DISABLE_NONE".equals(args[0]) ? "" : args[0]); boolean disabled = false; - if (disabledName.equals("")) { + + if (disabledName.isEmpty()) { Security.setProperty("jdk.disabled.namedCurves", ""); + Security.setProperty("jdk.certpath.disabledAlgorithms", ""); } else { disabled = true; - Security.setProperty("jdk.certpath.disabledAlgorithms", "secp384r1"); + Security.setProperty("jdk.certpath.disabledAlgorithms", disabledName); + if (!disabledName.contains("MLKEM")) { + Security.setProperty("jdk.disabled.namedCurves", disabledName); + } else { + Security.setProperty("jdk.disabled.namedCurves", ""); + } } // Re-enable TLSv1 and TLSv1.1 since test depends on it. SecurityUtils.removeFromDisabledTlsAlgs("TLSv1", "TLSv1.1"); + String namedGroups = System.getProperty("jdk.tls.namedGroups", ""); + boolean hybridGroup = namedGroups.contains("MLKEM"); + for (index = 0; index < protocols.length; index++) { + if (hybridGroup) { + String[] clientProtos = protocols[index][0]; + String[] serverProtos = protocols[index][1]; + + if (!(isTLS13(clientProtos) && isTLS13(serverProtos))) { + continue; + } + } + try { (new DisabledCurve()).run(); if (expected.equals("FAIL")) { @@ -123,4 +156,8 @@ public class DisabledCurve extends SSLSocketTemplate { } } + + private static boolean isTLS13(String[] protocols) { + return protocols.length == 1 && "TLSv1.3".equals(protocols[0]); + } } diff --git a/test/jdk/sun/security/ssl/CipherSuite/NamedGroupsWithCipherSuite.java b/test/jdk/sun/security/ssl/CipherSuite/NamedGroupsWithCipherSuite.java index 5732f42982c..9080f549683 100644 --- a/test/jdk/sun/security/ssl/CipherSuite/NamedGroupsWithCipherSuite.java +++ b/test/jdk/sun/security/ssl/CipherSuite/NamedGroupsWithCipherSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -21,6 +21,8 @@ * questions. */ +import java.util.Arrays; +import java.util.List; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLSocket; @@ -29,7 +31,7 @@ import jdk.test.lib.security.SecurityUtils; /* * @test - * @bug 8224650 8242929 + * @bug 8224650 8242929 8314323 * @library /javax/net/ssl/templates * /javax/net/ssl/TLSCommon * /test/lib @@ -44,17 +46,20 @@ import jdk.test.lib.security.SecurityUtils; * @run main/othervm NamedGroupsWithCipherSuite ffdhe4096 * @run main/othervm NamedGroupsWithCipherSuite ffdhe6144 * @run main/othervm NamedGroupsWithCipherSuite ffdhe8192 + * @run main/othervm NamedGroupsWithCipherSuite X25519MLKEM768 + * @run main/othervm NamedGroupsWithCipherSuite SecP256r1MLKEM768 + * @run main/othervm NamedGroupsWithCipherSuite SecP384r1MLKEM1024 */ public class NamedGroupsWithCipherSuite extends SSLSocketTemplate { - private static final Protocol[] PROTOCOLS = new Protocol[] { + private static final List PROTOCOLS = List.of( Protocol.TLSV1_3, Protocol.TLSV1_2, Protocol.TLSV1_1, Protocol.TLSV1 - }; + ); - private static final CipherSuite[] CIPHER_SUITES = new CipherSuite[] { + private static final List CIPHER_SUITES = List.of( CipherSuite.TLS_AES_128_GCM_SHA256, CipherSuite.TLS_AES_256_GCM_SHA384, CipherSuite.TLS_CHACHA20_POLY1305_SHA256, @@ -75,7 +80,23 @@ public class NamedGroupsWithCipherSuite extends SSLSocketTemplate { CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - }; + ); + + private static final List HYBRID_NAMEDGROUPS = List.of( + "X25519MLKEM768", + "SecP256r1MLKEM768", + "SecP384r1MLKEM1024" + ); + + private static final List HYBRID_PROTOCOL = List.of( + Protocol.TLSV1_3 + ); + + private static final List HYBRID_CIPHER_SUITES = List.of( + CipherSuite.TLS_AES_128_GCM_SHA256, + CipherSuite.TLS_AES_256_GCM_SHA384, + CipherSuite.TLS_CHACHA20_POLY1305_SHA256 + ); private String protocol; private String cipher; @@ -151,48 +172,59 @@ public class NamedGroupsWithCipherSuite extends SSLSocketTemplate { // Re-enable TLSv1 and TLSv1.1 since test depends on it. SecurityUtils.removeFromDisabledTlsAlgs("TLSv1", "TLSv1.1"); - for (Protocol protocol : PROTOCOLS) { - for (CipherSuite cipherSuite : CIPHER_SUITES) { - // Named group converted to lower case just - // to satisfy Test condition + boolean hybridGroup = HYBRID_NAMEDGROUPS.contains(namedGroup); + List protocolList = hybridGroup ? + HYBRID_PROTOCOL : PROTOCOLS; + List cipherList = hybridGroup ? + HYBRID_CIPHER_SUITES : CIPHER_SUITES; + + // non-Hybrid named group converted to lower case just + // to satisfy Test condition + String normalizedGroup = hybridGroup ? + namedGroup : namedGroup.toLowerCase(); + + for (Protocol protocol : protocolList) { + for (CipherSuite cipherSuite : cipherList) { if (cipherSuite.supportedByProtocol(protocol) - && groupSupportdByCipher(namedGroup.toLowerCase(), - cipherSuite)) { + && groupSupportedByCipher(normalizedGroup, + cipherSuite)) { System.out.printf("Protocol: %s, cipher suite: %s%n", protocol, cipherSuite); - // Named group converted to lower case just - // to satisfy Test condition new NamedGroupsWithCipherSuite(protocol, - cipherSuite, namedGroup.toLowerCase()).run(); + cipherSuite, normalizedGroup).run(); } } } } - private static boolean groupSupportdByCipher(String group, + private static boolean groupSupportedByCipher(String group, CipherSuite cipherSuite) { + if (HYBRID_NAMEDGROUPS.contains(group)) { + return cipherSuite.keyExAlgorithm == null; + } + return (group.startsWith("x") - && xdhGroupSupportdByCipher(cipherSuite)) + && xdhGroupSupportedByCipher(cipherSuite)) || (group.startsWith("secp") - && ecdhGroupSupportdByCipher(cipherSuite)) + && ecdhGroupSupportedByCipher(cipherSuite)) || (group.startsWith("ffdhe") - && ffdhGroupSupportdByCipher(cipherSuite)); + && ffdhGroupSupportedByCipher(cipherSuite)); } - private static boolean xdhGroupSupportdByCipher( + private static boolean xdhGroupSupportedByCipher( CipherSuite cipherSuite) { return cipherSuite.keyExAlgorithm == null || cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_RSA; } - private static boolean ecdhGroupSupportdByCipher( + private static boolean ecdhGroupSupportedByCipher( CipherSuite cipherSuite) { return cipherSuite.keyExAlgorithm == null || cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_RSA || cipherSuite.keyExAlgorithm == KeyExAlgorithm.ECDHE_ECDSA; } - private static boolean ffdhGroupSupportdByCipher( + private static boolean ffdhGroupSupportedByCipher( CipherSuite cipherSuite) { return cipherSuite.keyExAlgorithm == null || cipherSuite.keyExAlgorithm == KeyExAlgorithm.DHE_DSS diff --git a/test/jdk/sun/security/ssl/CipherSuite/RestrictNamedGroup.java b/test/jdk/sun/security/ssl/CipherSuite/RestrictNamedGroup.java index c4c343bf84e..4ff0c6e6e15 100644 --- a/test/jdk/sun/security/ssl/CipherSuite/RestrictNamedGroup.java +++ b/test/jdk/sun/security/ssl/CipherSuite/RestrictNamedGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8226374 8242929 + * @bug 8226374 8242929 8314323 * @library /javax/net/ssl/templates * @summary Restrict signature algorithms and named groups * @run main/othervm RestrictNamedGroup x25519 @@ -36,6 +36,9 @@ * @run main/othervm RestrictNamedGroup ffdhe4096 * @run main/othervm RestrictNamedGroup ffdhe6144 * @run main/othervm RestrictNamedGroup ffdhe8192 + * @run main/othervm RestrictNamedGroup X25519MLKEM768 + * @run main/othervm RestrictNamedGroup SecP256r1MLKEM768 + * @run main/othervm RestrictNamedGroup SecP384r1MLKEM1024 */ import java.security.Security; diff --git a/test/jdk/sun/security/ssl/CipherSuite/SupportedGroups.java b/test/jdk/sun/security/ssl/CipherSuite/SupportedGroups.java index 88b0bf2489a..8cf6ee2b5e6 100644 --- a/test/jdk/sun/security/ssl/CipherSuite/SupportedGroups.java +++ b/test/jdk/sun/security/ssl/CipherSuite/SupportedGroups.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8171279 + * @bug 8171279 8314323 * @library /javax/net/ssl/templates * @summary Test TLS connection with each individual supported group * @run main/othervm SupportedGroups x25519 @@ -36,6 +36,9 @@ * @run main/othervm SupportedGroups ffdhe4096 * @run main/othervm SupportedGroups ffdhe6144 * @run main/othervm SupportedGroups ffdhe8192 + * @run main/othervm SupportedGroups X25519MLKEM768 + * @run main/othervm SupportedGroups SecP256r1MLKEM768 + * @run main/othervm SupportedGroups SecP384r1MLKEM1024 */ import java.net.InetAddress; import java.util.Arrays; @@ -45,15 +48,24 @@ import javax.net.ssl.SSLServerSocket; public class SupportedGroups extends SSLSocketTemplate { private static volatile int index; - private static final String[][][] protocols = { + private static final String[][][] protocolsForClassic = { {{"TLSv1.3"}, {"TLSv1.3"}}, {{"TLSv1.3", "TLSv1.2"}, {"TLSv1.2"}}, {{"TLSv1.2"}, {"TLSv1.3", "TLSv1.2"}}, {{"TLSv1.2"}, {"TLSv1.2"}} }; - public SupportedGroups() { + private static final String[][][] protocolsForHybrid = { + {{"TLSv1.3"}, {"TLSv1.3"}}, + {{"TLSv1.3", "TLSv1.2"}, {"TLSv1.3"}}, + {{"TLSv1.3"}, {"TLSv1.3", "TLSv1.2"}} + }; + + private final String[][][] protocols; + + public SupportedGroups(String[][][] protocols) { this.serverAddress = InetAddress.getLoopbackAddress(); + this.protocols = protocols; } // Servers are configured before clients, increment test case after. @@ -85,8 +97,18 @@ public class SupportedGroups extends SSLSocketTemplate { public static void main(String[] args) throws Exception { System.setProperty("jdk.tls.namedGroups", args[0]); + boolean hybridGroup = hybridNamedGroup(args[0]); + String[][][] protocols = hybridGroup ? + protocolsForHybrid : protocolsForClassic; + for (index = 0; index < protocols.length; index++) { - (new SupportedGroups()).run(); + (new SupportedGroups(protocols)).run(); } } + + private static boolean hybridNamedGroup(String namedGroup) { + return namedGroup.equals("X25519MLKEM768") || + namedGroup.equals("SecP256r1MLKEM768") || + namedGroup.equals("SecP384r1MLKEM1024"); + } } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index 7874df3fd69..f81c35cea0b 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -294,34 +294,8 @@ public class JPackageCommand extends CommandArguments { public JPackageCommand setFakeRuntime() { verifyMutable(); - - ThrowingConsumer createBulkFile = path -> { - Files.createDirectories(path.getParent()); - try (FileOutputStream out = new FileOutputStream(path.toFile())) { - byte[] bytes = new byte[4 * 1024]; - new SecureRandom().nextBytes(bytes); - out.write(bytes); - } - }; - addPrerequisiteAction(cmd -> { - Path fakeRuntimeDir = TKit.createTempDirectory("fake_runtime"); - - TKit.trace(String.format("Init fake runtime in [%s] directory", - fakeRuntimeDir)); - - if (TKit.isOSX()) { - // Make MacAppImageBuilder happy - createBulkFile.accept(fakeRuntimeDir.resolve(Path.of( - "lib/jli/libjli.dylib"))); - } - - // Make sure fake runtime takes some disk space. - // Package bundles with 0KB size are unexpected and considered - // an error by PackageTest. - createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("lib", "bulk"))); - - cmd.setArgumentValue("--runtime-image", fakeRuntimeDir); + cmd.setArgumentValue("--runtime-image", createInputRuntimeImage(RuntimeImageType.RUNTIME_TYPE_FAKE)); }); return this; @@ -390,24 +364,77 @@ public class JPackageCommand extends CommandArguments { return cmd; } + public enum RuntimeImageType { + + /** + * Runtime suitable for running the default "Hello" test app. + */ + RUNTIME_TYPE_HELLO_APP, + + /** + * Fake runtime. + */ + RUNTIME_TYPE_FAKE, + + ; + } + public static Path createInputRuntimeImage() { + return createInputRuntimeImage(RuntimeImageType.RUNTIME_TYPE_HELLO_APP); + } + + public static Path createInputRuntimeImage(RuntimeImageType role) { + Objects.requireNonNull(role); final Path runtimeImageDir; + switch (role) { - if (JPackageCommand.DEFAULT_RUNTIME_IMAGE != null) { - runtimeImageDir = JPackageCommand.DEFAULT_RUNTIME_IMAGE; - } else { - runtimeImageDir = TKit.createTempDirectory("runtime-image").resolve("data"); + case RUNTIME_TYPE_FAKE -> { + Consumer createBulkFile = ThrowingConsumer.toConsumer(path -> { + Files.createDirectories(path.getParent()); + try (FileOutputStream out = new FileOutputStream(path.toFile())) { + byte[] bytes = new byte[4 * 1024]; + new SecureRandom().nextBytes(bytes); + out.write(bytes); + } + }); - new Executor().setToolProvider(JavaTool.JLINK) - .dumpOutput() - .addArguments( - "--output", runtimeImageDir.toString(), - "--add-modules", "java.desktop", - "--strip-debug", - "--no-header-files", - "--no-man-pages") - .execute(); + runtimeImageDir = TKit.createTempDirectory("fake_runtime"); + + TKit.trace(String.format("Init fake runtime in [%s] directory", runtimeImageDir)); + + if (TKit.isOSX()) { + // Make MacAppImageBuilder happy + createBulkFile.accept(runtimeImageDir.resolve(Path.of("lib/jli/libjli.dylib"))); + } + + // Make sure fake runtime takes some disk space. + // Package bundles with 0KB size are unexpected and considered + // an error by PackageTest. + createBulkFile.accept(runtimeImageDir.resolve(Path.of("lib", "bulk"))); + } + + case RUNTIME_TYPE_HELLO_APP -> { + if (JPackageCommand.DEFAULT_RUNTIME_IMAGE != null && !isFakeRuntime(DEFAULT_RUNTIME_IMAGE)) { + runtimeImageDir = JPackageCommand.DEFAULT_RUNTIME_IMAGE; + } else { + runtimeImageDir = TKit.createTempDirectory("runtime-image").resolve("data"); + + new Executor().setToolProvider(JavaTool.JLINK) + .dumpOutput() + .addArguments( + "--output", runtimeImageDir.toString(), + "--add-modules", "java.desktop", + "--strip-debug", + "--no-header-files", + "--no-man-pages") + .execute(); + } + } + + default -> { + throw ExceptionBox.reachedUnreachable(); + } } return runtimeImageDir; diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java index 1cb5532d46a..dc1a7b3512b 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java @@ -46,6 +46,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -66,6 +67,7 @@ import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; +import jdk.jpackage.internal.util.Enquoter; import jdk.jpackage.internal.util.FileUtils; import jdk.jpackage.internal.util.MacBundle; import jdk.jpackage.internal.util.PListReader; @@ -75,6 +77,8 @@ import jdk.jpackage.internal.util.XmlUtils; import jdk.jpackage.internal.util.function.ThrowingConsumer; import jdk.jpackage.internal.util.function.ThrowingSupplier; import jdk.jpackage.test.MacSign.CertificateRequest; +import jdk.jpackage.test.MacSign.CertificateType; +import jdk.jpackage.test.MacSign.ResolvedKeychain; import jdk.jpackage.test.PackageTest.PackageHandlers; import jdk.jpackage.test.RunnablePackageTest.Action; import org.xml.sax.SAXException; @@ -430,18 +434,50 @@ public final class MacHelper { } } + public static final class RuntimeBundleBuilder { + + public Path create() { + return createRuntimeBundle(type, Optional.ofNullable(mutator)); + } + + public RuntimeBundleBuilder type(JPackageCommand.RuntimeImageType v) { + type = Objects.requireNonNull(v); + return this; + } + + public RuntimeBundleBuilder mutator(Consumer v) { + mutator = v; + return this; + } + + public RuntimeBundleBuilder mutate(Consumer mutator) { + mutator.accept(this); + return this; + } + + private RuntimeBundleBuilder() { + } + + private JPackageCommand.RuntimeImageType type = JPackageCommand.RuntimeImageType.RUNTIME_TYPE_HELLO_APP; + private Consumer mutator; + }; + + public static RuntimeBundleBuilder buildRuntimeBundle() { + return new RuntimeBundleBuilder(); + } + public static Path createRuntimeBundle(Consumer mutator) { - return createRuntimeBundle(Optional.of(mutator)); + return buildRuntimeBundle().mutator(Objects.requireNonNull(mutator)).create(); } public static Path createRuntimeBundle() { - return createRuntimeBundle(Optional.empty()); + return buildRuntimeBundle().create(); } - public static Path createRuntimeBundle(Optional> mutator) { + private static Path createRuntimeBundle(JPackageCommand.RuntimeImageType type, Optional> mutator) { Objects.requireNonNull(mutator); - final var runtimeImage = JPackageCommand.createInputRuntimeImage(); + final var runtimeImage = JPackageCommand.createInputRuntimeImage(type); final var runtimeBundleWorkDir = TKit.createTempDirectory("runtime-bundle"); @@ -495,25 +531,244 @@ public final class MacHelper { return cmd; } - public record SignKeyOption(Type type, CertificateRequest certRequest) { + public static final class ResolvableCertificateRequest { + + public ResolvableCertificateRequest( + CertificateRequest certRequest, + Function certResolver, + String label) { + + Objects.requireNonNull(certRequest); + Objects.requireNonNull(certResolver); + Objects.requireNonNull(label); + if (label.isBlank()) { + throw new IllegalArgumentException(); + } + + this.certRequest = certRequest; + this.certResolver = certResolver; + this.label = label; + } + + public ResolvableCertificateRequest( + CertificateRequest certRequest, + ResolvedKeychain keychain, + String label) { + this(certRequest, keychain.asCertificateResolver(), label); + } + + @Override + public String toString() { + return label; + } + + public CertificateRequest certRequest() { + return certRequest; + } + + public X509Certificate cert() { + return certResolver.apply(certRequest); + } + + public CertificateType type() { + return certRequest.type(); + } + + public String name() { + return certRequest.name(); + } + + public String shortName() { + return certRequest.shortName(); + } + + public int days() { + return certRequest.days(); + } + + public boolean expired() { + return certRequest.expired(); + } + + public boolean trusted() { + return certRequest.trusted(); + } + + private final CertificateRequest certRequest; + private final Function certResolver; + private final String label; + } + + public interface NamedCertificateRequestSupplier { + + String name(); + + CertificateRequest certRequest(); + + default ResolvableCertificateRequest certRequest(ResolvedKeychain keychain) { + Objects.requireNonNull(keychain); + var certRequest = Objects.requireNonNull(certRequest()); + if (keychain.spec().certificateRequests().contains(certRequest)) { + return new ResolvableCertificateRequest(certRequest, keychain.asCertificateResolver(), name()); + } else { + throw new IllegalArgumentException(String.format( + "Certificate request %s not found in [%s] keychain", + name(), keychain.spec().keychain().name())); + } + } + } + + public record SignKeyOption(Type type, ResolvableCertificateRequest certRequest, Optional customOptionValue) { public SignKeyOption { Objects.requireNonNull(type); Objects.requireNonNull(certRequest); + Objects.requireNonNull(customOptionValue); + if (customOptionValue.isEmpty() == (type == Type.SIGN_KEY_USER_NAME)) { + throw new IllegalArgumentException(); + } + } + + public SignKeyOption(Type type, ResolvableCertificateRequest certRequest) { + this(type, certRequest, Optional.empty()); + } + + public SignKeyOption( + Type type, + NamedCertificateRequestSupplier certRequestSupplier, + ResolvedKeychain keychain) { + + this(type, certRequestSupplier.certRequest(keychain)); + } + + public SignKeyOption(String optionValue, ResolvableCertificateRequest certRequest) { + this(Type.SIGN_KEY_USER_NAME, certRequest, Optional.of(optionValue)); + } + + public SignKeyOption( + String optionValue, + NamedCertificateRequestSupplier certRequestSupplier, + ResolvedKeychain keychain) { + + this(optionValue, certRequestSupplier.certRequest(keychain)); + } + + public enum Name { + KEY_USER_NAME("--mac-signing-key-user-name"), + KEY_IDENTITY_APP_IMAGE("--mac-app-image-sign-identity"), + KEY_IDENTITY_INSTALLER("--mac-installer-sign-identity"), + ; + + Name(String optionName) { + this.optionName = Objects.requireNonNull(optionName); + } + + public String optionName() { + return optionName; + } + + public boolean passThrough() { + return this != KEY_USER_NAME; + } + + private final String optionName; } public enum Type { - SIGN_KEY_USER_NAME, - SIGN_KEY_IDENTITY, + /** + * "--mac-signing-key-user-name" option with custom value + */ + SIGN_KEY_USER_NAME(Name.KEY_USER_NAME), + + /** + * "--mac-signing-key-user-name" option with the short user name, e.g.: + * {@code --mac-signing-key-user-name foo} + */ + SIGN_KEY_USER_SHORT_NAME(Name.KEY_USER_NAME), + + /** + * "--mac-signing-key-user-name" option with the full user name (aka signing + * identity name), e.g.: + * {@code --mac-signing-key-user-name 'Developer ID Application: foo'} + */ + SIGN_KEY_USER_FULL_NAME(Name.KEY_USER_NAME), + + /** + * "--mac-installer-sign-identity" or "--mac-app-image-sign-identity" option + * with the signing identity name, e.g.: + * {@code --mac-app-image-sign-identity 'Developer ID Application: foo'} + */ + SIGN_KEY_IDENTITY(Map.of( + MacSign.CertificateType.CODE_SIGN, Name.KEY_IDENTITY_APP_IMAGE, + MacSign.CertificateType.INSTALLER, Name.KEY_IDENTITY_INSTALLER)), + + /** + * "--mac-app-image-sign-identity" regardless of the type of signing identity + * (for signing app image or .pkg installer). + */ + SIGN_KEY_IDENTITY_APP_IMAGE(Name.KEY_IDENTITY_APP_IMAGE), + + /** + * "--mac-installer-sign-identity" regardless of the type of signing identity + * (for signing app image or .pkg installer). + */ + SIGN_KEY_IDENTITY_INSTALLER(Name.KEY_IDENTITY_INSTALLER), + ; + + Type(Map optionNameMap) { + Objects.requireNonNull(optionNameMap); + this.optionNameMapper = certType -> { + return Optional.of(optionNameMap.get(certType)); + }; + } + + Type(Name optionName) { + Objects.requireNonNull(optionName); + this.optionNameMapper = _ -> Optional.of(optionName); + } + + Type() { + this.optionNameMapper = _ -> Optional.empty(); + } + + public Optional mapOptionName(MacSign.CertificateType certType) { + return optionNameMapper.apply(Objects.requireNonNull(certType)); + } + + public static Type[] defaultValues() { + return new Type[] { + SIGN_KEY_USER_SHORT_NAME, + SIGN_KEY_USER_FULL_NAME, + SIGN_KEY_IDENTITY + }; + } + + private final Function> optionNameMapper; } @Override public String toString() { var sb = new StringBuilder(); + sb.append('{'); applyTo((optionName, _) -> { - sb.append(String.format("{%s: %s}", optionName, certRequest)); + sb.append(optionName); + switch (type) { + case SIGN_KEY_USER_FULL_NAME -> { + sb.append("/full"); + } + case SIGN_KEY_USER_NAME -> { + customOptionValue.ifPresent(optionValue -> { + sb.append("=").append(ENQUOTER.applyTo(optionValue)); + }); + } + default -> { + // NOP + } + } + sb.append(": "); }); + sb.append(certRequest).append('}'); return sb.toString(); } @@ -527,35 +782,127 @@ public final class MacHelper { return sign(cmd); } - private void applyTo(BiConsumer sink) { - switch (certRequest.type()) { - case INSTALLER -> { - switch (type) { - case SIGN_KEY_IDENTITY -> { - sink.accept("--mac-installer-sign-identity", certRequest.name()); - return; - } - case SIGN_KEY_USER_NAME -> { - sink.accept("--mac-signing-key-user-name", certRequest.shortName()); - return; - } - } - } - case CODE_SIGN -> { - switch (type) { - case SIGN_KEY_IDENTITY -> { - sink.accept("--mac-app-image-sign-identity", certRequest.name()); - return; - } - case SIGN_KEY_USER_NAME -> { - sink.accept("--mac-signing-key-user-name", certRequest.shortName()); - return; - } - } - } - } + public List asCmdlineArgs() { + String[] args = new String[2]; + applyTo((optionName, optionValue) -> { + args[0] = optionName; + args[1] = optionValue; + }); + return List.of(args); + } - throw new AssertionError(); + private void applyTo(BiConsumer sink) { + type.mapOptionName(certRequest.type()).ifPresent(optionName -> { + sink.accept(optionName.optionName(), optionValue()); + }); + } + + private String optionValue() { + return customOptionValue.orElseGet(() -> { + switch (type) { + case SIGN_KEY_IDENTITY, + SIGN_KEY_USER_FULL_NAME, + SIGN_KEY_IDENTITY_APP_IMAGE, + SIGN_KEY_IDENTITY_INSTALLER -> { + return certRequest.name(); + } + case SIGN_KEY_USER_SHORT_NAME -> { + return certRequest.shortName(); + } + default -> { + throw new IllegalStateException(); + } + } + }); + } + + private static final Enquoter ENQUOTER = Enquoter.identity() + .setEnquotePredicate(Enquoter.QUOTE_IF_WHITESPACES).setQuoteChar('\''); + } + + public record SignKeyOptionWithKeychain(SignKeyOption signKeyOption, ResolvedKeychain keychain) { + + public SignKeyOptionWithKeychain { + Objects.requireNonNull(signKeyOption); + Objects.requireNonNull(keychain); + } + + public SignKeyOptionWithKeychain( + SignKeyOption.Type type, + ResolvableCertificateRequest certRequest, + ResolvedKeychain keychain) { + + this(new SignKeyOption(type, certRequest), keychain); + } + + public SignKeyOptionWithKeychain( + SignKeyOption.Type type, + NamedCertificateRequestSupplier certRequestSupplier, + ResolvedKeychain keychain) { + + this(type, certRequestSupplier.certRequest(keychain), keychain); + } + + public SignKeyOptionWithKeychain( + String optionValue, + ResolvableCertificateRequest certRequest, + ResolvedKeychain keychain) { + + this(new SignKeyOption(optionValue, certRequest), keychain); + } + + public SignKeyOptionWithKeychain( + String optionValue, + NamedCertificateRequestSupplier certRequestSupplier, + ResolvedKeychain keychain) { + + this(optionValue, certRequestSupplier.certRequest(keychain), keychain); + } + + public SignKeyOptionWithKeychain( + SignKeyOption.Type type, + CertificateRequest certRequest, + ResolvedKeychain keychain) { + + this(new SignKeyOption( + type, + new ResolvableCertificateRequest( + certRequest, + keychain.asCertificateResolver(), + certRequest.toString())), + keychain); + } + + @Override + public String toString() { + return String.format("%s@%s", signKeyOption, keychain.name()); + } + + public SignKeyOption.Type type() { + return signKeyOption.type(); + } + + public ResolvableCertificateRequest certRequest() { + return signKeyOption.certRequest(); + } + + public JPackageCommand addTo(JPackageCommand cmd) { + Optional.ofNullable(cmd.getArgumentValue("--mac-signing-keychain")).ifPresentOrElse(configuredKeychain -> { + if (!configuredKeychain.equals(keychain.name())) { + throw new IllegalStateException(String.format( + "Command line [%s] already has the '--mac-signing-keychain' option, not adding another one with [%s] value", + cmd, keychain.name())); + } + }, () -> { + useKeychain(cmd, keychain); + }); + return signKeyOption.addTo(cmd); + } + + public JPackageCommand setTo(JPackageCommand cmd) { + cmd.removeArgumentWithValue("--mac-signing-keychain"); + useKeychain(cmd, keychain); + return signKeyOption.setTo(cmd); } } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java index 70de6ba92af..3bbbf436300 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSign.java @@ -61,6 +61,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; import javax.naming.ldap.LdapName; @@ -1132,6 +1133,18 @@ public final class MacSign { return certMap; } + public Function asCertificateResolver() { + return certRequest -> { + if (!spec.certificateRequests().contains(certRequest)) { + throw new IllegalArgumentException(String.format( + "Certificate request %s not found in [%s] keychain", + certRequest, name())); + } else { + return Objects.requireNonNull(mapCertificateRequests().get(certRequest)); + } + }; + } + private final KeychainWithCertsSpec spec; private volatile Map certMap; } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java index 9c469c9362e..ddf899d603c 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacSignVerify.java @@ -33,6 +33,7 @@ import java.util.Objects; import java.util.Optional; import java.util.regex.Pattern; import jdk.jpackage.internal.util.PListReader; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; import jdk.jpackage.test.MacSign.CertificateHash; import jdk.jpackage.test.MacSign.CertificateRequest; @@ -41,12 +42,10 @@ import jdk.jpackage.test.MacSign.CertificateRequest; */ public final class MacSignVerify { - public static void verifyAppImageSigned( - JPackageCommand cmd, CertificateRequest certRequest, MacSign.ResolvedKeychain keychain) { + public static void verifyAppImageSigned(JPackageCommand cmd, ResolvableCertificateRequest certRequest) { - cmd.verifyIsOfType(PackageType.MAC); + cmd.verifyIsOfType(PackageType.MAC_DMG, PackageType.MAC_PKG, PackageType.IMAGE); Objects.requireNonNull(certRequest); - Objects.requireNonNull(keychain); final Path bundleRoot; if (cmd.isImagePackageType()) { @@ -70,13 +69,12 @@ public final class MacSignVerify { String.format("Check [%s] has sign origin as expected", bundleRoot)); } - public static void verifyPkgSigned(JPackageCommand cmd, CertificateRequest certRequest, MacSign.ResolvedKeychain keychain) { + public static void verifyPkgSigned(JPackageCommand cmd, ResolvableCertificateRequest certRequest) { cmd.verifyIsOfType(PackageType.MAC_PKG); - assertPkgSigned(cmd.outputBundle(), certRequest, - Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest))); + assertPkgSigned(cmd.outputBundle(), certRequest); } - public static void assertSigned(Path path, CertificateRequest certRequest) { + public static void assertSigned(Path path, ResolvableCertificateRequest certRequest) { assertSigned(path); TKit.assertEquals(certRequest.name(), findCodesignSignOrigin(path).orElse(null), String.format("Check [%s] signed with certificate", path)); @@ -108,6 +106,10 @@ public final class MacSignVerify { String.format("Check [%s] unsigned", path)); } + public static void assertPkgSigned(Path path, ResolvableCertificateRequest certRequest) { + assertPkgSigned(path, certRequest.certRequest(), certRequest.cert()); + } + public static void assertPkgSigned(Path path, CertificateRequest certRequest, X509Certificate cert) { final var expectedCertChain = List.of(new SignIdentity(certRequest.name(), CertificateHash.of(cert, SHA256))); final var actualCertChain = getPkgCertificateChain(path); diff --git a/test/jdk/tools/jpackage/macosx/EntitlementsTest.java b/test/jdk/tools/jpackage/macosx/EntitlementsTest.java index 6e03c858db3..052e301a9ae 100644 --- a/test/jdk/tools/jpackage/macosx/EntitlementsTest.java +++ b/test/jdk/tools/jpackage/macosx/EntitlementsTest.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 @@ -56,10 +56,9 @@ import jdk.jpackage.test.TKit; * @test * @summary jpackage with --type app-image "--mac-entitlements" parameter * @library /test/jdk/tools/jpackage/helpers - * @library base - * @build SigningBase * @build jdk.jpackage.test.* - * @build EntitlementsTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror EntitlementsTest.java * @requires (jpackage.test.MacSignTests == "run") * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main * --jpt-run=EntitlementsTest @@ -142,7 +141,8 @@ public class EntitlementsTest { cmd.mutate(MacHelper.useKeychain(keychain)).mutate(new SignKeyOption( SignKeyOption.Type.SIGN_KEY_IDENTITY, - SigningBase.StandardCertificateRequest.CODESIGN.spec() + SigningBase.StandardCertificateRequest.CODESIGN, + keychain )::addTo); cmd.mutate(new AdditionalLauncher("x")::applyTo); diff --git a/test/jdk/tools/jpackage/macosx/MacSignTest.java b/test/jdk/tools/jpackage/macosx/MacSignTest.java index af7cf448bdc..a824fdb0925 100644 --- a/test/jdk/tools/jpackage/macosx/MacSignTest.java +++ b/test/jdk/tools/jpackage/macosx/MacSignTest.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 @@ -21,12 +21,16 @@ * questions. */ +import static jdk.jpackage.test.MacHelper.SignKeyOption.Type.SIGN_KEY_IDENTITY; +import static jdk.jpackage.test.MacHelper.SignKeyOption.Type.SIGN_KEY_USER_FULL_NAME; +import static jdk.jpackage.test.MacHelper.SignKeyOption.Type.SIGN_KEY_USER_SHORT_NAME; +import static jdk.jpackage.test.MacHelper.SignKeyOption.Type.SIGN_KEY_IDENTITY_APP_IMAGE; + import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; @@ -37,8 +41,11 @@ import jdk.jpackage.test.CannedFormattedString; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.JPackageStringBundle; import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacHelper.NamedCertificateRequestSupplier; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; -import jdk.jpackage.test.MacSign.CertificateRequest; import jdk.jpackage.test.MacSign.CertificateType; import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageType; @@ -48,10 +55,9 @@ import jdk.jpackage.test.TKit; * @test * @summary jpackage with --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base - * @build SigningBase * @build jdk.jpackage.test.* - * @build MacSignTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror MacSignTest.java * @requires (jpackage.test.MacSignTests == "run") * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main * --jpt-run=MacSignTest @@ -79,22 +85,28 @@ public class MacSignTest { } MacSign.withKeychain(keychain -> { + + var signingKeyOption = new SignKeyOptionWithKeychain( + SIGN_KEY_IDENTITY, + SigningBase.StandardCertificateRequest.CODESIGN, + keychain); + // --app-content and --type app-image // Expect `message.codesign.failed.reason.app.content` message in the log. // This is not a fatal error, just a warning. // To make jpackage fail, specify bad additional content. - final var cmd = JPackageCommand.helloAppImage() + JPackageCommand.helloAppImage() .ignoreDefaultVerbose(true) .validateOutput(expectedStrings.toArray(CannedFormattedString[]::new)) .addArguments("--app-content", appContent) - .addArguments("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec().name()); + .mutate(signingKeyOption::addTo) + .mutate(cmd -> { + if (MacHelper.isXcodeDevToolsInstalled()) { + // Check there is no warning about missing xcode command line developer tools. + cmd.validateOutput(TKit.assertTextStream(xcodeWarning.getValue()).negate()); + } + }).execute(1); - if (MacHelper.isXcodeDevToolsInstalled()) { - // Check there is no warning about missing xcode command line developer tools. - cmd.validateOutput(TKit.assertTextStream(xcodeWarning.getValue()).negate()); - } - - MacHelper.useKeychain(cmd, keychain).execute(1); }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); } @@ -116,13 +128,19 @@ public class MacSignTest { expectedStrings.add(JPackageStringBundle.MAIN.cannedFormattedString("error.tool.failed.with.output", "codesign")); MacSign.withKeychain(keychain -> { - final var cmd = new JPackageCommand().setPackageType(PackageType.IMAGE) + + var signingKeyOption = new SignKeyOptionWithKeychain( + SIGN_KEY_IDENTITY, + SigningBase.StandardCertificateRequest.CODESIGN, + keychain); + + new JPackageCommand().setPackageType(PackageType.IMAGE) .ignoreDefaultVerbose(true) .validateOutput(expectedStrings.toArray(CannedFormattedString[]::new)) .addArguments("--app-image", appImageCmd.outputBundle()) - .addArguments("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec().name()); + .mutate(signingKeyOption::addTo) + .execute(1); - MacHelper.useKeychain(cmd, keychain).execute(1); }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); } @@ -189,71 +207,78 @@ public class MacSignTest { @Test @ParameterSupplier @ParameterSupplier("testSelectSigningIdentity_JDK_8371094") - public static void testSelectSigningIdentity(String signingKeyUserName, CertificateRequest certRequest) { + public static void testSelectSigningIdentity(SignKeyOptionWithKeychain signKeyOption) { MacSign.withKeychain(keychain -> { - final var cmd = MacHelper.useKeychain(JPackageCommand.helloAppImage(), keychain) - .setFakeRuntime() - .addArguments("--mac-signing-key-user-name", signingKeyUserName); + final var cmd = JPackageCommand.helloAppImage().setFakeRuntime().mutate(signKeyOption::addTo); - cmd.executeAndAssertHelloAppImageCreated(); + cmd.executeAndAssertImageCreated(); - MacSignVerify.assertSigned(cmd.outputBundle(), certRequest); + MacSignVerify.verifyAppImageSigned(cmd, signKeyOption.certRequest()); }, MacSign.Keychain.UsageBuilder::addToSearchList, SigningBase.StandardKeychain.MAIN.keychain()); } public static Collection testSelectSigningIdentity() { + var keychain = SigningBase.StandardKeychain.MAIN.keychain(); return Stream.of( SigningBase.StandardCertificateRequest.CODESIGN, SigningBase.StandardCertificateRequest.CODESIGN_UNICODE - ).map(SigningBase.StandardCertificateRequest::spec).mapMulti((certRequest, acc) -> { - acc.accept(new Object[] {certRequest.shortName(), certRequest}); - acc.accept(new Object[] {certRequest.name(), certRequest}); + ).map(certRequest -> { + return Stream.of( + SIGN_KEY_USER_FULL_NAME, + SIGN_KEY_USER_SHORT_NAME + ).map(type -> { + return new SignKeyOptionWithKeychain(type, certRequest, keychain); + }); + }).flatMap(x -> x).map(v -> { + return new Object[] {v}; }).toList(); } public static Collection testSelectSigningIdentity_JDK_8371094() { return List.of(new Object[] { - "ACME Technologies Limited", SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD.spec() + new SignKeyOptionWithKeychain( + "ACME Technologies Limited", + SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD, + SigningBase.StandardKeychain.MAIN.keychain()) }); } enum SignOption { - EXPIRED_SIGNING_KEY_USER_NAME("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED.spec(), true, false), - EXPIRED_SIGNING_KEY_USER_NAME_PKG("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.PKG_EXPIRED.spec(), true, false), - EXPIRED_SIGN_IDENTITY("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED.spec(), false, false), - EXPIRED_CODESIGN_SIGN_IDENTITY("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED.spec(), false, true), - EXPIRED_PKG_SIGN_IDENTITY("--mac-installer-sign-identity", SigningBase.StandardCertificateRequest.PKG_EXPIRED.spec(), false, true), - GOOD_SIGNING_KEY_USER_NAME("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.CODESIGN.spec(), true, false), - GOOD_SIGNING_KEY_USER_NAME_PKG("--mac-signing-key-user-name", SigningBase.StandardCertificateRequest.PKG.spec(), true, false), - GOOD_CODESIGN_SIGN_IDENTITY("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.CODESIGN.spec(), false, true), - GOOD_PKG_SIGN_IDENTITY("--mac-app-image-sign-identity", SigningBase.StandardCertificateRequest.PKG.spec(), false, true); + EXPIRED_SIGNING_KEY_USER_NAME(SIGN_KEY_USER_SHORT_NAME, SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED), + EXPIRED_SIGNING_KEY_USER_NAME_PKG(SIGN_KEY_USER_SHORT_NAME, SigningBase.StandardCertificateRequest.PKG_EXPIRED), + EXPIRED_SIGN_IDENTITY(SIGN_KEY_USER_FULL_NAME, SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED), + EXPIRED_CODESIGN_SIGN_IDENTITY(SIGN_KEY_IDENTITY, SigningBase.StandardCertificateRequest.CODESIGN_EXPIRED), + EXPIRED_PKG_SIGN_IDENTITY(SIGN_KEY_IDENTITY, SigningBase.StandardCertificateRequest.PKG_EXPIRED), + GOOD_SIGNING_KEY_USER_NAME(SIGN_KEY_USER_SHORT_NAME, SigningBase.StandardCertificateRequest.CODESIGN), + GOOD_SIGNING_KEY_USER_NAME_PKG(SIGN_KEY_USER_SHORT_NAME, SigningBase.StandardCertificateRequest.PKG), + GOOD_CODESIGN_SIGN_IDENTITY(SIGN_KEY_IDENTITY, SigningBase.StandardCertificateRequest.CODESIGN), + GOOD_PKG_SIGN_IDENTITY(SIGN_KEY_IDENTITY_APP_IMAGE, SigningBase.StandardCertificateRequest.PKG); - SignOption(String option, MacSign.CertificateRequest cert, boolean shortName, boolean passThrough) { - this.option = Objects.requireNonNull(option); - this.cert = Objects.requireNonNull(cert); - this.shortName = shortName; - this.passThrough = passThrough; + SignOption(SignKeyOption.Type optionType, NamedCertificateRequestSupplier certRequestSupplier) { + this.option = new SignKeyOption(optionType, new ResolvableCertificateRequest(certRequestSupplier.certRequest(), _ -> { + throw new UnsupportedOperationException(); + }, certRequestSupplier.name())); } boolean passThrough() { - return passThrough; + return option.type().mapOptionName(option.certRequest().type()).orElseThrow().passThrough(); } boolean expired() { - return cert.expired(); + return option.certRequest().expired(); } String identityName() { - return cert.name(); + return option.certRequest().name(); } CertificateType identityType() { - return cert.type(); + return option.certRequest().type(); } List args() { - return List.of(option, shortName ? cert.shortName() : cert.name()); + return option.asCmdlineArgs(); } static JPackageCommand configureOutputValidation(JPackageCommand cmd, List options, @@ -274,9 +299,6 @@ public class MacSignTest { return cmd; } - private final String option; - private final MacSign.CertificateRequest cert; - private final boolean shortName; - private final boolean passThrough; + private final SignKeyOption option; } } diff --git a/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java index 37ba8a6c299..0f299cb5a24 100644 --- a/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.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 @@ -21,90 +21,76 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; - -import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import jdk.jpackage.test.AdditionalLauncher; -import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; /** - * Tests generation of app image with --mac-sign and related arguments. Test will - * generate app image and verify signature of main launcher and app bundle itself. - * This test requires that machine is configured with test certificate for - * "Developer ID Application: jpackage.openjdk.java.net" or alternately - * "Developer ID Application: " + name specified by system property: - * "jpackage.mac.signing.key.user.name" - * in the jpackagerTest keychain (or alternately the keychain specified with - * the system property "jpackage.mac.signing.keychain". - * If this certificate is self-signed, it must have be set to - * always allowed access to this keychain" for user which runs test. - * (If cert is real (not self signed), the do not set trust to allow.) + * Tests signing of an app image. + * + *

+ * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ /* * @test * @summary jpackage with --type app-image --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningAppImageTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningAppImageTest.java * @requires (jpackage.test.MacSignTests == "run") - * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main * --jpt-run=SigningAppImageTest * --jpt-before-run=SigningBase.verifySignTestEnvReady */ public class SigningAppImageTest { @Test - // ({"sign or not", "signing-key or sign-identity", "certificate index"}) - // Sign, signing-key and ASCII certificate - @Parameter({"true", "true", "ASCII_INDEX"}) - // Sign, signing-key and UNICODE certificate - @Parameter({"true", "true", "UNICODE_INDEX"}) - // Sign, signing-indentity and UNICODE certificate - @Parameter({"true", "false", "UNICODE_INDEX"}) - // Unsigned - @Parameter({"false", "true", "INVALID_INDEX"}) - public void test(boolean doSign, boolean signingKey, SigningBase.CertIndex certEnum) throws Exception { - MacSign.withKeychain(toConsumer(keychain -> { - test(keychain, doSign, signingKey, certEnum); - }), SigningBase.StandardKeychain.MAIN.keychain()); - } + @ParameterSupplier + public static void test(SignKeyOptionWithKeychain sign) { - private void test(MacSign.ResolvedKeychain keychain, boolean doSign, boolean signingKey, SigningBase.CertIndex certEnum) throws Exception { - final var certIndex = certEnum.value(); + var cmd = JPackageCommand.helloAppImage(); - JPackageCommand cmd = JPackageCommand.helloAppImage(); - if (doSign) { - cmd.addArguments("--mac-sign", - "--mac-signing-keychain", - keychain.name()); - if (signingKey) { - cmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(certIndex)); - } else { - cmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(certIndex)); - } - } - AdditionalLauncher testAL = new AdditionalLauncher("testAL"); + var testAL = new AdditionalLauncher("testAL"); testAL.applyTo(cmd); cmd.executeAndAssertHelloAppImageCreated(); - Path launcherPath = cmd.appLauncherPath(); - SigningBase.verifyCodesign(launcherPath, doSign, certIndex); + MacSign.withKeychain(keychain -> { + sign.addTo(cmd); + cmd.executeAndAssertHelloAppImageCreated(); + MacSignVerify.verifyAppImageSigned(cmd, sign.certRequest()); + }, sign.keychain()); + } - Path testALPath = launcherPath.getParent().resolve("testAL"); - SigningBase.verifyCodesign(testALPath, doSign, certIndex); + public static Collection test() { - Path appImage = cmd.outputBundle(); - SigningBase.verifyCodesign(appImage, doSign, certIndex); - if (doSign) { - SigningBase.verifySpctl(appImage, "exec", certIndex); + List data = new ArrayList<>(); + + for (var certRequest : List.of( + SigningBase.StandardCertificateRequest.CODESIGN, + SigningBase.StandardCertificateRequest.CODESIGN_UNICODE + )) { + for (var signIdentityType : SignKeyOption.Type.defaultValues()) { + data.add(new SignKeyOptionWithKeychain( + signIdentityType, + certRequest, + SigningBase.StandardKeychain.MAIN.keychain())); + } } + + return data.stream().map(v -> { + return new Object[] {v}; + }).toList(); } } diff --git a/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java b/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java index 906734e6a9c..2c7ae205e69 100644 --- a/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTwoStepsTest.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 @@ -21,38 +21,40 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; - -import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; import jdk.jpackage.test.AdditionalLauncher; -import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageType; import jdk.jpackage.test.TKit; /** - * Tests generation of app image and then signs generated app image with --mac-sign - * and related arguments. Test will generate app image and verify signature of main - * launcher and app bundle itself. This test requires that machine is configured with - * test certificate for "Developer ID Application: jpackage.openjdk.java.net" or - * alternately "Developer ID Application: " + name specified by system property: - * "jpackage.mac.signing.key.user.name" in the jpackagerTest keychain - * (or alternately the keychain specified with the system property - * "jpackage.mac.signing.keychain". If this certificate is self-signed, it must - * have be set to always allowed access to this keychain" for user which runs test. - * (If cert is real (not self signed), the do not set trust to allow.) + * Tests signing of a signed/unsigned predefined app image. + * + *

+ * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ /* * @test * @summary jpackage with --type app-image --app-image "appImage" --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningAppImageTwoStepsTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningAppImageTwoStepsTest.java * @requires (jpackage.test.MacSignTests == "run") * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main * --jpt-run=SigningAppImageTwoStepsTest @@ -61,67 +63,123 @@ import jdk.jpackage.test.TKit; public class SigningAppImageTwoStepsTest { @Test - // ({"sign or not", "signing-key or sign-identity"}) - // Sign and signing-key - @Parameter({"true", "true"}) - // Sign and sign-identity - @Parameter({"true", "false"}) - // Unsigned - @Parameter({"false", "true"}) - public void test(boolean signAppImage, boolean signingKey) throws Exception { - MacSign.withKeychain(toConsumer(keychain -> { - test(keychain, signAppImage, signingKey); - }), SigningBase.StandardKeychain.MAIN.keychain()); + @ParameterSupplier + public static void test(TestSpec spec) { + spec.test(); } - private static void test(MacSign.ResolvedKeychain keychain, boolean signAppImage, boolean signingKey) throws Exception { + public record TestSpec(Optional signAppImage, SignKeyOptionWithKeychain sign) { - Path appimageOutput = TKit.createTempDirectory("appimage"); + public TestSpec { + Objects.requireNonNull(signAppImage); + Objects.requireNonNull(sign); + } - // Generate app image. Signed or unsigned based on test - // parameter. We should able to sign predfined app images - // which are signed or unsigned. - JPackageCommand appImageCmd = JPackageCommand.helloAppImage() - .setArgumentValue("--dest", appimageOutput); - if (signAppImage) { - appImageCmd.addArguments("--mac-sign", - "--mac-signing-keychain", - keychain.name()); - if (signingKey) { - appImageCmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); - } else { - appImageCmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(SigningBase.DEFAULT_INDEX)); + @Override + public String toString() { + return Stream.of( + String.format("app-image=%s", signAppImage.map(Objects::toString).orElse("unsigned")), + sign.toString() + ).collect(Collectors.joining("; ")); + } + + static Builder build() { + return new Builder(); + } + + static class Builder { + + TestSpec create() { + return new TestSpec(Optional.ofNullable(signAppImage), sign); + } + + Builder certRequest(SigningBase.StandardCertificateRequest v) { + certRequest = Objects.requireNonNull(v); + return this; + } + + Builder signIdentityType(SignKeyOption.Type v) { + signIdentityType = Objects.requireNonNull(v); + return this; + } + + Builder sign() { + sign = createSignKeyOption(); + return this; + } + + Builder signAppImage() { + signAppImage = createSignKeyOption(); + return this; + } + + private SignKeyOptionWithKeychain createSignKeyOption() { + return new SignKeyOptionWithKeychain( + signIdentityType, + certRequest, + SigningBase.StandardKeychain.MAIN.keychain()); + } + + private SigningBase.StandardCertificateRequest certRequest = SigningBase.StandardCertificateRequest.CODESIGN; + private SignKeyOption.Type signIdentityType = SignKeyOption.Type.SIGN_KEY_IDENTITY; + + private SignKeyOptionWithKeychain signAppImage; + private SignKeyOptionWithKeychain sign; + } + + void test() { + var appImageCmd = JPackageCommand.helloAppImage() + .setFakeRuntime() + .setArgumentValue("--dest", TKit.createTempDirectory("appimage")); + + // Add an additional launcher + AdditionalLauncher testAL = new AdditionalLauncher("testAL"); + testAL.applyTo(appImageCmd); + + signAppImage.ifPresentOrElse(signOption -> { + MacSign.withKeychain(keychain -> { + signOption.addTo(appImageCmd); + appImageCmd.execute(); + MacSignVerify.verifyAppImageSigned(appImageCmd, signOption.certRequest()); + }, signOption.keychain()); + }, appImageCmd::execute); + + var cmd = new JPackageCommand() + .setPackageType(PackageType.IMAGE) + .addArguments("--app-image", appImageCmd.outputBundle()) + .mutate(sign::addTo); + + cmd.executeAndAssertHelloAppImageCreated(); + MacSignVerify.verifyAppImageSigned(cmd, sign.certRequest()); + } + } + + public static Collection test() { + + List data = new ArrayList<>(); + + for (var appImageSign : withAndWithout(SignKeyOption.Type.SIGN_KEY_IDENTITY)) { + var builder = TestSpec.build(); + appImageSign.ifPresent(signIdentityType -> { + // Sign the input app image bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of the input app image bundle. + builder.signIdentityType(signIdentityType) + .certRequest(SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD) + .signAppImage(); + }); + for (var signIdentityType : SignKeyOption.Type.defaultValues()) { + builder.signIdentityType(signIdentityType) + .certRequest(SigningBase.StandardCertificateRequest.CODESIGN); + data.add(builder.sign().create()); } } - // Add addtional launcher - AdditionalLauncher testAL = new AdditionalLauncher("testAL"); - testAL.applyTo(appImageCmd); + return data.stream().map(v -> { + return new Object[] {v}; + }).toList(); + } - // Generate app image - appImageCmd.executeAndAssertHelloAppImageCreated(); - - // Double check if it is signed or unsigned based on signAppImage - SigningBase.verifyAppImageSignature(appImageCmd, signAppImage, "testAL"); - - // Sign app image - JPackageCommand cmd = new JPackageCommand(); - cmd.setPackageType(PackageType.IMAGE) - .addArguments("--app-image", appImageCmd.outputBundle().toAbsolutePath()) - .addArguments("--mac-sign") - .addArguments("--mac-signing-keychain", keychain.name()); - if (signingKey) { - cmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(SigningBase.DEFAULT_INDEX)); - } else { - cmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(SigningBase.DEFAULT_INDEX)); - } - cmd.executeAndAssertImageCreated(); - - // Should be signed app image - SigningBase.verifyAppImageSignature(appImageCmd, true, "testAL"); + private static List> withAndWithout(T value) { + return List.of(Optional.empty(), Optional.of(value)); } } diff --git a/test/jdk/tools/jpackage/macosx/SigningBase.java b/test/jdk/tools/jpackage/macosx/SigningBase.java new file mode 100644 index 00000000000..5f4367e6096 --- /dev/null +++ b/test/jdk/tools/jpackage/macosx/SigningBase.java @@ -0,0 +1,178 @@ +/* + * 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 + * 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.security.cert.X509Certificate; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; +import jdk.jpackage.test.MacHelper.NamedCertificateRequestSupplier; +import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSign.CertificateRequest; +import jdk.jpackage.test.MacSign.CertificateType; +import jdk.jpackage.test.MacSign.KeychainWithCertsSpec; +import jdk.jpackage.test.MacSign.ResolvedKeychain; +import jdk.jpackage.test.TKit; + + +/* + * @test + * @summary Setup the environment for jpackage macos signing tests. + * Creates required keychains and signing identities. + * Does NOT run any jpackag tests. + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @compile -Xlint:all -Werror SigningBase.java + * @requires (jpackage.test.MacSignTests == "setup") + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=SigningBase.setUp + */ + +/* + * @test + * @summary Tear down the environment for jpackage macos signing tests. + * Deletes required keychains and signing identities. + * Does NOT run any jpackag tests. + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @compile -Xlint:all -Werror SigningBase.java + * @requires (jpackage.test.MacSignTests == "teardown") + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=SigningBase.tearDown + */ + +public class SigningBase { + + public enum StandardCertificateRequest implements NamedCertificateRequestSupplier { + CODESIGN(cert().userName(NAME_ASCII)), + CODESIGN_COPY(cert().days(100).userName(NAME_ASCII)), + CODESIGN_ACME_TECH_LTD(cert().days(100).userName("ACME Technologies Limited (ABC12345)")), + PKG(cert().type(CertificateType.INSTALLER).userName(NAME_ASCII)), + PKG_COPY(cert().type(CertificateType.INSTALLER).days(100).userName(NAME_ASCII)), + CODESIGN_UNICODE(cert().userName(NAME_UNICODE)), + PKG_UNICODE(cert().type(CertificateType.INSTALLER).userName(NAME_UNICODE)), + CODESIGN_EXPIRED(cert().expired().userName("expired jpackage test")), + PKG_EXPIRED(cert().expired().type(CertificateType.INSTALLER).userName("expired jpackage test")); + + StandardCertificateRequest(CertificateRequest.Builder specBuilder) { + this.spec = specBuilder.create(); + } + + @Override + public CertificateRequest certRequest() { + return spec; + } + + private static CertificateRequest.Builder cert() { + return new CertificateRequest.Builder(); + } + + private final CertificateRequest spec; + } + + /** + * Standard keychains used in signing tests. + */ + public enum StandardKeychain { + /** + * The primary keychain with good certificates. + */ + MAIN("jpackagerTest.keychain", + StandardCertificateRequest.CODESIGN, + StandardCertificateRequest.PKG, + StandardCertificateRequest.CODESIGN_UNICODE, + StandardCertificateRequest.PKG_UNICODE, + StandardCertificateRequest.CODESIGN_ACME_TECH_LTD), + /** + * A keychain with some good and some expired certificates. + */ + EXPIRED("jpackagerTest-expired.keychain", + StandardCertificateRequest.CODESIGN, + StandardCertificateRequest.PKG, + StandardCertificateRequest.CODESIGN_EXPIRED, + StandardCertificateRequest.PKG_EXPIRED), + /** + * A keychain with duplicated certificates. + */ + DUPLICATE("jpackagerTest-duplicate.keychain", + StandardCertificateRequest.CODESIGN, + StandardCertificateRequest.PKG, + StandardCertificateRequest.CODESIGN_COPY, + StandardCertificateRequest.PKG_COPY), + ; + + StandardKeychain(String keychainName, StandardCertificateRequest... certs) { + this(keychainName, + certs[0].certRequest(), + Stream.of(certs).skip(1).map(StandardCertificateRequest::certRequest).toArray(CertificateRequest[]::new)); + } + + StandardKeychain(String keychainName, CertificateRequest cert, CertificateRequest... otherCerts) { + final var builder = keychain(keychainName).addCert(cert); + List.of(otherCerts).forEach(builder::addCert); + this.keychain = new ResolvedKeychain(builder.create()); + } + + public ResolvedKeychain keychain() { + return keychain; + } + + public X509Certificate mapCertificateRequest(CertificateRequest certRequest) { + return Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest)); + } + + public boolean contains(StandardCertificateRequest certRequest) { + return keychain.spec().certificateRequests().contains(certRequest.spec); + } + + private static KeychainWithCertsSpec.Builder keychain(String name) { + return new KeychainWithCertsSpec.Builder().name(name); + } + + private static List signingEnv() { + return Stream.of(values()).map(StandardKeychain::keychain).map(ResolvedKeychain::spec).toList(); + } + + private final ResolvedKeychain keychain; + } + + public static void setUp() { + MacSign.setUp(StandardKeychain.signingEnv()); + } + + public static void tearDown() { + MacSign.tearDown(StandardKeychain.signingEnv()); + } + + public static void verifySignTestEnvReady() { + if (!Inner.SIGN_ENV_READY) { + TKit.throwSkippedException(new IllegalStateException("Misconfigured signing test environment")); + } + } + + private final class Inner { + private static final boolean SIGN_ENV_READY = MacSign.isDeployed(StandardKeychain.signingEnv()); + } + + private static final String NAME_ASCII = "jpackage.openjdk.java.net"; + private static final String NAME_UNICODE = "jpackage.openjdk.java.net (รถ)"; +} diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java index b1e9155dacb..8a93ce5f749 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageTest.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 @@ -21,180 +21,247 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; - -import java.nio.file.Path; -import jdk.jpackage.test.Annotations.Parameter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.SequencedSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import jdk.jpackage.internal.util.function.ExceptionBox; +import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; -import jdk.jpackage.test.ApplicationLayout; -import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; +import jdk.jpackage.test.MacHelper.SignKeyOption; import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageTest; import jdk.jpackage.test.PackageType; /** - * Tests generation of dmg and pkg with --mac-sign and related arguments. - * Test will generate pkg and verifies its signature. It verifies that dmg - * is not signed, but app image inside dmg is signed. This test requires that - * the machine is configured with test certificate for - * "Developer ID Installer: jpackage.openjdk.java.net" in - * jpackagerTest keychain with - * always allowed access to this keychain for user which runs test. - * note: - * "jpackage.openjdk.java.net" can be over-ridden by system property - * "jpackage.mac.signing.key.user.name", and - * "jpackagerTest" can be over-ridden by system property - * "jpackage.mac.signing.keychain" + * Tests bundling of .pkg and .dmg packages with various signing options. + * + *

+ * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ + /* * @test * @summary jpackage with --type pkg,dmg --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base * @key jpackagePlatformPackage - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningPackageTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningPackageTest.java * @requires (jpackage.test.MacSignTests == "run") * @requires (jpackage.test.SQETest != null) * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=SigningPackageTest - * --jpt-space-subst=* - * --jpt-include=SigningPackageTest.test(true,*true,*true,*ASCII_INDEX) - * --jpt-before-run=SigningBase.verifySignTestEnvReady + * --jpt-run=SigningPackageTest.test + * --jpt-space-subst=* + * --jpt-include=({--mac-signing-key-user-name:*CODESIGN},*{--mac-signing-key-user-name:*PKG},*MAC_DMG+MAC_PKG) + * --jpt-before-run=SigningBase.verifySignTestEnvReady */ /* * @test * @summary jpackage with --type pkg,dmg --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base * @key jpackagePlatformPackage - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningPackageTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningPackageTest.java * @requires (jpackage.test.MacSignTests == "run") * @requires (jpackage.test.SQETest == null) - * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=SigningPackageTest + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=SigningPackageTest.test * --jpt-before-run=SigningBase.verifySignTestEnvReady */ public class SigningPackageTest { - private static boolean isAppImageSigned(JPackageCommand cmd) { - return cmd.hasArgument("--mac-signing-key-user-name") || - cmd.hasArgument("--mac-app-image-sign-identity"); + @Test + @ParameterSupplier + public static void test(TestSpec spec) { + MacSign.withKeychain(_ -> { + spec.test(); + }, spec.keychain()); } - private static boolean isPKGSigned(JPackageCommand cmd) { - return cmd.hasArgument("--mac-signing-key-user-name") || - cmd.hasArgument("--mac-installer-sign-identity"); + public static Collection test() { + return TestSpec.testCases(true).stream().map(v -> { + return new Object[] {v}; + }).toList(); } - private static void verifyPKG(JPackageCommand cmd) { - Path outputBundle = cmd.outputBundle(); - SigningBase.verifyPkgutil(outputBundle, isPKGSigned(cmd), getCertIndex(cmd)); - if (isPKGSigned(cmd)) { - SigningBase.verifySpctl(outputBundle, "install", getCertIndex(cmd)); - } - } + record TestSpec( + Optional appImageSignOption, + Optional packageSignOption, + Set packageTypes) { - private static void verifyDMG(JPackageCommand cmd) { - Path outputBundle = cmd.outputBundle(); - SigningBase.verifyDMG(outputBundle); - } + TestSpec { + Objects.requireNonNull(appImageSignOption); + Objects.requireNonNull(packageSignOption); + Objects.requireNonNull(packageTypes); - private static void verifyAppImageInDMG(JPackageCommand cmd) { - MacHelper.withExplodedDmg(cmd, dmgImage -> { - Path launcherPath = ApplicationLayout.platformAppImage() - .resolveAt(dmgImage).launchersDirectory().resolve(cmd.name()); - // We will be called with all folders in DMG since JDK-8263155, but - // we only need to verify app. - if (dmgImage.endsWith(cmd.name() + ".app")) { - SigningBase.verifyCodesign(launcherPath, isAppImageSigned(cmd), - getCertIndex(cmd)); - SigningBase.verifyCodesign(dmgImage, isAppImageSigned(cmd), - getCertIndex(cmd)); - if (isAppImageSigned(cmd)) { - SigningBase.verifySpctl(dmgImage, "exec", getCertIndex(cmd)); + if (appImageSignOption.isEmpty() && packageSignOption.isEmpty()) { + // No signing. + throw new IllegalArgumentException(); + } + + if (packageTypes.isEmpty() || !PackageType.MAC.containsAll(packageTypes)) { + // Invalid package types. + throw new IllegalArgumentException(); + } + + if (packageSignOption.isPresent()) { + if (!packageTypes.contains(PackageType.MAC_PKG)) { + // .pkg installer should be signed, but .pkg type is missing. + throw new IllegalArgumentException(); + } + + if (appImageSignOption.isEmpty()) { + if (packageSignOption.get().type() != SignKeyOption.Type.SIGN_KEY_IDENTITY) { + // They request to sign the .pkg installer without + // the "--mac-installer-sign-identity" option, + // but didn't specify a signing option for the packaged app image. + // This is wrong because only the "--mac-installer-sign-identity" option + // allows signing a .pkg installer without signing its packaged app image. + throw new IllegalArgumentException(); + } + } else if (appImageSignOption.get().type() != packageSignOption.get().type()) { + // Signing option types should be the same. + throw new IllegalArgumentException(); } } - }); - } - private static int getCertIndex(JPackageCommand cmd) { - if (cmd.hasArgument("--mac-signing-key-user-name")) { - String devName = cmd.getArgumentValue("--mac-signing-key-user-name"); - return SigningBase.getDevNameIndex(devName); - } else { - // Signing-indentity - return SigningBase.CertIndex.UNICODE_INDEX.value(); + if (!(packageTypes instanceof SequencedSet)) { + packageTypes = new TreeSet<>(packageTypes); + } + } + + TestSpec( + Optional appImageSignOption, + Optional packageSignOption, + PackageType... packageTypes) { + this(appImageSignOption, packageSignOption, Set.of(packageTypes)); + } + + @Override + public String toString() { + return Stream.of( + signKeyOptions(), + Stream.of(packageTypes.stream().map(Object::toString).collect(Collectors.joining("+"))) + ).flatMap(x -> x).map(Object::toString).collect(Collectors.joining(", ")); + } + + Stream signKeyOptions() { + return Stream.concat(appImageSignOption.stream(), packageSignOption.stream()); + } + + Optional bundleSignIdentity(PackageType type) { + switch (type) { + case MAC_DMG -> { + return Optional.empty(); + } + case MAC_PKG -> { + return packageSignOption.map(SignKeyOption::certRequest); + } + default -> { + throw new IllegalArgumentException(); + } + } + } + + void test() { + initTest().configureHelloApp().addInstallVerifier(cmd -> { + appImageSignOption.map(SignKeyOption::certRequest).ifPresent(signIdentity -> { + MacSignVerify.verifyAppImageSigned(cmd, signIdentity); + }); + }).run(); + } + + PackageTest initTest() { + return new PackageTest().forTypes(packageTypes).mutate(test -> { + appImageSignOption.ifPresent(signOption -> { + test.addInitializer(signOption::setTo); + }); + packageSignOption.ifPresent(signOption -> { + test.forTypes(PackageType.MAC_PKG, () -> { + test.addInitializer(signOption::setTo); + }); + }); + }).addBundleVerifier(cmd -> { + bundleSignIdentity(cmd.packageType()).ifPresent(signIdentity -> { + MacSignVerify.verifyPkgSigned(cmd, signIdentity); + }); + }).addInitializer(MacHelper.useKeychain(keychain())::accept); + } + + MacSign.ResolvedKeychain keychain() { + return SigningBase.StandardKeychain.MAIN.keychain(); + } + + static List testCases(boolean withUnicode) { + + List data = new ArrayList<>(); + + List> certRequestGroups; + if (withUnicode) { + certRequestGroups = List.of( + List.of(SigningBase.StandardCertificateRequest.CODESIGN, SigningBase.StandardCertificateRequest.PKG), + List.of(SigningBase.StandardCertificateRequest.CODESIGN_UNICODE, SigningBase.StandardCertificateRequest.PKG_UNICODE) + ); + } else { + certRequestGroups = List.of( + List.of(SigningBase.StandardCertificateRequest.CODESIGN, SigningBase.StandardCertificateRequest.PKG) + ); + } + + for (var certRequests : certRequestGroups) { + for (var signIdentityType : SignKeyOption.Type.defaultValues()) { + var keychain = SigningBase.StandardKeychain.MAIN.keychain(); + var appImageSignKeyOption = new SignKeyOption(signIdentityType, certRequests.getFirst(), keychain); + var pkgSignKeyOption = new SignKeyOption(signIdentityType, certRequests.getLast(), keychain); + + switch (signIdentityType) { + case SIGN_KEY_IDENTITY -> { + // Use "--mac-installer-sign-identity" and "--mac-app-image-sign-identity" signing options. + // They allows to sign the packaged app image and the installer (.pkg) separately. + data.add(new TestSpec(Optional.of(appImageSignKeyOption), Optional.empty(), PackageType.MAC)); + data.add(new TestSpec(Optional.empty(), Optional.of(pkgSignKeyOption), PackageType.MAC_PKG)); + } + case SIGN_KEY_USER_SHORT_NAME -> { + // Use "--mac-signing-key-user-name" signing option with short user name or implicit signing option. + // It signs both the packaged app image and the installer (.pkg). + // Thus, if the installer is not signed, it can be used only with .dmg packaging. + data.add(new TestSpec(Optional.of(appImageSignKeyOption), Optional.empty(), PackageType.MAC_DMG)); + } + case SIGN_KEY_USER_FULL_NAME -> { + // Use "--mac-signing-key-user-name" signing option with the full user name. + // Like SIGN_KEY_USER_SHORT_NAME, jpackage will try to use it to sign both + // the packaged app image and the installer (.pkg). + // It will fail to sign the installer, though, because the signing identity is unsuitable. + // That is why, use it with .dmg packaging only and not with .pkg packaging. + data.add(new TestSpec(Optional.of(appImageSignKeyOption), Optional.empty(), PackageType.MAC_DMG)); + continue; + } + default -> { + // SignKeyOption.Type.defaultValues() should return + // such a sequence that makes this code location unreachable. + throw ExceptionBox.reachedUnreachable(); + } + } + data.add(new TestSpec(Optional.of(appImageSignKeyOption), Optional.of(pkgSignKeyOption), PackageType.MAC)); + } + } + + return data; } } - - @Test - // ("signing-key or sign-identity", "sign app-image", "sign pkg", "certificate index"}) - // Signing-key and ASCII certificate - @Parameter({"true", "true", "true", "ASCII_INDEX"}) - // Signing-key and UNICODE certificate - @Parameter({"true", "true", "true", "UNICODE_INDEX"}) - // Signing-indentity and UNICODE certificate - @Parameter({"false", "true", "true", "UNICODE_INDEX"}) - // Signing-indentity, but sign app-image only and UNICODE certificate - @Parameter({"false", "true", "false", "UNICODE_INDEX"}) - // Signing-indentity, but sign pkg only and UNICODE certificate - @Parameter({"false", "false", "true", "UNICODE_INDEX"}) - public static void test(boolean signingKey, boolean signAppImage, boolean signPKG, SigningBase.CertIndex certEnum) throws Exception { - MacSign.withKeychain(toConsumer(keychain -> { - test(keychain, signingKey, signAppImage, signPKG, certEnum); - }), SigningBase.StandardKeychain.MAIN.keychain()); - } - - private static void test(MacSign.ResolvedKeychain keychain, boolean signingKey, boolean signAppImage, boolean signPKG, SigningBase.CertIndex certEnum) throws Exception { - final var certIndex = certEnum.value(); - - new PackageTest() - .configureHelloApp() - .forTypes(PackageType.MAC) - .addInitializer(cmd -> { - cmd.addArguments("--mac-sign", - "--mac-signing-keychain", keychain.name()); - if (signingKey) { - cmd.addArguments("--mac-signing-key-user-name", - SigningBase.getDevName(certIndex)); - } else { - if (signAppImage) { - cmd.addArguments("--mac-app-image-sign-identity", - SigningBase.getAppCert(certIndex)); - } - if (signPKG) { - cmd.addArguments("--mac-installer-sign-identity", - SigningBase.getInstallerCert(certIndex)); - } - } - }) - .forTypes(PackageType.MAC_PKG) - .addBundleVerifier(SigningPackageTest::verifyPKG) - .forTypes(PackageType.MAC_DMG) - .addInitializer(cmd -> { - if (!signingKey) { - // jpackage throws expected error with - // --mac-installer-sign-identity and DMG type - cmd.removeArgumentWithValue("--mac-installer-sign-identity"); - // In case of not signing app image and DMG we need to - // remove signing completely, otherwise we will default - // to --mac-signing-key-user-name once - // --mac-installer-sign-identity is removed. - if (!signAppImage) { - cmd.removeArgumentWithValue("--mac-signing-keychain"); - cmd.removeArgument("--mac-sign"); - } - } - }) - .addBundleVerifier(SigningPackageTest::verifyDMG) - .addBundleVerifier(SigningPackageTest::verifyAppImageInDMG) - .run(); - } } diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java index 586d8d68444..70ec0e600de 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.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 @@ -21,25 +21,22 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; - import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.CannedFormattedString; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.JPackageStringBundle; -import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; import jdk.jpackage.test.MacSignVerify; import jdk.jpackage.test.PackageFile; @@ -52,22 +49,23 @@ import jdk.jpackage.test.TKit; * signed/unsigned .pkg or .dmg package. * *

- * Prerequisites: A keychain with self-signed certificates as specified in - * {@link SigningBase.StandardKeychain#MAIN}. + * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ /* * @test * @summary jpackage with --type pkg,dmg --app-image * @library /test/jdk/tools/jpackage/helpers - * @library base * @key jpackagePlatformPackage - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningPackageTwoStepTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningPackageTest.java + * @compile -Xlint:all -Werror SigningPackageTwoStepTest.java * @requires (jpackage.test.MacSignTests == "run") * @requires (jpackage.test.SQETest == null) - * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main * --jpt-run=SigningPackageTwoStepTest * --jpt-before-run=SigningBase.verifySignTestEnvReady */ @@ -75,176 +73,148 @@ public class SigningPackageTwoStepTest { @Test @ParameterSupplier - public static void test(TestSpec spec) { - MacSign.withKeychain(toConsumer(keychain -> { - spec.test(keychain); - }), SigningBase.StandardKeychain.MAIN.keychain()); + public static void test(TwoStepsTestSpec spec) { + spec.test(); } - public record TestSpec(Optional signAppImage, Map signPackage) { + @Test + public static void testBundleSignedAppImage() { - public TestSpec { - Objects.requireNonNull(signAppImage); - Objects.requireNonNull(signPackage); + var appImageCmd = JPackageCommand.helloAppImage(); - if ((signAppImage.isEmpty() && signPackage.isEmpty()) || !PackageType.MAC.containsAll(signPackage.keySet())) { - // Unexpected package types. - throw new IllegalArgumentException(); - } + var predefinedAppImageSignOption = predefinedAppImageSignOption(); - // Ensure stable result of toString() call. - if (!SortedMap.class.isInstance(signPackage)) { - signPackage = new TreeMap<>(signPackage); - } - } - - @Override - public String toString() { - var sb = new StringBuilder(); - - signAppImage.ifPresent(signOption -> { - sb.append(String.format("app-image=%s", signOption)); - }); - - if (!sb.isEmpty() && !signPackage.isEmpty()) { - sb.append("; "); - } - - if (!signPackage.isEmpty()) { - sb.append(signPackage); - } - - return sb.toString(); - } - - boolean signNativeBundle() { - return signPackage.isEmpty(); - } - - static Builder build() { - return new Builder(); - } - - static class Builder { - - TestSpec create() { - return new TestSpec(Optional.ofNullable(signAppImage), signPackage); - } - - Builder certRequest(SigningBase.StandardCertificateRequest v) { - return certRequest(v.spec()); - } - - Builder certRequest(MacSign.CertificateRequest v) { - certRequest = Objects.requireNonNull(v); - return this; - } - - Builder signIdentityType(SignKeyOption.Type v) { - signIdentityType = Objects.requireNonNull(v); - return this; - } - - Builder signAppImage() { - signAppImage = createSignKeyOption(); - return this; - } - - Builder signPackage(PackageType type) { - Objects.requireNonNull(type); - signPackage.put(type, createSignKeyOption()); - return this; - } - - Builder signPackage() { - PackageType.MAC.forEach(this::signPackage); - return this; - } - - private SignKeyOption createSignKeyOption() { - return new SignKeyOption(signIdentityType, certRequest); - } - - private MacSign.CertificateRequest certRequest = SigningBase.StandardCertificateRequest.CODESIGN.spec(); - private SignKeyOption.Type signIdentityType = SignKeyOption.Type.SIGN_KEY_IDENTITY; - - private SignKeyOption signAppImage; - private Map signPackage = new HashMap<>(); - } - - void test(MacSign.ResolvedKeychain keychain) { - - var appImageCmd = JPackageCommand.helloAppImage().setFakeRuntime(); - MacHelper.useKeychain(appImageCmd, keychain); - signAppImage.ifPresent(signOption -> { - signOption.setTo(appImageCmd); - }); - - var test = new PackageTest(); - - signAppImage.map(SignKeyOption::certRequest).ifPresent(certRequest -> { - // The predefined app image is signed, verify bundled app image is signed too. - test.addInstallVerifier(cmd -> { - MacSignVerify.verifyAppImageSigned(cmd, certRequest, keychain); - }); - }); - - Optional.ofNullable(signPackage.get(PackageType.MAC_PKG)).map(SignKeyOption::certRequest).ifPresent(certRequest -> { - test.forTypes(PackageType.MAC_PKG, () -> { - test.addBundleVerifier(cmd -> { - MacSignVerify.verifyPkgSigned(cmd, certRequest, keychain); - }); - }); - }); - - test.forTypes(signPackage.keySet()).addRunOnceInitializer(() -> { - appImageCmd.setArgumentValue("--dest", TKit.createTempDirectory("appimage")).execute(0); - }).usePredefinedAppImage(appImageCmd).addInitializer(cmd -> { - MacHelper.useKeychain(cmd, keychain); - Optional.ofNullable(signPackage.get(cmd.packageType())).ifPresent(signOption -> { - signOption.setTo(cmd); - }); - - if (signAppImage.isPresent()) { - // Predefined app image is signed. Expect a warning. - cmd.validateOutput(JPackageStringBundle.MAIN.cannedFormattedString( - "warning.per.user.app.image.signed", - PackageFile.getPathInAppImage(Path.of("")))); - } else if (cmd.packageType() == PackageType.MAC_PKG && signPackage.containsKey(cmd.packageType())) { - // Create signed ".pkg" bundle from the unsigned predefined app image. Expect a warning. - cmd.validateOutput(JPackageStringBundle.MAIN.cannedFormattedString("warning.unsigned.app.image", "pkg")); - } - }) - .run(); - } + new PackageTest().addRunOnceInitializer(() -> { + createPredefinedAppImage(appImageCmd, Optional.of(predefinedAppImageSignOption)); + }).usePredefinedAppImage(appImageCmd).addInitializer(cmd -> { + configureOutputValidator(cmd, true, false); + }).addInstallVerifier(cmd -> { + MacSignVerify.verifyAppImageSigned(cmd, predefinedAppImageSignOption.certRequest()); + }).run(); } public static Collection test() { - List data = new ArrayList<>(); + List data = new ArrayList<>(); - Stream.of(SignKeyOption.Type.values()).flatMap(signIdentityType -> { - return Stream.of( - // Sign both predefined app image and native package. - TestSpec.build().signIdentityType(signIdentityType) - .signAppImage() - .signPackage() - .certRequest(SigningBase.StandardCertificateRequest.PKG) - .signPackage(PackageType.MAC_PKG), + for (var signAppImage : List.of(true, false)) { + Optional appImageSignOption; + if (signAppImage) { + // Sign the predefined app image bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of + // the predefined app image bundle when backing it in the pkg or dmg installer. + appImageSignOption = Optional.of(predefinedAppImageSignOption()); + } else { + appImageSignOption = Optional.empty(); + } - // Don't sign predefined app image, sign native package. - TestSpec.build().signIdentityType(signIdentityType) - .signPackage() - .certRequest(SigningBase.StandardCertificateRequest.PKG) - .signPackage(PackageType.MAC_PKG), + for (var signPackage : SigningPackageTest.TestSpec.testCases(false)) { + data.add(new TwoStepsTestSpec(appImageSignOption, signPackage)); + } + } - // Sign predefined app image, don't sign native package. - TestSpec.build().signIdentityType(signIdentityType).signAppImage() - ); - }).forEach(data::add); - - return data.stream().map(TestSpec.Builder::create).map(v -> { + return data.stream().map(v -> { return new Object[] {v}; }).toList(); } + + record TwoStepsTestSpec(Optional signAppImage, SigningPackageTest.TestSpec signPackage) { + + TwoStepsTestSpec { + Objects.requireNonNull(signAppImage); + Objects.requireNonNull(signPackage); + } + + @Override + public String toString() { + return Stream.of( + String.format("app-image=%s", signAppImage.map(Objects::toString).orElse("unsigned")), + signPackage.toString() + ).collect(Collectors.joining("; ")); + } + + Optional packagedAppImageSignIdentity() { + return signAppImage.map(SignKeyOptionWithKeychain::certRequest); + } + + void test() { + + var appImageCmd = JPackageCommand.helloAppImage(); + + var test = signPackage.initTest().addRunOnceInitializer(() -> { + createPredefinedAppImage(appImageCmd, signAppImage); + }).usePredefinedAppImage(appImageCmd).addInitializer(cmd -> { + configureOutputValidator(cmd, + signAppImage.isPresent(), + (cmd.packageType() == PackageType.MAC_PKG) && signPackage.packageSignOption().isPresent()); + }).addInstallVerifier(cmd -> { + packagedAppImageSignIdentity().ifPresent(certRequest -> { + MacSignVerify.verifyAppImageSigned(cmd, certRequest); + }); + }); + + MacSign.withKeychain(_ -> { + test.run(); + }, signPackage.keychain()); + } + } + + private static SignKeyOptionWithKeychain predefinedAppImageSignOption() { + // Sign the predefined app image bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of the input app image bundle. + return new SignKeyOptionWithKeychain( + SignKeyOption.Type.SIGN_KEY_USER_SHORT_NAME, + SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD, + SigningBase.StandardKeychain.MAIN.keychain()); + } + + private static void createPredefinedAppImage(JPackageCommand appImageCmd, Optional signAppImage) { + Objects.requireNonNull(appImageCmd); + Objects.requireNonNull(signAppImage); + + appImageCmd.setFakeRuntime().setArgumentValue("--dest", TKit.createTempDirectory("appimage")); + + signAppImage.ifPresentOrElse(signOption -> { + signOption.setTo(appImageCmd); + + MacSign.withKeychain(_ -> { + appImageCmd.execute(0); + }, signOption.keychain()); + + // Verify that the predefined app image is signed. + MacSignVerify.verifyAppImageSigned(appImageCmd, signOption.certRequest()); + }, () -> { + appImageCmd.execute(0); + }); + } + + private static void configureOutputValidator(JPackageCommand cmd, boolean signAppImage, boolean signPackage) { + var signedPredefinedAppImageWarning = JPackageStringBundle.MAIN.cannedFormattedString( + "warning.per.user.app.image.signed", + PackageFile.getPathInAppImage(Path.of(""))); + + var signedInstallerFromUnsignedPredefinedAppImageWarning = + JPackageStringBundle.MAIN.cannedFormattedString("warning.unsigned.app.image", "pkg"); + + // The warnings are mutually exclusive + final Optional expected; + final List unexpected = new ArrayList<>(); + + if (signAppImage) { + expected = Optional.of(signedPredefinedAppImageWarning); + } else { + unexpected.add(signedPredefinedAppImageWarning); + if (signPackage) { + expected = Optional.of(signedInstallerFromUnsignedPredefinedAppImageWarning); + } else { + expected = Optional.empty(); + unexpected.add(signedInstallerFromUnsignedPredefinedAppImageWarning); + } + } + + expected.ifPresent(cmd::validateOutput); + unexpected.forEach(str -> { + cmd.validateOutput(TKit.assertTextStream(cmd.getValue(str)).negate()); + }); + } } diff --git a/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java b/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java index ccc39f7a367..604399ebd7a 100644 --- a/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.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 @@ -21,17 +21,31 @@ * questions. */ -import static jdk.jpackage.internal.util.function.ThrowingConsumer.toConsumer; +import static jdk.jpackage.test.JPackageCommand.RuntimeImageType.RUNTIME_TYPE_FAKE; import java.nio.file.Path; -import java.util.function.Predicate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; -import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.internal.util.MacBundle; +import jdk.jpackage.internal.util.Slot; +import jdk.jpackage.test.Annotations.ParameterSupplier; import jdk.jpackage.test.Annotations.Test; import jdk.jpackage.test.JPackageCommand; import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.MacHelper.ResolvableCertificateRequest; +import jdk.jpackage.test.MacHelper.SignKeyOption; +import jdk.jpackage.test.MacHelper.SignKeyOptionWithKeychain; import jdk.jpackage.test.MacSign; +import jdk.jpackage.test.MacSignVerify; +import jdk.jpackage.test.MacSignVerify.SpctlType; import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.TKit; /** * Tests generation of dmg and pkg with --mac-sign and related arguments. Test @@ -43,126 +57,182 @@ import jdk.jpackage.test.PackageTest; * app image signing and it will be covered by SigningPackageTest. * *

- * Following combinations are tested: - *

    - *
  1. "--runtime-image" points to unsigned JDK bundle and --mac-sign is not - * provided. Expected result: runtime image ad-hoc signed. - *
  2. "--runtime-image" points to unsigned JDK bundle and --mac-sign is - * provided. Expected result: Everything is signed with provided certificate. - *
  3. "--runtime-image" points to signed JDK bundle and --mac-sign is not - * provided. Expected result: runtime image is signed with original certificate. - *
  4. "--runtime-image" points to signed JDK bundle and --mac-sign is provided. - * Expected result: runtime image is signed with provided certificate. - *
  5. "--runtime-image" points to JDK image and --mac-sign is not provided. - * Expected result: runtime image ad-hoc signed. - *
  6. "--runtime-image" points to JDK image and --mac-sign is provided. - * Expected result: Everything is signed with provided certificate. - *
- * - * This test requires that the machine is configured with test certificate for - * "Developer ID Installer: jpackage.openjdk.java.net" in jpackagerTest keychain - * with always allowed access to this keychain for user which runs test. note: - * "jpackage.openjdk.java.net" can be over-ridden by system property - * "jpackage.mac.signing.key.user.name" + * Prerequisites: Keychains with self-signed certificates as specified in + * {@link SigningBase.StandardKeychain#MAIN} and + * {@link SigningBase.StandardKeychain#SINGLE}. */ /* * @test * @summary jpackage with --type pkg,dmg --runtime-image --mac-sign * @library /test/jdk/tools/jpackage/helpers - * @library base * @key jpackagePlatformPackage - * @build SigningBase * @build jdk.jpackage.test.* - * @build SigningRuntimeImagePackageTest + * @compile -Xlint:all -Werror SigningBase.java + * @compile -Xlint:all -Werror SigningPackageTest.java + * @compile -Xlint:all -Werror SigningRuntimeImagePackageTest.java * @requires (jpackage.test.MacSignTests == "run") * @requires (jpackage.test.SQETest == null) - * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main + * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main * --jpt-run=SigningRuntimeImagePackageTest * --jpt-before-run=SigningBase.verifySignTestEnvReady */ public class SigningRuntimeImagePackageTest { - private static JPackageCommand addSignOptions(JPackageCommand cmd, MacSign.ResolvedKeychain keychain, int certIndex) { - if (certIndex != SigningBase.CertIndex.INVALID_INDEX.value()) { - cmd.addArguments( - "--mac-sign", - "--mac-signing-keychain", keychain.name(), - "--mac-signing-key-user-name", SigningBase.getDevName(certIndex)); - } - return cmd; - } - - private static Path createInputRuntimeBundle(MacSign.ResolvedKeychain keychain, int certIndex) { - return MacHelper.createRuntimeBundle(cmd -> { - addSignOptions(cmd, keychain, certIndex); - }); + @Test + @ParameterSupplier + public static void test(RuntimeTestSpec spec) { + spec.test(); } @Test - // useJDKBundle - If "true" predefined runtime image will be converted to - // JDK bundle. If "false" JDK image will be used. - // JDKBundleCert - Certificate to sign JDK bundle before calling jpackage. - // signCert - Certificate to sign bundle produced by jpackage. - // 1) unsigned JDK bundle and --mac-sign is not provided - @Parameter({"true", "INVALID_INDEX", "INVALID_INDEX"}) - // 2) unsigned JDK bundle and --mac-sign is provided - @Parameter({"true", "INVALID_INDEX", "ASCII_INDEX"}) - // 3) signed JDK bundle and --mac-sign is not provided - @Parameter({"true", "UNICODE_INDEX", "INVALID_INDEX"}) - // 4) signed JDK bundle and --mac-sign is provided - @Parameter({"true", "UNICODE_INDEX", "ASCII_INDEX"}) - // 5) JDK image and --mac-sign is not provided - @Parameter({"false", "INVALID_INDEX", "INVALID_INDEX"}) - // 6) JDK image and --mac-sign is provided - @Parameter({"false", "INVALID_INDEX", "ASCII_INDEX"}) - public static void test(boolean useJDKBundle, - SigningBase.CertIndex jdkBundleCert, - SigningBase.CertIndex signCert) throws Exception { - MacSign.withKeychain(toConsumer(keychain -> { - test(keychain, useJDKBundle, jdkBundleCert, signCert); - }), SigningBase.StandardKeychain.MAIN.keychain()); + public static void testBundleSignedRuntime() { + + Slot predefinedRuntime = Slot.createEmpty(); + + var signRuntime = runtimeImageSignOption(); + + new PackageTest().addRunOnceInitializer(() -> { + predefinedRuntime.set(createRuntime(Optional.of(signRuntime), RuntimeType.BUNDLE)); + }).addInitializer(cmd -> { + cmd.ignoreDefaultRuntime(true); + cmd.removeArgumentWithValue("--input"); + cmd.setArgumentValue("--runtime-image", predefinedRuntime.get()); + }).addInstallVerifier(cmd -> { + MacSignVerify.verifyAppImageSigned(cmd, signRuntime.certRequest()); + }).run(); } - private static void test(MacSign.ResolvedKeychain keychain, boolean useJDKBundle, - SigningBase.CertIndex jdkBundleCert, - SigningBase.CertIndex signCert) throws Exception { + public static Collection test() { - final Path inputRuntime[] = new Path[1]; + List data = new ArrayList<>(); - new PackageTest() - .addRunOnceInitializer(() -> { - if (useJDKBundle) { - inputRuntime[0] = createInputRuntimeBundle(keychain, jdkBundleCert.value()); - } else { - inputRuntime[0] = JPackageCommand.createInputRuntimeImage(); - } - }) - .addInitializer(cmd -> { - cmd.addArguments("--runtime-image", inputRuntime[0]); - // Remove --input parameter from jpackage command line as we don't - // create input directory in the test and jpackage fails - // if --input references non existent directory. - cmd.removeArgumentWithValue("--input"); - addSignOptions(cmd, keychain, signCert.value()); - }) - .addInstallVerifier(cmd -> { - final var certIndex = Stream.of(signCert, jdkBundleCert) - .filter(Predicate.isEqual(SigningBase.CertIndex.INVALID_INDEX).negate()) - .findFirst().orElse(SigningBase.CertIndex.INVALID_INDEX).value(); + for (var runtimeSpec : List.of( + Map.entry(RuntimeType.IMAGE, false /* unsigned */), + Map.entry(RuntimeType.BUNDLE, false /* unsigned */), + Map.entry(RuntimeType.BUNDLE, true /* signed */) + )) { + var runtimeType = runtimeSpec.getKey(); + var signRuntime = runtimeSpec.getValue(); - final var signed = certIndex != SigningBase.CertIndex.INVALID_INDEX.value(); + Optional runtimeSignOption; + if (signRuntime) { + // Sign the runtime bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of + // the predefined runtime bundle when backing it in the pkg or dmg installer. + runtimeSignOption = Optional.of(runtimeImageSignOption()); + } else { + runtimeSignOption = Optional.empty(); + } - final var unfoldedBundleDir = cmd.appRuntimeDirectory(); + for (var signPackage : SigningPackageTest.TestSpec.testCases(false)) { + data.add(new RuntimeTestSpec(runtimeSignOption, runtimeType, signPackage)); + } + } - final var libjli = unfoldedBundleDir.resolve("Contents/MacOS/libjli.dylib"); + return data.stream().map(v -> { + return new Object[] {v}; + }).toList(); + } - SigningBase.verifyCodesign(libjli, signed, certIndex); - SigningBase.verifyCodesign(unfoldedBundleDir, signed, certIndex); - if (signed) { - SigningBase.verifySpctl(unfoldedBundleDir, "exec", certIndex); - } - }) - .run(); + enum RuntimeType { + IMAGE, + BUNDLE, + ; + } + + record RuntimeTestSpec( + Optional signRuntime, + RuntimeType runtimeType, + SigningPackageTest.TestSpec signPackage) { + + RuntimeTestSpec { + Objects.requireNonNull(signRuntime); + Objects.requireNonNull(runtimeType); + Objects.requireNonNull(signPackage); + } + + @Override + public String toString() { + var runtimeToken = new StringBuilder(); + runtimeToken.append("runtime={").append(runtimeType); + signRuntime.ifPresent(v -> { + runtimeToken.append(", ").append(v); + }); + runtimeToken.append('}'); + return Stream.of(runtimeToken, signPackage).map(Objects::toString).collect(Collectors.joining("; ")); + } + + Optional packagedAppImageSignIdentity() { + if (runtimeType == RuntimeType.IMAGE) { + return signPackage.appImageSignOption().map(SignKeyOption::certRequest); + } else { + return signPackage.appImageSignOption().or(() -> { + return signRuntime.map(SignKeyOptionWithKeychain::signKeyOption); + }).map(SignKeyOption::certRequest); + } + } + + void test() { + + Slot predefinedRuntime = Slot.createEmpty(); + + var test = signPackage.initTest().addRunOnceInitializer(() -> { + predefinedRuntime.set(createRuntime(signRuntime, runtimeType)); + }).addInitializer(cmd -> { + cmd.ignoreDefaultRuntime(true); + cmd.removeArgumentWithValue("--input"); + cmd.setArgumentValue("--runtime-image", predefinedRuntime.get()); + }).addInstallVerifier(cmd -> { + packagedAppImageSignIdentity().ifPresent(certRequest -> { + MacSignVerify.verifyAppImageSigned(cmd, certRequest); + }); + }); + + MacSign.withKeychain(_ -> { + test.run(); + }, signPackage.keychain()); + } + } + + private static SignKeyOptionWithKeychain runtimeImageSignOption() { + // Sign the runtime bundle with the key not used in the jpackage command line being tested. + // This way we can test if jpackage keeps or replaces the signature of + // the predefined runtime bundle when backing it in the pkg or dmg installer. + return new SignKeyOptionWithKeychain( + SignKeyOption.Type.SIGN_KEY_USER_SHORT_NAME, + SigningBase.StandardCertificateRequest.CODESIGN_ACME_TECH_LTD, + SigningBase.StandardKeychain.MAIN.keychain()); + } + + private static Path createRuntime(Optional signRuntime, RuntimeType runtimeType) { + if (runtimeType == RuntimeType.IMAGE && signRuntime.isEmpty()) { + return JPackageCommand.createInputRuntimeImage(RUNTIME_TYPE_FAKE); + } else { + Slot runtimeBundle = Slot.createEmpty(); + + MacSign.withKeychain(keychain -> { + var runtimeBundleBuilder = MacHelper.buildRuntimeBundle(); + signRuntime.ifPresent(signingOption -> { + runtimeBundleBuilder.mutator(signingOption::setTo); + }); + runtimeBundle.set(runtimeBundleBuilder.type(RUNTIME_TYPE_FAKE).create()); + }, SigningBase.StandardKeychain.MAIN.keychain()); + + if (runtimeType == RuntimeType.IMAGE) { + return MacBundle.fromPath(runtimeBundle.get()).orElseThrow().homeDir(); + } else { + // Verify the runtime bundle is properly signed/unsigned. + signRuntime.map(SignKeyOptionWithKeychain::certRequest).ifPresentOrElse(certRequest -> { + MacSignVerify.assertSigned(runtimeBundle.get(), certRequest); + var signOrigin = MacSignVerify.findSpctlSignOrigin(SpctlType.EXEC, runtimeBundle.get()).orElse(null); + TKit.assertEquals(certRequest.name(), signOrigin, + String.format("Check [%s] has sign origin as expected", runtimeBundle.get())); + }, () -> { + MacSignVerify.assertAdhocSigned(runtimeBundle.get()); + }); + return runtimeBundle.get(); + } + } } } diff --git a/test/jdk/tools/jpackage/macosx/base/SigningBase.java b/test/jdk/tools/jpackage/macosx/base/SigningBase.java deleted file mode 100644 index b1a709c9cf0..00000000000 --- a/test/jdk/tools/jpackage/macosx/base/SigningBase.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * 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.nio.file.Path; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; -import jdk.jpackage.test.JPackageCommand; -import jdk.jpackage.test.MacSign; -import jdk.jpackage.test.MacSign.CertificateRequest; -import jdk.jpackage.test.MacSign.CertificateType; -import jdk.jpackage.test.MacSign.KeychainWithCertsSpec; -import jdk.jpackage.test.MacSign.ResolvedKeychain; -import jdk.jpackage.test.MacSignVerify; -import jdk.jpackage.test.TKit; - - -/* - * @test - * @summary Setup the environment for jpackage macos signing tests. - * Creates required keychains and signing identities. - * Does NOT run any jpackag tests. - * @library /test/jdk/tools/jpackage/helpers - * @build jdk.jpackage.test.* - * @compile -Xlint:all -Werror SigningBase.java - * @requires (jpackage.test.MacSignTests == "setup") - * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=SigningBase.setUp - */ - -/* - * @test - * @summary Tear down the environment for jpackage macos signing tests. - * Deletes required keychains and signing identities. - * Does NOT run any jpackag tests. - * @library /test/jdk/tools/jpackage/helpers - * @build jdk.jpackage.test.* - * @compile -Xlint:all -Werror SigningBase.java - * @requires (jpackage.test.MacSignTests == "teardown") - * @run main/othervm/timeout=1440 -Xmx512m jdk.jpackage.test.Main - * --jpt-run=SigningBase.tearDown - */ - -public class SigningBase { - - public enum StandardCertificateRequest { - CODESIGN(cert().userName(DEV_NAMES[CertIndex.ASCII_INDEX.value()])), - CODESIGN_COPY(cert().days(100).userName(DEV_NAMES[CertIndex.ASCII_INDEX.value()])), - CODESIGN_ACME_TECH_LTD(cert().days(100).userName("ACME Technologies Limited (ABC12345)")), - PKG(cert().type(CertificateType.INSTALLER).userName(DEV_NAMES[CertIndex.ASCII_INDEX.value()])), - PKG_COPY(cert().type(CertificateType.INSTALLER).days(100).userName(DEV_NAMES[CertIndex.ASCII_INDEX.value()])), - CODESIGN_UNICODE(cert().userName(DEV_NAMES[CertIndex.UNICODE_INDEX.value()])), - PKG_UNICODE(cert().type(CertificateType.INSTALLER).userName(DEV_NAMES[CertIndex.UNICODE_INDEX.value()])), - CODESIGN_EXPIRED(cert().expired().userName("expired jpackage test")), - PKG_EXPIRED(cert().expired().type(CertificateType.INSTALLER).userName("expired jpackage test")); - - StandardCertificateRequest(CertificateRequest.Builder specBuilder) { - this.spec = specBuilder.create(); - } - - public CertificateRequest spec() { - return spec; - } - - private static CertificateRequest.Builder cert() { - return new CertificateRequest.Builder(); - } - - private final CertificateRequest spec; - } - - /** - * Standard keychains used in signing tests. - */ - public enum StandardKeychain { - /** - * The primary keychain with good certificates. - */ - MAIN("jpackagerTest.keychain", - StandardCertificateRequest.CODESIGN, - StandardCertificateRequest.PKG, - StandardCertificateRequest.CODESIGN_UNICODE, - StandardCertificateRequest.PKG_UNICODE, - StandardCertificateRequest.CODESIGN_ACME_TECH_LTD), - /** - * A keychain with some good and some expired certificates. - */ - EXPIRED("jpackagerTest-expired.keychain", - StandardCertificateRequest.CODESIGN, - StandardCertificateRequest.PKG, - StandardCertificateRequest.CODESIGN_EXPIRED, - StandardCertificateRequest.PKG_EXPIRED), - /** - * A keychain with duplicated certificates. - */ - DUPLICATE("jpackagerTest-duplicate.keychain", - StandardCertificateRequest.CODESIGN, - StandardCertificateRequest.PKG, - StandardCertificateRequest.CODESIGN_COPY, - StandardCertificateRequest.PKG_COPY); - - StandardKeychain(String keychainName, StandardCertificateRequest... certs) { - this(keychainName, certs[0].spec(), Stream.of(certs).skip(1).map(StandardCertificateRequest::spec).toArray(CertificateRequest[]::new)); - } - - StandardKeychain(String keychainName, CertificateRequest cert, CertificateRequest... otherCerts) { - final var builder = keychain(keychainName).addCert(cert); - List.of(otherCerts).forEach(builder::addCert); - this.keychain = new ResolvedKeychain(builder.create()); - } - - public ResolvedKeychain keychain() { - return keychain; - } - - public X509Certificate mapCertificateRequest(CertificateRequest certRequest) { - return Objects.requireNonNull(keychain.mapCertificateRequests().get(certRequest)); - } - - private static KeychainWithCertsSpec.Builder keychain(String name) { - return new KeychainWithCertsSpec.Builder().name(name); - } - - private static List signingEnv() { - return Stream.of(values()).map(StandardKeychain::keychain).map(ResolvedKeychain::spec).toList(); - } - - private final ResolvedKeychain keychain; - } - - public static void setUp() { - MacSign.setUp(StandardKeychain.signingEnv()); - } - - public static void tearDown() { - MacSign.tearDown(StandardKeychain.signingEnv()); - } - - public static void verifySignTestEnvReady() { - if (!Inner.SIGN_ENV_READY) { - TKit.throwSkippedException(new IllegalStateException("Misconfigured signing test environment")); - } - } - - private final class Inner { - private static final boolean SIGN_ENV_READY = MacSign.isDeployed(StandardKeychain.signingEnv()); - } - - enum CertIndex { - ASCII_INDEX(0), - UNICODE_INDEX(1), - INVALID_INDEX(-1); - - CertIndex(int value) { - this.value = value; - } - - int value() { - return value; - } - - private final int value; - } - - public static int DEFAULT_INDEX = 0; - private static String [] DEV_NAMES = { - "jpackage.openjdk.java.net", - "jpackage.openjdk.java.net (รถ)", - }; - - public static String getDevName(int certIndex) { - // Always use values from system properties if set - String value = System.getProperty("jpackage.mac.signing.key.user.name"); - if (value != null) { - return value; - } - - return DEV_NAMES[certIndex]; - } - - public static int getDevNameIndex(String devName) { - return Arrays.binarySearch(DEV_NAMES, devName); - } - - public static String getAppCert(int certIndex) { - return "Developer ID Application: " + getDevName(certIndex); - } - - public static String getInstallerCert(int certIndex) { - return "Developer ID Installer: " + getDevName(certIndex); - } - - public static void verifyCodesign(Path target, boolean signed, int certIndex) { - if (signed) { - final var certRequest = getCertRequest(certIndex); - MacSignVerify.assertSigned(target, certRequest); - } else { - MacSignVerify.assertAdhocSigned(target); - } - } - - // Since we no longer have unsigned app image, but we need to check - // DMG which is not adhoc or certificate signed and we cannot use verifyCodesign - // for this. verifyDMG() is introduced to check that DMG is unsigned. - // Should not be used to validated anything else. - public static void verifyDMG(Path target) { - if (!target.toString().toLowerCase().endsWith(".dmg")) { - throw new IllegalArgumentException("Unexpected target: " + target); - } - - MacSignVerify.assertUnsigned(target); - } - - public static void verifySpctl(Path target, String type, int certIndex) { - final var standardCertIndex = Stream.of(CertIndex.values()).filter(v -> { - return v.value() == certIndex; - }).findFirst().orElseThrow(); - - final var standardType = Stream.of(MacSignVerify.SpctlType.values()).filter(v -> { - return v.value().equals(type); - }).findFirst().orElseThrow(); - - final String expectedSignOrigin; - if (standardCertIndex == CertIndex.INVALID_INDEX) { - expectedSignOrigin = null; - } else if (standardType == MacSignVerify.SpctlType.EXEC) { - expectedSignOrigin = getCertRequest(certIndex).name(); - } else if (standardType == MacSignVerify.SpctlType.INSTALL) { - expectedSignOrigin = getPkgCertRequest(certIndex).name(); - } else { - throw new IllegalArgumentException(); - } - - final var signOrigin = MacSignVerify.findSpctlSignOrigin(standardType, target).orElse(null); - - TKit.assertEquals(signOrigin, expectedSignOrigin, - String.format("Check [%s] has sign origin as expected", target)); - } - - public static void verifyPkgutil(Path target, boolean signed, int certIndex) { - if (signed) { - final var certRequest = getPkgCertRequest(certIndex); - MacSignVerify.assertPkgSigned(target, certRequest, StandardKeychain.MAIN.mapCertificateRequest(certRequest)); - } else { - MacSignVerify.assertUnsigned(target); - } - } - - public static void verifyAppImageSignature(JPackageCommand appImageCmd, - boolean isSigned, String... launchers) throws Exception { - Path launcherPath = appImageCmd.appLauncherPath(); - SigningBase.verifyCodesign(launcherPath, isSigned, SigningBase.DEFAULT_INDEX); - - final List launchersList = List.of(launchers); - launchersList.forEach(launcher -> { - Path testALPath = launcherPath.getParent().resolve(launcher); - SigningBase.verifyCodesign(testALPath, isSigned, SigningBase.DEFAULT_INDEX); - }); - - Path appImage = appImageCmd.outputBundle(); - SigningBase.verifyCodesign(appImage, isSigned, SigningBase.DEFAULT_INDEX); - if (isSigned) { - SigningBase.verifySpctl(appImage, "exec", SigningBase.DEFAULT_INDEX); - } - } - - private static CertificateRequest getCertRequest(int certIndex) { - switch (CertIndex.values()[certIndex]) { - case ASCII_INDEX -> { - return StandardCertificateRequest.CODESIGN.spec(); - } - case UNICODE_INDEX -> { - return StandardCertificateRequest.CODESIGN_UNICODE.spec(); - } - default -> { - throw new IllegalArgumentException(); - } - } - } - - private static CertificateRequest getPkgCertRequest(int certIndex) { - switch (CertIndex.values()[certIndex]) { - case ASCII_INDEX -> { - return StandardCertificateRequest.PKG.spec(); - } - case UNICODE_INDEX -> { - return StandardCertificateRequest.PKG_UNICODE.spec(); - } - default -> { - throw new IllegalArgumentException(); - } - } - } -} diff --git a/test/langtools/jdk/jshell/CompletionAPITest.java b/test/langtools/jdk/jshell/CompletionAPITest.java index ad5dea90a95..932482ce6dc 100644 --- a/test/langtools/jdk/jshell/CompletionAPITest.java +++ b/test/langtools/jdk/jshell/CompletionAPITest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8366691 + * @bug 8366691 8375015 * @summary Test JShell Completion API * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -67,7 +67,7 @@ import org.junit.jupiter.api.Test; public class CompletionAPITest extends KullaTesting { - private static final long TIMEOUT = 2_000; + private static final long TIMEOUT = 20_000; @Test public void testAPI() { @@ -144,7 +144,7 @@ public class CompletionAPITest extends KullaTesting { } @Test - public void testDocumentation() { + public void testDocumentation() throws Exception { waitIndexingFinished(); Path classes = prepareZip(); @@ -171,6 +171,7 @@ public class CompletionAPITest extends KullaTesting { while (clazz.get().get() != null && (System.currentTimeMillis() - start) < TIMEOUT) { System.gc(); + Thread.sleep(100); } assertNull(clazz.get().get()); diff --git a/test/micro/org/openjdk/bench/java/security/SSLHandshake.java b/test/micro/org/openjdk/bench/java/security/SSLHandshake.java index d8773781b58..b46704a01de 100644 --- a/test/micro/org/openjdk/bench/java/security/SSLHandshake.java +++ b/test/micro/org/openjdk/bench/java/security/SSLHandshake.java @@ -44,6 +44,7 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLParameters; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManagerFactory; @@ -75,8 +76,15 @@ public class SSLHandshake { @Param({"true", "false"}) boolean resume; - @Param({"TLSv1.2", "TLS"}) - String tlsVersion; + @Param({ + "TLSv1.2-secp256r1", + "TLSv1.3-x25519", "TLSv1.3-secp256r1", "TLSv1.3-secp384r1", + "TLSv1.3-X25519MLKEM768", "TLSv1.3-SecP256r1MLKEM768", "TLSv1.3-SecP384r1MLKEM1024" + }) + String versionAndGroup; + + private String tlsVersion; + private String namedGroup; private static SSLContext getServerContext() { try { @@ -96,6 +104,10 @@ public class SSLHandshake { @Setup(Level.Trial) public void init() throws Exception { + String[] components = versionAndGroup.split("-", 2); + tlsVersion = components[0]; + namedGroup = components[1]; + KeyStore ts = TestCertificates.getTrustStore(); TrustManagerFactory tmf = TrustManagerFactory.getInstance( @@ -195,5 +207,14 @@ public class SSLHandshake { */ clientEngine = sslClientCtx.createSSLEngine("client", 80); clientEngine.setUseClientMode(true); + + // Set the key exchange named group in client and server engines + SSLParameters clientParams = clientEngine.getSSLParameters(); + clientParams.setNamedGroups(new String[]{namedGroup}); + clientEngine.setSSLParameters(clientParams); + + SSLParameters serverParams = serverEngine.getSSLParameters(); + serverParams.setNamedGroups(new String[]{namedGroup}); + serverEngine.setSSLParameters(serverParams); } } diff --git a/test/micro/org/openjdk/bench/javax/crypto/full/KEMBench.java b/test/micro/org/openjdk/bench/javax/crypto/full/KEMBench.java index 3386039c62e..dc6d2060f5e 100644 --- a/test/micro/org/openjdk/bench/javax/crypto/full/KEMBench.java +++ b/test/micro/org/openjdk/bench/javax/crypto/full/KEMBench.java @@ -23,34 +23,69 @@ package org.openjdk.bench.javax.crypto.full; import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.OperationsPerInvocation; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.infra.Blackhole; import javax.crypto.DecapsulateException; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; import javax.crypto.KEM; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.security.spec.ECGenParameterSpec; -public class KEMBench extends CryptoBase { +public abstract class KEMBench extends CryptoBase { public static final int SET_SIZE = 128; - @Param({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024" }) + @Param({}) private String algorithm; + @Param({""}) // Used when the KeyPairGenerator Alg != KEM Alg + private String kpgSpec; + private KeyPair[] keys; private byte[][] messages; private KEM kem; @Setup - public void setup() throws NoSuchAlgorithmException, InvalidKeyException { - kem = (prov == null) ? KEM.getInstance(algorithm) : KEM.getInstance(algorithm, prov); - KeyPairGenerator generator = (prov == null) ? KeyPairGenerator.getInstance(algorithm) : KeyPairGenerator.getInstance(algorithm, prov); + public void setup() throws NoSuchAlgorithmException, InvalidKeyException, + InvalidAlgorithmParameterException { + String kpgAlg; + String kpgParams; + kem = (prov == null) ? KEM.getInstance(algorithm) : + KEM.getInstance(algorithm, prov); + + // By default use the same provider for KEM and KPG + Provider kpgProv = prov; + if (kpgSpec.isEmpty()) { + kpgAlg = algorithm; + kpgParams = ""; + } else { + // The key pair generation spec is broken down from a colon- + // delimited string spec into 3 fields: + // [0] - the provider name + // [1] - the algorithm name + // [2] - the parameters (i.e. the name of the curve) + String[] kpgTok = kpgSpec.split(":"); + kpgProv = Security.getProvider(kpgTok[0]); + kpgAlg = kpgTok[1]; + kpgParams = kpgTok[2]; + } + KeyPairGenerator generator = (kpgProv == null) ? + KeyPairGenerator.getInstance(kpgAlg) : + KeyPairGenerator.getInstance(kpgAlg, kpgProv); + if (kpgParams != null && !kpgParams.isEmpty()) { + generator.initialize(new ECGenParameterSpec(kpgParams)); + } keys = new KeyPair[SET_SIZE]; for (int i = 0; i < keys.length; i++) { keys[i] = generator.generateKeyPair(); @@ -63,20 +98,79 @@ public class KEMBench extends CryptoBase { } } + private static Provider getInternalJce() { + try { + Class dhClazz = Class.forName("sun.security.ssl.HybridProvider"); + return (Provider) dhClazz.getField("PROVIDER").get(null); + } catch (ReflectiveOperationException exc) { + throw new RuntimeException(exc); + } + } + @Benchmark @OperationsPerInvocation(SET_SIZE) public void encapsulate(Blackhole bh) throws InvalidKeyException { for (KeyPair kp : keys) { - bh.consume(kem.newEncapsulator(kp.getPublic()).encapsulate().encapsulation()); + bh.consume(kem.newEncapsulator(kp.getPublic()).encapsulate(). + encapsulation()); } } @Benchmark @OperationsPerInvocation(SET_SIZE) - public void decapsulate(Blackhole bh) throws InvalidKeyException, DecapsulateException { + public void decapsulate(Blackhole bh) throws InvalidKeyException, + DecapsulateException { for (int i = 0; i < messages.length; i++) { - bh.consume(kem.newDecapsulator(keys[i].getPrivate()).decapsulate(messages[i])); + bh.consume(kem.newDecapsulator(keys[i].getPrivate()). + decapsulate(messages[i])); } } + public static class MLKEM extends KEMBench { + @Param({"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024" }) + private String algorithm; + + @Param({""}) // ML-KEM uses the same alg for KPG and KEM + private String kpgSpec; + } + + @Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens", + "java.base/sun.security.ssl=ALL-UNNAMED"}) + public static class JSSE_DHasKEM extends KEMBench { + @Setup + public void init() { + try { + prov = getInternalJce(); + super.setup(); + } catch (GeneralSecurityException gse) { + throw new RuntimeException(gse); + } + } + + @Param({"DH"}) + private String algorithm; + + @Param({"SunEC:XDH:x25519", "SunEC:EC:secp256r1", "SunEC:EC:secp384r1"}) + private String kpgSpec; + } + + @Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens", + "java.base/sun.security.ssl=ALL-UNNAMED"}) + public static class JSSE_Hybrid extends KEMBench { + @Setup + public void init() { + try { + prov = getInternalJce(); + super.setup(); + } catch (GeneralSecurityException gse) { + throw new RuntimeException(gse); + } + } + + @Param({"X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024"}) + private String algorithm; + + @Param({""}) // ML-KEM uses the same alg for KPG and KEM + private String kpgSpec; + } } diff --git a/test/micro/org/openjdk/bench/javax/crypto/full/KeyPairGeneratorBench.java b/test/micro/org/openjdk/bench/javax/crypto/full/KeyPairGeneratorBench.java index 58daff28d88..5a3c72fb263 100644 --- a/test/micro/org/openjdk/bench/javax/crypto/full/KeyPairGeneratorBench.java +++ b/test/micro/org/openjdk/bench/javax/crypto/full/KeyPairGeneratorBench.java @@ -22,13 +22,16 @@ */ package org.openjdk.bench.javax.crypto.full; +import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Setup; +import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.Provider; public class KeyPairGeneratorBench extends CryptoBase { @@ -45,11 +48,21 @@ public class KeyPairGeneratorBench extends CryptoBase { setupProvider(); generator = (prov == null) ? KeyPairGenerator.getInstance(algorithm) : KeyPairGenerator.getInstance(algorithm, prov); - if (keyLength > 0) { // not all key pair generators allow the use of key length + // not all key pair generators allow the use of key length + if (keyLength > 0) { generator.initialize(keyLength); } } + private static Provider getInternalJce() { + try { + Class dhClazz = Class.forName("sun.security.ssl.HybridProvider"); + return (Provider) dhClazz.getField("PROVIDER").get(null); + } catch (ReflectiveOperationException exc) { + throw new RuntimeException(exc); + } + } + @Benchmark public KeyPair generateKeyPair() { return generator.generateKeyPair(); @@ -118,4 +131,23 @@ public class KeyPairGeneratorBench extends CryptoBase { private int keyLength; } + @Fork(value = 5, jvmArgs = {"-XX:+AlwaysPreTouch", "--add-opens", + "java.base/sun.security.ssl=ALL-UNNAMED"}) + public static class JSSE_Hybrid extends KeyPairGeneratorBench { + @Setup + public void init() { + try { + prov = getInternalJce(); + super.setup(); + } catch (GeneralSecurityException gse) { + throw new RuntimeException(gse); + } + } + + @Param({"X25519MLKEM768", "SecP256r1MLKEM768", "SecP384r1MLKEM1024"}) + private String algorithm; + + @Param({"0"}) // Hybrid KPGs don't need key lengths + private int keyLength; + } }