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