From a5ccd3beaf069bdfe81736f6c62e5b4b9e18b5fe Mon Sep 17 00:00:00 2001 From: Jorn Vernee Date: Tue, 28 Nov 2023 10:17:58 +0000 Subject: [PATCH] 8267532: C2: Profile and prune untaken exception handlers 8310011: Arena with try-with-resources is slower than it should be Reviewed-by: thartmann, vlivanov --- src/hotspot/share/c1/c1_GraphBuilder.cpp | 1 - src/hotspot/share/ci/ciMethodData.cpp | 64 ++++-- src/hotspot/share/ci/ciMethodData.hpp | 37 +++- .../share/interpreter/interpreterRuntime.cpp | 2 + src/hotspot/share/oops/method.cpp | 10 + src/hotspot/share/oops/method.hpp | 3 + src/hotspot/share/oops/methodData.cpp | 43 +++- src/hotspot/share/oops/methodData.hpp | 65 +++++- src/hotspot/share/opto/c2_globals.hpp | 2 + src/hotspot/share/opto/doCall.cpp | 2 +- src/hotspot/share/opto/graphKit.cpp | 6 +- src/hotspot/share/opto/graphKit.hpp | 2 +- src/hotspot/share/opto/parse1.cpp | 16 ++ .../share/runtime/continuationFreezeThaw.cpp | 34 +-- src/hotspot/share/runtime/deoptimization.cpp | 11 + src/hotspot/share/runtime/globals.hpp | 3 + src/hotspot/share/runtime/sharedRuntime.cpp | 4 + .../jtreg/compiler/c2/TestExHandlerTrap.java | 153 +++++++++++++ .../c2/irTests/TestPrunedExHandler.java | 202 ++++++++++++++++++ .../compiler/lib/ir_framework/IRNode.java | 23 +- .../java/lang/foreign/AllocFromSliceTest.java | 17 +- .../java/lang/foreign/AllocFromTest.java | 28 ++- .../bench/java/lang/foreign/AllocTest.java | 21 +- .../lang/foreign/ResourceScopeCloseMin.java | 100 +++++++++ .../bench/java/lang/foreign/StrLenTest.java | 9 +- .../java/lang/foreign/ToCStringTest.java | 7 +- 26 files changed, 755 insertions(+), 110 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestExHandlerTrap.java create mode 100644 test/hotspot/jtreg/compiler/c2/irTests/TestPrunedExHandler.java create mode 100644 test/micro/org/openjdk/bench/java/lang/foreign/ResourceScopeCloseMin.java diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index 3f7f086220f..82c48ab171f 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -3506,7 +3506,6 @@ int GraphBuilder::recursive_inline_level(ciMethod* cur_callee) const { return recur_level; } - bool GraphBuilder::try_inline(ciMethod* callee, bool holder_known, bool ignore_return, Bytecodes::Code bc, Value receiver) { const char* msg = nullptr; diff --git a/src/hotspot/share/ci/ciMethodData.cpp b/src/hotspot/share/ci/ciMethodData.cpp index 9bd73d32ff9..8d1772f655f 100644 --- a/src/hotspot/share/ci/ciMethodData.cpp +++ b/src/hotspot/share/ci/ciMethodData.cpp @@ -42,6 +42,8 @@ ciMethodData::ciMethodData(MethodData* md) : ciMetadata(md), _data_size(0), _extra_data_size(0), _data(nullptr), + _parameters_data_offset(0), + _exception_handlers_data_offset(0), // Set an initial hint. Don't use set_hint_di() because // first_di() may be out of bounds if data_size is 0. _hint_di(first_di()), @@ -50,8 +52,7 @@ ciMethodData::ciMethodData(MethodData* md) // Initialize the escape information (to "don't know."); _eflags(0), _arg_local(0), _arg_stack(0), _arg_returned(0), _invocation_counter(0), - _orig(), - _parameters(nullptr) {} + _orig() {} // Check for entries that reference an unloaded method class PrepareExtraDataClosure : public CleanExtraDataClosure { @@ -134,8 +135,16 @@ void ciMethodData::load_remaining_extra_data() { // Copy the extra data once it is prepared (i.e. cache populated, no release of extra data lock anymore) Copy::disjoint_words_atomic((HeapWord*) mdo->extra_data_base(), - (HeapWord*)((address) _data + _data_size), - (_extra_data_size - mdo->parameters_size_in_bytes()) / HeapWordSize); + (HeapWord*) extra_data_base(), + // copy everything from extra_data_base() up to parameters_data_base() + pointer_delta(parameters_data_base(), extra_data_base(), HeapWordSize)); + + // skip parameter data copying. Already done in 'load_data' + + // copy exception handler data + Copy::disjoint_words_atomic((HeapWord*) mdo->exception_handler_data_base(), + (HeapWord*) exception_handler_data_base(), + exception_handler_data_size() / HeapWordSize); // speculative trap entries also hold a pointer to a Method so need to be translated DataLayout* dp_src = mdo->extra_data_base(); @@ -195,12 +204,17 @@ bool ciMethodData::load_data() { // args_data_limit: --------------------------- // | parameter data entries | // | ... | + // param_data_limit: --------------------------- + // | ex handler data entries | + // | ... | // extra_data_limit: --------------------------- // // _data_size = extra_data_base - data_base // _extra_data_size = extra_data_limit - extra_data_base // total_size = _data_size + _extra_data_size - // args_data_limit = data_base + total_size - parameter_data_size + // args_data_limit = param_data_base + // param_data_limit = exception_handler_data_base + // extra_data_limit = extra_data_limit #ifndef ZERO // Some Zero platforms do not have expected alignment, and do not use @@ -218,12 +232,15 @@ bool ciMethodData::load_data() { Copy::disjoint_words_atomic((HeapWord*) mdo->data_base(), (HeapWord*) _data, _data_size / HeapWordSize); + // Copy offsets. This is used below + _parameters_data_offset = mdo->parameters_type_data_di(); + _exception_handlers_data_offset = mdo->exception_handlers_data_di(); int parameters_data_size = mdo->parameters_size_in_bytes(); if (parameters_data_size > 0) { // Snapshot the parameter data - Copy::disjoint_words_atomic((HeapWord*) mdo->args_data_limit(), - (HeapWord*) ((address)_data + total_size - parameters_data_size), + Copy::disjoint_words_atomic((HeapWord*) mdo->parameters_data_base(), + (HeapWord*) parameters_data_base(), parameters_data_size / HeapWordSize); } // Traverse the profile data, translating any oops into their @@ -237,12 +254,12 @@ bool ciMethodData::load_data() { data = mdo->next_data(data); } if (mdo->parameters_type_data() != nullptr) { - _parameters = data_layout_at(mdo->parameters_type_data_di()); - ciParametersTypeData* parameters = new ciParametersTypeData(_parameters); + DataLayout* parameters_data = data_layout_at(_parameters_data_offset); + ciParametersTypeData* parameters = new ciParametersTypeData(parameters_data); parameters->translate_from(mdo->parameters_type_data()); } - assert((DataLayout*) ((address)_data + total_size - parameters_data_size) == args_data_limit(), + assert((DataLayout*) ((address)_data + total_size - parameters_data_size - exception_handler_data_size()) == args_data_limit(), "sanity - parameter data starts after the argument data of the single ArgInfoData entry"); load_remaining_extra_data(); @@ -367,16 +384,24 @@ ciProfileData* ciMethodData::next_data(ciProfileData* current) { return next; } -DataLayout* ciMethodData::next_data_layout(DataLayout* current) { +DataLayout* ciMethodData::next_data_layout_helper(DataLayout* current, bool extra) { int current_index = dp_to_di((address)current); int next_index = current_index + current->size_in_bytes(); - if (out_of_bounds(next_index)) { + if (extra ? out_of_bounds_extra(next_index) : out_of_bounds(next_index)) { return nullptr; } DataLayout* next = data_layout_at(next_index); return next; } +DataLayout* ciMethodData::next_data_layout(DataLayout* current) { + return next_data_layout_helper(current, false); +} + +DataLayout* ciMethodData::next_extra_data_layout(DataLayout* current) { + return next_data_layout_helper(current, true); +} + ciProfileData* ciMethodData::bci_to_extra_data(int bci, ciMethod* m, bool& two_free_slots) { DataLayout* dp = extra_data_base(); DataLayout* end = args_data_limit(); @@ -438,6 +463,19 @@ ciProfileData* ciMethodData::bci_to_data(int bci, ciMethod* m) { return nullptr; } +ciBitData ciMethodData::exception_handler_bci_to_data(int bci) { + assert(ProfileExceptionHandlers, "not profiling"); + assert(_data != nullptr, "must be initialized"); + for (DataLayout* data = exception_handler_data_base(); data < exception_handler_data_limit(); data = next_extra_data_layout(data)) { + assert(data != nullptr, "out of bounds?"); + if (data->bci() == bci) { + return ciBitData(data); + } + } + // called with invalid bci or wrong Method/MethodData + ShouldNotReachHere(); +} + // Conservatively decode the trap_state of a ciProfileData. int ciMethodData::has_trap_at(ciProfileData* data, int reason) { typedef Deoptimization::DeoptReason DR_t; @@ -612,7 +650,7 @@ uint ciMethodData::arg_modified(int arg) const { } ciParametersTypeData* ciMethodData::parameters_type_data() const { - return _parameters != nullptr ? new ciParametersTypeData(_parameters) : nullptr; + return parameter_data_size() != 0 ? new ciParametersTypeData(data_layout_at(_parameters_data_offset)) : nullptr; } ByteSize ciMethodData::offset_of_slot(ciProfileData* data, ByteSize slot_offset_in_data) { diff --git a/src/hotspot/share/ci/ciMethodData.hpp b/src/hotspot/share/ci/ciMethodData.hpp index 005fa214647..dedbfc9f4d6 100644 --- a/src/hotspot/share/ci/ciMethodData.hpp +++ b/src/hotspot/share/ci/ciMethodData.hpp @@ -379,6 +379,10 @@ private: // Data entries intptr_t* _data; + // layout of _data + int _parameters_data_offset; + int _exception_handlers_data_offset; + // Cached hint for data_layout_before() int _hint_di; @@ -403,17 +407,13 @@ private: // Coherent snapshot of original header. MethodData::CompilerCounters _orig; - // Area dedicated to parameters. null if no parameter profiling for this method. - DataLayout* _parameters; - int parameters_size() const { - return _parameters == nullptr ? 0 : parameters_type_data()->size_in_bytes(); - } - ciMethodData(MethodData* md = nullptr); // Accessors int data_size() const { return _data_size; } int extra_data_size() const { return _extra_data_size; } + int parameter_data_size() const { return _exception_handlers_data_offset - _parameters_data_offset; } + int exception_handler_data_size() const { return dp_to_di((address) exception_handler_data_limit()) - _exception_handlers_data_offset; } intptr_t * data() const { return _data; } MethodData* get_MethodData() const { @@ -425,7 +425,7 @@ private: void print_impl(outputStream* st); DataLayout* data_layout_at(int data_index) const { - assert(data_index % sizeof(intptr_t) == 0, "unaligned"); + assert(data_index % sizeof(intptr_t) == 0, "unaligned: %d", data_index); return (DataLayout*) (((address)_data) + data_index); } @@ -433,6 +433,12 @@ private: return data_index >= data_size(); } + bool out_of_bounds_extra(int data_index) { + return data_index < data_size() || data_index >= data_size() + extra_data_size(); + } + + DataLayout* next_data_layout_helper(DataLayout* current, bool extra); + // hint accessors int hint_di() const { return _hint_di; } void set_hint_di(int di) { @@ -500,7 +506,7 @@ public: bool load_data(); // Convert a dp (data pointer) to a di (data index). - int dp_to_di(address dp) { + int dp_to_di(address dp) const { return pointer_delta_as_int(dp, ((address)_data)); } @@ -511,17 +517,28 @@ public: ciProfileData* first_data() { return data_at(first_di()); } ciProfileData* next_data(ciProfileData* current); DataLayout* next_data_layout(DataLayout* current); + DataLayout* next_extra_data_layout(DataLayout* current); bool is_valid(ciProfileData* current) { return current != nullptr; } bool is_valid(DataLayout* current) { return current != nullptr; } + // pointers to sections in _data + // NOTE: these may be called before ciMethodData::load_data + // this works out since everything is initialized to 0 (i.e. there will appear to be no data) DataLayout* extra_data_base() const { return data_layout_at(data_size()); } - DataLayout* args_data_limit() const { return data_layout_at(data_size() + extra_data_size() - - parameters_size()); } + DataLayout* extra_data_limit() const { return data_layout_at(data_size() + extra_data_size()); } + // pointers to sections in extra data + DataLayout* args_data_limit() const { return parameters_data_base(); } + DataLayout* parameters_data_base() const { return data_layout_at(_parameters_data_offset); } + DataLayout* parameters_data_limit() const { return exception_handler_data_base(); } + DataLayout* exception_handler_data_base() const { return data_layout_at(_exception_handlers_data_offset); } + DataLayout* exception_handler_data_limit() const { return extra_data_limit(); } // Get the data at an arbitrary bci, or null if there is none. If m // is not null look for a SpeculativeTrapData if any first. ciProfileData* bci_to_data(int bci, ciMethod* m = nullptr); + ciBitData exception_handler_bci_to_data(int bci); + uint overflow_trap_count() const { return _orig.overflow_trap_count(); } diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index a82092216e3..70439459d35 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -460,6 +460,7 @@ JRT_END // bci where the exception happened. If the exception was propagated back // from a call, the expression stack contains the values for the bci at the // invoke w/o arguments (i.e., as if one were inside the call). +// Note that the implementation of this method assumes it's only called when an exception has actually occured JRT_ENTRY(address, InterpreterRuntime::exception_handler_for_exception(JavaThread* current, oopDesc* exception)) // We get here after we have unwound from a callee throwing an exception // into the interpreter. Any deferred stack processing is notified of @@ -574,6 +575,7 @@ JRT_ENTRY(address, InterpreterRuntime::exception_handler_for_exception(JavaThrea } else { // handler in this method => change bci/bcp to handler bci/bcp and continue there handler_pc = h_method->code_base() + handler_bci; + h_method->set_exception_handler_entered(handler_bci); // profiling #ifndef ZERO set_bcp_and_mdp(handler_pc, current); continuation = Interpreter::dispatch_table(vtos)[*handler_pc]; diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index c408bc23be7..0e116161ccf 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -648,6 +648,16 @@ bool Method::init_method_counters(MethodCounters* counters) { return Atomic::replace_if_null(&_method_counters, counters); } +void Method::set_exception_handler_entered(int handler_bci) { + if (ProfileExceptionHandlers) { + MethodData* mdo = method_data(); + if (mdo != nullptr) { + BitData handler_data = mdo->exception_handler_bci_to_data(handler_bci); + handler_data.set_exception_handler_entered(); + } + } +} + int Method::extra_stack_words() { // not an inline function, to avoid a header dependency on Interpreter return extra_stack_entries() * Interpreter::stackElementSize; diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 1dab585a660..c8b7dd1d91d 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -308,6 +308,9 @@ class Method : public Metadata { return _method_data; } + // mark an exception handler as entered (used to prune dead catch blocks in C2) + void set_exception_handler_entered(int handler_bci); + MethodCounters* method_counters() const { return _method_counters; } diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index 71f950bafec..af0fcdeeb5f 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -965,6 +965,12 @@ int MethodData::compute_allocation_size_in_bytes(const methodHandle& method) { if (args_cell > 0) { object_size += DataLayout::compute_size_in_bytes(args_cell); } + + if (ProfileExceptionHandlers && method()->has_exception_handler()) { + int num_exception_handlers = method()->exception_table_length(); + object_size += num_exception_handlers * single_exception_handler_data_size(); + } + return object_size; } @@ -1275,8 +1281,10 @@ void MethodData::initialize() { // for method entry so they don't fit with the framework for the // profiling of bytecodes). We store the offset within the MDO of // this area (or -1 if no parameter is profiled) + int parm_data_size = 0; if (parms_cell > 0) { - object_size += DataLayout::compute_size_in_bytes(parms_cell); + parm_data_size = DataLayout::compute_size_in_bytes(parms_cell); + object_size += parm_data_size; _parameters_type_data_di = data_size + extra_size + arg_data_size; DataLayout *dp = data_layout_at(data_size + extra_size + arg_data_size); dp->initialize(DataLayout::parameters_type_data_tag, 0, parms_cell); @@ -1284,6 +1292,17 @@ void MethodData::initialize() { _parameters_type_data_di = no_parameters; } + _exception_handler_data_di = data_size + extra_size + arg_data_size + parm_data_size; + if (ProfileExceptionHandlers && method()->has_exception_handler()) { + int num_exception_handlers = method()->exception_table_length(); + object_size += num_exception_handlers * single_exception_handler_data_size(); + ExceptionTableElement* exception_handlers = method()->exception_table_start(); + for (int i = 0; i < num_exception_handlers; i++) { + DataLayout *dp = exception_handler_data_at(i); + dp->initialize(DataLayout::bit_data_tag, exception_handlers[i].handler_pc, single_exception_handler_data_cell_count()); + } + } + // Set an initial hint. Don't use set_hint_di() because // first_di() may be out of bounds if data_size is 0. // In that situation, _hint_di is never used, but at @@ -1378,6 +1397,28 @@ ProfileData* MethodData::bci_to_data(int bci) { return bci_to_extra_data(bci, nullptr, false); } +DataLayout* MethodData::exception_handler_bci_to_data_helper(int bci) { + assert(ProfileExceptionHandlers, "not profiling"); + for (int i = 0; i < num_exception_handler_data(); i++) { + DataLayout* exception_handler_data = exception_handler_data_at(i); + if (exception_handler_data->bci() == bci) { + return exception_handler_data; + } + } + return nullptr; +} + +BitData* MethodData::exception_handler_bci_to_data_or_null(int bci) { + DataLayout* data = exception_handler_bci_to_data_helper(bci); + return data != nullptr ? new BitData(data) : nullptr; +} + +BitData MethodData::exception_handler_bci_to_data(int bci) { + DataLayout* data = exception_handler_bci_to_data_helper(bci); + assert(data != nullptr, "invalid bci"); + return BitData(data); +} + DataLayout* MethodData::next_extra(DataLayout* dp) { int nb_cells = 0; switch(dp->tag()) { diff --git a/src/hotspot/share/oops/methodData.hpp b/src/hotspot/share/oops/methodData.hpp index 1d4dd7f19e9..ee49bd18949 100644 --- a/src/hotspot/share/oops/methodData.hpp +++ b/src/hotspot/share/oops/methodData.hpp @@ -491,10 +491,11 @@ protected: enum : u1 { // null_seen: // saw a null operand (cast/aastore/instanceof) - null_seen_flag = DataLayout::first_flag + 0 + null_seen_flag = DataLayout::first_flag + 0, + exception_handler_entered_flag = null_seen_flag + 1 #if INCLUDE_JVMCI // bytecode threw any exception - , exception_seen_flag = null_seen_flag + 1 + , exception_seen_flag = exception_handler_entered_flag + 1 #endif }; enum { bit_cell_count = 0 }; // no additional data fields needed. @@ -525,6 +526,10 @@ public: void set_exception_seen() { set_flag_at(exception_seen_flag); } #endif + // true if a ex handler block at this bci was entered + bool exception_handler_entered() { return flag_at(exception_handler_entered_flag); } + void set_exception_handler_entered() { set_flag_at(exception_handler_entered_flag); } + // Code generation support static u1 null_seen_byte_constant() { return flag_number_to_constant(null_seen_flag); @@ -2063,7 +2068,11 @@ private: enum { no_parameters = -2, parameters_uninitialized = -1 }; int _parameters_type_data_di; + // data index of exception handler profiling data + int _exception_handler_data_di; + // Beginning of the data entries + // See comment in ciMethodData::load_data intptr_t _data[1]; // Helper for size computation @@ -2078,6 +2087,22 @@ private: return (DataLayout*) (((address)_data) + data_index); } + static int single_exception_handler_data_cell_count() { + return BitData::static_cell_count(); + } + + static int single_exception_handler_data_size() { + return DataLayout::compute_size_in_bytes(single_exception_handler_data_cell_count()); + } + + DataLayout* exception_handler_data_at(int exception_handler_index) const { + return data_layout_at(_exception_handler_data_di + (exception_handler_index * single_exception_handler_data_size())); + } + + int num_exception_handler_data() const { + return exception_handlers_data_size() / single_exception_handler_data_size(); + } + // Initialize an individual data segment. Returns the size of // the segment in bytes. int initialize_data(BytecodeStream* stream, int data_index); @@ -2143,6 +2168,8 @@ private: void clean_extra_data_helper(DataLayout* dp, int shift, bool reset = false); void verify_extra_data_clean(CleanExtraDataClosure* cl); + DataLayout* exception_handler_bci_to_data_helper(int bci); + public: void clean_extra_data(CleanExtraDataClosure* cl); @@ -2279,8 +2306,11 @@ public: } int parameters_size_in_bytes() const { - ParametersTypeData* param = parameters_type_data(); - return param == nullptr ? 0 : param->size_in_bytes(); + return pointer_delta_as_int((address) parameters_data_limit(), (address) parameters_data_base()); + } + + int exception_handlers_data_size() const { + return pointer_delta_as_int((address) exception_handler_data_limit(), (address) exception_handler_data_base()); } // Accessors @@ -2333,11 +2363,26 @@ public: return bci_to_extra_data(bci, nullptr, true); } + BitData* exception_handler_bci_to_data_or_null(int bci); + BitData exception_handler_bci_to_data(int bci); + // Add a handful of extra data records, for trap tracking. + // Only valid after 'set_size' is called at the end of MethodData::initialize DataLayout* extra_data_base() const { return limit_data_position(); } DataLayout* extra_data_limit() const { return (DataLayout*)((address)this + size_in_bytes()); } - DataLayout* args_data_limit() const { return (DataLayout*)((address)this + size_in_bytes() - - parameters_size_in_bytes()); } + // pointers to sections in extra data + DataLayout* args_data_limit() const { return parameters_data_base(); } + DataLayout* parameters_data_base() const { + assert(_parameters_type_data_di != parameters_uninitialized, "called too early"); + return _parameters_type_data_di != no_parameters ? data_layout_at(_parameters_type_data_di) : parameters_data_limit(); + } + DataLayout* parameters_data_limit() const { + assert(_parameters_type_data_di != parameters_uninitialized, "called too early"); + return exception_handler_data_base(); + } + DataLayout* exception_handler_data_base() const { return data_layout_at(_exception_handler_data_di); } + DataLayout* exception_handler_data_limit() const { return extra_data_limit(); } + int extra_data_size() const { return (int)((address)extra_data_limit() - (address)extra_data_base()); } static DataLayout* next_extra(DataLayout* dp); @@ -2385,8 +2430,12 @@ public: } int parameters_type_data_di() const { - assert(_parameters_type_data_di != parameters_uninitialized && _parameters_type_data_di != no_parameters, "no args type data"); - return _parameters_type_data_di; + assert(_parameters_type_data_di != parameters_uninitialized, "called too early"); + return _parameters_type_data_di != no_parameters ? _parameters_type_data_di : exception_handlers_data_di(); + } + + int exception_handlers_data_di() const { + return _exception_handler_data_di; } // Support for code generation diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 3e466539c87..9a398e0dbb5 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -783,6 +783,8 @@ "more than this threshold") \ range(0, 100) \ \ + develop(bool, StressPrunedExceptionHandlers, false, \ + "Always prune exception handlers") \ // end of C2_FLAGS diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index bad44ca46d4..122da9b447c 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -941,7 +941,7 @@ void Parse::catch_inline_exceptions(SafePointNode* ex_map) { // Get the exception oop klass from its header Node* ex_klass_node = nullptr; - if (has_ex_handler() && !ex_type->klass_is_exact()) { + if (has_exception_handler() && !ex_type->klass_is_exact()) { Node* p = basic_plus_adr( ex_node, ex_node, oopDesc::klass_offset_in_bytes()); ex_klass_node = _gvn.transform(LoadKlassNode::make(_gvn, nullptr, immutable_memory(), p, TypeInstPtr::KLASS, TypeInstKlassPtr::OBJECT)); diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index b271dd6f63c..1a8ae950305 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -181,9 +181,9 @@ bool GraphKit::stopped() { } -//-----------------------------has_ex_handler---------------------------------- +//-----------------------------has_exception_handler---------------------------------- // Tell if this method or any caller method has exception handlers. -bool GraphKit::has_ex_handler() { +bool GraphKit::has_exception_handler() { for (JVMState* jvmsp = jvms(); jvmsp != nullptr; jvmsp = jvmsp->caller()) { if (jvmsp->has_method() && jvmsp->method()->has_exception_handlers()) { return true; @@ -548,7 +548,7 @@ void GraphKit::builtin_throw(Deoptimization::DeoptReason reason) { // as hot if there has been at least one in this method. if (C->trap_count(reason) != 0 && method()->method_data()->trap_count(reason) != 0 - && has_ex_handler()) { + && has_exception_handler()) { treat_throw_as_hot = true; } } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 92180371c06..3f917ec882b 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -194,7 +194,7 @@ class GraphKit : public Phase { bool stopped(); // Tell if this method or any caller method has exception handlers. - bool has_ex_handler(); + bool has_exception_handler(); // Save an exception without blowing stack contents or other JVM state. // (The extra pointer is stuck with add_req on the map, beyond the JVMS.) diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 0b867c69ebc..1e2edbb4bf8 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -1532,6 +1532,22 @@ void Parse::do_one_block() { // Set iterator to start of block. iter().reset_to_bci(block()->start()); + if (ProfileExceptionHandlers && block()->is_handler()) { + ciMethodData* methodData = method()->method_data(); + if (methodData->is_mature()) { + ciBitData data = methodData->exception_handler_bci_to_data(block()->start()); + if (!data.exception_handler_entered() || StressPrunedExceptionHandlers) { + // dead catch block + // Emit an uncommon trap instead of processing the block. + set_parse_bci(block()->start()); + uncommon_trap(Deoptimization::Reason_unreached, + Deoptimization::Action_reinterpret, + nullptr, "dead catch block"); + return; + } + } + } + CompileLog* log = C->log(); // Parse bytecodes diff --git a/src/hotspot/share/runtime/continuationFreezeThaw.cpp b/src/hotspot/share/runtime/continuationFreezeThaw.cpp index ca8affdca21..fe1dfc62ee4 100644 --- a/src/hotspot/share/runtime/continuationFreezeThaw.cpp +++ b/src/hotspot/share/runtime/continuationFreezeThaw.cpp @@ -1498,21 +1498,21 @@ static void jvmti_yield_cleanup(JavaThread* thread, ContinuationWrapper& cont) { #endif // INCLUDE_JVMTI #ifdef ASSERT -static bool monitors_on_stack(JavaThread* thread) { - ContinuationEntry* ce = thread->last_continuation(); - RegisterMap map(thread, - RegisterMap::UpdateMap::include, - RegisterMap::ProcessFrames::include, - RegisterMap::WalkContinuation::skip); - map.set_include_argument_oops(false); - for (frame f = thread->last_frame(); Continuation::is_frame_in_continuation(ce, f); f = f.sender(&map)) { - if ((f.is_interpreted_frame() && ContinuationHelper::InterpretedFrame::is_owning_locks(f)) || - (f.is_compiled_frame() && ContinuationHelper::CompiledFrame::is_owning_locks(map.thread(), &map, f))) { - return true; - } - } - return false; -} +// static bool monitors_on_stack(JavaThread* thread) { +// ContinuationEntry* ce = thread->last_continuation(); +// RegisterMap map(thread, +// RegisterMap::UpdateMap::include, +// RegisterMap::ProcessFrames::include, +// RegisterMap::WalkContinuation::skip); +// map.set_include_argument_oops(false); +// for (frame f = thread->last_frame(); Continuation::is_frame_in_continuation(ce, f); f = f.sender(&map)) { +// if ((f.is_interpreted_frame() && ContinuationHelper::InterpretedFrame::is_owning_locks(f)) || +// (f.is_compiled_frame() && ContinuationHelper::CompiledFrame::is_owning_locks(map.thread(), &map, f))) { +// return true; +// } +// } +// return false; +// } bool FreezeBase::interpreted_native_or_deoptimized_on_stack() { ContinuationEntry* ce = _thread->last_continuation(); @@ -1575,8 +1575,8 @@ static inline int freeze_internal(JavaThread* current, intptr_t* const sp) { assert(entry->is_virtual_thread() == (entry->scope(current) == java_lang_VirtualThread::vthread_scope()), ""); - assert(monitors_on_stack(current) == ((current->held_monitor_count() - current->jni_monitor_count()) > 0), - "Held monitor count and locks on stack invariant: " INT64_FORMAT " JNI: " INT64_FORMAT, (int64_t)current->held_monitor_count(), (int64_t)current->jni_monitor_count()); + // assert(monitors_on_stack(current) == ((current->held_monitor_count() - current->jni_monitor_count()) > 0), + // "Held monitor count and locks on stack invariant: " INT64_FORMAT " JNI: " INT64_FORMAT, (int64_t)current->held_monitor_count(), (int64_t)current->jni_monitor_count()); if (entry->is_pinned() || current->held_monitor_count() > 0) { log_develop_debug(continuations)("PINNED due to critical section/hold monitor"); diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index f809cd031c3..a5f2e09afaa 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -2444,6 +2444,17 @@ JRT_ENTRY(void, Deoptimization::uncommon_trap_inner(JavaThread* current, jint tr nm->method()->set_not_compilable("give up compiling", CompLevel_full_optimization); } + if (ProfileExceptionHandlers && trap_mdo != nullptr) { + BitData* exception_handler_data = trap_mdo->exception_handler_bci_to_data_or_null(trap_bci); + if (exception_handler_data != nullptr) { + // uncommon trap at the start of an exception handler. + // C2 generates these for un-entered exception handlers. + // mark the handler as entered to avoid generating + // another uncommon trap the next time the handler is compiled + exception_handler_data->set_exception_handler_entered(); + } + } + } // Free marked resources } diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 7a5b8615594..99d2488a593 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1998,6 +1998,9 @@ const int ObjectAlignmentInBytes = 8; "more eagerly at the cost of higher overhead. A value of 0 " \ "(default) disables native heap trimming.") \ range(0, UINT_MAX) \ + \ + product(bool, ProfileExceptionHandlers, true, \ + "Profile exception handlers") \ // end of RUNTIME_FLAGS diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 536944a61dc..cbb7c84d0d9 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -677,6 +677,7 @@ JRT_END // ret_pc points into caller; we are returning caller's exception handler // for given exception +// Note that the implementation of this method assumes it's only called when an exception has actually occured address SharedRuntime::compute_compiled_exc_handler(CompiledMethod* cm, address ret_pc, Handle& exception, bool force_unwind, bool top_frame_only, bool& recursive_exception_occurred) { assert(cm != nullptr, "must exist"); @@ -779,6 +780,9 @@ address SharedRuntime::compute_compiled_exc_handler(CompiledMethod* cm, address return nullptr; } + if (handler_bci != -1) { // did we find a handler in this method? + sd->method()->set_exception_handler_entered(handler_bci); // profile + } return nm->code_begin() + t->pco(); } diff --git a/test/hotspot/jtreg/compiler/c2/TestExHandlerTrap.java b/test/hotspot/jtreg/compiler/c2/TestExHandlerTrap.java new file mode 100644 index 00000000000..611f3da2bba --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestExHandlerTrap.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.c2; + +import java.io.PrintStream; +import java.util.List; +import java.util.ArrayList; + +/* + * @test id=default_config + * @bug 8267532 + * @summary Test whether trap in place of pruned exception handler block works + * + * @run main/othervm + * -Xbatch + * -Xlog:deoptimization=trace + * -XX:CompileCommand=PrintCompilation,compiler.c2.TestExHandlerTrap::payload + * -XX:CompileCommand=dontinline,compiler.c2.TestExHandlerTrap::payload + * -XX:CompileCommand=dontinline,compiler.c2.TestExHandlerTrap::maybeThrow + * compiler.c2.TestExHandlerTrap + */ + +/* + * @test id=no_profiling + * @bug 8267532 + * @summary basic smoke test for disabled ex. handler profiling + * + * @run main/othervm + * -Xbatch + * -Xlog:deoptimization=trace + * -XX:CompileCommand=PrintCompilation,compiler.c2.TestExHandlerTrap::payload + * -XX:CompileCommand=dontinline,compiler.c2.TestExHandlerTrap::payload + * -XX:CompileCommand=dontinline,compiler.c2.TestExHandlerTrap::maybeThrow + * -XX:-ProfileExceptionHandlers + * compiler.c2.TestExHandlerTrap + */ + +/* + * @test id=stress + * @bug 8267532 + * @summary basic smoke test for stressing ex. handler pruning + * @requires vm.debug + * + * @run main/othervm + * -Xbatch + * -Xlog:deoptimization=trace + * -XX:CompileCommand=PrintCompilation,compiler.c2.TestExHandlerTrap::payload + * -XX:CompileCommand=dontinline,compiler.c2.TestExHandlerTrap::payload + * -XX:CompileCommand=dontinline,compiler.c2.TestExHandlerTrap::maybeThrow + * -XX:+StressPrunedExceptionHandlers + * compiler.c2.TestExHandlerTrap + */ + +public class TestExHandlerTrap { + + private static final String EX_MESSAGE = "Testing trap"; + + public static void main(String[] args) throws Throwable { + // warmup, compile payload + for (int i = 0; i < 20_000; i++) { + payload(false); + } + + try { + // trigger uncommon trap in pruned catch block + payload(true); + } catch (IllegalStateException e) { + if (!e.getMessage().equals(EX_MESSAGE)) { + throw e; + } + } + + // continue for a bit, to see if anything breaks + for (int i = 0; i < 1_000; i++) { + payload(false); + } + } + + public static void payload(boolean shouldThrow) { + doIt(shouldThrow); // mix in some inlining + } + + public static void doIt(boolean shouldThrow) { + PrintStream err = System.err; + try (ConfinedScope r = new ConfinedScope()) { + r.addCloseAction(dummy); + maybeThrow(shouldThrow); // out of line to prevent 'payload' from being deoptimized by unstable if + } catch (IllegalArgumentException e) { + // some side effects to see whether deopt is successful + err.println("Exception message: " + e.getMessage()); + err.println("shouldThrow: " + shouldThrow); + } + } + + private static void maybeThrow(boolean shouldThrow) { + if (shouldThrow) { + throw new IllegalStateException(EX_MESSAGE); + } + } + + static final Runnable dummy = () -> {}; + + static class ConfinedScope implements AutoCloseable { + final Thread owner; + boolean closed; + final List resources = new ArrayList<>(); + + private void checkState() { + if (closed) { + throw new AssertionError("Closed"); + } else if (owner != Thread.currentThread()) { + throw new AssertionError("Wrong thread"); + } + } + + ConfinedScope() { + this.owner = Thread.currentThread(); + } + + void addCloseAction(Runnable runnable) { + checkState(); + resources.add(runnable); + } + + public void close() { + checkState(); + closed = true; + for (Runnable r : resources) { + r.run(); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestPrunedExHandler.java b/test/hotspot/jtreg/compiler/c2/irTests/TestPrunedExHandler.java new file mode 100644 index 00000000000..eacaa9fca85 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestPrunedExHandler.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.c2.irTests; + +import compiler.lib.ir_framework.*; + +/* + * @test + * @bug 8267532 + * @summary check that uncommon trap is generated for unhandled catch block + * @library /test/lib / + * @run driver compiler.c2.irTests.TestPrunedExHandler + */ + +public class TestPrunedExHandler { + public static void main(String[] args) { + TestFramework.runWithFlags( + "-XX:+TieredCompilation", // we only profile in tier 3 + "-XX:CompileCommand=inline,compiler.c2.irTests.TestPrunedExHandler::inlinee", + "-XX:CompileCommand=dontinline,compiler.c2.irTests.TestPrunedExHandler::outOfLine"); + } + + @Test + @IR(counts = {IRNode.UNREACHED_TRAP, "1"}) + public static void testTrap() { + try { + outOfLine(false); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + private static void outOfLine(boolean shouldThrow) { + if (shouldThrow) { + throw new IllegalStateException(); + } + } + + @Test + @IR(counts = {IRNode.UNREACHED_TRAP, "0"}) + public static void testNoTrap(boolean shouldThrow) { + try { + outOfLine(shouldThrow); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + @Run(test = "testNoTrap", mode = RunMode.STANDALONE) + public static void runNoTrap(RunInfo info) { + for (int i = 0; i < 2_000; i++) { // tier 3 + testNoTrap(false); + } + + TestFramework.assertCompiledAtLevel(info.getTest(), CompLevel.C1_FULL_PROFILE); + + testNoTrap(true); // mark ex handler as entered + + for (int i = 0; i < 20_000; i++) { // tier 4 + testNoTrap(false); // should have no trap + } + } + + @Test + @IR(counts = {IRNode.UNREACHED_TRAP, "0"}) + public static void testNoTrapAfterDeopt(boolean shouldThrow) { + try { + outOfLine(shouldThrow); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + @Run(test = "testNoTrapAfterDeopt", mode = RunMode.STANDALONE) + public static void runNoTrapAfterDeopt(RunInfo info) { + for (int i = 0; i < 20_000; i++) { // tier 4 + testNoTrapAfterDeopt(false); + } + + TestFramework.assertCompiledByC2(info.getTest()); + + testNoTrapAfterDeopt(true); // deopt + mark ex handler as entered + + TestFramework.assertDeoptimizedByC2(info.getTest()); + + for (int i = 0; i < 20_000; i++) { // tier 4 again + testNoTrapAfterDeopt(false); // should have no trap + } + } + + @Test + @IR(counts = {IRNode.UNREACHED_TRAP, "0"}) + public static void testNoTrapAfterDeoptInlined(boolean shouldThrow) { + // check that we handle exception thrown in inlinee, caught in caller. + // C2 handles exception dispatch differently for those cases + try { + inlinee(shouldThrow); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + private static void inlinee(boolean shouldThrow) { + outOfLine(shouldThrow); + } + + @Run(test = "testNoTrapAfterDeoptInlined", mode = RunMode.STANDALONE) + public static void runNoTrapAfterDeoptInlined(RunInfo info) { + for (int i = 0; i < 20_000; i++) { // tier 4 + testNoTrapAfterDeoptInlined(false); + } + + TestFramework.assertCompiledByC2(info.getTest()); + + testNoTrapAfterDeoptInlined(true); // deopt + mark ex handler as entered + + TestFramework.assertDeoptimizedByC2(info.getTest()); + + for (int i = 0; i < 20_000; i++) { // tier 4 again + testNoTrapAfterDeoptInlined(false); // should have no trap + } + } + + @Test + @IR(counts = {IRNode.UNREACHED_TRAP, "1"}) + public static void testThrowBeforeProfiling(boolean shouldThrow) { + try { + outOfLine(shouldThrow); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + @Run(test = "testThrowBeforeProfiling", mode = RunMode.STANDALONE) + public static void runThrowBeforeProfiling(RunInfo info) { + testThrowBeforeProfiling(true); + // this exception should not be profiled, as MDO has not been created yet + + for (int i = 0; i < 20_000; i++) { // tier 4 + testThrowBeforeProfiling(false); + } + // should have trap + } + + @Test + @IR(counts = {IRNode.UNREACHED_TRAP, "0"}) + public static void testInterpreterProfiling(boolean takeBranch, boolean shouldThrow) { + if (takeBranch) { + System.out.println("testInterpreterProfiling: branch taken"); + } + + try { + outOfLine(shouldThrow); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + @Run(test = "testInterpreterProfiling", mode = RunMode.STANDALONE) + public static void runInterpreterProfiling(RunInfo info) { + for (int i = 0; i < 20_000; i++) { // tier 4 + testInterpreterProfiling(false, false); + } + TestFramework.assertCompiledByC2(info.getTest()); + // should have no trap at this point + + testInterpreterProfiling(true, false); // take branch -> deopt due to unstable if + TestFramework.assertDeoptimizedByC2(info.getTest()); + + // continue in the interpreter: + testInterpreterProfiling(false, false); + // throw exception in the interpreter, test interpreter profiling: + testInterpreterProfiling(false, true); + + for (int i = 0; i < 20_000; i++) { // tier 4 again + testInterpreterProfiling(false, false); + } + // should have no trap + } + +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 9be4cc7be6b..9e91268edff 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -549,12 +549,12 @@ public class IRNode { // Does not work for VM builds without JVMCI like x86_32 (a rule containing this regex will be skipped without having JVMCI built). public static final String INTRINSIC_OR_TYPE_CHECKED_INLINING_TRAP = PREFIX + "INTRINSIC_OR_TYPE_CHECKED_INLINING_TRAP" + POSTFIX; static { - trapNodes(INTRINSIC_OR_TYPE_CHECKED_INLINING_TRAP,"intrinsic_or_type_checked_inlining"); + trapNodes(INTRINSIC_OR_TYPE_CHECKED_INLINING_TRAP, "intrinsic_or_type_checked_inlining"); } public static final String INTRINSIC_TRAP = PREFIX + "INTRINSIC_TRAP" + POSTFIX; static { - trapNodes(INTRINSIC_TRAP,"intrinsic"); + trapNodes(INTRINSIC_TRAP, "intrinsic"); } // Is only supported on riscv64. @@ -1040,12 +1040,12 @@ public class IRNode { public static final String NULL_ASSERT_TRAP = PREFIX + "NULL_ASSERT_TRAP" + POSTFIX; static { - trapNodes(NULL_ASSERT_TRAP,"null_assert"); + trapNodes(NULL_ASSERT_TRAP, "null_assert"); } public static final String NULL_CHECK_TRAP = PREFIX + "NULL_CHECK_TRAP" + POSTFIX; static { - trapNodes(NULL_CHECK_TRAP,"null_check"); + trapNodes(NULL_CHECK_TRAP, "null_check"); } public static final String OR_VB = VECTOR_PREFIX + "OR_VB" + POSTFIX; @@ -1129,12 +1129,12 @@ public class IRNode { public static final String PREDICATE_TRAP = PREFIX + "PREDICATE_TRAP" + POSTFIX; static { - trapNodes(PREDICATE_TRAP,"predicate"); + trapNodes(PREDICATE_TRAP, "predicate"); } public static final String RANGE_CHECK_TRAP = PREFIX + "RANGE_CHECK_TRAP" + POSTFIX; static { - trapNodes(RANGE_CHECK_TRAP,"range_check"); + trapNodes(RANGE_CHECK_TRAP, "range_check"); } public static final String REPLICATE_B = VECTOR_PREFIX + "REPLICATE_B" + POSTFIX; @@ -1486,7 +1486,7 @@ public class IRNode { public static final String TRAP = PREFIX + "TRAP" + POSTFIX; static { - trapNodes(TRAP,"reason"); + trapNodes(TRAP, "reason"); } public static final String UDIV_I = PREFIX + "UDIV_I" + POSTFIX; @@ -1521,12 +1521,17 @@ public class IRNode { public static final String UNHANDLED_TRAP = PREFIX + "UNHANDLED_TRAP" + POSTFIX; static { - trapNodes(UNHANDLED_TRAP,"unhandled"); + trapNodes(UNHANDLED_TRAP, "unhandled"); } public static final String UNSTABLE_IF_TRAP = PREFIX + "UNSTABLE_IF_TRAP" + POSTFIX; static { - trapNodes(UNSTABLE_IF_TRAP,"unstable_if"); + trapNodes(UNSTABLE_IF_TRAP, "unstable_if"); + } + + public static final String UNREACHED_TRAP = PREFIX + "UNREACHED_TRAP" + POSTFIX; + static { + trapNodes(UNREACHED_TRAP, "unreached"); } public static final String URSHIFT = PREFIX + "URSHIFT" + POSTFIX; diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromSliceTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromSliceTest.java index ff73699bd30..390add8801c 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromSliceTest.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromSliceTest.java @@ -63,18 +63,17 @@ public class AllocFromSliceTest extends CLayouts { @Benchmark public MemorySegment alloc_confined() { - Arena arena = Arena.ofConfined(); - MemorySegment segment = arena.allocate(size); - MemorySegment.copy(arr, start, segment, C_CHAR, 0, size); - arena.close(); - return segment; + try (Arena arena = Arena.ofConfined()) { + MemorySegment segment = arena.allocate(size); + MemorySegment.copy(arr, start, segment, C_CHAR, 0, size); + return segment; + } } @Benchmark public MemorySegment alloc_confined_slice() { - Arena arena = Arena.ofConfined(); - MemorySegment segment = arena.allocateFrom(C_CHAR, MemorySegment.ofArray(arr), C_CHAR, start, size); - arena.close(); - return segment; + try (Arena arena = Arena.ofConfined()) { + return arena.allocateFrom(C_CHAR, MemorySegment.ofArray(arr), C_CHAR, start, size); + } } } diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromTest.java index a0c5d5be289..fcd79870ca0 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromTest.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromTest.java @@ -68,34 +68,30 @@ public class AllocFromTest extends CLayouts { @Benchmark public MemorySegment alloc_confined() { - Arena arena = Arena.ofConfined(); - MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); - arena.close(); - return segment; + try (Arena arena = Arena.ofConfined()) { + return arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); + } } @Benchmark public MemorySegment alloc_malloc_arena() { - MallocArena arena = new MallocArena(); - MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); - arena.close(); - return segment; + try (MallocArena arena = new MallocArena()) { + return arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); + } } @Benchmark public MemorySegment alloc_unsafe_arena() { - UnsafeArena arena = new UnsafeArena(); - MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); - arena.close(); - return segment; + try (UnsafeArena arena = new UnsafeArena()) { + return arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); + } } @Benchmark public MemorySegment alloc_pool_arena() { - Arena arena = pool.acquire(); - MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); - arena.close(); - return segment; + try (Arena arena = pool.acquire()) { + return arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); + } } static class SlicingPool { diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java index b9b5a3f30da..b7e4bfd5f57 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java @@ -65,26 +65,23 @@ public class AllocTest extends CLayouts { @Benchmark public MemorySegment alloc_confined() { - Arena arena = Arena.ofConfined(); - MemorySegment segment = arena.allocate(size); - arena.close(); - return segment; + try (Arena arena = Arena.ofConfined()) { + return arena.allocate(size); + } } @Benchmark public long alloc_calloc_arena() { - CallocArena arena = new CallocArena(); - MemorySegment segment = arena.allocate(size); - arena.close(); - return segment.address(); + try (CallocArena arena = new CallocArena()) { + return arena.allocate(size).address(); + } } @Benchmark public long alloc_unsafe_arena() { - UnsafeArena arena = new UnsafeArena(); - MemorySegment segment = arena.allocate(size); - arena.close(); - return segment.address(); + try (UnsafeArena arena = new UnsafeArena()) { + return arena.allocate(size).address(); + } } public static class CallocArena implements Arena { diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/ResourceScopeCloseMin.java b/test/micro/org/openjdk/bench/java/lang/foreign/ResourceScopeCloseMin.java new file mode 100644 index 00000000000..51c6d72b3e0 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/ResourceScopeCloseMin.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.java.lang.foreign; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@State(org.openjdk.jmh.annotations.Scope.Thread) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@Fork(value = 3) +public class ResourceScopeCloseMin { + + Runnable dummy = () -> {}; + + static class ConfinedScope { + final Thread owner; + boolean closed; + final List resources = new ArrayList<>(); + + private void checkState() { + if (closed) { + throw new AssertionError("Closed"); + } else if (owner != Thread.currentThread()) { + throw new AssertionError("Wrong thread"); + } + } + + ConfinedScope() { + this.owner = Thread.currentThread(); + } + + void addCloseAction(Runnable runnable) { + checkState(); + resources.add(runnable); + } + + public void close() { + checkState(); + closed = true; + for (Runnable r : resources) { + r.run(); + } + } + } + + @Benchmark + public void confined_close() { + ConfinedScope scope = new ConfinedScope(); + try { // simulate TWR + scope.addCloseAction(dummy); + scope.close(); + } catch (RuntimeException ex) { + scope.close(); + throw ex; + } + } + + @Benchmark + public void confined_close_notry() { + ConfinedScope scope = new ConfinedScope(); + scope.addCloseAction(dummy); + scope.close(); + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java index 83751d07eda..d50b8d0900f 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java @@ -90,7 +90,7 @@ public class StrLenTest extends CLayouts { } @Benchmark - public int panama_strlen() throws Throwable { + public int panama_strlen_alloc() throws Throwable { try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocateFrom(str); return (int)STRLEN.invokeExact(segment); @@ -104,10 +104,9 @@ public class StrLenTest extends CLayouts { @Benchmark public int panama_strlen_pool() throws Throwable { - Arena arena = pool.acquire(); - int l = (int) STRLEN.invokeExact(arena.allocateFrom(str)); - arena.close(); - return l; + try (Arena arena = pool.acquire()) { + return (int) STRLEN.invokeExact(arena.allocateFrom(str)); + } } @Benchmark diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java index 7194dec9b23..8ed0f10e0be 100644 --- a/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java @@ -82,10 +82,9 @@ public class ToCStringTest extends CLayouts { @Benchmark public MemorySegment panama_writeString() throws Throwable { - Arena arena = Arena.ofConfined(); - MemorySegment segment = arena.allocateFrom(str); - arena.close(); - return segment; + try (Arena arena = Arena.ofConfined()) { + return arena.allocateFrom(str); + } } static native long writeString(String str);