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