| /* |
| * Copyright (C) 2025 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "virtual_thread_common.h" |
| |
| #include "art_field-inl.h" |
| #include "art_method-inl.h" |
| #include "art_method.h" |
| #include "class_linker.h" |
| #include "class_root-inl.h" |
| #include "gc/heap.h" |
| #include "handle.h" |
| #include "handle_scope-inl.h" |
| #include "interpreter/shadow_frame.h" |
| #include "mirror/class-alloc-inl.h" |
| #include "mirror/object.h" |
| #include "mirror/object_array-alloc-inl.h" |
| #include "mirror/object_array-inl.h" |
| #include "mirror/object_array.h" |
| #include "mirror/throwable.h" |
| #include "monitor.h" |
| #include "obj_ptr.h" |
| #include "runtime.h" |
| #include "scoped_thread_state_change.h" |
| #include "stack.h" |
| #include "thread.h" |
| #include "well_known_classes.h" |
| |
| namespace art HIDDEN { |
| |
| inline static ArtMethod* GetEnterMethod(bool is_continuation_api) { |
| return is_continuation_api ? WellKnownClasses::jdk_internal_vm_Continuation_enterSpecial |
| : nullptr; |
| } |
| |
| inline static ArtMethod* GetParkMethod(bool is_continuation_api) { |
| if (is_continuation_api) { |
| return WellKnownClasses::jdk_internal_vm_Continuation_doYieldNative; |
| } else { |
| return WellKnownClasses::java_lang_Thread_parkVirtualInternal; |
| } |
| } |
| |
| struct VirtualThreadParkingVisitor final : public StackVisitor { |
| VirtualThreadParkingVisitor(Thread* thread, bool is_continuation_api) |
| REQUIRES_SHARED(Locks::mutator_lock_) |
| : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames, true), |
| enter_method_(GetEnterMethod(is_continuation_api)), |
| park_method_(GetParkMethod(is_continuation_api)), |
| shadow_frame_count_(0), |
| reason_(kNoReason) {} |
| bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) { |
| ShadowFrame* shadow_frame = GetCurrentShadowFrame(); |
| |
| if (shadow_frame == nullptr) { |
| ArtMethod** quick_frame = GetCurrentQuickFrame(); |
| ArtMethod* method = quick_frame != nullptr ? *quick_frame : nullptr; |
| if (method != nullptr && method->IsNative()) { |
| if (method == park_method_) { |
| // Stack walking continues only if the only non-interpreted frame is |
| // a known method parking the virtual thread / yielding the continuation. |
| return true; |
| } else if (method == enter_method_) { |
| // The rest of the stack belongs to the carrier thread. |
| return false; |
| } |
| |
| reason_ = kNativeMethod; |
| return false; |
| } |
| |
| reason_ = kUnsupportedFrame; |
| return false; |
| } |
| |
| if (!shadow_frame->GetLockCountData().IsEmpty()) { |
| reason_ = kMonitor; |
| return false; |
| } |
| |
| // If the verifier was able to verify the locks are balanced, the interpreter won't update the |
| // lock cound data. We need to walk the stack to find the locks here. Or should we just have an |
| // increment/decrement counter? |
| Monitor::VisitLocks(this, LockVisitingCallback, this); |
| if (reason_ == kMonitor) { |
| return false; |
| } |
| DCHECK(reason_ == kNoReason); |
| |
| shadow_frame_count_++; |
| shadow_frames_.push_back(shadow_frame); |
| |
| return true; |
| } |
| |
| static void LockVisitingCallback(ObjPtr<mirror::Object> obj, void* visitor) { |
| DCHECK(!obj.IsNull()); |
| reinterpret_cast<VirtualThreadParkingVisitor*>(visitor)->reason_ = kMonitor; |
| } |
| |
| const ArtMethod* const enter_method_; |
| const ArtMethod* const park_method_; |
| std::vector<const ShadowFrame*> shadow_frames_; |
| size_t shadow_frame_count_; |
| PinningReason reason_; |
| }; |
| |
| bool VirtualThreadPark(ObjPtr<mirror::Object> v_context, |
| ObjPtr<mirror::Object> parked_states, |
| ObjPtr<mirror::Throwable> vm_error, |
| bool is_continuation_api, |
| PinningReason& reason_) { |
| Thread* self = Thread::Current(); |
| if (self->AreVirtualThreadFlagsEnabled(kContinuation) != is_continuation_api) { |
| self->ThrowNewExceptionF("Ljava/lang/IllegalStateException;", |
| "unmatched kContinuation value when Virtual Thread is parking: %d", |
| is_continuation_api); |
| return false; |
| } |
| |
| StackHandleScope<9> hs(self); |
| |
| Handle<mirror::Object> v_context_h = hs.NewHandle(v_context); |
| Handle<mirror::Object> parked_states_h = hs.NewHandle(parked_states); |
| Handle<mirror::Object> opeer_h = hs.NewHandle(self->GetPeer()); |
| Handle<mirror::Throwable> vm_error_h = hs.NewHandle(vm_error); |
| |
| VirtualThreadParkingVisitor dump_visitor(self, is_continuation_api); |
| dump_visitor.WalkStack(); |
| |
| DCHECK_NE(dump_visitor.reason_, kUnsupportedFrame) << "JIT / AOT frame isn't supported."; |
| reason_ = dump_visitor.reason_; |
| if (dump_visitor.reason_ != kNoReason) { |
| WellKnownClasses::dalvik_system_VirtualThreadContext_pinnedCarrierThread->SetObject<false>( |
| v_context_h.Get(), opeer_h.Get()); |
| // Return to the java code to park the carrier thread |
| return false; |
| } |
| |
| size_t num_frames = dump_visitor.shadow_frames_.size(); |
| |
| DCHECK(!Runtime::Current()->IsActiveTransaction()); |
| |
| Handle<mirror::ObjectArray<mirror::Object>> frames_h = |
| hs.NewHandle(mirror::ObjectArray<mirror::Object>::Alloc( |
| self, |
| WellKnownClasses::ToClass(WellKnownClasses::dalvik_system_VirtualThreadFrame__array), |
| num_frames)); |
| |
| MutableHandle<mirror::Object> vtf = hs.NewHandle<mirror::Object>(nullptr); |
| MutableHandle<mirror::ByteArray> frame_bytes = hs.NewHandle<mirror::ByteArray>(nullptr); |
| MutableHandle<mirror::Object> declaring_class = hs.NewHandle<mirror::Object>(nullptr); |
| MutableHandle<mirror::ObjectArray<mirror::Object>> refs = |
| hs.NewHandle<mirror::ObjectArray<mirror::Object>>(nullptr); |
| |
| for (size_t i = 0; i < num_frames; i++) { |
| const ShadowFrame* sf = dump_visitor.shadow_frames_[i]; |
| DCHECK(sf != nullptr); |
| sf->CheckConsistentVRegs(); |
| |
| size_t num_vergs = sf->NumberOfVRegs(); |
| int32_t non_vref_size = ShadowFrame::ComputeSizeWithoutReferences(num_vergs); |
| |
| vtf.Assign(WellKnownClasses::dalvik_system_VirtualThreadFrame.Get()->Alloc( |
| self, Runtime::Current()->GetHeap()->GetCurrentAllocator())); |
| frame_bytes.Assign(mirror::ByteArray::Alloc(self, non_vref_size)); |
| declaring_class.Assign(sf->GetMethod()->GetDeclaringClass()); |
| refs.Assign(mirror::ObjectArray<mirror::Object>::Alloc( |
| self, GetClassRoot<mirror::ObjectArray<mirror::Object>>(), num_vergs)); |
| DCHECK(!vtf.IsNull()); |
| DCHECK(!frame_bytes.IsNull()); |
| DCHECK(!refs.IsNull()); |
| |
| bool areRefsAllNull = true; |
| for (uint32_t j = 0; j < sf->NumberOfVRegs(); j++) { |
| ObjPtr<mirror::Object> obj = sf->GetVRegReference(j); |
| if (obj != nullptr) { |
| refs->Set(j, obj); |
| areRefsAllNull = false; |
| } |
| } |
| |
| if (!areRefsAllNull) { |
| WellKnownClasses::dalvik_system_VirtualThreadFrame_refs->SetObject<false>(vtf.Get(), |
| refs.Get()); |
| } |
| frame_bytes->Memcpy(0, reinterpret_cast<const int8_t*>(sf), 0, non_vref_size); |
| WellKnownClasses::dalvik_system_VirtualThreadFrame_frame->SetObject<false>(vtf.Get(), |
| frame_bytes.Get()); |
| WellKnownClasses::dalvik_system_VirtualThreadFrame_declaringClass->SetObject<false>( |
| vtf.Get(), declaring_class.Get()); |
| frames_h->Set(i, vtf.Get()); |
| } |
| |
| if (self->IsExceptionPending()) { |
| // It's likely a OOME. Let's throw it at the java level. |
| // Consider handling it in another way, e.g. pinning virtual thread, in the future. |
| return false; |
| } |
| |
| WellKnownClasses::dalvik_system_VirtualThreadParkedStates_frames->SetObject<false>( |
| parked_states_h.Get(), frames_h.Get()); |
| WellKnownClasses::dalvik_system_VirtualThreadContext_parkedStates->SetObject<false>( |
| v_context_h.Get(), parked_states_h.Get()); |
| self->SetVirtualThreadFlags(VirtualThreadFlag::kParking, true); |
| |
| // Throw a VirtualThreadParkingError to unwind the stack and park the virtual thread. |
| self->SetException(vm_error_h.Get()); |
| return true; |
| } |
| |
| } // namespace art |