• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "runtime/deoptimization.h"
17 
18 #include "libpandabase/events/events.h"
19 #include "libpandafile/file_items.h"
20 #include "runtime/include/locks.h"
21 #include "runtime/include/runtime.h"
22 #include "runtime/include/panda_vm.h"
23 #include "runtime/mem/rendezvous.h"
24 
25 namespace panda {
26 
27 /**
28  * \brief Deoptimize CFrame that lies after another CFrame.
29  * @param thread       Pointer to thread
30  * @param pc           PC from which interpreter starts execution
31  * @param frame        Pointer to the first interpreter frame to that CFrame will be converted. It will be released via
32  *                     FreeFrame inside this function
33  * @param cframe_fp    Pointer to Cframe to be deoptimized
34  * @param last_frame   Pointer to the last interpreter frame to that CFrame will be converted. It will be released via
35  *                     FreeFrame inside this function
36  * @param callee_regs  Pointer to a callee-saved registers buffer from StackWalker
37  */
38 extern "C" [[noreturn]] void DeoptimizeAfterCFrame(ManagedThread *thread, const uint8_t *pc, Frame *frame,
39                                                    void *cframe_fp, Frame *last_frame, void *callee_regs);
40 /**
41  * \brief Deoptimize CFrame that lies after interpreter frame.
42  * @param thread       Pointer to thread
43  * @param pc           PC from which interpreter starts execution
44  * @param frame        Pointer to the first interpreter frame to that CFrame will be converted. It will be released via
45  *                     FreeFrame inside this function
46  * @param cframe_fp    Pointer to Cframe to be deoptimized
47  * @param last_frame   Pointer to the last interpreter frame to that CFrame will be converted. It will be released via
48  *                     FreeFrame inside this function
49  * @param callee_regs  Pointer to a callee-saved registers buffer from StackWalker
50  */
51 extern "C" [[noreturn]] void DeoptimizeAfterIFrame(ManagedThread *thread, const uint8_t *pc, Frame *frame,
52                                                    void *cframe_fp, Frame *last_frame, void *callee_regs);
53 /**
54  * \brief Drop given CFrame and return to its caller.
55  * Drop means that we set stack pointer to the top of given CFrame, restores its return address and invoke `return`
56  * instruction.
57  * @param cframe_fp    Pointer to Cframe to be dropped
58  * @param callee_regs  Pointer to a callee-saved registers buffer from StackWalker
59  */
60 extern "C" [[noreturn]] void DropCompiledFrameAndReturn(void *cframe_fp, void *callee_vregs);
61 
UnpoisonAsanStack(void * ptr)62 static void UnpoisonAsanStack([[maybe_unused]] void *ptr)
63 {
64 #ifdef PANDA_ASAN_ON
65     uint8_t sp;
66     ASAN_UNPOISON_MEMORY_REGION(&sp, reinterpret_cast<uint8_t *>(ptr) - &sp);
67 #endif  // PANDA_ASAN_ON
68 }
69 
70 // NO_THREAD_SAFETY_ANALYSIS because it doesn't know about mutator_lock status in this scope
InvalidateCompiledEntryPoint(const PandaSet<Method * > & methods,bool is_cha)71 void InvalidateCompiledEntryPoint(const PandaSet<Method *> &methods, bool is_cha) NO_THREAD_SAFETY_ANALYSIS
72 {
73     PandaVM *vm = Thread::GetCurrent()->GetVM();
74     ScopedSuspendAllThreadsRunning ssat(vm->GetRendezvous());
75     // TODO(msherstennikov): remove this loop and check `methods` contains frame's method in stack traversing
76     for (const auto &method : methods) {
77 #ifdef PANDA_EVENTS_ENABLED
78         size_t in_stack_count = 0;
79         vm->GetThreadManager()->EnumerateThreads([method, &in_stack_count, is_cha](ManagedThread *thread) {
80 #else
81         vm->GetThreadManager()->EnumerateThreads([method, is_cha](ManagedThread *thread) {
82 #endif
83             ASSERT(thread != nullptr);
84             // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
85             for (auto stack = StackWalker::Create(thread); stack.HasFrame(); stack.NextFrame()) {
86                 if (stack.IsCFrame() && stack.GetMethod() == method) {
87                     auto &cframe = stack.GetCFrame();
88                     cframe.SetShouldDeoptimize(true);
89                     cframe.SetDeoptCodeEntry(stack.GetCompiledCodeEntry());
90                     if (is_cha) {
91                         LOG(DEBUG, CLASS_LINKER)
92                             << "[CHA]   Set ShouldDeoptimize for method: " << cframe.GetMethod()->GetFullName();
93                     } else {
94                         LOG(DEBUG, CLASS_LINKER)
95                             << "[IC]   Set ShouldDeoptimize for method: " << cframe.GetMethod()->GetFullName();
96                     }
97 #ifdef PANDA_EVENTS_ENABLED
98                     in_stack_count++;
99 #endif
100                 }
101             }
102             return true;
103         });
104         if (is_cha) {
105             EVENT_CHA_DEOPTIMIZE(std::string(method->GetFullName()), in_stack_count);
106         }
107         // TODO (Trubenkov)  clean up compiled code(See issue 1706)
108         method->SetInterpreterEntryPoint();
109         Thread::GetCurrent()->GetVM()->GetCompiler()->RemoveOsrCode(method);
110         method->SetCompilationStatus(Method::NOT_COMPILED);
111     }
112 }
113 
114 [[noreturn]] NO_ADDRESS_SANITIZE void Deoptimize(StackWalker *stack, const uint8_t *pc, bool has_exception,
115                                                  bool destroy_compiled_code)
116 {
117     ASSERT(stack != nullptr);
118     auto *thread = ManagedThread::GetCurrent();
119     ASSERT(thread != nullptr);
120     ASSERT(stack->IsCFrame());
121     auto &cframe = stack->GetCFrame();
122     UnpoisonAsanStack(cframe.GetFrameOrigin());
123 
124     LOG(DEBUG, INTEROP) << "Deoptimize frame: " << stack->GetMethod()->GetFullName();
125 
126     if (pc == nullptr) {
127         ASSERT(cframe.GetMethod() != nullptr);
128         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
129         pc = stack->GetMethod()->GetInstructions() + stack->GetBytecodePc();
130     }
131 
132     // We must run InvalidateCompiledEntryPoint before we convert the frame, because GC is already may be in the
133     // collecting phase and it can move some object in the deoptimized frame.
134     if (destroy_compiled_code) {
135         PandaSet<Method *> destroy_method;
136         destroy_method.insert(cframe.GetMethod());
137         InvalidateCompiledEntryPoint(destroy_method, false);
138     }
139 
140     FrameKind prev_frame_kind;
141     // We need to execute(find catch block) in all inlined methods. For this we calculate the number of inlined method
142     // Else we can execute previus interpreter frames and we will FreeFrames in incorrect order
143     uint32_t num_inlined_methods = 0;
144     Frame *iframe = stack->ConvertToIFrame(&prev_frame_kind, &num_inlined_methods);
145     ASSERT(iframe != nullptr);
146 
147     Frame *last_iframe = iframe;
148     while (num_inlined_methods-- != 0) {
149         EVENT_METHOD_EXIT(last_iframe->GetMethod()->GetFullName() + "(deopt)", events::MethodExitKind::INLINED,
150                           thread->RecordMethodExit());
151         last_iframe = last_iframe->GetPrevFrame();
152         ASSERT(!StackWalker::IsBoundaryFrame<FrameKind::INTERPRETER>(last_iframe));
153     }
154 
155     EVENT_METHOD_EXIT(last_iframe->GetMethod()->GetFullName() + "(deopt)", events::MethodExitKind::COMPILED,
156                       thread->RecordMethodExit());
157 
158     if (thread->HasPendingException()) {
159         LOG(DEBUG, INTEROP) << "Deoptimization has pending exception: "
160                             << thread->GetException()->ClassAddr<Class>()->GetName();
161         auto context = thread->GetVM()->GetLanguageContext();
162         context.SetExceptionToVReg(iframe->GetAcc(), thread->GetException());
163     }
164 
165     if (!has_exception) {
166         thread->ClearException();
167     } else {
168         ASSERT(thread->HasPendingException());
169     }
170 
171     switch (prev_frame_kind) {
172         case FrameKind::COMPILER:
173             LOG(DEBUG, INTEROP) << "Deoptimize after cframe";
174             EVENT_DEOPTIMIZATION(std::string(cframe.GetMethod()->GetFullName()),
175                                  pc - stack->GetMethod()->GetInstructions(), events::DeoptimizationAfter::CFRAME);
176             // We need to set current frame kind to `compiled` as it's possible that we came here from other Deoptimize
177             // call and in this case frame kind will be `non-compiled`:
178             //
179             //     compiled code
180             //          |
181             //      Deoptimize
182             //          |
183             // DeoptimizeAfterCFrame
184             //          |
185             // (change frame kind to `non-compiled`) interpreter::Execute -- we don't return after this call
186             //          |
187             // FindCatchBlockInCallStack
188             //          |
189             //      Deoptimize
190             thread->SetCurrentFrameIsCompiled(true);
191             DeoptimizeAfterCFrame(thread, pc, iframe, cframe.GetFrameOrigin(), last_iframe,
192                                   stack->GetCalleeRegsForDeoptimize().end());
193         case FrameKind::NONE:
194         case FrameKind::INTERPRETER:
195             EVENT_DEOPTIMIZATION(std::string(cframe.GetMethod()->GetFullName()),
196                                  pc - stack->GetMethod()->GetInstructions(),
197                                  prev_frame_kind == FrameKind::NONE ? events::DeoptimizationAfter::TOP
198                                                                     : events::DeoptimizationAfter::IFRAME);
199             LOG(DEBUG, INTEROP) << "Deoptimize after iframe";
200             DeoptimizeAfterIFrame(thread, pc, iframe, cframe.GetFrameOrigin(), last_iframe,
201                                   stack->GetCalleeRegsForDeoptimize().end());
202     }
203     UNREACHABLE();
204 }
205 
206 [[noreturn]] void DropCompiledFrame(StackWalker *stack)
207 {
208     LOG(DEBUG, INTEROP) << "Drop compiled frame: " << stack->GetMethod()->GetFullName();
209     auto cframe_fp = stack->GetCFrame().GetFrameOrigin();
210     EVENT_METHOD_EXIT(stack->GetMethod()->GetFullName() + "(drop)", events::MethodExitKind::COMPILED,
211                       ManagedThread::GetCurrent()->RecordMethodExit());
212     UnpoisonAsanStack(cframe_fp);
213     DropCompiledFrameAndReturn(cframe_fp, stack->GetCalleeRegsForDeoptimize().end());
214     UNREACHABLE();
215 }
216 
217 }  // namespace panda
218