diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp index 980fedb406d..22c2383816c 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp @@ -1174,7 +1174,7 @@ void InterpreterMacroAssembler::notify_method_exit( // Whenever JVMTI is interp_only_mode, method entry/exit events are sent to // track stack depth. If it is possible to enter interp_only_mode we add // the code to check if the event should be sent. - if (mode == NotifyJVMTI && JvmtiExport::can_post_interpreter_events()) { + if (mode == NotifyJVMTI && (JvmtiExport::can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) { Label L; // Note: frame::interpreter_frame_result has a dependency on how the // method result is saved across the call to post_method_exit. If this @@ -1183,8 +1183,15 @@ void InterpreterMacroAssembler::notify_method_exit( // template interpreter will leave the result on the top of the stack. push(state); - ldrw(r3, Address(rthread, JavaThread::interp_only_mode_offset())); - cbz(r3, L); + + ldr(rscratch1, Address(rthread, JavaThread::jvmti_thread_state_offset())); + cbz(rscratch1, L); // if (thread->jvmti_thread_state() == nullptr) exit; + + ldrw(rscratch1, Address(rscratch1, JvmtiThreadState::frame_pop_cnt_offset())); + ldrw(rscratch2, Address(rthread, JavaThread::interp_only_mode_offset())); + orrw(rscratch1, rscratch1, rscratch2); + cbzw(rscratch1, L); + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::post_method_exit)); bind(L); diff --git a/src/hotspot/cpu/arm/interp_masm_arm.cpp b/src/hotspot/cpu/arm/interp_masm_arm.cpp index aee407864ee..89f7791626d 100644 --- a/src/hotspot/cpu/arm/interp_masm_arm.cpp +++ b/src/hotspot/cpu/arm/interp_masm_arm.cpp @@ -1576,14 +1576,21 @@ void InterpreterMacroAssembler::notify_method_exit( // Whenever JVMTI is interp_only_mode, method entry/exit events are sent to // track stack depth. If it is possible to enter interp_only_mode we add // the code to check if the event should be sent. - if (mode == NotifyJVMTI && can_post_interpreter_events()) { + if (mode == NotifyJVMTI && (can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) { Label L; + const Register thread_state = R2_tmp; + // Note: frame::interpreter_frame_result has a dependency on how the // method result is saved across the call to post_method_exit. If this // is changed then the interpreter_frame_result implementation will // need to be updated too. + ldr(thread_state, Address(Rthread, JavaThread::jvmti_thread_state_offset())); + cbz(thread_state, L); // if (thread->jvmti_thread_state() == nullptr) exit; + + ldr_s32(thread_state, Address(thread_state, JvmtiThreadState::frame_pop_cnt_offset())); ldr_s32(Rtemp, Address(Rthread, JavaThread::interp_only_mode_offset())); + orr(Rtemp, Rtemp, thread_state); cbz(Rtemp, L); if (native) { diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp index 56eade8e533..82471035138 100644 --- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp @@ -2345,18 +2345,12 @@ void InterpreterMacroAssembler::notify_method_exit(bool is_native_method, TosSta // entry/exit events are sent for that thread to track stack // depth. If it is possible to enter interp_only_mode we add // the code to check if the event should be sent. - if (mode == NotifyJVMTI && JvmtiExport::can_post_interpreter_events()) { - Label jvmti_post_done; - - lwz(R0, in_bytes(JavaThread::interp_only_mode_offset()), R16_thread); - cmpwi(CR0, R0, 0); - beq(CR0, jvmti_post_done); + if (mode == NotifyJVMTI && (JvmtiExport::can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) { if (!is_native_method) { push(state); } // Expose tos to GC. call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::post_method_exit), check_exceptions); if (!is_native_method) { pop(state); } align(32, 12); - bind(jvmti_post_done); } // Dtrace support not implemented. diff --git a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp index 804c2072ba5..443f3e3d17f 100644 --- a/src/hotspot/cpu/riscv/interp_masm_riscv.cpp +++ b/src/hotspot/cpu/riscv/interp_masm_riscv.cpp @@ -1220,8 +1220,7 @@ void InterpreterMacroAssembler::notify_method_exit( // Whenever JVMTI is interp_only_mode, method entry/exit events are sent to // track stack depth. If it is possible to enter interp_only_mode we add // the code to check if the event should be sent. - if (mode == NotifyJVMTI && JvmtiExport::can_post_interpreter_events()) { - Label L; + if (mode == NotifyJVMTI && (JvmtiExport::can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) { // Note: frame::interpreter_frame_result has a dependency on how the // method result is saved across the call to post_method_exit. If this // is changed then the interpreter_frame_result implementation will @@ -1229,11 +1228,8 @@ void InterpreterMacroAssembler::notify_method_exit( // template interpreter will leave the result on the top of the stack. push(state); - lwu(x13, Address(xthread, JavaThread::interp_only_mode_offset())); - beqz(x13, L); call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::post_method_exit)); - bind(L); pop(state); } diff --git a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp index 7327e2a13f2..03c90a499fb 100644 --- a/src/hotspot/cpu/s390/interp_masm_s390.cpp +++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp @@ -2002,14 +2002,10 @@ void InterpreterMacroAssembler::notify_method_exit(bool native_method, // entry/exit events are sent for that thread to track stack // depth. If it is possible to enter interp_only_mode we add // the code to check if the event should be sent. - if (mode == NotifyJVMTI && JvmtiExport::can_post_interpreter_events()) { - Label jvmti_post_done; - MacroAssembler::load_and_test_int(Z_R0, Address(Z_thread, JavaThread::interp_only_mode_offset())); - z_bre(jvmti_post_done); + if (mode == NotifyJVMTI && (JvmtiExport::can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) { if (!native_method) push(state); // see frame::interpreter_frame_result() call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::post_method_exit)); if (!native_method) pop(state); - bind(jvmti_post_done); } } diff --git a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp index a38971c86fb..a9745398f71 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.cpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp @@ -1603,7 +1603,7 @@ void InterpreterMacroAssembler::notify_method_exit( // the code to check if the event should be sent. Register rthread = r15_thread; Register rarg = c_rarg1; - if (mode == NotifyJVMTI && JvmtiExport::can_post_interpreter_events()) { + if (mode == NotifyJVMTI && (JvmtiExport::can_post_interpreter_events() || JvmtiExport::can_post_frame_pop())) { Label L; // Note: frame::interpreter_frame_result has a dependency on how the // method result is saved across the call to post_method_exit. If this @@ -1612,9 +1612,18 @@ void InterpreterMacroAssembler::notify_method_exit( // template interpreter will leave the result on the top of the stack. push(state); - movl(rdx, Address(rthread, JavaThread::interp_only_mode_offset())); - testl(rdx, rdx); + + movptr(rdx, Address(rthread, JavaThread::jvmti_thread_state_offset())); + testptr(rdx, rdx); + jcc(Assembler::zero, L); // if (thread->jvmti_thread_state() == nullptr) exit; + + movl(rdx, Address(rdx, JvmtiThreadState::frame_pop_cnt_offset())); + movl(rcx, Address(rthread, JavaThread::interp_only_mode_offset())); + + orl(rdx, rcx); + testl(rdx,rdx); jcc(Assembler::zero, L); + call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::post_method_exit)); bind(L); diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index 0e7ddb0f317..a8f3d501083 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -1057,6 +1057,10 @@ JRT_ENTRY(nmethod*, LastFrameAccessor last_frame(current); assert(last_frame.is_interpreted_frame(), "must come from interpreter"); + + if (JvmtiExport::can_post_frame_pop() && JvmtiExport::has_frame_pop_for_top_frame(current)) { + return nullptr; // no OSR if there is a FramePop event request for top frame + } methodHandle method(current, last_frame.method()); const int branch_bci = branch_bcp != nullptr ? method->bci_from(branch_bcp) : InvocationEntryBci; const int bci = branch_bcp != nullptr ? method->bci_from(last_frame.bcp()) : InvocationEntryBci; diff --git a/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp b/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp index 74e7b9eec34..a25cd936cea 100644 --- a/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp +++ b/src/hotspot/share/interpreter/zero/bytecodeInterpreter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -3145,10 +3145,14 @@ run: // Whenever JVMTI puts a thread in interp_only_mode, method // entry/exit events are sent for that thread to track stack depth. - if (JVMTI_ENABLED && !suppress_exit_event && THREAD->is_interp_only_mode()) { - // Prevent any HandleMarkCleaner from freeing our live handles - HandleMark __hm(THREAD); - CALL_VM_NOCHECK(InterpreterRuntime::post_method_exit(THREAD)); + if (JVMTI_ENABLED && !suppress_exit_event) { + JvmtiThreadState* state = THREAD->jvmti_thread_state(); + int frame_pop_cnt = state == nullptr ? 0 : state->frame_pop_cnt(); + if (THREAD->is_interp_only_mode() || frame_pop_cnt) { + // Prevent any HandleMarkCleaner from freeing our live handles + HandleMark __hm(THREAD); + CALL_VM_NOCHECK(InterpreterRuntime::post_method_exit(THREAD)); + } } // diff --git a/src/hotspot/share/oops/stackChunkOop.hpp b/src/hotspot/share/oops/stackChunkOop.hpp index 55ed9ee237c..dcc752cd92d 100644 --- a/src/hotspot/share/oops/stackChunkOop.hpp +++ b/src/hotspot/share/oops/stackChunkOop.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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -154,6 +154,7 @@ public: inline void set_has_bitmap(bool value); inline bool has_thaw_slowpath_condition() const; + inline void force_slow_path(); inline bool requires_barriers(); diff --git a/src/hotspot/share/oops/stackChunkOop.inline.hpp b/src/hotspot/share/oops/stackChunkOop.inline.hpp index 4fe5b913ade..d0ddbe8dfe6 100644 --- a/src/hotspot/share/oops/stackChunkOop.inline.hpp +++ b/src/hotspot/share/oops/stackChunkOop.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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -192,6 +192,15 @@ inline void stackChunkOopDesc::set_has_bitmap(bool value) { set_flag(FL inline bool stackChunkOopDesc::has_thaw_slowpath_condition() const { return flags() != 0; } +inline void stackChunkOopDesc::force_slow_path() { +#if INCLUDE_ZGC || INCLUDE_SHENANDOAHGC + if (UseZGC || UseShenandoahGC) { + relativize_derived_pointers_concurrently(); + } +#endif + set_flag(FLAG_HAS_INTERPRETED_FRAMES, true); +} + inline bool stackChunkOopDesc::requires_barriers() { return Universe::heap()->requires_barriers(this); } diff --git a/src/hotspot/share/prims/jvmtiEnvBase.cpp b/src/hotspot/share/prims/jvmtiEnvBase.cpp index 886c2da1dee..9fff32f8e78 100644 --- a/src/hotspot/share/prims/jvmtiEnvBase.cpp +++ b/src/hotspot/share/prims/jvmtiEnvBase.cpp @@ -1363,6 +1363,27 @@ JvmtiEnvBase::set_frame_pop(JvmtiThreadState* state, javaVFrame* jvf, jint depth if (ets->is_frame_pop(frame_number)) { return JVMTI_ERROR_DUPLICATE; } + JavaThread* thread = state->get_thread(); + frame fr = jvf->fr(); + + if (jvf->is_compiled_frame()) { + if (!fr.can_be_deoptimized()) { + return JVMTI_ERROR_OPAQUE_FRAME; + } + + if (state->is_virtual() && (thread == nullptr || !thread->is_vthread_mounted())) { // unmounted virtual thread + assert(fr.is_heap_frame(), "sanity check"); + fr = jvf->stack_chunk()->derelativize(fr); + jvf->stack_chunk()->force_slow_path(); + fr.deoptimize(nullptr); + } else { // platform thread or mounted virtual thread + if (fr.is_heap_frame()) { + fr = jvf->stack_chunk()->derelativize(fr); + jvf->stack_chunk()->force_slow_path(); + } + Deoptimization::deoptimize(thread, fr); + } + } ets->set_frame_pop(frame_number); return JVMTI_ERROR_NONE; } @@ -2514,6 +2535,8 @@ SetOrClearFramePopClosure::do_vthread(Handle target_h) { _result = _env->clear_all_frame_pops(_state); return; } + assert(_state->get_thread() == _target_jt, "sanity check"); + javaVFrame *jvf = JvmtiEnvBase::get_vthread_jvf(target_h()); _result = _env->set_frame_pop(_state, jvf, _depth); } diff --git a/src/hotspot/share/prims/jvmtiEnvThreadState.cpp b/src/hotspot/share/prims/jvmtiEnvThreadState.cpp index 303923076b1..c2a203e6b1b 100644 --- a/src/hotspot/share/prims/jvmtiEnvThreadState.cpp +++ b/src/hotspot/share/prims/jvmtiEnvThreadState.cpp @@ -229,6 +229,7 @@ void JvmtiEnvThreadState::set_frame_pop(int frame_number) { "frame pop data only accessible from same or detached thread or direct handshake"); JvmtiFramePop fpop(frame_number); JvmtiEventController::set_frame_pop(this, fpop); + _state->incr_frame_pop_cnt(); } @@ -237,18 +238,21 @@ void JvmtiEnvThreadState::clear_frame_pop(int frame_number) { "frame pop data only accessible from same or detached thread or direct handshake"); JvmtiFramePop fpop(frame_number); JvmtiEventController::clear_frame_pop(this, fpop); + _state->decr_frame_pop_cnt(); } void JvmtiEnvThreadState::clear_all_frame_pops() { assert(get_thread() == nullptr || get_thread()->is_handshake_safe_for(Thread::current()), "frame pop data only accessible from same or detached thread or direct handshake"); + int frame_pop_delta = _frame_pops == nullptr ? 0 : _frame_pops->length(); + _state->decr_frame_pop_cnt(frame_pop_delta); JvmtiEventController::clear_all_frame_pops(this); } bool JvmtiEnvThreadState::is_frame_pop(int cur_frame_number) { assert(get_thread() == nullptr || get_thread()->is_handshake_safe_for(Thread::current()), "frame pop data only accessible from same or detached thread or direct handshake"); - if (!jvmti_thread_state()->is_interp_only_mode() || _frame_pops == nullptr) { + if (_frame_pops == nullptr) { return false; } JvmtiFramePop fp(cur_frame_number); diff --git a/src/hotspot/share/prims/jvmtiEventController.cpp b/src/hotspot/share/prims/jvmtiEventController.cpp index 832c8a33c88..a0cec5ddc0e 100644 --- a/src/hotspot/share/prims/jvmtiEventController.cpp +++ b/src/hotspot/share/prims/jvmtiEventController.cpp @@ -102,9 +102,9 @@ static const jlong MONITOR_BITS = MONITOR_CONTENDED_ENTER_BIT | MONITOR_CONTEND MONITOR_WAIT_BIT | MONITOR_WAITED_BIT; static const jlong EXCEPTION_BITS = EXCEPTION_THROW_BIT | EXCEPTION_CATCH_BIT; static const jlong INTERP_EVENT_BITS = SINGLE_STEP_BIT | METHOD_ENTRY_BIT | METHOD_EXIT_BIT | - FRAME_POP_BIT | FIELD_ACCESS_BIT | FIELD_MODIFICATION_BIT; + FIELD_ACCESS_BIT | FIELD_MODIFICATION_BIT; static const jlong THREAD_FILTERED_EVENT_BITS = INTERP_EVENT_BITS | EXCEPTION_BITS | MONITOR_BITS | VTHREAD_FILTERED_EVENT_BITS | - BREAKPOINT_BIT | CLASS_LOAD_BIT | CLASS_PREPARE_BIT | THREAD_END_BIT | + BREAKPOINT_BIT | FRAME_POP_BIT | CLASS_LOAD_BIT | CLASS_PREPARE_BIT | THREAD_END_BIT | SAMPLED_OBJECT_ALLOC_BIT; static const jlong NEED_THREAD_LIFE_EVENTS = THREAD_FILTERED_EVENT_BITS | THREAD_START_BIT | VTHREAD_START_BIT; static const jlong EARLY_EVENT_BITS = CLASS_FILE_LOAD_HOOK_BIT | CLASS_LOAD_BIT | CLASS_PREPARE_BIT | @@ -592,10 +592,6 @@ JvmtiEventControllerPrivate::recompute_thread_enabled(JvmtiThreadState *state) { } julong was_any_env_enabled = state->thread_event_enable()->_event_enabled.get_bits(); julong any_env_enabled = 0; - // JVMTI_EVENT_FRAME_POP can be disabled (in the case FRAME_POP_BIT is not set), - // but we need to set interp_only if some JvmtiEnvThreadState has frame pop set - // to clear the request - bool has_frame_pops = false; { // This iteration will include JvmtiEnvThreadStates whose environments @@ -604,7 +600,6 @@ JvmtiEventControllerPrivate::recompute_thread_enabled(JvmtiThreadState *state) { JvmtiEnvThreadStateIterator it(state); for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) { any_env_enabled |= recompute_env_thread_enabled(ets, state); - has_frame_pops |= ets->has_frame_pops(); } } @@ -620,7 +615,7 @@ JvmtiEventControllerPrivate::recompute_thread_enabled(JvmtiThreadState *state) { } } // compute interp_only mode - bool should_be_interp = (any_env_enabled & INTERP_EVENT_BITS) != 0 || has_frame_pops; + bool should_be_interp = (any_env_enabled & INTERP_EVENT_BITS) != 0; bool is_now_interp = state->is_interp_only_mode() || state->is_pending_interp_only_mode(); if (should_be_interp != is_now_interp) { diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index 98d8cfd990d..963c5497b13 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -1362,6 +1362,26 @@ JvmtiThreadState* JvmtiExport::hide_single_stepping(JavaThread *thread) { } } +bool JvmtiExport::has_frame_pop_for_top_frame(JavaThread *current) { + assert(current == JavaThread::current(), "must be"); + JvmtiThreadState *state = current->jvmti_thread_state(); + if (state == nullptr || !state->is_enabled(JVMTI_EVENT_FRAME_POP)) { + return false; + } + if (state->frame_pop_cnt() == 0) { + return false; + } + JvmtiEnvThreadStateIterator it(state); + int top_frame_num = state->count_frames(); + for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) { + if (ets->has_frame_pops() && ets->is_frame_pop(top_frame_num)) { + assert(ets->is_enabled(JVMTI_EVENT_FRAME_POP), "sanity check"); + return true; + } + } + return false; +} + void JvmtiExport::post_class_load(JavaThread *thread, Klass* klass) { if (JvmtiEnv::get_phase() < JVMTI_PHASE_PRIMORDIAL) { return; @@ -1898,12 +1918,13 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur } JvmtiThreadState* state; // should be initialized in vm state only JavaThread* current = thread; // for JRT_BLOCK - bool interp_only; // might be changed in JRT_BLOCK_END + JRT_BLOCK - state = get_jvmti_thread_state(thread); - interp_only = state != nullptr && state->is_interp_only_mode(); - if (interp_only) { - if (state->is_enabled(JVMTI_EVENT_METHOD_EXIT)) { + bool interp_only = thread->is_interp_only_mode(); + // Avoid calls to get_jvmti_thread_state if is_interp_only_mode was not enabled. + state = interp_only ? get_jvmti_thread_state(thread) : thread->jvmti_thread_state(); + if (state != nullptr) { + if (interp_only && state->is_enabled(JVMTI_EVENT_METHOD_EXIT)) { // Deferred saving Object result into value. if (is_reference_type(type)) { value.l = JNIHandles::make_local(thread, result()); @@ -1917,7 +1938,7 @@ void JvmtiExport::post_method_exit(JavaThread* thread, Method* method, frame cur post_method_exit_inner(thread, mh, state, false /* not exception exit */, current_frame, value); } JRT_BLOCK_END - if (interp_only) { + if (state != nullptr) { // The JRT_BLOCK_END can safepoint in ThreadInVMfromJava destructor. Now it is safe to allow // adding FramePop event requests as no safepoint can happen before removing activation. state->clr_top_frame_is_exiting(); @@ -1943,7 +1964,8 @@ void JvmtiExport::post_method_exit_inner(JavaThread* thread, (mh() == nullptr) ? "null" : mh()->klass_name()->as_C_string(), (mh() == nullptr) ? "null" : mh()->name()->as_C_string() )); - if (state->is_enabled(JVMTI_EVENT_METHOD_EXIT)) { + // Need to check is_interp_only_mode to consistently post method exit event for all frames. + if (thread->is_interp_only_mode() && state->is_enabled(JVMTI_EVENT_METHOD_EXIT)) { JvmtiEnvThreadStateIterator it(state); for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) { if (ets->is_enabled(JVMTI_EVENT_METHOD_EXIT)) { @@ -2154,21 +2176,14 @@ void JvmtiExport::notice_unwind_due_to_exception(JavaThread *thread, Method* met if (state->is_exception_detected()) { + // The cached cur_stack_depth might have changed from the operations of frame pop or method exit. + // We are not 100% sure the cached cur_stack_depth is still valid depth so invalidate it. state->invalidate_cur_stack_depth(); if (!in_handler_frame) { // Not in exception handler. - if(state->is_interp_only_mode()) { - // method exit and frame pop events are posted only in interp mode. - // When these events are enabled code should be in running in interp mode. - jvalue no_value; - no_value.j = 0L; - JvmtiExport::post_method_exit_inner(thread, mh, state, true, thread->last_frame(), no_value); - // The cached cur_stack_depth might have changed from the - // operations of frame pop or method exit. We are not 100% sure - // the cached cur_stack_depth is still valid depth so invalidate - // it. - state->invalidate_cur_stack_depth(); - } + jvalue no_value; + no_value.j = 0L; + JvmtiExport::post_method_exit_inner(thread, mh, state, true, thread->last_frame(), no_value); } else { // In exception handler frame. Report exception catch. assert(location != nullptr, "must be a known location"); diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp index f6c9f3a74d5..086667ae72d 100644 --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -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 @@ -320,6 +320,9 @@ class JvmtiExport : public AllStatic { static void expose_single_stepping(JvmtiThreadState* state) NOT_JVMTI_RETURN; static JvmtiThreadState* hide_single_stepping(JavaThread *thread) NOT_JVMTI_RETURN_(nullptr); + // frame pop management + static bool has_frame_pop_for_top_frame(JavaThread *current); + // Methods that notify the debugger that something interesting has happened in the VM. static void post_early_vm_start () NOT_JVMTI_RETURN; static void post_vm_start () NOT_JVMTI_RETURN; diff --git a/src/hotspot/share/prims/jvmtiImpl.hpp b/src/hotspot/share/prims/jvmtiImpl.hpp index 0fd63556a78..53e0ede476f 100644 --- a/src/hotspot/share/prims/jvmtiImpl.hpp +++ b/src/hotspot/share/prims/jvmtiImpl.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,9 @@ #include "runtime/vmOperations.hpp" #include "utilities/ostream.hpp" +// Forward Declaration + +class JvmtiEnv; /////////////////////////////////////////////////////////////// // diff --git a/src/hotspot/share/prims/jvmtiManageCapabilities.cpp b/src/hotspot/share/prims/jvmtiManageCapabilities.cpp index 29502bb8cd5..b18e58ca1e5 100644 --- a/src/hotspot/share/prims/jvmtiManageCapabilities.cpp +++ b/src/hotspot/share/prims/jvmtiManageCapabilities.cpp @@ -327,7 +327,6 @@ void JvmtiManageCapabilities::update() { avail.can_generate_field_access_events || avail.can_generate_field_modification_events || avail.can_generate_single_step_events || - avail.can_generate_frame_pop_events || avail.can_generate_method_entry_events || avail.can_generate_method_exit_events; #ifdef ZERO diff --git a/src/hotspot/share/prims/jvmtiThreadState.cpp b/src/hotspot/share/prims/jvmtiThreadState.cpp index 32bf2c4e98e..5f496bbc6d4 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.cpp +++ b/src/hotspot/share/prims/jvmtiThreadState.cpp @@ -30,6 +30,7 @@ #include "prims/jvmtiEventController.inline.hpp" #include "prims/jvmtiImpl.hpp" #include "prims/jvmtiThreadState.inline.hpp" +#include "runtime/deoptimization.hpp" #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/jniHandles.inline.hpp" @@ -61,6 +62,7 @@ JvmtiThreadState::JvmtiThreadState(JavaThread* thread, oop thread_oop) _hide_single_stepping = false; _pending_interp_only_mode = false; _hide_level = 0; + _frame_pop_cnt = 0; _pending_step_for_popframe = false; _class_being_redefined = nullptr; _class_load_kind = jvmti_class_load_kind_load; @@ -470,21 +472,19 @@ void JvmtiThreadState::process_pending_step_for_popframe() { // Called by: PopFrame // void JvmtiThreadState::update_for_pop_top_frame() { - if (is_interp_only_mode()) { - // remove any frame pop notification request for the top frame - // in any environment - int popframe_number = cur_stack_depth(); - { - JvmtiEnvThreadStateIterator it(this); - for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) { - if (ets->is_frame_pop(popframe_number)) { - ets->clear_frame_pop(popframe_number); - } + // remove any frame pop notification request for the top frame + // in any environment + int popframe_number = cur_stack_depth(); + { + JvmtiEnvThreadStateIterator it(this); + for (JvmtiEnvThreadState* ets = it.first(); ets != nullptr; ets = it.next(ets)) { + if (ets->is_frame_pop(popframe_number)) { + ets->clear_frame_pop(popframe_number); } } - // force stack depth to be recalculated - invalidate_cur_stack_depth(); } + // force stack depth to be recalculated + invalidate_cur_stack_depth(); } diff --git a/src/hotspot/share/prims/jvmtiThreadState.hpp b/src/hotspot/share/prims/jvmtiThreadState.hpp index fa77518a8b6..6555303278b 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.hpp @@ -29,7 +29,7 @@ #include "memory/allocation.hpp" #include "oops/instanceKlass.hpp" #include "oops/oopHandle.hpp" -#include "prims/jvmtiEventController.hpp" +#include "prims/jvmtiEventController.inline.hpp" #include "prims/jvmtiExport.hpp" #include "runtime/javaThread.hpp" #include "runtime/mutexLocker.hpp" @@ -139,6 +139,7 @@ class JvmtiThreadState : public CHeapObj { bool _top_frame_is_exiting; bool _saved_interp_only_mode; int _hide_level; + volatile int _frame_pop_cnt; public: enum ExceptionState { @@ -241,6 +242,29 @@ class JvmtiThreadState : public CHeapObj { return _next; } + // Optimizations for FramePop support. + static ByteSize frame_pop_cnt_offset() { return byte_offset_of(JvmtiThreadState, _frame_pop_cnt); } + + int frame_pop_cnt() { return AtomicAccess::load(&_frame_pop_cnt); } + + void incr_frame_pop_cnt() { + assert(Threads::number_of_threads() == 0 || JvmtiThreadState_lock->is_locked(), "sanity check"); + AtomicAccess::inc(&_frame_pop_cnt); + assert(_frame_pop_cnt > 0, "Unexpected count: %d", _frame_pop_cnt); + } + + void decr_frame_pop_cnt() { + assert(Threads::number_of_threads() == 0 || JvmtiThreadState_lock->is_locked(), "sanity check"); + AtomicAccess::dec(&_frame_pop_cnt); + assert(_frame_pop_cnt >= 0, "Unexpected count: %d", _frame_pop_cnt); + } + + void decr_frame_pop_cnt(int delta) { + assert(Threads::number_of_threads() == 0 || JvmtiThreadState_lock->is_locked(), "sanity check"); + AtomicAccess::store(&_frame_pop_cnt, AtomicAccess::load(&_frame_pop_cnt) - delta); + assert(_frame_pop_cnt >= 0, "Unexpected count: %d", _frame_pop_cnt); + } + // Current stack depth is only valid when is_interp_only_mode() returns true. // These functions should only be called at a safepoint - usually called from same thread. // Returns the number of Java activations on the stack. diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index a87ee974fdd..a1c33f4fda0 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -366,17 +366,23 @@ void frame::deoptimize(JavaThread* thread) { #ifdef ASSERT if (thread != nullptr) { - frame check = thread->last_frame(); - if (is_older(check.id())) { - RegisterMap map(thread, - RegisterMap::UpdateMap::skip, - RegisterMap::ProcessFrames::include, - RegisterMap::WalkContinuation::skip); - while (id() != check.id()) { - check = check.sender(&map); + frame fr = thread->last_frame(); + RegisterMap map(thread, + RegisterMap::UpdateMap::skip, + RegisterMap::ProcessFrames::include, + !is_heap_frame() ? RegisterMap::WalkContinuation::skip : RegisterMap::WalkContinuation::include); + intptr_t* fr_id = fr.id(); + while (id() != fr_id) { + fr = fr.sender(&map); + if (fr.is_heap_frame()) { + assert(is_heap_frame(), ""); + frame derel_fr = map.stack_chunk()->derelativize(fr); + fr_id = derel_fr.id(); + } else { + fr_id = fr.id(); } - assert(check.is_deoptimized_frame(), "missed deopt"); } + assert(fr.is_deoptimized_frame(), "missed deopt"); } #endif // ASSERT } diff --git a/test/hotspot/jtreg/serviceability/jvmti/NotifyFramePop/NotifyFramePopTest.java b/test/hotspot/jtreg/serviceability/jvmti/NotifyFramePop/NotifyFramePopTest.java index c8d0773c7e1..7b9c37c3d2c 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/NotifyFramePop/NotifyFramePopTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/NotifyFramePop/NotifyFramePopTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 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 @@ -50,7 +50,7 @@ public class NotifyFramePopTest { } // Sanity testing that FRAME_POP works. - test("sanity", true, () -> { + test("sanity", 2, () -> { // FramePop events expected: 2 setFramePopNotificationMode(true); notifyFramePop(null); }); @@ -58,14 +58,14 @@ public class NotifyFramePopTest { // Request notification and then disable FRAME_POP event notification. // This should not prevent the notification for the frame being cleared // when we return from the method. - test("requestAndDisable", false, () -> { + test("requestAndDisable", 0, () -> { // FramePop events expected: 0 setFramePopNotificationMode(true); notifyFramePop(null); setFramePopNotificationMode(false); }); // Ensure there is no pending event - test("ensureCleared", false, () -> { + test("ensureCleared", 0, () -> { // FramePop events expected: 0 setFramePopNotificationMode(true); }); @@ -75,7 +75,7 @@ public class NotifyFramePopTest { private native static boolean canGenerateFramePopEvents(); private native static void setFramePopNotificationMode(boolean enabled); private native static void notifyFramePop(Thread thread); - private native static boolean framePopReceived(); + private native static int framePopsReceived(); private static void log(String msg) { System.out.println(msg); @@ -85,16 +85,16 @@ public class NotifyFramePopTest { void test(); } - private static void test(String name, boolean framePopExpected, Test theTest) { + private static void test(String name, int framePopExpected, Test theTest) { log("test: " + name); theTest.test(); - boolean actual = framePopReceived(); + int actual = framePopsReceived(); if (framePopExpected != actual) { throw new RuntimeException("unexpected notification:" - + " FramePop expected: " + (framePopExpected ? "yes" : "no") - + ", actually received: " + (actual ? "yes" : "no")); + + " FramePop expected: " + framePopExpected + + ", actually received: " + actual); } - log(" - OK (" + (actual ? "received" : "NOT received") + ")"); + log(" - OK (received: " + actual + ")"); } } diff --git a/test/hotspot/jtreg/serviceability/jvmti/NotifyFramePop/libNotifyFramePopTest.c b/test/hotspot/jtreg/serviceability/jvmti/NotifyFramePop/libNotifyFramePopTest.c index 8553f6632c3..507a47d7e1b 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/NotifyFramePop/libNotifyFramePopTest.c +++ b/test/hotspot/jtreg/serviceability/jvmti/NotifyFramePop/libNotifyFramePopTest.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 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 @@ -44,7 +44,7 @@ extern "C" { static jvmtiEnv *jvmti = NULL; static jvmtiCapabilities caps; static jvmtiEventCallbacks callbacks; -static jboolean framePopReceived = JNI_FALSE; +static jint framePopsReceived = 0; static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved); @@ -80,7 +80,7 @@ FramePop(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thread, char* name = NULL; char* sign = NULL; - framePopReceived = JNI_TRUE; + ++framePopsReceived; err = (*jvmti_env)->GetMethodDeclaringClass(jvmti_env, method, &cls); if (err != JVMTI_ERROR_NONE) { @@ -155,16 +155,21 @@ Java_NotifyFramePopTest_setFramePopNotificationMode(JNIEnv *env, jclass cl, jboo JNIEXPORT void JNICALL Java_NotifyFramePopTest_notifyFramePop(JNIEnv *env, jclass cls, jthread thread) { - jvmtiError err= (*jvmti)->NotifyFramePop(jvmti, thread, 1); + // Request two FramePop events at depth 1 and 2. + jvmtiError err = (*jvmti)->NotifyFramePop(jvmti, thread, 1); if (err != JVMTI_ERROR_NONE) { - reportError("NotifyFramePop failed", err); + reportError("NotifyFramePop at depth 1 failed", err); + } + err = (*jvmti)->NotifyFramePop(jvmti, thread, 2); + if (err != JVMTI_ERROR_NONE) { + reportError("NotifyFramePop at depth 2 failed", err); } } -JNIEXPORT jboolean JNICALL -Java_NotifyFramePopTest_framePopReceived(JNIEnv *env, jclass cls) { - jboolean result = framePopReceived; - framePopReceived = JNI_FALSE; +JNIEXPORT jint JNICALL +Java_NotifyFramePopTest_framePopsReceived(JNIEnv *env, jclass cls) { + jint result = framePopsReceived; + framePopsReceived = 0; return result; } diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/NotifyFramePopStressTest/libNotifyFramePopStressTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/NotifyFramePopStressTest/libNotifyFramePopStressTest.cpp index e4206c2766a..40ca33bbb47 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/NotifyFramePopStressTest/libNotifyFramePopStressTest.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/NotifyFramePopStressTest/libNotifyFramePopStressTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -156,7 +156,9 @@ Java_NotifyFramePopStressTest_notifyFramePop(JNIEnv *jni, jclass cls, jthread th return JNI_FALSE; } check_jvmti_status(jni, err, "notifyFramePop: Failed in JVMTI notifyFramePop"); - LOG("\nNotifyFramePop called for method %s\n", name); + + LOG("\nnotifyFramePop: requested FramePop event for frame with method: %s\n", name); + print_stack_trace(jvmti, jni, thread); if (isMain) { LOG("notifyFramePop not counting main method\n"); diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadStateTest/ThreadStateTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadStateTest/ThreadStateTest.java index c6a7debed1a..1e560c7c2cc 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadStateTest/ThreadStateTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadStateTest/ThreadStateTest.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 @@ -44,6 +44,7 @@ public class ThreadStateTest { private static native void setSingleSteppingMode(boolean enable); private static native void setMonitorContendedMode(boolean enable); + private static native void setFramePopEvent(Thread thread); private static native void testGetThreadState(Thread thread); private static native void testGetThreadListStackTraces(Thread thread); @@ -51,6 +52,10 @@ public class ThreadStateTest { testGetThreadState(Thread.currentThread()); testGetThreadListStackTraces(Thread.currentThread()); Thread.yield(); + for (int i = 0; i < 10; i++) { + testGetThreadListStackTraces(Thread.currentThread()); + Thread.yield(); + } }; private void runTest() throws Exception { @@ -75,7 +80,13 @@ public class ThreadStateTest { } // Give some time for vthreads to finish. - Thread.sleep(10); + Thread.sleep(50); + + for (Thread t : virtualThreads) { + if (tryCount % 4 == 0) { + setFramePopEvent(t); + } + } // Trigger race of JvmtiThreadState creation with terminating vthreads. setMonitorContendedMode(false); diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadStateTest/libThreadStateTest.cpp b/test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadStateTest/libThreadStateTest.cpp index 10dfdfda347..a7d4bb46199 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadStateTest/libThreadStateTest.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/ThreadStateTest/libThreadStateTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,6 +29,8 @@ // set by Agent_OnLoad static jvmtiEnv* jvmti = nullptr; +static jrawMonitorID agent_event_lock = nullptr; +static int frame_pops_cnt = 0; static const jint EXP_VT_STATE = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE; static const jint EXP_CT_STATE = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY; @@ -41,6 +43,18 @@ SingleStep(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, jmethodID method, jlocation location) { } +static void JNICALL +FramePop(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, + jmethodID method, jboolean by_exception) { + const char* tname = get_thread_name(jvmti, jni, thread); + const char* mname = get_method_name(jvmti, jni, method); + + RawMonitorLocker event_locker(jvmti, jni, agent_event_lock); + LOG("FramePop event #%d: thread: %s method: %s\n", ++frame_pops_cnt, tname, mname); + deallocate(jvmti, jni, (void*)tname); + deallocate(jvmti, jni, (void*)mname); +} + static void JNICALL MonitorContended(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject object) { @@ -48,7 +62,7 @@ MonitorContended(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, static void JNICALL check_thread_state(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jint state, jint exp_state, const char* msg) { - if (state != exp_state) { + if ((state & ~JVMTI_THREAD_STATE_SUSPENDED) != exp_state) { const char* tname = get_thread_name(jvmti, jni, thread); LOG("FAILED: %p: %s: thread state: %x expected state: %x\n", @@ -59,6 +73,33 @@ check_thread_state(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jint state, jin } } +JNIEXPORT void JNICALL +Java_ThreadStateTest_setFramePopEvent(JNIEnv* jni, jclass klass, jthread thread) { + RawMonitorLocker event_locker(jvmti, jni, agent_event_lock); + + jvmtiError err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FRAME_POP, thread); + if (err != JVMTI_ERROR_NONE) { + if (err == JVMTI_ERROR_THREAD_NOT_ALIVE || err == JVMTI_ERROR_NO_MORE_FRAMES) { + return; + } else { + check_jvmti_status(jni, err, "setFramePopEvent error in JVMTI SetEventNotificationMode for JVMTI_EVENT_FRAME_POP"); + } + } + err = jvmti->SuspendThread(thread); + if (err == JVMTI_ERROR_THREAD_NOT_ALIVE) { + return; + } + check_jvmti_status(jni, err, "setFramePopEvent error in JVMTI SuspendThread"); + + err = jvmti->NotifyFramePop(thread, 4); + if (err != JVMTI_ERROR_NO_MORE_FRAMES && err != JVMTI_ERROR_OPAQUE_FRAME) { + check_jvmti_status(jni, err, "setFramePopEvent error in JVMTI NotifyFramePop"); + } + + err = jvmti->ResumeThread(thread); + check_jvmti_status(jni, err, "setFramePopEvent error in JVMTI ResumeThread"); +} + JNIEXPORT void JNICALL Java_ThreadStateTest_setSingleSteppingMode(JNIEnv* jni, jclass klass, jboolean enable) { jvmtiError err = jvmti->SetEventNotificationMode(enable ? JVMTI_ENABLE : JVMTI_DISABLE, JVMTI_EVENT_SINGLE_STEP, nullptr); @@ -111,6 +152,8 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) memset(&caps, 0, sizeof(caps)); caps.can_generate_single_step_events = 1; + caps.can_generate_frame_pop_events = 1; + caps.can_suspend = 1; caps.can_support_virtual_threads = 1; caps.can_generate_monitor_events = 1; @@ -120,12 +163,14 @@ JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) } memset(&callbacks, 0, sizeof(callbacks)); - callbacks.SingleStep = &SingleStep; + callbacks.SingleStep = &SingleStep; + callbacks.FramePop = &FramePop; callbacks.MonitorContendedEnter = &MonitorContended; err = jvmti->SetEventCallbacks(&callbacks, sizeof(jvmtiEventCallbacks)); if (err != JVMTI_ERROR_NONE) { LOG("Agent_OnLoad: Error in JVMTI SetEventCallbacks: %d\n", err); } + agent_event_lock = create_raw_monitor(jvmti, "agent_event_lock"); printf("Agent_OnLoad: finished\n"); return 0; diff --git a/test/jdk/com/sun/jdi/EATests.java b/test/jdk/com/sun/jdi/EATests.java index 29f56f977a8..f4d860e8a2b 100644 --- a/test/jdk/com/sun/jdi/EATests.java +++ b/test/jdk/com/sun/jdi/EATests.java @@ -2953,6 +2953,8 @@ class EAForceEarlyReturnNotInlined extends EATestCaseBaseDebugger { // frame[3]: EATestCaseBaseTarget.run() // frame[4]: EATestsTarget.main(java.lang.String[]) + env.stepOverLine(thread); // needed to keep target thread interp-only, so dontinline_brkpt_iret is not inlined + msg("Step out"); env.stepOut(thread); // return from dontinline_brkpt printStack(thread); diff --git a/test/jdk/com/sun/jdi/StepOverStressTest.java b/test/jdk/com/sun/jdi/StepOverStressTest.java new file mode 100644 index 00000000000..4a934b7aeb2 --- /dev/null +++ b/test/jdk/com/sun/jdi/StepOverStressTest.java @@ -0,0 +1,183 @@ +/* + * 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. + */ + +// THIS TEST IS LINE NUMBER SENSITIVE + +/** + * @test + * @bug 6960970 + * @summary Avoid interp-only mode during stepping over method calls. + * + * @run build TestScaffold VMConnection TargetListener TargetAdapter + * @run compile -g StepOverStressTest.java + * @run driver/timeout=600 StepOverStressTest + */ + +import com.sun.jdi.*; +import com.sun.jdi.event.*; +import com.sun.jdi.request.*; + +import java.util.*; + +class TestTarg { + public final static int BKPT_LINE1 = 54; + public final static int BKPT_LINE2 = 56; + + static void log(String msg) { System.out.println(msg); } + + static int busyWork() { + // When we enter this method we hit a breakpoint on the BKPT_LINE1 line below. + // The debugger enables single step events and then resume. We time the compute + // intensive task in busyWork1 method. Single step events are disabled when + // second breakpoint on the BKPT_LINE2 line is hit. This will check if the + // thread continues execution in interpOnly mode in the busyWork1 method. + int x = 1; // <-- BKPT_LINE1 + intensiveWork(); + return x; // <-- BKPT_LINE2 + } + + // Do something compute intensive and time it. + static void intensiveWork() { + long start = System.currentTimeMillis(); + for (int iter = 0; iter < 10; iter++) { + LinkedList list = new LinkedList(); + for (int i = 0; i < 100 * 1024; i++) { + list.addFirst(Integer.valueOf(i)); + } + Collections.sort(list); + } + long end = System.currentTimeMillis(); + log("Total time #1: " + (end - start)); + } + + public static void main(String[] args) { + long start = System.currentTimeMillis(); + busyWork(); + long end = System.currentTimeMillis(); + log("Total time #2: " + (end - start)); + } +} + + /********** test program **********/ + +public class StepOverStressTest extends TestScaffold { + ClassType targetClass; + ThreadReference mainThread; + + static void log(String msg) { System.out.println(msg); } + + StepOverStressTest (String args[]) { + super(args); + } + + public static void main(String[] args) throws Exception { + new StepOverStressTest(args).startTests(); + } + + /********** event handlers **********/ + + EventRequestManager erm; + BreakpointRequest bkptReq1; + BreakpointRequest bkptReq2; + StepRequest stepRequest; + static boolean wasBreakpointHit = false; + + public void breakpointReached(BreakpointEvent event) { + if (!wasBreakpointHit) { + log("Got BreakpointEvent #1: " + event); + stepRequest = erm.createStepRequest(mainThread, + StepRequest.STEP_LINE, + StepRequest.STEP_OVER); + wasBreakpointHit = true; + stepRequest.enable(); + bkptReq1.disable(); + } else { + log("Got BreakpointEvent #2: " + event); + stepRequest.disable(); + bkptReq2.disable(); + } + } + + public void stepCompleted(StepEvent event) { + log("Got StepEvent: " + event); + } + + public void eventSetComplete(EventSet set) { + set.resume(); + } + + public void vmDisconnected(VMDisconnectEvent event) { + log("Got VMDisconnectEvent"); + } + + /********** test core **********/ + + protected void runTests() throws Exception { + // Get to the top of main() to determine targetClass and mainThread. + BreakpointEvent bpe = startToMain("TestTarg"); + targetClass = (ClassType)bpe.location().declaringType(); + mainThread = bpe.thread(); + erm = vm().eventRequestManager(); + + Location loc1 = findLocation( + targetClass, + TestTarg.BKPT_LINE1); + + Location loc2 = findLocation( + targetClass, + TestTarg.BKPT_LINE2); + + bkptReq1 = erm.createBreakpointRequest(loc1); + bkptReq2 = erm.createBreakpointRequest(loc2); + bkptReq1.enable(); + bkptReq2.enable(); + + try { + addListener(this); + } catch (Exception ex){ + ex.printStackTrace(); + failure("failure: Could not add listener"); + throw new Exception("StepOverStressTest: failed"); + } + + vm().resume(); + while (!vmDisconnected) { + try { + Thread.sleep(5000); + } catch (InterruptedException ee) { + } + } + + println("done with loop"); + removeListener(this); + + // Deal with results of test if anything has called failure("foo") + // testFailed will be true. + if (!testFailed) { + println("StepOverStressTest: passed"); + } else { + throw new Exception("StepOverStressTest: failed"); + } + } +} + diff --git a/test/lib/jdk/test/lib/jvmti/jvmti_common.hpp b/test/lib/jdk/test/lib/jvmti/jvmti_common.hpp index 4ed946b243d..6b9c18297f1 100644 --- a/test/lib/jdk/test/lib/jvmti/jvmti_common.hpp +++ b/test/lib/jdk/test/lib/jvmti/jvmti_common.hpp @@ -279,6 +279,15 @@ get_frame_count(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread) { return frame_count; } +static jmethodID +get_frame_method(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, jint depth) { + jmethodID method; + jlocation loc; + jvmtiError err = jvmti->GetFrameLocation(thread, depth, &method, &loc); + check_jvmti_status(jni, err, "notifyFramePop: Failed in JVMTI GetFrameLocation"); + return method; +} + static jvmtiThreadInfo get_thread_info(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread) { jvmtiThreadInfo thr_info;