| /* | |
| * Copyright (c) 1997, 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 "classfile/javaClasses.inline.hpp" | |
| #include "code/codeCache.hpp" | |
| #include "code/debugInfoRec.hpp" | |
| #include "code/nmethod.hpp" | |
| #include "code/pcDesc.hpp" | |
| #include "code/scopeDesc.hpp" | |
| #include "interpreter/interpreter.hpp" | |
| #include "interpreter/oopMapCache.hpp" | |
| #include "oops/instanceKlass.hpp" | |
| #include "oops/oop.inline.hpp" | |
| #include "prims/jvmtiDeferredUpdates.hpp" | |
| #include "runtime/basicLock.hpp" | |
| #include "runtime/continuation.hpp" | |
| #include "runtime/frame.inline.hpp" | |
| #include "runtime/handles.inline.hpp" | |
| #include "runtime/monitorChunk.hpp" | |
| #include "runtime/registerMap.hpp" | |
| #include "runtime/signature.hpp" | |
| #include "runtime/stackValue.hpp" | |
| #include "runtime/stubRoutines.hpp" | |
| #include "runtime/vframeArray.hpp" | |
| #include "runtime/vframe_hp.hpp" | |
| // ------------- compiledVFrame -------------- | |
| StackValueCollection* compiledVFrame::locals() const { | |
| // Natives has no scope | |
| if (scope() == nullptr) return new StackValueCollection(0); | |
| GrowableArray<ScopeValue*>* scv_list = scope()->locals(); | |
| if (scv_list == nullptr) return new StackValueCollection(0); | |
| // scv_list is the list of ScopeValues describing the JVM stack state. | |
| // There is one scv_list entry for every JVM stack state in use. | |
| int length = scv_list->length(); | |
| StackValueCollection* result = new StackValueCollection(length); | |
| for (int i = 0; i < length; i++) { | |
| result->add(create_stack_value(scv_list->at(i))); | |
| } | |
| // Replace the original values with any stores that have been | |
| // performed through compiledVFrame::update_locals. | |
| if (!register_map()->in_cont()) { // LOOM TODO | |
| GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread()); | |
| if (list != nullptr ) { | |
| // In real life this never happens or is typically a single element search | |
| for (int i = 0; i < list->length(); i++) { | |
| if (list->at(i)->matches(this)) { | |
| list->at(i)->update_locals(result); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| void compiledVFrame::set_locals(StackValueCollection* values) const { | |
| fatal("Should use update_local for each local update"); | |
| } | |
| void compiledVFrame::update_local(BasicType type, int index, jvalue value) { | |
| assert(index >= 0 && index < method()->max_locals(), "out of bounds"); | |
| update_deferred_value(type, index, value); | |
| } | |
| void compiledVFrame::update_stack(BasicType type, int index, jvalue value) { | |
| assert(index >= 0 && index < method()->max_stack(), "out of bounds"); | |
| update_deferred_value(type, index + method()->max_locals(), value); | |
| } | |
| void compiledVFrame::update_monitor(int index, MonitorInfo* val) { | |
| assert(index >= 0, "out of bounds"); | |
| jvalue value; | |
| value.l = cast_from_oop<jobject>(val->owner()); | |
| update_deferred_value(T_OBJECT, index + method()->max_locals() + method()->max_stack(), value); | |
| } | |
| void compiledVFrame::update_deferred_value(BasicType type, int index, jvalue value) { | |
| assert(fr().is_deoptimized_frame(), "frame must be scheduled for deoptimization"); | |
| assert(!Continuation::is_frame_in_continuation(thread(), fr()), "No support for deferred values in continuations"); | |
| GrowableArray<jvmtiDeferredLocalVariableSet*>* deferred = JvmtiDeferredUpdates::deferred_locals(thread()); | |
| jvmtiDeferredLocalVariableSet* locals = nullptr; | |
| if (deferred != nullptr ) { | |
| // See if this vframe has already had locals with deferred writes | |
| for (int f = 0; f < deferred->length(); f++ ) { | |
| if (deferred->at(f)->matches(this)) { | |
| locals = deferred->at(f); | |
| break; | |
| } | |
| } | |
| // No matching vframe must push a new vframe | |
| } else { | |
| // No deferred updates pending for this thread. | |
| // allocate in C heap | |
| JvmtiDeferredUpdates::create_for(thread()); | |
| deferred = JvmtiDeferredUpdates::deferred_locals(thread()); | |
| } | |
| if (locals == nullptr) { | |
| locals = new jvmtiDeferredLocalVariableSet(method(), bci(), fr().id(), vframe_id()); | |
| deferred->push(locals); | |
| assert(locals->id() == fr().id(), "Huh? Must match"); | |
| } | |
| locals->set_value_at(index, type, value); | |
| } | |
| // After object deoptimization, that is object reallocation and relocking, we | |
| // create deferred updates for all objects in scope. No new update will be | |
| // created if a deferred update already exists. It is not easy to see how this | |
| // is achieved: the deoptimized objects are in the arrays returned by locals(), | |
| // expressions(), and monitors(). For each object in these arrays we create a | |
| // deferred updated. If an update already exists, then it will override the | |
| // corresponding deoptimized object returned in one of the arrays. So the | |
| // original update is kept. | |
| void compiledVFrame::create_deferred_updates_after_object_deoptimization() { | |
| // locals | |
| GrowableArray<ScopeValue*>* scopedValues = scope()->locals(); | |
| StackValueCollection* lcls = locals(); | |
| if (lcls != nullptr) { | |
| for (int i2 = 0; i2 < lcls->size(); i2++) { | |
| StackValue* var = lcls->at(i2); | |
| if (var->type() == T_OBJECT && scopedValues->at(i2)->is_object()) { | |
| jvalue val; | |
| val.l = cast_from_oop<jobject>(lcls->at(i2)->get_obj()()); | |
| update_local(T_OBJECT, i2, val); | |
| } | |
| } | |
| } | |
| // expressions | |
| GrowableArray<ScopeValue*>* scopeExpressions = scope()->expressions(); | |
| StackValueCollection* exprs = expressions(); | |
| if (exprs != nullptr) { | |
| for (int i2 = 0; i2 < exprs->size(); i2++) { | |
| StackValue* var = exprs->at(i2); | |
| if (var->type() == T_OBJECT && scopeExpressions->at(i2)->is_object()) { | |
| jvalue val; | |
| val.l = cast_from_oop<jobject>(exprs->at(i2)->get_obj()()); | |
| update_stack(T_OBJECT, i2, val); | |
| } | |
| } | |
| } | |
| // monitors | |
| GrowableArray<MonitorInfo*>* mtrs = monitors(); | |
| if (mtrs != nullptr) { | |
| for (int i2 = 0; i2 < mtrs->length(); i2++) { | |
| if (mtrs->at(i2)->eliminated()) { | |
| assert(!mtrs->at(i2)->owner_is_scalar_replaced(), | |
| "reallocation failure, should not update"); | |
| update_monitor(i2, mtrs->at(i2)); | |
| } | |
| } | |
| } | |
| } | |
| StackValueCollection* compiledVFrame::expressions() const { | |
| // Natives has no scope | |
| if (scope() == nullptr) return new StackValueCollection(0); | |
| GrowableArray<ScopeValue*>* scv_list = scope()->expressions(); | |
| if (scv_list == nullptr) return new StackValueCollection(0); | |
| // scv_list is the list of ScopeValues describing the JVM stack state. | |
| // There is one scv_list entry for every JVM stack state in use. | |
| int length = scv_list->length(); | |
| StackValueCollection* result = new StackValueCollection(length); | |
| for (int i = 0; i < length; i++) { | |
| result->add(create_stack_value(scv_list->at(i))); | |
| } | |
| if (!register_map()->in_cont()) { // LOOM TODO | |
| // Replace the original values with any stores that have been | |
| // performed through compiledVFrame::update_stack. | |
| GrowableArray<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread()); | |
| if (list != nullptr ) { | |
| // In real life this never happens or is typically a single element search | |
| for (int i = 0; i < list->length(); i++) { | |
| if (list->at(i)->matches(this)) { | |
| list->at(i)->update_stack(result); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| // The implementation of the following two methods was factorized into the | |
| // class StackValue because it is also used from within deoptimization.cpp for | |
| // rematerialization and relocking of non-escaping objects. | |
| StackValue *compiledVFrame::create_stack_value(ScopeValue *sv) const { | |
| stackChunkOop c = _reg_map.stack_chunk()(); | |
| int index = _reg_map.stack_chunk_index(); | |
| const_cast<RegisterMap*>(&_reg_map)->set_stack_chunk(_chunk()); | |
| StackValue* res = StackValue::create_stack_value(&_fr, register_map(), sv); | |
| const_cast<RegisterMap*>(&_reg_map)->set_stack_chunk(c); | |
| const_cast<RegisterMap*>(&_reg_map)->set_stack_chunk_index(index); | |
| return res; | |
| } | |
| BasicLock* compiledVFrame::resolve_monitor_lock(Location location) const { | |
| return StackValue::resolve_monitor_lock(stack_chunk() == nullptr ? _fr : stack_chunk()->derelativize(_fr), location); | |
| } | |
| GrowableArray<MonitorInfo*>* compiledVFrame::monitors() const { | |
| // Natives has no scope | |
| if (scope() == nullptr) { | |
| nmethod* nm = code(); | |
| Method* method = nm->method(); | |
| assert(method->is_native(), "Expect a native method"); | |
| if (!method->is_synchronized()) { | |
| return new GrowableArray<MonitorInfo*>(0); | |
| } | |
| // This monitor is not really needed but return it for now as it might be | |
| // useful for stack traces and tools | |
| GrowableArray<MonitorInfo*> *monitors = new GrowableArray<MonitorInfo*>(1); | |
| // Casting away const | |
| frame& fr = (frame&) _fr; | |
| MonitorInfo* info = new MonitorInfo( | |
| fr.get_native_receiver(), fr.get_native_monitor(), false, false); | |
| monitors->push(info); | |
| return monitors; | |
| } | |
| GrowableArray<MonitorValue*>* monitors = scope()->monitors(); | |
| if (monitors == nullptr) { | |
| return new GrowableArray<MonitorInfo*>(0); | |
| } | |
| GrowableArray<MonitorInfo*>* result = new GrowableArray<MonitorInfo*>(monitors->length()); | |
| for (int index = 0; index < monitors->length(); index++) { | |
| MonitorValue* mv = monitors->at(index); | |
| ScopeValue* ov = mv->owner(); | |
| StackValue *owner_sv = create_stack_value(ov); // it is an oop | |
| if (ov->is_object() && owner_sv->obj_is_scalar_replaced()) { // The owner object was scalar replaced | |
| assert(mv->eliminated(), "monitor should be eliminated for scalar replaced object"); | |
| // Put klass for scalar replaced object. | |
| ScopeValue* kv = ((ObjectValue *)ov)->klass(); | |
| assert(kv->is_constant_oop(), "klass should be oop constant for scalar replaced object"); | |
| Handle k(Thread::current(), ((ConstantOopReadValue*)kv)->value()()); | |
| assert(java_lang_Class::is_instance(k()), "must be"); | |
| result->push(new MonitorInfo(k(), resolve_monitor_lock(mv->basic_lock()), | |
| mv->eliminated(), true)); | |
| } else { | |
| result->push(new MonitorInfo(owner_sv->get_obj()(), resolve_monitor_lock(mv->basic_lock()), | |
| mv->eliminated(), false)); | |
| } | |
| } | |
| // Replace the original values with any stores that have been | |
| // performed through compiledVFrame::update_monitors. | |
| if (thread() == nullptr) return result; // Unmounted continuations have no thread so nothing to do. | |
| GrowableArrayView<jvmtiDeferredLocalVariableSet*>* list = JvmtiDeferredUpdates::deferred_locals(thread()); | |
| if (list != nullptr ) { | |
| // In real life this never happens or is typically a single element search | |
| for (int i = 0; i < list->length(); i++) { | |
| if (list->at(i)->matches(this)) { | |
| list->at(i)->update_monitors(result); | |
| break; | |
| } | |
| } | |
| } | |
| return result; | |
| } | |
| compiledVFrame::compiledVFrame(const frame* fr, const RegisterMap* reg_map, JavaThread* thread, nmethod* nm) | |
| : javaVFrame(fr, reg_map, thread) { | |
| _scope = nullptr; | |
| _vframe_id = 0; | |
| // Compiled method (native stub or Java code) | |
| // native wrappers have no scope data, it is implied | |
| if (!nm->is_native_method()) { | |
| _scope = nm->scope_desc_at(_fr.pc()); | |
| } | |
| } | |
| compiledVFrame::compiledVFrame(const frame* fr, const RegisterMap* reg_map, JavaThread* thread, ScopeDesc* scope, int vframe_id) | |
| : javaVFrame(fr, reg_map, thread) { | |
| _scope = scope; | |
| _vframe_id = vframe_id; | |
| guarantee(_scope != nullptr, "scope must be present"); | |
| } | |
| compiledVFrame* compiledVFrame::at_scope(int decode_offset, int vframe_id) { | |
| if (scope()->decode_offset() != decode_offset) { | |
| ScopeDesc* scope = this->scope()->at_offset(decode_offset); | |
| return new compiledVFrame(frame_pointer(), register_map(), thread(), scope, vframe_id); | |
| } | |
| assert(_vframe_id == vframe_id, "wrong frame id"); | |
| return this; | |
| } | |
| bool compiledVFrame::is_top() const { | |
| // FIX IT: Remove this when new native stubs are in place | |
| if (scope() == nullptr) return true; | |
| return scope()->is_top(); | |
| } | |
| nmethod* compiledVFrame::code() const { | |
| return CodeCache::find_nmethod(_fr.pc()); | |
| } | |
| Method* compiledVFrame::method() const { | |
| if (scope() == nullptr) { | |
| // native nmethods have no scope the method is implied | |
| nmethod* nm = code(); | |
| assert(nm->is_native_method(), "must be native"); | |
| return nm->method(); | |
| } | |
| return scope()->method(); | |
| } | |
| int compiledVFrame::bci() const { | |
| int raw = raw_bci(); | |
| return raw == SynchronizationEntryBCI ? 0 : raw; | |
| } | |
| int compiledVFrame::raw_bci() const { | |
| if (scope() == nullptr) { | |
| // native nmethods have no scope the method/bci is implied | |
| nmethod* nm = code(); | |
| assert(nm->is_native_method(), "must be native"); | |
| return 0; | |
| } | |
| return scope()->bci(); | |
| } | |
| bool compiledVFrame::should_reexecute() const { | |
| if (scope() == nullptr) { | |
| // native nmethods have no scope the method/bci is implied | |
| nmethod* nm = code(); | |
| assert(nm->is_native_method(), "must be native"); | |
| return false; | |
| } | |
| return scope()->should_reexecute(); | |
| } | |
| bool compiledVFrame::has_ea_local_in_scope() const { | |
| if (scope() == nullptr) { | |
| // native nmethod, all objs escape | |
| assert(code()->is_native_method(), "must be native"); | |
| return false; | |
| } | |
| return (scope()->objects() != nullptr) || scope()->has_ea_local_in_scope(); | |
| } | |
| bool compiledVFrame::arg_escape() const { | |
| if (scope() == nullptr) { | |
| // native nmethod, all objs escape | |
| assert(code()->is_native_method(), "must be native"); | |
| return false; | |
| } | |
| return scope()->arg_escape(); | |
| } | |
| vframe* compiledVFrame::sender() const { | |
| const frame f = fr(); | |
| if (scope() == nullptr) { | |
| // native nmethods have no scope the method/bci is implied | |
| nmethod* nm = code(); | |
| assert(nm->is_native_method(), "must be native"); | |
| return vframe::sender(); | |
| } else { | |
| return scope()->is_top() | |
| ? vframe::sender() | |
| : new compiledVFrame(&f, register_map(), thread(), scope()->sender(), vframe_id() + 1); | |
| } | |
| } | |
| jvmtiDeferredLocalVariableSet::jvmtiDeferredLocalVariableSet(Method* method, int bci, intptr_t* id, int vframe_id) { | |
| _method = method; | |
| _bci = bci; | |
| _id = id; | |
| _vframe_id = vframe_id; | |
| // Always will need at least one, must be on C heap | |
| _locals = new(mtCompiler) GrowableArray<jvmtiDeferredLocalVariable*> (1, mtCompiler); | |
| _objects_are_deoptimized = false; | |
| } | |
| jvmtiDeferredLocalVariableSet::~jvmtiDeferredLocalVariableSet() { | |
| for (int i = 0; i < _locals->length(); i++ ) { | |
| delete _locals->at(i); | |
| } | |
| // Free growableArray and c heap for elements | |
| delete _locals; | |
| } | |
| bool jvmtiDeferredLocalVariableSet::matches(const vframe* vf) { | |
| if (!vf->is_compiled_frame()) return false; | |
| compiledVFrame* cvf = (compiledVFrame*)vf; | |
| if (cvf->fr().id() == id() && cvf->vframe_id() == vframe_id()) { | |
| assert(cvf->method() == method() && cvf->bci() == bci(), "must agree"); | |
| return true; | |
| } | |
| return false; | |
| } | |
| void jvmtiDeferredLocalVariableSet::set_value_at(int idx, BasicType type, jvalue val) { | |
| for (int i = 0; i < _locals->length(); i++) { | |
| if (_locals->at(i)->index() == idx) { | |
| assert(_locals->at(i)->type() == type, "Wrong type"); | |
| _locals->at(i)->set_value(val); | |
| return; | |
| } | |
| } | |
| _locals->push(new jvmtiDeferredLocalVariable(idx, type, val)); | |
| } | |
| void jvmtiDeferredLocalVariableSet::update_value(StackValueCollection* locals, BasicType type, int index, jvalue value) { | |
| switch (type) { | |
| case T_BOOLEAN: | |
| locals->set_int_at(index, value.z); | |
| break; | |
| case T_CHAR: | |
| locals->set_int_at(index, value.c); | |
| break; | |
| case T_FLOAT: | |
| locals->set_float_at(index, value.f); | |
| break; | |
| case T_DOUBLE: | |
| locals->set_double_at(index, value.d); | |
| break; | |
| case T_BYTE: | |
| locals->set_int_at(index, value.b); | |
| break; | |
| case T_SHORT: | |
| locals->set_int_at(index, value.s); | |
| break; | |
| case T_INT: | |
| locals->set_int_at(index, value.i); | |
| break; | |
| case T_LONG: | |
| locals->set_long_at(index, value.j); | |
| break; | |
| case T_OBJECT: | |
| { | |
| Handle obj(Thread::current(), cast_to_oop(value.l)); | |
| locals->set_obj_at(index, obj); | |
| } | |
| break; | |
| default: | |
| ShouldNotReachHere(); | |
| } | |
| } | |
| void jvmtiDeferredLocalVariableSet::update_locals(StackValueCollection* locals) { | |
| for (int l = 0; l < _locals->length(); l ++) { | |
| jvmtiDeferredLocalVariable* val = _locals->at(l); | |
| if (val->index() >= 0 && val->index() < method()->max_locals()) { | |
| update_value(locals, val->type(), val->index(), val->value()); | |
| } | |
| } | |
| } | |
| void jvmtiDeferredLocalVariableSet::update_stack(StackValueCollection* expressions) { | |
| for (int l = 0; l < _locals->length(); l ++) { | |
| jvmtiDeferredLocalVariable* val = _locals->at(l); | |
| if (val->index() >= method()->max_locals() && val->index() < method()->max_locals() + method()->max_stack()) { | |
| update_value(expressions, val->type(), val->index() - method()->max_locals(), val->value()); | |
| } | |
| } | |
| } | |
| void jvmtiDeferredLocalVariableSet::update_monitors(GrowableArray<MonitorInfo*>* monitors) { | |
| for (int l = 0; l < _locals->length(); l ++) { | |
| jvmtiDeferredLocalVariable* val = _locals->at(l); | |
| if (val->index() >= method()->max_locals() + method()->max_stack()) { | |
| int lock_index = val->index() - (method()->max_locals() + method()->max_stack()); | |
| MonitorInfo* info = monitors->at(lock_index); | |
| // Originally the owner may have been scalar replaced but as an update | |
| // exists it must have been deoptimized, i.e. reallocated to the heap, and | |
| // now it is considered not to be scalar replaced. | |
| MonitorInfo* new_info = new MonitorInfo((oopDesc*)val->value().l, info->lock(), | |
| info->eliminated(), false); | |
| monitors->at_put(lock_index, new_info); | |
| } | |
| } | |
| } | |
| void jvmtiDeferredLocalVariableSet::oops_do(OopClosure* f) { | |
| // The Method* is on the stack so a live activation keeps it alive | |
| // either by mirror in interpreter or code in compiled code. | |
| for (int i = 0; i < _locals->length(); i++) { | |
| if (_locals->at(i)->type() == T_OBJECT) { | |
| f->do_oop(_locals->at(i)->oop_addr()); | |
| } | |
| } | |
| } | |
| jvmtiDeferredLocalVariable::jvmtiDeferredLocalVariable(int index, BasicType type, jvalue value) { | |
| _index = index; | |
| _type = type; | |
| _value = value; | |
| } |