blob: 956272e6ddabd1f9fef767dbe475c44296b20d7d [file] [log] [blame]
/*
* 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