| /* | |
| * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| * | |
| */ | |
| #include "code/nmethod.hpp" | |
| #include "code/scopeDesc.hpp" | |
| #include "gc/shared/barrierSet.hpp" | |
| #include "gc/shared/barrierSetStackChunk.hpp" | |
| #include "logging/log.hpp" | |
| #include "logging/logStream.hpp" | |
| #include "memory/memRegion.hpp" | |
| #include "oops/instanceStackChunkKlass.inline.hpp" | |
| #include "oops/oop.inline.hpp" | |
| #include "oops/stackChunkOop.inline.hpp" | |
| #include "runtime/frame.hpp" | |
| #include "runtime/registerMap.hpp" | |
| #include "runtime/smallRegisterMap.inline.hpp" | |
| #include "runtime/stackChunkFrameStream.inline.hpp" | |
| // Note: Some functions in this file work with stale object pointers, e.g. | |
| // DerivedPointerSupport. Be extra careful to not put those pointers into | |
| // variables of the 'oop' type. There's extra GC verification around oops | |
| // that may fail when stale oops are being used. | |
| template <typename RegisterMapT> | |
| class FrameOopIterator : public OopIterator { | |
| private: | |
| const frame& _f; | |
| const RegisterMapT* _map; | |
| public: | |
| FrameOopIterator(const frame& f, const RegisterMapT* map) | |
| : _f(f), | |
| _map(map) { | |
| } | |
| virtual void oops_do(OopClosure* cl) override { | |
| if (_f.is_interpreted_frame()) { | |
| _f.oops_interpreted_do(cl, nullptr); | |
| } else { | |
| OopMapDo<OopClosure, DerivedOopClosure, IncludeAllValues> visitor(cl, nullptr); | |
| visitor.oops_do(&_f, _map, _f.oop_map()); | |
| } | |
| } | |
| }; | |
| class LockStackOopIterator : public OopIterator { | |
| private: | |
| const stackChunkOop _chunk; | |
| public: | |
| LockStackOopIterator(const stackChunkOop chunk) : _chunk(chunk) {} | |
| virtual void oops_do(OopClosure* cl) override { | |
| int cnt = _chunk->lockstack_size(); | |
| oop* lockstack_start = (oop*)_chunk->start_address(); | |
| for (int i = 0; i < cnt; i++) { | |
| cl->do_oop(&lockstack_start[i]); | |
| } | |
| } | |
| }; | |
| frame stackChunkOopDesc::top_frame(RegisterMap* map) { | |
| assert(!is_empty(), ""); | |
| StackChunkFrameStream<ChunkFrames::Mixed> fs(this); | |
| map->set_stack_chunk(this); | |
| fs.initialize_register_map(map); | |
| frame f = fs.to_frame(); | |
| assert(to_offset(f.sp()) == sp(), "f.offset_sp(): %d sp(): %d async: %d", f.offset_sp(), sp(), map->is_async()); | |
| relativize_frame(f); | |
| f.set_frame_index(0); | |
| return f; | |
| } | |
| frame stackChunkOopDesc::sender(const frame& f, RegisterMap* map) { | |
| assert(map->in_cont(), ""); | |
| assert(!map->include_argument_oops(), ""); | |
| assert(!f.is_empty(), ""); | |
| assert(map->stack_chunk() == this, ""); | |
| assert(!is_empty(), ""); | |
| int index = f.frame_index(); // we need to capture the index before calling derelativize, which destroys it | |
| StackChunkFrameStream<ChunkFrames::Mixed> fs(this, derelativize(f)); | |
| fs.next(map); | |
| if (!fs.is_done()) { | |
| frame sender = fs.to_frame(); | |
| assert(is_usable_in_chunk(sender.unextended_sp()), ""); | |
| relativize_frame(sender); | |
| sender.set_frame_index(index+1); | |
| return sender; | |
| } | |
| if (parent() != nullptr) { | |
| assert(!parent()->is_empty(), ""); | |
| return parent()->top_frame(map); | |
| } | |
| return Continuation::continuation_parent_frame(map); | |
| } | |
| static int num_java_frames(nmethod* nm, address pc) { | |
| int count = 0; | |
| for (ScopeDesc* scope = nm->scope_desc_at(pc); scope != nullptr; scope = scope->sender()) { | |
| count++; | |
| } | |
| return count; | |
| } | |
| static int num_java_frames(const StackChunkFrameStream<ChunkFrames::Mixed>& f) { | |
| assert(f.is_interpreted() | |
| || (f.cb() != nullptr && f.cb()->is_nmethod() && f.cb()->as_nmethod()->is_java_method()), ""); | |
| return f.is_interpreted() ? 1 : num_java_frames(f.cb()->as_nmethod(), f.orig_pc()); | |
| } | |
| int stackChunkOopDesc::num_java_frames() const { | |
| int n = 0; | |
| for (StackChunkFrameStream<ChunkFrames::Mixed> f(const_cast<stackChunkOopDesc*>(this)); !f.is_done(); | |
| f.next(SmallRegisterMap::instance())) { | |
| if (!f.is_stub()) { | |
| n += ::num_java_frames(f); | |
| } | |
| } | |
| return n; | |
| } | |
| template <stackChunkOopDesc::BarrierType barrier> | |
| class DoBarriersStackClosure { | |
| const stackChunkOop _chunk; | |
| public: | |
| DoBarriersStackClosure(stackChunkOop chunk) : _chunk(chunk) {} | |
| template <ChunkFrames frame_kind, typename RegisterMapT> | |
| bool do_frame(const StackChunkFrameStream<frame_kind>& f, const RegisterMapT* map) { | |
| _chunk->do_barriers0<barrier>(f, map); | |
| return true; | |
| } | |
| }; | |
| template <stackChunkOopDesc::BarrierType barrier> | |
| void stackChunkOopDesc::do_barriers() { | |
| DoBarriersStackClosure<barrier> closure(this); | |
| iterate_stack(&closure); | |
| } | |
| template void stackChunkOopDesc::do_barriers<stackChunkOopDesc::BarrierType::Load> (); | |
| template void stackChunkOopDesc::do_barriers<stackChunkOopDesc::BarrierType::Store>(); | |
| class DerivedPointersSupport { | |
| public: | |
| static void relativize(derived_base* base_loc, derived_pointer* derived_loc) { | |
| // The base oop could be stale from the GC's point-of-view. Treat it as an | |
| // uintptr_t to stay clear of the oop verification code in oopsHierarcy.hpp. | |
| uintptr_t base = *(uintptr_t*)base_loc; | |
| if (base == 0) { | |
| return; | |
| } | |
| assert(!UseCompressedOops || !CompressedOops::is_base((void*)base), ""); | |
| // This is always a full derived pointer | |
| uintptr_t derived_int_val = *(uintptr_t*)derived_loc; | |
| // Make the pointer an offset (relativize) and store it at the same location | |
| uintptr_t offset = derived_int_val - base; | |
| *(uintptr_t*)derived_loc = offset; | |
| } | |
| static void derelativize(derived_base* base_loc, derived_pointer* derived_loc) { | |
| uintptr_t base = *(uintptr_t*)base_loc; | |
| if (base == 0) { | |
| return; | |
| } | |
| assert(!UseCompressedOops || !CompressedOops::is_base((void*)base), ""); | |
| // All derived pointers should have been relativized into offsets | |
| uintptr_t offset = *(uintptr_t*)derived_loc; | |
| // Restore the original derived pointer | |
| *(uintptr_t*)derived_loc = base + offset; | |
| } | |
| struct RelativizeClosure : public DerivedOopClosure { | |
| virtual void do_derived_oop(derived_base* base_loc, derived_pointer* derived_loc) override { | |
| DerivedPointersSupport::relativize(base_loc, derived_loc); | |
| } | |
| }; | |
| struct DerelativizeClosure : public DerivedOopClosure { | |
| virtual void do_derived_oop(derived_base* base_loc, derived_pointer* derived_loc) override { | |
| DerivedPointersSupport::derelativize(base_loc, derived_loc); | |
| } | |
| }; | |
| }; | |
| template <typename DerivedPointerClosureType> | |
| class EncodeGCModeConcurrentFrameClosure { | |
| stackChunkOop _chunk; | |
| DerivedPointerClosureType* _cl; | |
| public: | |
| EncodeGCModeConcurrentFrameClosure(stackChunkOop chunk, DerivedPointerClosureType* cl) | |
| : _chunk(chunk), | |
| _cl(cl) { | |
| } | |
| template <ChunkFrames frame_kind, typename RegisterMapT> | |
| bool do_frame(const StackChunkFrameStream<frame_kind>& f, const RegisterMapT* map) { | |
| f.iterate_derived_pointers(_cl, map); | |
| BarrierSetStackChunk* bs_chunk = BarrierSet::barrier_set()->barrier_set_stack_chunk(); | |
| frame fr = f.to_frame(); | |
| FrameOopIterator<RegisterMapT> iterator(fr, map); | |
| bs_chunk->encode_gc_mode(_chunk, &iterator); | |
| return true; | |
| } | |
| bool do_lockstack() { | |
| BarrierSetStackChunk* bs_chunk = BarrierSet::barrier_set()->barrier_set_stack_chunk(); | |
| LockStackOopIterator iterator(_chunk); | |
| bs_chunk->encode_gc_mode(_chunk, &iterator); | |
| return true; | |
| } | |
| }; | |
| bool stackChunkOopDesc::try_acquire_relativization() { | |
| for (;;) { | |
| // We use an acquiring load when reading the flags to ensure that if we leave this | |
| // function thinking that relativization is finished, we know that if another thread | |
| // did the relativization, we will still be able to observe the relativized derived | |
| // pointers, which is important as subsequent modifications of derived pointers must | |
| // happen after relativization. | |
| uint8_t flags_before = flags_acquire(); | |
| if ((flags_before & FLAG_GC_MODE) != 0) { | |
| // Terminal state - relativization is ensured | |
| return false; | |
| } | |
| if ((flags_before & FLAG_CLAIM_RELATIVIZE) != 0) { | |
| // Someone else has claimed relativization - wait for completion | |
| MonitorLocker ml(ContinuationRelativize_lock, Mutex::_no_safepoint_check_flag); | |
| uint8_t flags_under_lock = flags_acquire(); | |
| if ((flags_under_lock & FLAG_GC_MODE) != 0) { | |
| // Terminal state - relativization is ensured | |
| return false; | |
| } | |
| if ((flags_under_lock & FLAG_NOTIFY_RELATIVIZE) != 0) { | |
| // Relativization is claimed by another thread, and it knows it needs to notify | |
| ml.wait(); | |
| } else if (try_set_flags(flags_under_lock, flags_under_lock | FLAG_NOTIFY_RELATIVIZE)) { | |
| // Relativization is claimed by another thread, and it knows it needs to notify | |
| ml.wait(); | |
| } | |
| // Retry - rerun the loop | |
| continue; | |
| } | |
| if (try_set_flags(flags_before, flags_before | FLAG_CLAIM_RELATIVIZE)) { | |
| // Claimed relativization - let's do it | |
| return true; | |
| } | |
| } | |
| } | |
| void stackChunkOopDesc::release_relativization() { | |
| for (;;) { | |
| uint8_t flags_before = flags(); | |
| if ((flags_before & FLAG_NOTIFY_RELATIVIZE) != 0) { | |
| MonitorLocker ml(ContinuationRelativize_lock, Mutex::_no_safepoint_check_flag); | |
| // No need to CAS the terminal state; nobody else can be racingly mutating here | |
| // as both claim and notify flags are already set (and monotonic) | |
| // We do however need to use a releasing store on the flags, to ensure that | |
| // the reader of that value (using load_acquire) will be able to observe | |
| // the relativization of the derived pointers | |
| uint8_t flags_under_lock = flags(); | |
| release_set_flags(flags_under_lock | FLAG_GC_MODE); | |
| ml.notify_all(); | |
| return; | |
| } | |
| if (try_set_flags(flags_before, flags_before | FLAG_GC_MODE)) { | |
| // Successfully set the terminal state; we are done | |
| return; | |
| } | |
| } | |
| } | |
| void stackChunkOopDesc::relativize_derived_pointers_concurrently() { | |
| if (!try_acquire_relativization()) { | |
| // Already relativized | |
| return; | |
| } | |
| DerivedPointersSupport::RelativizeClosure derived_cl; | |
| EncodeGCModeConcurrentFrameClosure<decltype(derived_cl)> frame_cl(this, &derived_cl); | |
| iterate_stack(&frame_cl); | |
| frame_cl.do_lockstack(); | |
| release_relativization(); | |
| } | |
| class TransformStackChunkClosure { | |
| stackChunkOop _chunk; | |
| public: | |
| TransformStackChunkClosure(stackChunkOop chunk) : _chunk(chunk) { } | |
| template <ChunkFrames frame_kind, typename RegisterMapT> | |
| bool do_frame(const StackChunkFrameStream<frame_kind>& f, const RegisterMapT* map) { | |
| DerivedPointersSupport::RelativizeClosure derived_cl; | |
| f.iterate_derived_pointers(&derived_cl, map); | |
| BarrierSetStackChunk* bs_chunk = BarrierSet::barrier_set()->barrier_set_stack_chunk(); | |
| frame fr = f.to_frame(); | |
| FrameOopIterator<RegisterMapT> iterator(fr, map); | |
| bs_chunk->encode_gc_mode(_chunk, &iterator); | |
| return true; | |
| } | |
| bool do_lockstack() { | |
| BarrierSetStackChunk* bs_chunk = BarrierSet::barrier_set()->barrier_set_stack_chunk(); | |
| LockStackOopIterator iterator(_chunk); | |
| bs_chunk->encode_gc_mode(_chunk, &iterator); | |
| return true; | |
| } | |
| }; | |
| void stackChunkOopDesc::transform() { | |
| assert(!is_gc_mode(), "Should only be called once per chunk"); | |
| set_gc_mode(true); | |
| assert(!has_bitmap(), "Should only be set once"); | |
| set_has_bitmap(true); | |
| bitmap().clear(); | |
| TransformStackChunkClosure closure(this); | |
| iterate_stack(&closure); | |
| closure.do_lockstack(); | |
| } | |
| template <stackChunkOopDesc::BarrierType barrier, bool compressedOopsWithBitmap> | |
| class BarrierClosure: public OopClosure { | |
| NOT_PRODUCT(intptr_t* _sp;) | |
| public: | |
| BarrierClosure(intptr_t* sp) NOT_PRODUCT(: _sp(sp)) {} | |
| virtual void do_oop(oop* p) override { compressedOopsWithBitmap ? do_oop_work((narrowOop*)p) : do_oop_work(p); } | |
| virtual void do_oop(narrowOop* p) override { do_oop_work(p); } | |
| template <class T> inline void do_oop_work(T* p) { | |
| oop value = (oop)HeapAccess<>::oop_load(p); | |
| if (barrier == stackChunkOopDesc::BarrierType::Store) { | |
| HeapAccess<>::oop_store(p, value); | |
| } | |
| } | |
| }; | |
| template <stackChunkOopDesc::BarrierType barrier, ChunkFrames frame_kind, typename RegisterMapT> | |
| void stackChunkOopDesc::do_barriers0(const StackChunkFrameStream<frame_kind>& f, const RegisterMapT* map) { | |
| // We need to invoke the write barriers so as not to miss oops in old chunks that haven't yet been concurrently scanned | |
| assert (!f.is_done(), ""); | |
| if (f.is_interpreted()) { | |
| Method* m = f.to_frame().interpreter_frame_method(); | |
| // Class redefinition support | |
| m->record_gc_epoch(); | |
| } else if (f.is_compiled()) { | |
| nmethod* nm = f.cb()->as_nmethod(); | |
| // The entry barrier takes care of having the right synchronization | |
| // when keeping the nmethod alive during concurrent execution. | |
| nm->run_nmethod_entry_barrier(); | |
| // There is no need to mark the Method, as class redefinition will walk the | |
| // CodeCache, noting their Methods | |
| } | |
| if (has_bitmap() && UseCompressedOops) { | |
| BarrierClosure<barrier, true> oops_closure(f.sp()); | |
| f.iterate_oops(&oops_closure, map); | |
| } else { | |
| BarrierClosure<barrier, false> oops_closure(f.sp()); | |
| f.iterate_oops(&oops_closure, map); | |
| } | |
| } | |
| template void stackChunkOopDesc::do_barriers0<stackChunkOopDesc::BarrierType::Load> (const StackChunkFrameStream<ChunkFrames::Mixed>& f, const RegisterMap* map); | |
| template void stackChunkOopDesc::do_barriers0<stackChunkOopDesc::BarrierType::Store>(const StackChunkFrameStream<ChunkFrames::Mixed>& f, const RegisterMap* map); | |
| template void stackChunkOopDesc::do_barriers0<stackChunkOopDesc::BarrierType::Load> (const StackChunkFrameStream<ChunkFrames::CompiledOnly>& f, const RegisterMap* map); | |
| template void stackChunkOopDesc::do_barriers0<stackChunkOopDesc::BarrierType::Store>(const StackChunkFrameStream<ChunkFrames::CompiledOnly>& f, const RegisterMap* map); | |
| template void stackChunkOopDesc::do_barriers0<stackChunkOopDesc::BarrierType::Load> (const StackChunkFrameStream<ChunkFrames::Mixed>& f, const SmallRegisterMap* map); | |
| template void stackChunkOopDesc::do_barriers0<stackChunkOopDesc::BarrierType::Store>(const StackChunkFrameStream<ChunkFrames::Mixed>& f, const SmallRegisterMap* map); | |
| template void stackChunkOopDesc::do_barriers0<stackChunkOopDesc::BarrierType::Load> (const StackChunkFrameStream<ChunkFrames::CompiledOnly>& f, const SmallRegisterMap* map); | |
| template void stackChunkOopDesc::do_barriers0<stackChunkOopDesc::BarrierType::Store>(const StackChunkFrameStream<ChunkFrames::CompiledOnly>& f, const SmallRegisterMap* map); | |
| template <typename RegisterMapT> | |
| void stackChunkOopDesc::fix_thawed_frame(const frame& f, const RegisterMapT* map) { | |
| if (!(is_gc_mode() || requires_barriers())) { | |
| return; | |
| } | |
| BarrierSetStackChunk* bs_chunk = BarrierSet::barrier_set()->barrier_set_stack_chunk(); | |
| FrameOopIterator<RegisterMapT> iterator(f, map); | |
| bs_chunk->decode_gc_mode(this, &iterator); | |
| if (f.is_compiled_frame() && f.oop_map()->has_derived_oops()) { | |
| DerivedPointersSupport::DerelativizeClosure derived_closure; | |
| OopMapDo<OopClosure, DerivedPointersSupport::DerelativizeClosure, SkipNullValue> visitor(nullptr, &derived_closure); | |
| visitor.oops_do(&f, map, f.oop_map()); | |
| } | |
| } | |
| template void stackChunkOopDesc::fix_thawed_frame(const frame& f, const RegisterMap* map); | |
| template void stackChunkOopDesc::fix_thawed_frame(const frame& f, const SmallRegisterMap* map); | |
| void stackChunkOopDesc::transfer_lockstack(oop* dst, bool requires_barriers) { | |
| const bool requires_gc_barriers = is_gc_mode() || requires_barriers; | |
| const bool requires_uncompress = has_bitmap() && UseCompressedOops; | |
| const auto load_and_clear_obj = [&](intptr_t* at) -> oop { | |
| if (requires_gc_barriers) { | |
| if (requires_uncompress) { | |
| oop value = HeapAccess<>::oop_load(reinterpret_cast<narrowOop*>(at)); | |
| HeapAccess<>::oop_store(reinterpret_cast<narrowOop*>(at), nullptr); | |
| return value; | |
| } else { | |
| oop value = HeapAccess<>::oop_load(reinterpret_cast<oop*>(at)); | |
| HeapAccess<>::oop_store(reinterpret_cast<oop*>(at), nullptr); | |
| return value; | |
| } | |
| } else { | |
| oop value = *reinterpret_cast<oop*>(at); | |
| return value; | |
| } | |
| }; | |
| const int cnt = lockstack_size(); | |
| intptr_t* lockstack_start = start_address(); | |
| for (int i = 0; i < cnt; i++) { | |
| oop mon_owner = load_and_clear_obj(&lockstack_start[i]); | |
| assert(oopDesc::is_oop(mon_owner), "not an oop"); | |
| dst[i] = mon_owner; | |
| } | |
| } | |
| void stackChunkOopDesc::print_on(bool verbose, outputStream* st) const { | |
| if (*((juint*)this) == badHeapWordVal) { | |
| st->print_cr("BAD WORD"); | |
| } else { | |
| InstanceStackChunkKlass::print_chunk(const_cast<stackChunkOopDesc*>(this), verbose, st); | |
| } | |
| } | |
| #ifdef ASSERT | |
| class StackChunkVerifyOopsClosure : public OopClosure { | |
| stackChunkOop _chunk; | |
| int _count; | |
| public: | |
| StackChunkVerifyOopsClosure(stackChunkOop chunk) | |
| : _chunk(chunk), _count(0) {} | |
| void do_oop(oop* p) override { (_chunk->has_bitmap() && UseCompressedOops) ? do_oop_work((narrowOop*)p) : do_oop_work(p); } | |
| void do_oop(narrowOop* p) override { do_oop_work(p); } | |
| template <typename T> inline void do_oop_work(T* p) { | |
| _count++; | |
| oop obj = _chunk->load_oop(p); | |
| assert(obj == nullptr || dbg_is_good_oop(obj), "p: " PTR_FORMAT " obj: " PTR_FORMAT, p2i(p), p2i(obj)); | |
| if (_chunk->has_bitmap()) { | |
| BitMap::idx_t index = _chunk->bit_index_for(p); | |
| assert(_chunk->bitmap().at(index), "Bit not set at index %zu corresponding to " PTR_FORMAT, index, p2i(p)); | |
| } | |
| } | |
| int count() const { return _count; } | |
| }; | |
| class VerifyStackChunkFrameClosure { | |
| stackChunkOop _chunk; | |
| public: | |
| intptr_t* _sp; | |
| CodeBlob* _cb; | |
| bool _callee_interpreted; | |
| int _size; | |
| int _argsize; | |
| int _num_oops; | |
| int _num_frames; | |
| int _num_interpreted_frames; | |
| int _num_i2c; | |
| VerifyStackChunkFrameClosure(stackChunkOop chunk) | |
| : _chunk(chunk), _sp(nullptr), _cb(nullptr), _callee_interpreted(false), | |
| _size(0), _argsize(0), _num_oops(0), _num_frames(0), _num_interpreted_frames(0), _num_i2c(0) {} | |
| template <ChunkFrames frame_kind, typename RegisterMapT> | |
| bool do_frame(const StackChunkFrameStream<frame_kind>& f, const RegisterMapT* map) { | |
| _sp = f.sp(); | |
| _cb = f.cb(); | |
| int fsize = f.frame_size() - ((f.is_interpreted() == _callee_interpreted) ? _argsize : 0); | |
| int num_oops = f.num_oops(); | |
| assert(num_oops >= 0, ""); | |
| _argsize = f.stack_argsize() + frame::metadata_words_at_top; | |
| _size += fsize; | |
| _num_oops += num_oops; | |
| if (f.is_interpreted()) { | |
| _num_interpreted_frames++; | |
| } | |
| log_develop_trace(continuations)("debug_verify_stack_chunk frame: %d sp: " INTPTR_FORMAT " pc: " PTR_FORMAT " interpreted: %d size: %d argsize: %d oops: %d", _num_frames, f.sp() - _chunk->start_address(), p2i(f.pc()), f.is_interpreted(), fsize, _argsize, num_oops); | |
| LogTarget(Trace, continuations) lt; | |
| if (lt.develop_is_enabled()) { | |
| LogStream ls(lt); | |
| f.print_on(&ls); | |
| } | |
| assert(f.pc() != nullptr, | |
| "young: %d num_frames: %d sp: " PTR_FORMAT " start: " PTR_FORMAT " end: " PTR_FORMAT, | |
| !_chunk->requires_barriers(), _num_frames, p2i(f.sp()), p2i(_chunk->start_address()), p2i(_chunk->bottom_address())); | |
| if (_num_frames == 0) { | |
| assert(f.pc() == _chunk->pc(), ""); | |
| } | |
| if (_num_frames > 0 && !_callee_interpreted && f.is_interpreted()) { | |
| log_develop_trace(continuations)("debug_verify_stack_chunk i2c"); | |
| _num_i2c++; | |
| } | |
| StackChunkVerifyOopsClosure oops_closure(_chunk); | |
| f.iterate_oops(&oops_closure, map); | |
| assert(oops_closure.count() == num_oops, "oops: %d oopmap->num_oops(): %d", oops_closure.count(), num_oops); | |
| _callee_interpreted = f.is_interpreted(); | |
| _num_frames++; | |
| return true; | |
| } | |
| }; | |
| template <typename T> | |
| class StackChunkVerifyBitmapClosure : public BitMapClosure { | |
| stackChunkOop _chunk; | |
| public: | |
| int _count; | |
| StackChunkVerifyBitmapClosure(stackChunkOop chunk) : _chunk(chunk), _count(0) {} | |
| bool do_bit(BitMap::idx_t index) override { | |
| T* p = _chunk->address_for_bit<T>(index); | |
| _count++; | |
| oop obj = _chunk->load_oop(p); | |
| assert(obj == nullptr || dbg_is_good_oop(obj), | |
| "p: " PTR_FORMAT " obj: " PTR_FORMAT " index: %zu", | |
| p2i(p), p2i((oopDesc*)obj), index); | |
| return true; // continue processing | |
| } | |
| }; | |
| bool stackChunkOopDesc::verify(size_t* out_size, int* out_oops, int* out_frames, int* out_interpreted_frames) { | |
| DEBUG_ONLY(if (!VerifyContinuations) return true;) | |
| assert(oopDesc::is_oop(this), ""); | |
| assert(stack_size() >= 0, ""); | |
| assert(!has_bitmap() || is_gc_mode(), ""); | |
| if (is_empty()) { | |
| assert(max_thawing_size() == 0, ""); | |
| } else { | |
| assert(argsize() >= 0, ""); | |
| } | |
| assert(oopDesc::is_oop_or_null(parent()), ""); | |
| const bool concurrent = !Thread::current()->is_Java_thread(); | |
| // If argsize == 0 and the chunk isn't mixed, the chunk contains the metadata (pc, fp -- frame::sender_sp_offset) | |
| // for the top frame (below sp), and *not* for the bottom frame. | |
| int size = bottom() - sp(); | |
| assert(size >= 0, ""); | |
| assert((size == 0) == is_empty(), ""); | |
| const StackChunkFrameStream<ChunkFrames::Mixed> first(this); | |
| VerifyStackChunkFrameClosure closure(this); | |
| iterate_stack(&closure); | |
| assert(!is_empty() || closure._cb == nullptr, ""); | |
| if (closure._cb != nullptr && closure._cb->is_nmethod()) { | |
| assert(argsize() == | |
| (closure._cb->as_nmethod()->num_stack_arg_slots()*VMRegImpl::stack_slot_size) >>LogBytesPerWord, | |
| "chunk argsize: %d bottom frame argsize: %d", argsize(), | |
| (closure._cb->as_nmethod()->num_stack_arg_slots()*VMRegImpl::stack_slot_size) >>LogBytesPerWord); | |
| } | |
| assert(closure._num_interpreted_frames == 0 || has_mixed_frames(), ""); | |
| if (!concurrent) { | |
| assert(closure._size <= size + (stack_size() - bottom()), | |
| "size: %d bottom: %d closure.size: %d end sp: " PTR_FORMAT " start sp: %d chunk size: %d", | |
| size, bottom(), closure._size, closure._sp - start_address(), sp(), stack_size()); | |
| if (closure._num_frames > 0) { | |
| assert(closure._argsize >= frame::metadata_words_at_top, "should be set up"); | |
| assert(argsize() == closure._argsize - frame::metadata_words_at_top, | |
| "argsize(): %d closure.argsize: %d closure.callee_interpreted: %d", | |
| argsize(), closure._argsize, closure._callee_interpreted); | |
| } | |
| int calculated_max_size = closure._size | |
| + closure._num_i2c * frame::align_wiggle | |
| + closure._num_interpreted_frames * frame::align_wiggle; | |
| assert(max_thawing_size() == calculated_max_size, | |
| "max_size(): %d calculated_max_size: %d argsize: %d num_i2c: %d", | |
| max_thawing_size(), calculated_max_size, closure._argsize, closure._num_i2c); | |
| if (out_size != nullptr) *out_size += size; | |
| if (out_oops != nullptr) *out_oops += closure._num_oops; | |
| if (out_frames != nullptr) *out_frames += closure._num_frames; | |
| if (out_interpreted_frames != nullptr) *out_interpreted_frames += closure._num_interpreted_frames; | |
| } else { | |
| assert(out_size == nullptr, ""); | |
| assert(out_oops == nullptr, ""); | |
| assert(out_frames == nullptr, ""); | |
| assert(out_interpreted_frames == nullptr, ""); | |
| } | |
| if (has_bitmap()) { | |
| assert(bitmap().size() == InstanceStackChunkKlass::bitmap_size_in_bits(stack_size()), | |
| "bitmap().size(): %zu stack_size: %d", | |
| bitmap().size(), stack_size()); | |
| int oop_count; | |
| if (UseCompressedOops) { | |
| StackChunkVerifyBitmapClosure<narrowOop> bitmap_closure(this); | |
| bitmap().iterate(&bitmap_closure, | |
| bit_index_for((narrowOop*)(sp_address() - frame::metadata_words_at_bottom)), | |
| bit_index_for((narrowOop*)end_address())); | |
| oop_count = bitmap_closure._count; | |
| } else { | |
| StackChunkVerifyBitmapClosure<oop> bitmap_closure(this); | |
| bitmap().iterate(&bitmap_closure, | |
| bit_index_for((oop*)(sp_address() - frame::metadata_words_at_bottom)), | |
| bit_index_for((oop*)end_address())); | |
| oop_count = bitmap_closure._count; | |
| } | |
| assert(oop_count == closure._num_oops, | |
| "bitmap_closure._count: %d closure._num_oops: %d", oop_count, closure._num_oops); | |
| } | |
| return true; | |
| } | |
| #endif |