1 /**
2  * Copyright (c) 2021-2024 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 "include/cframe.h"
19 #include "include/managed_thread.h"
20 #include "include/stack_walker.h"
21 #include "libpandabase/events/events.h"
22 #include "libpandafile/file_items.h"
23 #include "macros.h"
24 #include "runtime/include/locks.h"
25 #include "runtime/include/runtime.h"
26 #include "runtime/include/panda_vm.h"
27 #include "runtime/profiling/profiling-inl.h"
28 #include "runtime/mem/rendezvous.h"
29 
30 namespace ark {
31 
32 /**
33  * @brief Deoptimize CFrame that lies after another CFrame.
34  * @param thread       Pointer to thread
35  * @param pc           PC from which interpreter starts execution
36  * @param frame        Pointer to the first interpreter frame to that CFrame will be converted. It will be released via
37  *                     FreeFrame inside this function
38  * @param cframe_fp    Pointer to Cframe to be deoptimized
39  * @param last_frame   Pointer to the last interpreter frame to that CFrame will be converted. It will be released via
40  *                     FreeFrame inside this function
41  * @param callee_regs  Pointer to a callee-saved registers buffer from StackWalker
42  */
43 // CC-OFFNXT(readability-function-size_parameters) asm bridge
44 extern "C" [[noreturn]] void DeoptimizeAfterCFrame(ManagedThread *thread, const uint8_t *pc, Frame *frame,
45                                                    void *cframeFp, Frame *lastFrame, void *calleeRegs);
46 /**
47  * @brief Deoptimize CFrame that lies after interpreter frame.
48  * @param thread       Pointer to thread
49  * @param pc           PC from which interpreter starts execution
50  * @param frame        Pointer to the first interpreter frame to that CFrame will be converted. It will be released via
51  *                     FreeFrame inside this function
52  * @param cframe_fp    Pointer to Cframe to be deoptimized
53  * @param last_frame   Pointer to the last interpreter frame to that CFrame will be converted. It will be released via
54  *                     FreeFrame inside this function
55  * @param callee_regs  Pointer to a callee-saved registers buffer from StackWalker
56  */
57 // CC-OFFNXT(readability-function-size_parameters) asm bridge
58 extern "C" [[noreturn]] void DeoptimizeAfterIFrame(ManagedThread *thread, const uint8_t *pc, Frame *frame,
59                                                    void *cframeFp, Frame *lastFrame, void *calleeRegs);
60 /**
61  * @brief Drop given CFrame and return to its caller.
62  * Drop means that we set stack pointer to the top of given CFrame, restores its return address and invoke `return`
63  * instruction.
64  * @param cframe_fp    Pointer to Cframe to be dropped
65  * @param callee_regs  Pointer to a callee-saved registers buffer from StackWalker
66  */
67 extern "C" [[noreturn]] void DropCompiledFrameAndReturn(void *cframeFp, void *calleeVregs);
68 
UnpoisonAsanStack([[maybe_unused]] void *ptr)69 static void UnpoisonAsanStack([[maybe_unused]] void *ptr)
70 {
71 #ifdef PANDA_ASAN_ON
72     uint8_t sp;
73     ASAN_UNPOISON_MEMORY_REGION(&sp, reinterpret_cast<uint8_t *>(ptr) - &sp);
74 #endif  // PANDA_ASAN_ON
75 }
76 
77 #ifdef PANDA_EVENTS_ENABLED
78 static bool InvalidateCompiledMethod(ManagedThread *thread, Method *method, bool isCha,
79                                      size_t &inStackCount) NO_THREAD_SAFETY_ANALYSIS
80 #else
81 static bool InvalidateCompiledMethod(ManagedThread *thread, Method *method, bool isCha) NO_THREAD_SAFETY_ANALYSIS
82 #endif
83 {
84     ASSERT(thread != nullptr);
85     // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
86     for (auto stack = StackWalker::Create(thread); stack.HasFrame(); stack.NextFrame()) {
87         if (stack.IsCFrame() && stack.GetMethod() == method) {
88             auto &cframe = stack.GetCFrame();
89             cframe.SetShouldDeoptimize(true);
90             cframe.SetDeoptCodeEntry(stack.GetCompiledCodeEntry());
91             if (isCha) {
92                 LOG(DEBUG, CLASS_LINKER) << "[CHA]   Set ShouldDeoptimize for method: "
93                                          << cframe.GetMethod()->GetFullName();
94             } else {
95                 LOG(DEBUG, CLASS_LINKER) << "[IC]   Set ShouldDeoptimize for method: "
96                                          << cframe.GetMethod()->GetFullName();
97             }
98 #ifdef PANDA_EVENTS_ENABLED
99             inStackCount++;
100 #endif
101         }
102     }
103     return true;
104 }
105 
106 // NO_THREAD_SAFETY_ANALYSIS because it doesn't know about mutator_lock status in this scope
107 void InvalidateCompiledEntryPoint(const PandaSet<Method *> &methods, bool isCha) NO_THREAD_SAFETY_ANALYSIS
108 {
109     PandaVM *vm = Thread::GetCurrent()->GetVM();
110     ScopedSuspendAllThreadsRunning ssat(vm->GetRendezvous());
111     // NOTE(msherstennikov): remove this loop and check `methods` contains frame's method in stack traversing
112     for (const auto &method : methods) {
113 #ifdef PANDA_EVENTS_ENABLED
114         size_t inStackCount = 0;
115         vm->GetThreadManager()->EnumerateThreads([method, &inStackCount, isCha](ManagedThread *thread) {
116             return InvalidateCompiledMethod(thread, method, isCha, inStackCount);
117 #else
118         vm->GetThreadManager()->EnumerateThreads([method, isCha](ManagedThread *thread) {
119             return InvalidateCompiledMethod(thread, method, isCha);
120 #endif
121         });
122         if (isCha) {
123             EVENT_CHA_DEOPTIMIZE(std::string(method->GetFullName()), inStackCount);
124         }
125         // NOTE (Trubenkov)  clean up compiled code(See issue 1706)
126         method->SetInterpreterEntryPoint();
127         Thread::GetCurrent()->GetVM()->GetCompiler()->RemoveOsrCode(method);
128         // If deoptimization ocure during OSR compilation, we reset status after finish the compilation
129         if (method->GetCompilationStatus() != Method::COMPILATION) {
130             method->SetCompilationStatus(Method::NOT_COMPILED);
131         }
132     }
133 }
134 
PrevFrameDeopt(FrameKind prevFrameKind, ManagedThread *thread, StackWalker *stack, const uint8_t *pc, Frame *lastIframe, Frame *iframe, CFrame &cframe)135 void PrevFrameDeopt(FrameKind prevFrameKind, ManagedThread *thread, StackWalker *stack, const uint8_t *pc,
136                     Frame *lastIframe, Frame *iframe, CFrame &cframe)
137 {
138     switch (prevFrameKind) {
139         case FrameKind::COMPILER:
140             LOG(DEBUG, INTEROP) << "Deoptimize after cframe";
141             EVENT_DEOPTIMIZATION(std::string(cframe.GetMethod()->GetFullName()),
142                                  pc - stack->GetMethod()->GetInstructions(), events::DeoptimizationAfter::CFRAME);
143             // We need to set current frame kind to `compiled` as it's possible that we came here from other Deoptimize
144             // call and in this case frame kind will be `non-compiled`:
145             //
146             //     compiled code
147             //          |
148             //      Deoptimize
149             //          |
150             // DeoptimizeAfterCFrame
151             //          |
152             // (change frame kind to `non-compiled`) interpreter::Execute -- we don't return after this call
153             //          |
154             // FindCatchBlockInCallStack
155             //          |
156             //      Deoptimize
157             thread->SetCurrentFrameIsCompiled(true);
158             DeoptimizeAfterCFrame(thread, pc, iframe, cframe.GetFrameOrigin(), lastIframe,
159                                   stack->GetCalleeRegsForDeoptimize().end());
160         case FrameKind::NONE:
161         case FrameKind::INTERPRETER:
162             EVENT_DEOPTIMIZATION(std::string(cframe.GetMethod()->GetFullName()),
163                                  pc - stack->GetMethod()->GetInstructions(),
164                                  prevFrameKind == FrameKind::NONE ? events::DeoptimizationAfter::TOP
165                                                                   : events::DeoptimizationAfter::IFRAME);
166             LOG(DEBUG, INTEROP) << "Deoptimize after iframe";
167             DeoptimizeAfterIFrame(thread, pc, iframe, cframe.GetFrameOrigin(), lastIframe,
168                                   stack->GetCalleeRegsForDeoptimize().end());
169     }
170 }
171 
DestroyMethodWithInvalidatingEP(Method *destroyMethod)172 NO_ADDRESS_SANITIZE void DestroyMethodWithInvalidatingEP(Method *destroyMethod)
173 {
174     LOG(DEBUG, INTEROP) << "Destroy compiled method: " << destroyMethod->GetFullName();
175     destroyMethod->SetDestroyed();
176     PandaSet<Method *> destroyMethods;
177     destroyMethods.insert(destroyMethod);
178     InvalidateCompiledEntryPoint(destroyMethods, false);
179 }
180 
Deoptimize(StackWalker *stack, const uint8_t *pc, bool hasException, Method *destroyMethod)181 [[noreturn]] NO_ADDRESS_SANITIZE void Deoptimize(StackWalker *stack, const uint8_t *pc, bool hasException,
182                                                  Method *destroyMethod)
183 {
184     ASSERT(stack != nullptr);
185     auto *thread = ManagedThread::GetCurrent();
186     ASSERT(thread != nullptr);
187     ASSERT(stack->IsCFrame());
188     auto &cframe = stack->GetCFrame();
189     UnpoisonAsanStack(cframe.GetFrameOrigin());
190     auto method = stack->GetMethod();
191     if (pc == nullptr) {
192         ASSERT(method != nullptr);
193         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
194         pc = method->GetInstructions() + stack->GetBytecodePc();
195     }
196 
197     LOG(INFO, INTEROP) << "Deoptimize frame: " << method->GetFullName() << ", pc=" << std::hex
198                        << pc - method->GetInstructions() << std::dec;
199 
200     thread->GetVM()->ClearInteropHandleScopes(thread->GetCurrentFrame());
201 
202     auto context = thread->GetVM()->GetLanguageContext();
203     // We must run InvalidateCompiledEntryPoint before we convert the frame, because GC is already may be in the
204     // collecting phase and it can move some object in the deoptimized frame.
205     if (destroyMethod != nullptr) {
206         DestroyMethodWithInvalidatingEP(destroyMethod);
207     }
208 
209     FrameKind prevFrameKind;
210     // We need to execute(find catch block) in all inlined methods. For this we calculate the number of inlined method
211     // Else we can execute previus interpreter frames and we will FreeFrames in incorrect order
212     uint32_t numInlinedMethods = 0;
213     Frame *iframe = stack->ConvertToIFrame(&prevFrameKind, &numInlinedMethods);
214     ASSERT(iframe != nullptr);
215 
216     Frame *lastIframe = iframe;
217     while (numInlinedMethods-- != 0) {
218         EVENT_METHOD_EXIT(last_iframe->GetMethod()->GetFullName() + "(deopt)", events::MethodExitKind::INLINED,
219                           thread->RecordMethodExit());
220         lastIframe = lastIframe->GetPrevFrame();
221         ASSERT(!StackWalker::IsBoundaryFrame<FrameKind::INTERPRETER>(lastIframe));
222     }
223 
224     EVENT_METHOD_EXIT(last_iframe->GetMethod()->GetFullName() + "(deopt)", events::MethodExitKind::COMPILED,
225                       thread->RecordMethodExit());
226 
227     if (thread->HasPendingException()) {
228         LOG(DEBUG, INTEROP) << "Deoptimization has pending exception: "
229                             << thread->GetException()->ClassAddr<Class>()->GetName();
230         context.SetExceptionToVReg(iframe->GetAcc(), thread->GetException());
231     }
232 
233     if (!hasException) {
234         thread->ClearException();
235     } else {
236         ASSERT(thread->HasPendingException());
237     }
238 
239     PrevFrameDeopt(prevFrameKind, thread, stack, pc, lastIframe, iframe, cframe);
240     UNREACHABLE();
241 }
242 
DropCompiledFrame(StackWalker *stack)243 [[noreturn]] void DropCompiledFrame(StackWalker *stack)
244 {
245     LOG(DEBUG, INTEROP) << "Drop compiled frame: " << stack->GetMethod()->GetFullName();
246     auto cframeFp = stack->GetCFrame().GetFrameOrigin();
247     EVENT_METHOD_EXIT(stack->GetMethod()->GetFullName() + "(drop)", events::MethodExitKind::COMPILED,
248                       ManagedThread::GetCurrent()->RecordMethodExit());
249     UnpoisonAsanStack(cframeFp);
250     DropCompiledFrameAndReturn(cframeFp, stack->GetCalleeRegsForDeoptimize().end());
251     UNREACHABLE();
252 }
253 
254 }  // namespace ark
255