diff --git a/src/cpu/x86/vm/interp_masm_x86_64.cpp b/src/cpu/x86/vm/interp_masm_x86_64.cpp index 727207ec13ea9cfddf3e7d24c8017af5cf3b6707..507d302d82f6dece7e3c854133d6ec0bb308e0b4 100644 --- a/src/cpu/x86/vm/interp_masm_x86_64.cpp +++ b/src/cpu/x86/vm/interp_masm_x86_64.cpp @@ -444,15 +444,27 @@ void InterpreterMacroAssembler::jump_from_interpreted(Register method, Register if (JvmtiExport::can_post_interpreter_events()) { Label run_compiled_code; + Label coroutine_skip_interpret; // JVMTI events, such as single-stepping, are implemented partly by avoiding running // compiled code in threads for which the event is enabled. Check here for // interp_only_mode if these events CAN be enabled. // interp_only is an int, on little endian it is sufficient to test the byte only // Is a cmpl faster? cmpb(Address(r15_thread, JavaThread::interp_only_mode_offset()), 0); - jccb(Assembler::zero, run_compiled_code); + jcc(Assembler::zero, run_compiled_code); + if (EnableCoroutine) { + cmpw(Address(method, Method::intrinsic_id_offset_in_bytes()), vmIntrinsics::_switchTo); + jcc(Assembler::zero, coroutine_skip_interpret); + cmpw(Address(method, Method::intrinsic_id_offset_in_bytes()), vmIntrinsics::_switchToAndExit); + jcc(Assembler::zero, coroutine_skip_interpret); + cmpw(Address(method, Method::intrinsic_id_offset_in_bytes()), vmIntrinsics::_switchToAndTerminate); + jcc(Assembler::zero, coroutine_skip_interpret); + } jmp(Address(method, Method::interpreter_entry_offset())); bind(run_compiled_code); + if (EnableCoroutine) { + bind(coroutine_skip_interpret); + } } jmp(Address(method, Method::from_interpreted_offset())); diff --git a/src/cpu/x86/vm/sharedRuntime_x86_32.cpp b/src/cpu/x86/vm/sharedRuntime_x86_32.cpp index 50e870f95dd287a5f0ea59d4eeed3df50ef98e9c..ae86d70b651441769f1155221d960dfefe679f9d 100644 --- a/src/cpu/x86/vm/sharedRuntime_x86_32.cpp +++ b/src/cpu/x86/vm/sharedRuntime_x86_32.cpp @@ -3636,6 +3636,10 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma __ movptr(Address(old_coroutine, Coroutine::resource_area_offset()), temp); __ movptr(temp, Address(thread, Thread::last_handle_mark_offset())); __ movptr(Address(old_coroutine, Coroutine::last_handle_mark_offset()), temp); + __ movptr(temp, Address(thread, Thread::active_handles_offset())); + __ movptr(Address(old_coroutine, Coroutine::active_handles_offset()), temp); + __ movptr(temp, Address(thread, Thread::metadata_handles_offset())); + __ movptr(Address(old_coroutine, Coroutine::metadata_handles_offset()), temp); // push the current IP and frame pointer onto the stack __ push(rbp); @@ -3658,7 +3662,8 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma Register temp2 = rdi; { Register thread = rax; - __ get_thread(rax); + __ get_thread(thread); + __ movptr(Address(thread, JavaThread::current_coroutine_offset()), target_coroutine); // set new handle and resource areas __ movptr(temp, Address(target_coroutine, Coroutine::handle_area_offset())); __ movptr(Address(target_coroutine, Coroutine::handle_area_offset()), (intptr_t)NULL_WORD); // TODO is this really needed? @@ -3669,6 +3674,12 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma __ movptr(temp, Address(target_coroutine, Coroutine::last_handle_mark_offset())); __ movptr(Address(target_coroutine, Coroutine::last_handle_mark_offset()), (intptr_t)NULL_WORD); // TODO is this really needed? __ movptr(Address(thread, Thread::last_handle_mark_offset()), temp); + __ movptr(temp, Address(target_coroutine, Coroutine::active_handles_offset())); + __ movptr(Address(target_coroutine, Coroutine::active_handles_offset()), (intptr_t)NULL_WORD); + __ movptr(Address(thread, Thread::active_handles_offset()), temp); + __ movptr(temp, Address(target_coroutine, Coroutine::metadata_handles_offset())); + __ movptr(Address(target_coroutine, Coroutine::metadata_handles_offset()), (intptr_t)NULL_WORD); + __ movptr(Address(thread, Thread::metadata_handles_offset()), temp); // update the thread's stack base and size __ movptr(temp, Address(target_stack, CoroutineStack::stack_base_offset())); diff --git a/src/cpu/x86/vm/sharedRuntime_x86_64.cpp b/src/cpu/x86/vm/sharedRuntime_x86_64.cpp index e87a1e3b2414f813e90b7b50b9082ac1d2ad126a..1ec5816ffd92c33c6b6d6170b67a27d989c12d69 100644 --- a/src/cpu/x86/vm/sharedRuntime_x86_64.cpp +++ b/src/cpu/x86/vm/sharedRuntime_x86_64.cpp @@ -4470,6 +4470,10 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma __ movptr(Address(old_coroutine, Coroutine::resource_area_offset()), temp); __ movptr(temp, Address(thread, Thread::last_handle_mark_offset())); __ movptr(Address(old_coroutine, Coroutine::last_handle_mark_offset()), temp); + __ movptr(temp, Address(thread, Thread::active_handles_offset())); + __ movptr(Address(old_coroutine, Coroutine::active_handles_offset()), temp); + __ movptr(temp, Address(thread, Thread::metadata_handles_offset())); + __ movptr(Address(old_coroutine, Coroutine::metadata_handles_offset()), temp); #ifdef ASSERT __ movl(temp, Address(thread, JavaThread::java_call_counter_offset())); __ movl(Address(old_coroutine, Coroutine::java_call_counter_offset()), temp); @@ -4492,6 +4496,7 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma Register temp2 = r9; { Register thread = r15; + __ movptr(Address(thread, JavaThread::current_coroutine_offset()), target_coroutine); // set new handle and resource areas __ movptr(temp, Address(target_coroutine, Coroutine::handle_area_offset())); __ movptr(Address(thread, Thread::handle_area_offset()), temp); @@ -4499,6 +4504,10 @@ void create_switchTo_contents(MacroAssembler *masm, int start, OopMapSet* oop_ma __ movptr(Address(thread, Thread::resource_area_offset()), temp); __ movptr(temp, Address(target_coroutine, Coroutine::last_handle_mark_offset())); __ movptr(Address(thread, Thread::last_handle_mark_offset()), temp); + __ movptr(temp, Address(target_coroutine, Coroutine::active_handles_offset())); + __ movptr(Address(thread, Thread::active_handles_offset()), temp); + __ movptr(temp, Address(target_coroutine, Coroutine::metadata_handles_offset())); + __ movptr(Address(thread, Thread::metadata_handles_offset()), temp); #ifdef ASSERT __ movl(temp, Address(target_coroutine, Coroutine::java_call_counter_offset())); diff --git a/src/share/vm/code/nmethod.cpp b/src/share/vm/code/nmethod.cpp index e230419a32b52a226ac4be8ce46f8708fad19a13..ecbc45a5d4872350909d490d30be2c76ceed4a1c 100644 --- a/src/share/vm/code/nmethod.cpp +++ b/src/share/vm/code/nmethod.cpp @@ -1438,6 +1438,19 @@ bool nmethod::make_not_entrant_or_zombie(unsigned int state) { assert(state == zombie || state == not_entrant, "must be zombie or not_entrant"); assert(!is_zombie(), "should not already be a zombie"); + if (EnableCoroutine && method() != NULL) { + // do not deal with intrinsic methods of coroutine klass + assert(!is_unloaded(), "wrong state"); + vmIntrinsics::ID intrinsic_id = method()->intrinsic_id(); + if (intrinsic_id == vmIntrinsics::_switchTo || + intrinsic_id == vmIntrinsics::_switchToAndExit || + intrinsic_id == vmIntrinsics::_switchToAndTerminate) { + assert(method()->constants()->pool_holder() == SystemDictionary::java_dyn_CoroutineSupport_klass(), "wrong method!"); + assert(state == not_entrant, "wrong state"); + return false; + } + } + // Make sure neither the nmethod nor the method is flushed in case of a safepoint in code below. nmethodLocker nml(this); methodHandle the_method(method()); diff --git a/src/share/vm/prims/jvmtiThreadState.cpp b/src/share/vm/prims/jvmtiThreadState.cpp index 34b5bdc8f448e644f14f1361029bf8ac8648b6a0..a4116e27a956e168785750cd4ee4239f73d5e389 100644 --- a/src/share/vm/prims/jvmtiThreadState.cpp +++ b/src/share/vm/prims/jvmtiThreadState.cpp @@ -288,6 +288,9 @@ int JvmtiThreadState::cur_stack_depth() { if (!is_interp_only_mode() || _cur_stack_depth == UNKNOWN_STACK_DEPTH) { _cur_stack_depth = count_frames(); } else { + if (EnableCoroutine) { + _cur_stack_depth = count_frames(); // update debug stack depth, for switchToAndExit + } // heavy weight assert assert(_cur_stack_depth == count_frames(), "cur_stack_depth out of sync"); diff --git a/src/share/vm/prims/unsafe.cpp b/src/share/vm/prims/unsafe.cpp index ea8f32a4e0321b2547f2002ec78ad9f77b6425ea..806359d30c259bbdd7e2400dcbb689712708ce40 100644 --- a/src/share/vm/prims/unsafe.cpp +++ b/src/share/vm/prims/unsafe.cpp @@ -1394,7 +1394,7 @@ void CoroutineSupport_switchToAndTerminate(JNIEnv* env, jclass klass, jobject ol } else { CoroutineStack::free_stack(stack, THREAD); } - Coroutine::free_coroutine(coro, THREAD); + delete coro; } void CoroutineSupport_switchToAndExit(JNIEnv* env, jclass klass, jobject old_coroutine, jobject target_coroutine) { @@ -1450,7 +1450,14 @@ jboolean CoroutineSupport_isDisposable(JNIEnv* env, jclass klass, jlong coroutin assert(coro != NULL, "cannot free NULL coroutine"); assert(!coro->is_thread_coroutine(), "cannot free thread coroutine"); - return coro->is_disposable(); + jboolean is_disposable = coro->is_disposable(); + if (is_disposable) { + CoroutineStack* stack = coro->stack(); + stack->remove_from_list(THREAD->coroutine_stack_list()); + CoroutineStack::free_stack(stack, THREAD); + delete coro; + } + return is_disposable; } jobject CoroutineSupport_cleanupCoroutine(JNIEnv* env, jclass klass) { diff --git a/src/share/vm/runtime/coroutine.cpp b/src/share/vm/runtime/coroutine.cpp index 121891ffac2708f422a59048e3eb074316c71c00..a388653b6f7a705213e5e2b64f0acba032ac0097 100644 --- a/src/share/vm/runtime/coroutine.cpp +++ b/src/share/vm/runtime/coroutine.cpp @@ -77,6 +77,7 @@ void Coroutine::run(jobject coroutine) { _thread->set_resource_area(new (mtThread) ResourceArea(32)); _thread->set_handle_area(new (mtThread) HandleArea(NULL, 32)); + _thread->set_metadata_handles(new (ResourceObj::C_HEAP, mtClass) GrowableArray(30, true)); { HandleMark hm(_thread); @@ -105,6 +106,8 @@ Coroutine* Coroutine::create_thread_coroutine(JavaThread* thread, CoroutineStack coro->_resource_area = NULL; coro->_handle_area = NULL; coro->_last_handle_mark = NULL; + coro->_active_handles = thread->active_handles(); + coro->_metadata_handles = NULL; #ifdef ASSERT coro->_java_call_counter = 0; #endif @@ -114,6 +117,12 @@ Coroutine* Coroutine::create_thread_coroutine(JavaThread* thread, CoroutineStack return coro; } +/** + * The initial value for corountine' active handles, which will be replaced with a real one + * when the coroutine invokes call_virtual (before running any real logic). + */ +static JNIHandleBlock* shared_empty_JNIHandleBlock = JNIHandleBlock::allocate_block(); + Coroutine* Coroutine::create_coroutine(JavaThread* thread, CoroutineStack* stack, oop coroutineObj) { Coroutine* coro = new Coroutine(); if (coro == NULL) { @@ -121,6 +130,8 @@ Coroutine* Coroutine::create_coroutine(JavaThread* thread, CoroutineStack* stack } intptr_t** d = (intptr_t**)stack->stack_base(); + // Make the 16 bytes(original is 8*5=40 bytes) alignation which is required by some instructions like movaps otherwise we will incur a crash. + *(--d) = NULL; *(--d) = NULL; jobject obj = JNIHandles::make_global(coroutineObj); *(--d) = (intptr_t*)obj; @@ -131,13 +142,15 @@ Coroutine* Coroutine::create_coroutine(JavaThread* thread, CoroutineStack* stack stack->set_last_sp((address) d); - coro->_state = _onstack; + coro->_state = _created; coro->_is_thread_coroutine = false; coro->_thread = thread; coro->_stack = stack; coro->_resource_area = NULL; coro->_handle_area = NULL; coro->_last_handle_mark = NULL; + coro->_active_handles = shared_empty_JNIHandleBlock; + coro->_metadata_handles = NULL; #ifdef ASSERT coro->_java_call_counter = 0; #endif @@ -147,13 +160,25 @@ Coroutine* Coroutine::create_coroutine(JavaThread* thread, CoroutineStack* stack return coro; } -void Coroutine::free_coroutine(Coroutine* coroutine, JavaThread* thread) { - coroutine->remove_from_list(thread->coroutine_list()); - delete coroutine; +Coroutine::~Coroutine() { + remove_from_list(_thread->coroutine_list()); + if (!_is_thread_coroutine && _state != Coroutine::_created) { + assert(_resource_area != NULL, "_resource_area is NULL"); + assert(_handle_area != NULL, "_handle_area is NULL"); + assert(_metadata_handles != NULL, "_metadata_handles is NULL"); + delete _resource_area; + delete _handle_area; + delete _metadata_handles; + JNIHandleBlock::release_block(active_handles(), _thread); + //allocated from JavaCalls::call_virtual during coroutine's first running + } } void Coroutine::frames_do(FrameClosure* fc) { switch (_state) { + case Coroutine::_created: + // the coroutine has never been run + break; case Coroutine::_current: // the contents of this coroutine have already been visited break; @@ -182,6 +207,7 @@ void Coroutine::oops_do(OopClosure* f, CLDClosure* cld_f, CodeBlobClosure* cf) { if (_state == _onstack &&_handle_area != NULL) { DEBUG_CORO_ONLY(tty->print_cr("collecting handle area %08x", _handle_area)); _handle_area->oops_do(f); + _active_handles->oops_do(f); } } @@ -207,6 +233,11 @@ public: }; void Coroutine::metadata_do(void f(Metadata*)) { + if (metadata_handles() != NULL) { + for (int i = 0; i< metadata_handles()->length(); i++) { + f(metadata_handles()->at(i)); + } + } metadata_do_Closure fc(f); frames_do(&fc); } @@ -225,7 +256,9 @@ void Coroutine::frames_do(void f(frame*, const RegisterMap* map)) { } bool Coroutine::is_disposable() { - return false; + //_handle_area == NULL indicates this coroutine has not been initialized, + //we should delete it directly. + return _handle_area == NULL; } @@ -290,7 +323,10 @@ CoroutineStack* CoroutineStack::create_stack(JavaThread* thread, intptr_t size/* } void CoroutineStack::free_stack(CoroutineStack* stack, JavaThread* thread) { - guarantee(!stack->is_thread_stack(), "cannot free thread stack"); + if (stack->is_thread_stack()) { + delete stack; + return; + } ThreadLocalStorage::remove_coroutine_stack(thread, stack->stack_base(), stack->stack_size()); if (stack->_reserved_space.size() > 0) { diff --git a/src/share/vm/runtime/coroutine.hpp b/src/share/vm/runtime/coroutine.hpp index cfd2f3eb63a768d67ddebbb4490b1706e225e618..944e5c5ab9164c82a1fdaec225ea8913b1e9b9c1 100644 --- a/src/share/vm/runtime/coroutine.hpp +++ b/src/share/vm/runtime/coroutine.hpp @@ -79,6 +79,7 @@ public: class Coroutine: public CHeapObj, public DoublyLinkedList { public: enum CoroutineState { + _created = 0x00000000, // not inited _onstack = 0x00000001, _current = 0x00000002, _dead = 0x00000003, // TODO is this really needed? @@ -96,6 +97,8 @@ private: ResourceArea* _resource_area; HandleArea* _handle_area; HandleMark* _last_handle_mark; + JNIHandleBlock* _active_handles; + GrowableArray* _metadata_handles; #ifdef ASSERT int _java_call_counter; #endif @@ -106,16 +109,16 @@ private: // objects of this type can only be created via static functions Coroutine() { } - virtual ~Coroutine() { } void frames_do(FrameClosure* fc); public: + virtual ~Coroutine(); + void run(jobject coroutine); static Coroutine* create_thread_coroutine(JavaThread* thread, CoroutineStack* stack); static Coroutine* create_coroutine(JavaThread* thread, CoroutineStack* stack, oop coroutineObj); - static void free_coroutine(Coroutine* coroutine, JavaThread* thread); CoroutineState state() const { return _state; } void set_state(CoroutineState x) { _state = x; } @@ -136,6 +139,12 @@ public: HandleMark* last_handle_mark() const { return _last_handle_mark; } void set_last_handle_mark(HandleMark* x){ _last_handle_mark = x; } + JNIHandleBlock* active_handles() const { return _active_handles; } + void set_active_handles(JNIHandleBlock* block) { _active_handles = block; } + + GrowableArray* metadata_handles() const { return _metadata_handles; } + void set_metadata_handles(GrowableArray* handles){ _metadata_handles = handles; } + #ifdef ASSERT int java_call_counter() const { return _java_call_counter; } void set_java_call_counter(int x) { _java_call_counter = x; } @@ -155,6 +164,8 @@ public: static ByteSize resource_area_offset() { return byte_offset_of(Coroutine, _resource_area); } static ByteSize handle_area_offset() { return byte_offset_of(Coroutine, _handle_area); } static ByteSize last_handle_mark_offset() { return byte_offset_of(Coroutine, _last_handle_mark); } + static ByteSize active_handles_offset() { return byte_offset_of(Coroutine, _active_handles); } + static ByteSize metadata_handles_offset() { return byte_offset_of(Coroutine, _metadata_handles); } #ifdef ASSERT static ByteSize java_call_counter_offset() { return byte_offset_of(Coroutine, _java_call_counter); } #endif diff --git a/src/share/vm/runtime/handles.hpp b/src/share/vm/runtime/handles.hpp index 97d51da795e940b5bbdcf787df3ba95ee0a020bd..f41b03350baca1626e38e2db2623ebe73ac07cce 100644 --- a/src/share/vm/runtime/handles.hpp +++ b/src/share/vm/runtime/handles.hpp @@ -233,6 +233,7 @@ class HandleArea: public Arena { _prev = prev; } + // Only coroutine uses this constructor HandleArea(HandleArea* prev, size_t init_size) : Arena(mtThread, init_size) { assert(EnableCoroutine, "EnableCoroutine is off"); debug_only(_handle_mark_nesting = 0); diff --git a/src/share/vm/runtime/thread.cpp b/src/share/vm/runtime/thread.cpp index 1996b0743b2a7911a17b0b06acf7f129cd4148f6..edc6adf0e3e8d2258954f1a8ccb350ab4c4d923e 100644 --- a/src/share/vm/runtime/thread.cpp +++ b/src/share/vm/runtime/thread.cpp @@ -1500,6 +1500,7 @@ void JavaThread::initialize() { _coroutine_stack_cache_size = 0; _coroutine_stack_list = NULL; _coroutine_list = NULL; + _current_coroutine = NULL; _thread_stat = NULL; _thread_stat = new ThreadStatistics(); @@ -1643,6 +1644,11 @@ JavaThread::~JavaThread() { CoroutineStack::free_stack(stack, this); } + while (EnableCoroutine && coroutine_list() != NULL) { + CoroutineStack::free_stack(coroutine_list()->stack(), this); + delete coroutine_list(); + } + if (TraceThreadEvents) { tty->print_cr("terminate thread %p", this); } diff --git a/src/share/vm/runtime/thread.hpp b/src/share/vm/runtime/thread.hpp index 6f49aa6a347c9c577c34696d684ceaad60bb91ea..cf2eaa014c056ab5dcffa08289ab291dd64f30f6 100644 --- a/src/share/vm/runtime/thread.hpp +++ b/src/share/vm/runtime/thread.hpp @@ -639,6 +639,7 @@ protected: static ByteSize exception_file_offset() { return byte_offset_of(Thread, _exception_file ); } static ByteSize exception_line_offset() { return byte_offset_of(Thread, _exception_line ); } static ByteSize active_handles_offset() { return byte_offset_of(Thread, _active_handles ); } + static ByteSize metadata_handles_offset() { return byte_offset_of(Thread, _metadata_handles); } static ByteSize stack_base_offset() { return byte_offset_of(Thread, _stack_base ); } static ByteSize stack_size_offset() { return byte_offset_of(Thread, _stack_size ); } @@ -980,6 +981,7 @@ class JavaThread: public Thread { uintx _coroutine_stack_cache_size; CoroutineStack* _coroutine_stack_list; Coroutine* _coroutine_list; + Coroutine* _current_coroutine; intptr_t _coroutine_temp; @@ -988,9 +990,12 @@ class JavaThread: public Thread { uintx& coroutine_stack_cache_size() { return _coroutine_stack_cache_size; } CoroutineStack*& coroutine_stack_list() { return _coroutine_stack_list; } Coroutine*& coroutine_list() { return _coroutine_list; } + Coroutine* current_coroutine() { return _current_coroutine; } static ByteSize coroutine_temp_offset() { return byte_offset_of(JavaThread, _coroutine_temp); } + static ByteSize current_coroutine_offset() { return byte_offset_of(JavaThread, _current_coroutine); } + void initialize_coroutine_support(); private: diff --git a/test/runtime/coroutine/Issue11230146.java b/test/runtime/coroutine/Issue11230146.java new file mode 100644 index 0000000000000000000000000000000000000000..e5f412f7c470eb2b4929b22a50332d90545809be --- /dev/null +++ b/test/runtime/coroutine/Issue11230146.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * @test Issue11230146 + * @library /testlibrary /testlibrary/whitebox + * @build Issue11230146 + * @run main ClassFileInstaller sun.hotspot.WhiteBox + * @run main/othervm -XX:-Inline -XX:+EnableCoroutine -Xmx10m -Xms10m -XX:ReservedCodeCacheSize=3m -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI Issue11230146 + * @summary Issue11230146, JTreg test for D181275 + */ + +import sun.hotspot.WhiteBox; +import java.dyn.Coroutine; +import java.io.*; +import java.lang.reflect.Method; +import java.util.*; + +public class Issue11230146 { + public static void main(String[] args) throws Exception { + for (int i = 0; i < 100; i++) { + // trigger method state translate: + // in_use -> not_entrant -> zombie -> unload -> zombie + // ^ + // | + // +-- crash happens here (described in issue11230146) + doTest(); + } + } + + public static void doTest() throws Exception { + WhiteBox whiteBox = WhiteBox.getWhiteBox(); + MyClassloader loader = new MyClassloader(); + try { + Class c = loader.loadClass("ClassTmp"); + Method fooMethod = c.getMethod("foo"); + Object fooObject = c.newInstance(); + for (int i = 0; i < 10000; i++) { + fooMethod.invoke(fooObject); + } + } catch (Exception e) { + e.printStackTrace(); + } + System.gc(); + // deoptimize methods + whiteBox.deoptimizeAll(); + } + + /* + * class file contet: + * + * public class ClassTmp { + * public String a; + * + * public void foo() { + * if (a != null) { + * System.out.println("foo..."); + * } + * } + * } + * + */ + public static String classContent = + "yv66vgAAADQAIQoABwASCQAGABMJABQAFQgAFgoAFwAYBwAZBwAaAQABYQEAEkxqYXZhL2xhbmcv" + + "U3RyaW5nOwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAANmb28BAA1T" + + "dGFja01hcFRhYmxlAQAKU291cmNlRmlsZQEADUNsYXNzVG1wLmphdmEMAAoACwwACAAJBwAbDAAc" + + "AB0BAAZmb28uLi4HAB4MAB8AIAEACENsYXNzVG1wAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEv" + + "bGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50" + + "U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAYABwAAAAEAAQAIAAkA" + + "AAACAAEACgALAAEADAAAAB0AAQABAAAABSq3AAGxAAAAAQANAAAABgABAAAAAQABAA4ACwABAAwA" + + "AAA5AAIAAQAAABAqtAACxgALsgADEgS2AAWxAAAAAgANAAAADgADAAAABQAHAAYADwAIAA8AAAAD" + + "AAEPAAEAEAAAAAIAEQ=="; + + /** + * decode Base64 + */ + public static byte[] decodeBase64(String str) throws Exception { + byte[] bt = null; + sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder(); + bt = decoder.decodeBuffer(str); + return bt; + } + + public static class MyClassloader extends ClassLoader { + public Class loadClass(String name) throws ClassNotFoundException { + if ("ClassTmp".equals(name)) { + try { + byte[] bs = decodeBase64(classContent); + return defineClass(name, bs, 0, bs.length); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } else { + return super.loadClass(name); + } + } + } +} diff --git a/test/runtime/coroutine/SimpleWispTest.java b/test/runtime/coroutine/SimpleWispTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b2154610eaeb03ba1ed5a6702cdeac6c92f42581 --- /dev/null +++ b/test/runtime/coroutine/SimpleWispTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import com.alibaba.wisp.engine.WispEngine; + +public class SimpleWispTest { + + public static void main(String[] args) { + + WispEngine.dispatch(() -> { + + }); + + } + +} diff --git a/test/runtime/coroutine/TestAvoidDeoptCoroutineMethod.java b/test/runtime/coroutine/TestAvoidDeoptCoroutineMethod.java new file mode 100644 index 0000000000000000000000000000000000000000..a9cb1844e5ed8f17084e646c6a5f07370e4116b6 --- /dev/null +++ b/test/runtime/coroutine/TestAvoidDeoptCoroutineMethod.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * @test TestAvoidDeoptCoroutineMethod + * @library /testlibrary /testlibrary/whitebox + * @build TestAvoidDeoptCoroutineMethod + * @run main ClassFileInstaller sun.hotspot.WhiteBox + * @run main/othervm -XX:+EnableCoroutine -Xmx10m -Xms10m -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI TestAvoidDeoptCoroutineMethod + * @summary test avoid coroutine intrinsic method to be deoptimized + */ + +import sun.hotspot.WhiteBox; +import java.dyn.Coroutine; +import java.io.*; + +public class TestAvoidDeoptCoroutineMethod { + public static void main(String[] args) throws Exception { + WhiteBox whiteBox = WhiteBox.getWhiteBox(); + runSomeCoroutines(); + // deoptimize all + whiteBox.deoptimizeAll(); + // if intrinsic methods of coroutine have been deoptimized, it will crash here + runSomeCoroutines(); + } + + private static void runSomeCoroutines() throws Exception { + for (int i = 0; i < 10000; i++) { + new Coroutine(() -> {}); + Coroutine.yield(); // switch to new created coroutine and let it die + } + System.out.println("end of run"); + } +} diff --git a/test/runtime/coroutine/TestMemLeak.java b/test/runtime/coroutine/TestMemLeak.java new file mode 100644 index 0000000000000000000000000000000000000000..625111285788f67faa7f158d5290beb7a7a80f02 --- /dev/null +++ b/test/runtime/coroutine/TestMemLeak.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * @test + * @summary test of memory leak while creating and destroying coroutine/thread + * @run main/othervm -XX:+EnableCoroutine -Xmx10m -Xms10m TestMemLeak + */ + +import java.dyn.Coroutine; +import java.io.*; + +public class TestMemLeak { + private final static Runnable r = () -> {}; + + public static void main(String[] args) throws Exception { + testThreadCoroutineLeak(); + testUserCreatedCoroutineLeak(); + } + + + /** + * Before fix: 35128kB -> 40124kB + * After fix : 28368kB -> 28424kB + */ + private static void testThreadCoroutineLeak() throws Exception { + // occupy rss + for (int i = 0; i < 20000; i++) { + Thread t = new Thread(r); + t.start(); + t.join(); + } + + int rss0 = getRssInKb(); + System.out.println(rss0); + + for (int i = 0; i < 20000; i++) { + Thread t = new Thread(r); + t.start(); + t.join(); + } + + int rss1 = getRssInKb(); + System.out.println(rss1); + + if (rss1 - rss0 > 1024) { // 1M + throw new Error("thread coroutine mem leak"); + } + } + + /** + * Before fix: 152892kB -> 280904kB + * After fix : 25436kB -> 25572kB + */ + private static void testUserCreatedCoroutineLeak() throws Exception { + // occupy rss + for (int i = 0; i < 200000; i++) { + new Coroutine(r); + Coroutine.yield(); // switch to new created coroutine and let it die + } + + int rss0 = getRssInKb(); + System.out.println(rss0); + + for (int i = 0; i < 200000; i++) { + new Coroutine(r); + Coroutine.yield(); + } + + int rss1 = getRssInKb(); + System.out.println(rss1); + if (rss1 - rss0 > 1024) { // 1M + throw new Error("user created coroutine mem leak"); + } + } + + + private static int getRssInKb() throws IOException { + try (BufferedReader br = new BufferedReader(new FileReader("/proc/self/status"))) { + int rss = -1; + String line; + while ((line = br.readLine()) != null) { + //i.e. VmRSS: 360 kB + if (line.trim().startsWith("VmRSS:")) { + int numEnd = line.length() - 3; + int numBegin = line.lastIndexOf(" ", numEnd - 1) + 1; + rss = Integer.parseInt(line.substring(numBegin, numEnd)); + break; + } + } + return rss; + } + } +} diff --git a/test/runtime/coroutine/coroutineBreakpointSwitchToTest.c b/test/runtime/coroutine/coroutineBreakpointSwitchToTest.c new file mode 100644 index 0000000000000000000000000000000000000000..0df1c19bd8c450bdfc416e37b9a497ee2d31427d --- /dev/null +++ b/test/runtime/coroutine/coroutineBreakpointSwitchToTest.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Alibaba designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include + +void Jvmti_Error(int errcode, const char *msg) { + if (errcode != JVMTI_ERROR_NONE) { + printf("%s, error code: [%d]\n", errcode, msg); + exit(1); + } +} + +#define JVMTI_CHECK(x, str) (Jvmti_Error(x, str)) + +void JNICALL +SingleStepCallBack(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jmethodID method, + jlocation location) +{ +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + + // 1. get the jvmti env + jvmtiEnv *jvmti = NULL; + JVMTI_CHECK((*jvm)->GetEnv(jvm, (void **)&jvmti, JVMTI_VERSION_1), "env get error"); + + // 2. grant the ability to the jvmti: can single step + jvmtiCapabilities noryoku; + memset((void *)&noryoku, 0, sizeof(jvmtiCapabilities)); + noryoku.can_generate_single_step_events = 1; + JVMTI_CHECK((*jvmti)->AddCapabilities(jvmti, &noryoku), "add capability error"); + + // 3. set: every single step, we can get a notification. + JVMTI_CHECK((*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, NULL), "set notification failed"); + + // 4. when a notification happens, we will get a callback! This callback will booooom. + jvmtiEventCallbacks callbacks; + memset((void *)&callbacks, 0, sizeof(callbacks)); + callbacks.SingleStep = &SingleStepCallBack; + JVMTI_CHECK((*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks)), "set callback failed"); + + return 0; +} diff --git a/test/runtime/coroutine/coroutineBreakpointSwitchToTest.sh b/test/runtime/coroutine/coroutineBreakpointSwitchToTest.sh new file mode 100644 index 0000000000000000000000000000000000000000..b2ea7061fb27559631002eef977e4c77f02f3bdc --- /dev/null +++ b/test/runtime/coroutine/coroutineBreakpointSwitchToTest.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +# +# Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Alibaba designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# +# @test +# @library /testlibrary +# @compile SimpleWispTest.java +# +# @summary test Coroutine SwitchTo() crash problem +# @run shell coroutineBreakpointSwitchToTest.sh +# + +OS=`uname -s` +case "$OS" in + AIX | Darwin | Linux | SunOS ) + FS="/" + ;; + Windows_* ) + FS="\\" + ;; + CYGWIN_* ) + FS="/" + ;; + * ) + echo "Unrecognized system!" + exit 1; + ;; +esac + +JAVA=${TESTJAVA}${FS}bin${FS}java + +export LD_LIBRARY_PATH=.:${TESTJAVA}${FS}jre${FS}lib${FS}amd64${FS}server:${FS}usr${FS}lib:${LD_LIBRARY_PATH} + +gcc_cmd=`which gcc` +if [ "x${gcc_cmd}" = "x" ]; then + echo "WARNING: gcc not found. Cannot execute test." 2>&1 + exit 0 +fi + +gcc -DLINUX -fPIC -shared -o libtest.so \ + -I${COMPILEJAVA}/include -I${COMPILEJAVA}/include/linux \ + ${TESTSRC}/coroutineBreakpointSwitchToTest.c + +${JAVA} -agentpath:libtest.so -XX:-UseBiasedLocking -XX:+EnableCoroutine -XX:+UseWispMonitor -Dcom.alibaba.transparentAsync=true -cp ${TESTCLASSES} SimpleWispTest + +exit $? diff --git a/test/runtime/coroutine/logCompilationTest.sh b/test/runtime/coroutine/logCompilationTest.sh new file mode 100644 index 0000000000000000000000000000000000000000..0b83160820c15b40fe967e9dd87f48433cc1e631 --- /dev/null +++ b/test/runtime/coroutine/logCompilationTest.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +# +# Copyright (c) 2020 Alibaba Group Holding Limited. All Rights Reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Alibaba designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# + +# +# @test +# @library /testlibrary +# @compile SimpleWispTest.java +# +# @summary test coroutine and -XX:+LogCompilation could work together +# @run shell logCompilationTest.sh +# + +OS=`uname -s` +case "$OS" in + AIX | Darwin | Linux | SunOS ) + FS="/" + ;; + Windows_* ) + FS="\\" + ;; + CYGWIN_* ) + FS="/" + ;; + * ) + echo "Unrecognized system!" + exit 1; + ;; +esac + +JAVA=${TESTJAVA}${FS}bin${FS}java + +${JAVA} -XX:+UnlockDiagnosticVMOptions -XX:+EnableCoroutine -XX:+UseWispMonitor -Dcom.alibaba.transparentAsync=true -XX:+LogCompilation -Xcomp -cp ${TESTCLASSES} SimpleWispTest + +exit $? \ No newline at end of file