| /* Copyright (c) 2019, 2022, 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. |
| * |
| */ |
| |
| #ifndef SHARE_OOPS_STACKCHUNKFRAMESTREAM_INLINE_HPP |
| #define SHARE_OOPS_STACKCHUNKFRAMESTREAM_INLINE_HPP |
| |
| #include "runtime/stackChunkFrameStream.hpp" |
| |
| #include "code/codeCache.inline.hpp" |
| #include "compiler/oopMap.hpp" |
| #include "interpreter/interpreter.hpp" |
| #include "logging/log.hpp" |
| #include "oops/method.hpp" |
| #include "oops/oop.hpp" |
| #include "oops/stackChunkOop.inline.hpp" |
| #include "oops/instanceStackChunkKlass.inline.hpp" |
| #include "runtime/frame.inline.hpp" |
| #include "utilities/debug.hpp" |
| #include "utilities/devirtualizer.inline.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| #include "utilities/macros.hpp" |
| #include CPU_HEADER_INLINE(stackChunkFrameStream) |
| |
| #ifdef ASSERT |
| extern "C" bool dbg_is_safe(const void* p, intptr_t errvalue); |
| #endif |
| |
| template <ChunkFrames frame_kind> |
| StackChunkFrameStream<frame_kind>::StackChunkFrameStream(stackChunkOop chunk) DEBUG_ONLY(: _chunk(chunk)) { |
| assert(chunk->is_stackChunk_noinline(), ""); |
| assert(frame_kind == ChunkFrames::Mixed || !chunk->has_mixed_frames(), ""); |
| |
| DEBUG_ONLY(_index = 0;) |
| _end = chunk->bottom_address(); |
| _sp = chunk->start_address() + chunk->sp(); |
| assert(_sp <= chunk->end_address() + frame::metadata_words, ""); |
| |
| get_cb(); |
| |
| if (frame_kind == ChunkFrames::Mixed) { |
| _unextended_sp = (!is_done() && is_interpreted()) ? unextended_sp_for_interpreter_frame() : _sp; |
| assert(_unextended_sp >= _sp - frame::metadata_words, ""); |
| } |
| DEBUG_ONLY(else _unextended_sp = nullptr;) |
| |
| if (is_stub()) { |
| get_oopmap(pc(), 0); |
| DEBUG_ONLY(_has_stub = true); |
| } DEBUG_ONLY(else _has_stub = false;) |
| } |
| |
| template <ChunkFrames frame_kind> |
| StackChunkFrameStream<frame_kind>::StackChunkFrameStream(stackChunkOop chunk, const frame& f) |
| DEBUG_ONLY(: _chunk(chunk)) { |
| assert(chunk->is_stackChunk_noinline(), ""); |
| assert(frame_kind == ChunkFrames::Mixed || !chunk->has_mixed_frames(), ""); |
| // assert(!is_empty(), ""); -- allowed to be empty |
| |
| DEBUG_ONLY(_index = 0;) |
| |
| _end = chunk->bottom_address(); |
| |
| assert(chunk->is_in_chunk(f.sp()), ""); |
| _sp = f.sp(); |
| if (frame_kind == ChunkFrames::Mixed) { |
| _unextended_sp = f.unextended_sp(); |
| assert(_unextended_sp >= _sp - frame::metadata_words, ""); |
| } |
| DEBUG_ONLY(else _unextended_sp = nullptr;) |
| assert(_sp >= chunk->start_address(), ""); |
| assert(_sp <= chunk->end_address() + frame::metadata_words, ""); |
| |
| if (f.cb() != nullptr) { |
| _oopmap = nullptr; |
| _cb = f.cb(); |
| } else { |
| get_cb(); |
| } |
| |
| if (is_stub()) { |
| get_oopmap(pc(), 0); |
| DEBUG_ONLY(_has_stub = true); |
| } DEBUG_ONLY(else _has_stub = false;) |
| } |
| |
| template <ChunkFrames frame_kind> |
| inline bool StackChunkFrameStream<frame_kind>::is_stub() const { |
| return cb() != nullptr && (_cb->is_safepoint_stub() || _cb->is_runtime_stub()); |
| } |
| |
| template <ChunkFrames frame_kind> |
| inline bool StackChunkFrameStream<frame_kind>::is_compiled() const { |
| return cb() != nullptr && _cb->is_compiled(); |
| } |
| |
| template <> |
| inline bool StackChunkFrameStream<ChunkFrames::Mixed>::is_interpreted() const { |
| return !is_done() && Interpreter::contains(pc()); |
| } |
| |
| template <> |
| inline bool StackChunkFrameStream<ChunkFrames::CompiledOnly>::is_interpreted() const { |
| return false; |
| } |
| |
| // StackChunkFrameStream<frame_kind>::frame_size() returns the words required to |
| // store the given frame as the only frame in a StackChunk. This is the size of the |
| // frame itself plus its stack arguments plus metadata at the caller's frame top (1) |
| // |
| // |====================| --- |
| // | F0's stackargs | ^ |
| // | | | |
| // |--------------------| | |
| // | metadata@top | <- caller's sp |
| // |====================| | |
| // | metadata@bottom(2) | | |
| // |--------------------| |
| // | | size S0 |
| // | Frame F0 | --- |====================| --- |
| // | | | ^ | F1's stackargs | ^ |
| // | | | | | | | |
| // |--------------------| | overlap |--------------------| | |
| // | metadata@top(1) |<- sp v v | metadata@top | <- caller's sp |
| // |====================| --- --- |====================| | |
| // | metadata@bottom | | |
| // | |--------------------| |
| // | | Frame F1 | size S1 |
| // Stack Growth | (F0's callee) | |
| // | | | | |
| // | | | | |
| // v |--------------------| | |
| // | metadata@top |<- sp v |
| // |====================| --- |
| // |
| // 2 frames of the same kind (interpreted or compiled) overlap. So the total |
| // size required in the StackChunk is S0 + S1 - overlap, where the overlap is |
| // the size of F1's stackargs plus frame::metadata_words_at_top. |
| // |
| // The callers of frame_size() are supposed to deduct the overlap. The bottom |
| // frame in the StackChunk obviously does not overlap with it's caller, as it is |
| // in the parent chunk. |
| // |
| // There is no overlap if caller/callee are of different kinds. In that case the |
| // caller is extended to accomodate the callee's stack arguments. The extension |
| // is not counted though in the caller's size, so there is indeed no overlap. |
| // |
| // See ppc implementation of StackChunkFrameStream<frame_kind>::interpreter_frame_size() |
| // for more details. |
| // |
| // (1) Metadata at frame top (see frame::metadata_words_at_top) |
| // Part of the overlap. Used on ppc64, empty on x86_64, aarch64 |
| // (2) Metadata at the frame bottom (see frame::metadata_words_at_bottom) |
| // Not part of the overlap. |
| // Used on x86_64 (saved rbp, ret. addr.), aarch64. Empty on ppc64. |
| // |
| template <ChunkFrames frame_kind> |
| inline int StackChunkFrameStream<frame_kind>::frame_size() const { |
| return is_interpreted() ? interpreter_frame_size() |
| : cb()->frame_size() + stack_argsize() + frame::metadata_words_at_top; |
| } |
| |
| template <ChunkFrames frame_kind> |
| inline int StackChunkFrameStream<frame_kind>::stack_argsize() const { |
| if (is_interpreted()) { |
| return interpreter_frame_stack_argsize(); |
| } |
| if (is_stub()) { |
| return 0; |
| } |
| assert(cb() != nullptr, ""); |
| assert(cb()->is_compiled(), ""); |
| assert(cb()->as_compiled_method()->method() != nullptr, ""); |
| return (cb()->as_compiled_method()->method()->num_stack_arg_slots() * VMRegImpl::stack_slot_size) >> LogBytesPerWord; |
| } |
| |
| template <ChunkFrames frame_kind> |
| inline int StackChunkFrameStream<frame_kind>::num_oops() const { |
| return is_interpreted() ? interpreter_frame_num_oops() : oopmap()->num_oops(); |
| } |
| |
| template <ChunkFrames frame_kind> |
| inline void StackChunkFrameStream<frame_kind>::initialize_register_map(RegisterMap* map) { |
| update_reg_map_pd(map); |
| } |
| |
| template <ChunkFrames frame_kind> |
| template <typename RegisterMapT> |
| inline void StackChunkFrameStream<frame_kind>::next(RegisterMapT* map, bool stop) { |
| update_reg_map(map); |
| bool safepoint = is_stub(); |
| if (frame_kind == ChunkFrames::Mixed) { |
| if (is_interpreted()) { |
| next_for_interpreter_frame(); |
| } else { |
| _sp = _unextended_sp + cb()->frame_size(); |
| if (_sp >= _end - frame::metadata_words) { |
| _sp = _end; |
| } |
| _unextended_sp = is_interpreted() ? unextended_sp_for_interpreter_frame() : _sp; |
| } |
| assert(_unextended_sp >= _sp - frame::metadata_words, ""); |
| } else { |
| _sp += cb()->frame_size(); |
| } |
| assert(!is_interpreted() || _unextended_sp == unextended_sp_for_interpreter_frame(), ""); |
| |
| DEBUG_ONLY(_index++;) |
| if (stop) { |
| return; |
| } |
| |
| get_cb(); |
| update_reg_map_pd(map); |
| if (safepoint && cb() != nullptr) { // there's no post-call nop and no fast oopmap lookup |
| _oopmap = cb()->oop_map_for_return_address(pc()); |
| } |
| } |
| |
| template <ChunkFrames frame_kind> |
| inline void StackChunkFrameStream<frame_kind>::get_cb() { |
| _oopmap = nullptr; |
| if (is_done() || is_interpreted()) { |
| _cb = nullptr; |
| return; |
| } |
| |
| assert(pc() != nullptr, ""); |
| assert(dbg_is_safe(pc(), -1), ""); |
| |
| _cb = CodeCache::find_blob_fast(pc()); |
| |
| assert(_cb != nullptr, ""); |
| assert(is_interpreted() || ((is_stub() || is_compiled()) && _cb->frame_size() > 0), ""); |
| } |
| |
| template <ChunkFrames frame_kind> |
| inline void StackChunkFrameStream<frame_kind>::get_oopmap() const { |
| if (is_interpreted()) { |
| return; |
| } |
| assert(is_compiled(), ""); |
| get_oopmap(pc(), CodeCache::find_oopmap_slot_fast(pc())); |
| } |
| |
| template <ChunkFrames frame_kind> |
| inline void StackChunkFrameStream<frame_kind>::get_oopmap(address pc, int oopmap_slot) const { |
| assert(cb() != nullptr, ""); |
| assert(!is_compiled() || !cb()->as_compiled_method()->is_deopt_pc(pc), ""); |
| if (oopmap_slot >= 0) { |
| assert(oopmap_slot >= 0, ""); |
| assert(cb()->oop_map_for_slot(oopmap_slot, pc) != nullptr, ""); |
| assert(cb()->oop_map_for_slot(oopmap_slot, pc) == cb()->oop_map_for_return_address(pc), ""); |
| |
| _oopmap = cb()->oop_map_for_slot(oopmap_slot, pc); |
| } else { |
| _oopmap = cb()->oop_map_for_return_address(pc); |
| } |
| assert(_oopmap != nullptr, ""); |
| } |
| |
| template <ChunkFrames frame_kind> |
| template <typename RegisterMapT> |
| inline void* StackChunkFrameStream<frame_kind>::reg_to_loc(VMReg reg, const RegisterMapT* map) const { |
| assert(!is_done(), ""); |
| return reg->is_reg() ? (void*)map->location(reg, sp()) // see frame::update_map_with_saved_link(&map, link_addr); |
| : (void*)((address)unextended_sp() + (reg->reg2stack() * VMRegImpl::stack_slot_size)); |
| } |
| |
| template<> |
| template<> |
| inline void StackChunkFrameStream<ChunkFrames::Mixed>::update_reg_map(RegisterMap* map) { |
| assert(!map->in_cont() || map->stack_chunk() == _chunk, ""); |
| if (map->update_map() && is_stub()) { |
| frame f = to_frame(); |
| oopmap()->update_register_map(&f, map); // we have callee-save registers in this case |
| } |
| } |
| |
| template<> |
| template<> |
| inline void StackChunkFrameStream<ChunkFrames::CompiledOnly>::update_reg_map(RegisterMap* map) { |
| assert(map->in_cont(), ""); |
| assert(map->stack_chunk()() == _chunk, ""); |
| if (map->update_map()) { |
| frame f = to_frame(); |
| oopmap()->update_register_map(&f, map); // we have callee-save registers in this case |
| } |
| } |
| |
| template <ChunkFrames frame_kind> |
| template <typename RegisterMapT> |
| inline void StackChunkFrameStream<frame_kind>::update_reg_map(RegisterMapT* map) {} |
| |
| template <ChunkFrames frame_kind> |
| inline address StackChunkFrameStream<frame_kind>::orig_pc() const { |
| address pc1 = pc(); |
| if (is_interpreted() || is_stub()) { |
| return pc1; |
| } |
| CompiledMethod* cm = cb()->as_compiled_method(); |
| if (cm->is_deopt_pc(pc1)) { |
| pc1 = *(address*)((address)unextended_sp() + cm->orig_pc_offset()); |
| } |
| |
| assert(pc1 != nullptr, ""); |
| assert(!cm->is_deopt_pc(pc1), ""); |
| assert(_cb == CodeCache::find_blob_fast(pc1), ""); |
| |
| return pc1; |
| } |
| |
| template<ChunkFrames frame_kind> |
| void StackChunkFrameStream<frame_kind>::handle_deopted() const { |
| assert(!is_done(), ""); |
| |
| if (_oopmap != nullptr) { |
| return; |
| } |
| if (is_interpreted()) { |
| return; |
| } |
| assert(is_compiled(), ""); |
| |
| address pc1 = pc(); |
| int oopmap_slot = CodeCache::find_oopmap_slot_fast(pc1); |
| if (oopmap_slot < 0) { // UNLIKELY; we could have marked frames for deoptimization in thaw_chunk |
| if (cb()->as_compiled_method()->is_deopt_pc(pc1)) { |
| pc1 = orig_pc(); |
| oopmap_slot = CodeCache::find_oopmap_slot_fast(pc1); |
| } |
| } |
| get_oopmap(pc1, oopmap_slot); |
| } |
| |
| template <ChunkFrames frame_kind> |
| template <class OopClosureType, class RegisterMapT> |
| inline void StackChunkFrameStream<frame_kind>::iterate_oops(OopClosureType* closure, const RegisterMapT* map) const { |
| if (is_interpreted()) { |
| frame f = to_frame(); |
| f.oops_interpreted_do(closure, nullptr, true); |
| } else { |
| DEBUG_ONLY(int oops = 0;) |
| for (OopMapStream oms(oopmap()); !oms.is_done(); oms.next()) { |
| OopMapValue omv = oms.current(); |
| if (omv.type() != OopMapValue::oop_value && omv.type() != OopMapValue::narrowoop_value) { |
| continue; |
| } |
| |
| assert(UseCompressedOops || omv.type() == OopMapValue::oop_value, ""); |
| DEBUG_ONLY(oops++;) |
| |
| void* p = reg_to_loc(omv.reg(), map); |
| assert(p != nullptr, ""); |
| assert((_has_stub && _index == 1) || is_in_frame(p), ""); |
| |
| log_develop_trace(continuations)("StackChunkFrameStream::iterate_oops narrow: %d reg: %s p: " INTPTR_FORMAT " sp offset: " INTPTR_FORMAT, |
| omv.type() == OopMapValue::narrowoop_value, omv.reg()->name(), p2i(p), (intptr_t*)p - sp()); |
| omv.type() == OopMapValue::narrowoop_value ? Devirtualizer::do_oop(closure, (narrowOop*)p) : Devirtualizer::do_oop(closure, (oop*)p); |
| } |
| assert(oops == oopmap()->num_oops(), "oops: %d oopmap->num_oops(): %d", oops, oopmap()->num_oops()); |
| } |
| } |
| |
| template <ChunkFrames frame_kind> |
| template <class DerivedOopClosureType, class RegisterMapT> |
| inline void StackChunkFrameStream<frame_kind>::iterate_derived_pointers(DerivedOopClosureType* closure, const RegisterMapT* map) const { |
| if (!is_compiled()) { |
| // Only compiled frames have derived pointers |
| return; |
| } |
| |
| assert(oopmap()->has_derived_oops() == oopmap()->has_any(OopMapValue::derived_oop_value), ""); |
| if (!oopmap()->has_derived_oops()) { |
| return; |
| } |
| |
| for (OopMapStream oms(oopmap()); !oms.is_done(); oms.next()) { |
| OopMapValue omv = oms.current(); |
| if (omv.type() != OopMapValue::derived_oop_value) { |
| continue; |
| } |
| |
| // see OopMapDo<OopMapFnT, DerivedOopFnT, ValueFilterT>::walk_derived_pointers1 |
| intptr_t* derived_loc = (intptr_t*)reg_to_loc(omv.reg(), map); |
| intptr_t* base_loc = (intptr_t*)reg_to_loc(omv.content_reg(), map); |
| |
| assert((_has_stub && _index == 1) || is_in_frame(base_loc), ""); |
| assert((_has_stub && _index == 1) || is_in_frame(derived_loc), ""); |
| assert(derived_loc != base_loc, "Base and derived in same location"); |
| assert(is_in_oops(base_loc, map), "not found: " INTPTR_FORMAT, p2i(base_loc)); |
| assert(!is_in_oops(derived_loc, map), "found: " INTPTR_FORMAT, p2i(derived_loc)); |
| |
| Devirtualizer::do_derived_oop(closure, (derived_base*)base_loc, (derived_pointer*)derived_loc); |
| } |
| } |
| |
| #ifdef ASSERT |
| |
| template <ChunkFrames frame_kind> |
| template <typename RegisterMapT> |
| bool StackChunkFrameStream<frame_kind>::is_in_oops(void* p, const RegisterMapT* map) const { |
| for (OopMapStream oms(oopmap()); !oms.is_done(); oms.next()) { |
| if (oms.current().type() != OopMapValue::oop_value) { |
| continue; |
| } |
| if (reg_to_loc(oms.current().reg(), map) == p) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| template <ChunkFrames frame_kind> |
| void StackChunkFrameStream<frame_kind>::assert_is_interpreted_and_frame_type_mixed() const { |
| assert(is_interpreted(), ""); |
| assert(frame_kind == ChunkFrames::Mixed, ""); |
| } |
| |
| #endif |
| |
| #endif // SHARE_OOPS_STACKCHUNKFRAMESTREAM_INLINE_HPP |