• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-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 #include "ecmascript/deoptimizer/deoptimizer.h"
16 
17 
18 #include "ecmascript/dfx/stackinfo/js_stackinfo.h"
19 #include "ecmascript/interpreter/interpreter_assembly.h"
20 #include "ecmascript/interpreter/slow_runtime_stub.h"
21 #include "ecmascript/jit/jit.h"
22 #include "ecmascript/js_tagged_value_internals.h"
23 #include "ecmascript/stubs/runtime_stubs-inl.h"
24 #include "ecmascript/base/gc_helper.h"
25 
26 namespace panda::ecmascript {
27 
GetDeoptHandlerAsmOffset(bool isArch32)28 extern "C" uintptr_t GetDeoptHandlerAsmOffset(bool isArch32)
29 {
30     return JSThread::GlueData::GetRTStubEntriesOffset(isArch32) +
31         RTSTUB_ID(DeoptHandlerAsm) * RuntimeStubs::RT_STUB_FUNC_SIZE;
32 }
33 
GetFixedReturnAddr(uintptr_t argGlue,uintptr_t prevCallSiteSp)34 extern "C" uintptr_t GetFixedReturnAddr(uintptr_t argGlue, uintptr_t prevCallSiteSp)
35 {
36     auto thread = JSThread::GlueToJSThread(argGlue);
37     uintptr_t fixed = thread->GetAndClearCallSiteReturnAddr(prevCallSiteSp);
38     if (fixed == 0) {
39         LOG_ECMA(FATAL) << "ilegal return addr found";
40     }
41     return fixed;
42 }
43 
44 // Not use lazy deopt on arkui_x.
45 #ifdef CROSS_PLATFORM
LazyDeoptEntry()46 JSTaggedType LazyDeoptEntry()
47 {
48     return 0;
49 }
50 #endif
51 
52 class FrameWriter {
53 public:
FrameWriter(Deoptimizier * deoptimizier)54     explicit FrameWriter(Deoptimizier *deoptimizier) : thread_(deoptimizier->GetThread())
55     {
56         JSTaggedType *prevSp = const_cast<JSTaggedType *>(thread_->GetCurrentSPFrame());
57         start_ = top_ = EcmaInterpreter::GetInterpreterFrameEnd(thread_, prevSp);
58     }
59 
PushValue(JSTaggedType value)60     void PushValue(JSTaggedType value)
61     {
62         *(--top_) = value;
63     }
64 
PushRawValue(uintptr_t value)65     void PushRawValue(uintptr_t value)
66     {
67         *(--top_) = value;
68     }
69 
Reserve(size_t size)70     bool Reserve(size_t size)
71     {
72         return !thread_->DoStackOverflowCheck(top_ - size);
73     }
74 
ReserveAsmInterpretedFrame()75     AsmInterpretedFrame *ReserveAsmInterpretedFrame()
76     {
77         auto frame = AsmInterpretedFrame::GetFrameFromSp(top_);
78         top_ = reinterpret_cast<JSTaggedType *>(frame);
79         return frame;
80     }
81 
GetStart() const82     JSTaggedType *GetStart() const
83     {
84         return start_;
85     }
86 
GetTop() const87     JSTaggedType *GetTop() const
88     {
89         return top_;
90     }
91 
GetFirstFrame() const92     JSTaggedType *GetFirstFrame() const
93     {
94         return firstFrame_;
95     }
96 
RecordFirstFrame()97     void RecordFirstFrame()
98     {
99         firstFrame_ = top_;
100     }
101 
ReviseValueByIndex(JSTaggedType value,size_t index)102     void ReviseValueByIndex(JSTaggedType value, size_t index)
103     {
104         ASSERT(index < static_cast<size_t>(start_ - top_));
105         *(top_ + index) = value;
106     }
107 
108 private:
109     JSThread *thread_ {nullptr};
110     JSTaggedType *start_ {nullptr};
111     JSTaggedType *top_ {nullptr};
112     JSTaggedType *firstFrame_ {nullptr};
113 };
114 
Deoptimizier(JSThread * thread,size_t depth,kungfu::DeoptType type)115 Deoptimizier::Deoptimizier(JSThread *thread, size_t depth, kungfu::DeoptType type)
116     : thread_(thread), inlineDepth_(depth), type_(static_cast<uint32_t>(type))
117 {
118     CalleeReg callreg;
119     numCalleeRegs_ = static_cast<size_t>(callreg.GetCallRegNum());
120     JSRuntimeOptions options = thread_->GetEcmaVM()->GetJSOptions();
121     traceDeopt_ = options.GetTraceDeopt();
122 }
123 
CollectVregs(const std::vector<kungfu::ARKDeopt> & deoptBundle,size_t shift)124 void Deoptimizier::CollectVregs(const std::vector<kungfu::ARKDeopt>& deoptBundle, size_t shift)
125 {
126     deoptVregs_.clear();
127     for (size_t i = 0; i < deoptBundle.size(); i++) {
128         ARKDeopt deopt = deoptBundle.at(i);
129         JSTaggedType v;
130         VRegId id = deopt.id;
131         if (static_cast<OffsetType>(id) == static_cast<OffsetType>(SpecVregIndex::INLINE_DEPTH)) {
132             continue;
133         }
134         if (std::holds_alternative<DwarfRegAndOffsetType>(deopt.value)) {
135             ASSERT(deopt.kind == LocationTy::Kind::INDIRECT);
136             auto value = std::get<DwarfRegAndOffsetType>(deopt.value);
137             DwarfRegType dwarfReg = value.first;
138             OffsetType offset = value.second;
139             ASSERT (dwarfReg == GCStackMapRegisters::FP || dwarfReg == GCStackMapRegisters::SP);
140             uintptr_t addr;
141             if (dwarfReg == GCStackMapRegisters::SP) {
142                 addr = context_.callsiteSp + offset;
143             } else {
144                 addr = context_.callsiteFp + offset;
145             }
146             v = *(reinterpret_cast<JSTaggedType *>(addr));
147         } else if (std::holds_alternative<LargeInt>(deopt.value)) {
148             ASSERT(deopt.kind == LocationTy::Kind::CONSTANTNDEX);
149             v = JSTaggedType(static_cast<int64_t>(std::get<LargeInt>(deopt.value)));
150         } else {
151             ASSERT(std::holds_alternative<IntType>(deopt.value));
152             ASSERT(deopt.kind == LocationTy::Kind::CONSTANT);
153             v = JSTaggedType(static_cast<int64_t>(std::get<IntType>(deopt.value)));
154         }
155         size_t curDepth = DecodeDeoptDepth(id, shift);
156         OffsetType vregId = static_cast<OffsetType>(DecodeVregIndex(id, shift));
157         if (vregId != static_cast<OffsetType>(SpecVregIndex::PC_OFFSET_INDEX)) {
158             deoptVregs_.insert({{curDepth, vregId}, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(v))});
159         } else {
160             pc_.insert({curDepth, static_cast<size_t>(v)});
161         }
162     }
163 }
164 
165 // when AOT trigger deopt, frame layout as the following
166 // * OptimizedJSFunctionFrame layout description as the following:
167 //               +--------------------------+ ---------------
168 //               |        ......            |               ^
169 //               |        ......            |       callerFunction
170 //               |        ......            |               |
171 //               |--------------------------|               |
172 //               |        args              |               v
173 //               +--------------------------+ ---------------
174 //               |       returnAddr         |               ^
175 //               |--------------------------|               |
176 //               |       callsiteFp         |               |
177 //               |--------------------------|   OptimizedJSFunction  FrameType:OPTIMIZED_JS_FUNCTION_FRAME
178 //               |       frameType          |               |
179 //               |--------------------------|               |
180 //               |       call-target        |               |
181 //               |--------------------------|               |
182 //               |       lexEnv             |               |
183 //               |--------------------------|               |
184 //               |       ...........        |               v
185 //               +--------------------------+ ---------------
186 //               |       returnAddr         |               ^
187 //               |--------------------------|               |
188 //               |       callsiteFp         |               |
189 //               |--------------------------|   __llvm_deoptimize  FrameType:OPTIMIZED_FRAME
190 //               |       frameType          |               |
191 //               |--------------------------|               |
192 //               |       No CalleeSave      |               |
193 //               |       Registers          |               v
194 //               +--------------------------+ ---------------
195 //               |       returnAddr         |               ^
196 //               |--------------------------|               |
197 //               |       callsiteFp         |               |
198 //               |--------------------------|   DeoptHandlerAsm  FrameType:ASM_BRIDGE_FRAME
199 //               |       frameType          |               |
200 //               |--------------------------|               |
201 //               |       glue               |               |
202 //               |--------------------------|               |
203 //               | CalleeSave Registers     |               v
204 //               +--------------------------+ ---------------
205 //               |       .........          |               ^
206 //               |       .........          |     CallRuntime   FrameType:LEAVE_FRAME
207 //               |       .........          |               |
208 //               |       .........          |               v
209 //               |--------------------------| ---------------
210 
211 // After gathering the necessary information(After Call Runtime), frame layout after constructing
212 // asminterpreterframe is shown as the following:
213 //               +----------------------------------+---------+
214 //               |        ......                    |         ^
215 //               |        ......                    |   callerFunction
216 //               |        ......                    |         |
217 //               |----------------------------------|         |
218 //               |        args                      |         v
219 //               +----------------------------------+---------+
220 //               |       returnAddr                 |         ^
221 //               |----------------------------------|         |
222 //               |       frameType                  |         |
223 //               |----------------------------------|    ASM_INTERPRETER_BRIDGE_FRAME
224 //               |       callsiteFp                 |         |
225 //               |----------------------------------|         |
226 //               |       ...........                |         v
227 //               +----------------------------------+---------+
228 //               |       returnAddr                 |
229 //               |----------------------------------|
230 //               |    argv[n-1]                     |
231 //               |----------------------------------|
232 //               |    ......                        |
233 //               |----------------------------------|
234 //               |    thisArg [maybe not exist]     |
235 //               |----------------------------------|
236 //               |    newTarget [maybe not exist]   |
237 //               |----------------------------------|
238 //               |    ......                        |
239 //               |----------------------------------|
240 //               |    Vregs [not exist in native]   |
241 //               +----------------------------------+--------+
242 //               |        .  .  .   .               |        ^
243 //               |     InterpretedFrameBase         |        |
244 //               |        .  .  .   .               |        |
245 //               |----------------------------------|        |
246 //               |    pc(bytecode addr)             |        |
247 //               |----------------------------------|        |
248 //               |    sp(current stack pointer)     |        |
249 //               |----------------------------------|   AsmInterpretedFrame 0
250 //               |    callSize                      |        |
251 //               |----------------------------------|        |
252 //               |    env                           |        |
253 //               |----------------------------------|        |
254 //               |    acc                           |        |
255 //               |----------------------------------|        |
256 //               |    thisObj                       |        |
257 //               |----------------------------------|        |
258 //               |    call-target                   |        v
259 //               +----------------------------------+--------+
260 //               |    argv[n-1]                     |
261 //               |----------------------------------|
262 //               |    ......                        |
263 //               |----------------------------------|
264 //               |    thisArg [maybe not exist]     |
265 //               |----------------------------------|
266 //               |    newTarget [maybe not exist]   |
267 //               |----------------------------------|
268 //               |    ......                        |
269 //               |----------------------------------|
270 //               |    Vregs [not exist in native]   |
271 //               +----------------------------------+--------+
272 //               |        .  .  .   .               |        ^
273 //               |     InterpretedFrameBase         |        |
274 //               |        .  .  .   .               |        |
275 //               |----------------------------------|        |
276 //               |    pc(bytecode addr)             |        |
277 //               |----------------------------------|        |
278 //               |    sp(current stack pointer)     |        |
279 //               |----------------------------------|   AsmInterpretedFrame 1
280 //               |    callSize                      |        |
281 //               |----------------------------------|        |
282 //               |    env                           |        |
283 //               |----------------------------------|        |
284 //               |    acc                           |        |
285 //               |----------------------------------|        |
286 //               |    thisObj                       |        |
287 //               |----------------------------------|        |
288 //               |    call-target                   |        v
289 //               +----------------------------------+--------+
290 //               |        .  .  .   .               |        ^
291 //               |        .  .  .   .               |   AsmInterpretedFrame n
292 //               |        .  .  .   .               |        v
293 //               +----------------------------------+--------+
294 
295 template<class T>
AssistCollectDeoptBundleVec(FrameIterator & it,T & frame)296 void Deoptimizier::AssistCollectDeoptBundleVec(FrameIterator &it, T &frame)
297 {
298     CalleeRegAndOffsetVec calleeRegInfo;
299     frame->GetFuncCalleeRegAndOffset(it, calleeRegInfo);
300     context_.calleeRegAndOffset = calleeRegInfo;
301     context_.callsiteSp = it.GetCallSiteSp();
302     context_.callsiteFp = reinterpret_cast<uintptr_t>(it.GetSp());
303     auto preFrameSp = frame->ComputePrevFrameSp(it);
304     frameArgc_ = frame->GetArgc(preFrameSp);
305     frameArgvs_ = frame->GetArgv(preFrameSp);
306     stackContext_.callFrameTop_ = it.GetPrevFrameCallSiteSp();
307     stackContext_.returnAddr_ = frame->GetReturnAddr();
308     stackContext_.callerFp_ = reinterpret_cast<uintptr_t>(frame->GetPrevFrameFp());
309     stackContext_.isFrameLazyDeopt_ = it.IsLazyDeoptFrameType();
310 }
311 
CollectDeoptBundleVec(std::vector<ARKDeopt> & deoptBundle)312 void Deoptimizier::CollectDeoptBundleVec(std::vector<ARKDeopt>& deoptBundle)
313 {
314     JSTaggedType *lastLeave = const_cast<JSTaggedType *>(thread_->GetLastLeaveFrame());
315     FrameIterator it(lastLeave, thread_);
316     // note: last deopt bridge frame is generated by DeoptHandlerAsm, callee Regs is grow from this frame
317     for (; !it.Done() && deoptBundle.empty(); it.Advance<GCVisitedFlag::DEOPT>()) {
318         FrameType type = it.GetFrameType();
319         switch (type) {
320             case FrameType::OPTIMIZED_JS_FAST_CALL_FUNCTION_FRAME:
321             case FrameType::OPTIMIZED_JS_FUNCTION_FRAME: {
322                 auto frame = it.GetFrame<OptimizedJSFunctionFrame>();
323                 frame->GetDeoptBundleInfo(it, deoptBundle);
324                 AssistCollectDeoptBundleVec(it, frame);
325                 break;
326             }
327             case FrameType::FASTJIT_FUNCTION_FRAME:
328             case FrameType::FASTJIT_FAST_CALL_FUNCTION_FRAME: {
329                 auto frame = it.GetFrame<FASTJITFunctionFrame>();
330                 frame->GetDeoptBundleInfo(it, deoptBundle);
331                 AssistCollectDeoptBundleVec(it, frame);
332                 break;
333             }
334             case FrameType::ASM_BRIDGE_FRAME: {
335                 auto sp = reinterpret_cast<uintptr_t*>(it.GetSp());
336                 static constexpr size_t TYPE_GLUE_SLOT = 2; // 2: skip type & glue
337                 sp -= TYPE_GLUE_SLOT;
338                 calleeRegAddr_ = sp - numCalleeRegs_;
339                 break;
340             }
341             case FrameType::OPTIMIZED_FRAME:
342             case FrameType::LEAVE_FRAME:
343                 break;
344             default: {
345                 LOG_FULL(FATAL) << "frame type error, type: " << std::hex << static_cast<long>(type)
346                                 << ", sp: " << lastLeave;
347                 UNREACHABLE();
348             }
349         }
350     }
351     ASSERT(!it.Done());
352 }
353 
GetMethod(JSTaggedValue & target)354 Method* Deoptimizier::GetMethod(JSTaggedValue &target)
355 {
356     ECMAObject *callTarget = reinterpret_cast<ECMAObject*>(target.GetTaggedObject());
357     ASSERT(callTarget != nullptr);
358     Method *method = callTarget->GetCallTarget(thread_);
359     return method;
360 }
361 
RelocateCalleeSave()362 void Deoptimizier::RelocateCalleeSave()
363 {
364     CalleeReg callreg;
365     for (auto &it: context_.calleeRegAndOffset) {
366         auto reg = it.first;
367         auto offset = it.second;
368         uintptr_t value = *(reinterpret_cast<uintptr_t *>(context_.callsiteFp + offset));
369         int order = callreg.FindCallRegOrder(reg);
370         calleeRegAddr_[order] = value;
371     }
372 }
373 
CollectVirtualRegisters(JSTaggedValue callTarget,Method * method,FrameWriter * frameWriter,size_t curDepth)374 bool Deoptimizier::CollectVirtualRegisters(JSTaggedValue callTarget, Method *method, FrameWriter *frameWriter,
375     size_t curDepth)
376 {
377     int32_t actualNumArgs = 0;
378     int32_t declaredNumArgs = 0;
379     if (curDepth == 0) {
380         actualNumArgs = static_cast<int32_t>(GetDeoptValue(curDepth,
381             static_cast<int32_t>(SpecVregIndex::ACTUAL_ARGC_INDEX)).GetInt());
382         declaredNumArgs = static_cast<int32_t>(method->GetNumArgsWithCallField());
383     } else {
384         // inline method actualNumArgs equal to declaredNumArgs
385         actualNumArgs = static_cast<int32_t>(method->GetNumArgsWithCallField());
386         declaredNumArgs = static_cast<int32_t>(method->GetNumArgsWithCallField());
387     }
388 
389     int32_t callFieldNumVregs = static_cast<int32_t>(method->GetNumVregsWithCallField());
390 
391     // layout of frame:
392     // [maybe argc] [actual args] [reserved args] [call field virtual regs]
393 
394     // [maybe argc]
395     bool isFastCall = JSFunctionBase::IsFastCallFromCallTarget(thread_, callTarget);
396     if (!isFastCall && declaredNumArgs != actualNumArgs) {
397         auto value = JSTaggedValue(actualNumArgs);
398         frameWriter->PushValue(value.GetRawData());
399     }
400     int32_t virtualIndex = declaredNumArgs + callFieldNumVregs +
401         static_cast<int32_t>(method->GetNumRevervedArgs()) - 1;
402     if (!frameWriter->Reserve(static_cast<size_t>(virtualIndex))) {
403         return false;
404     }
405     for (int32_t i = static_cast<int32_t>(declaredNumArgs - 1); i >= 0; i--) {
406         JSTaggedValue value = JSTaggedValue::Undefined();
407         // deopt value
408         if (HasDeoptValue(curDepth, virtualIndex)) {
409             value = GetDeoptValue(curDepth, virtualIndex);
410         }
411         frameWriter->PushValue(value.GetRawData());
412         virtualIndex--;
413     }
414 
415     // [reserved args]
416     if (method->HaveThisWithCallField()) {
417         JSTaggedValue value = deoptVregs_.at(
418             {curDepth, static_cast<OffsetType>(SpecVregIndex::THIS_OBJECT_INDEX)}).GetTaggedValue();
419         frameWriter->PushValue(value.GetRawData());
420         virtualIndex--;
421     }
422     if (method->HaveNewTargetWithCallField()) {
423         JSTaggedValue value = deoptVregs_.at(
424             {curDepth, static_cast<OffsetType>(SpecVregIndex::NEWTARGET_INDEX)}).GetTaggedValue();
425         frameWriter->PushValue(value.GetRawData());
426         virtualIndex--;
427     }
428     if (method->HaveFuncWithCallField()) {
429         JSTaggedValue value = deoptVregs_.at(
430             {curDepth, static_cast<OffsetType>(SpecVregIndex::FUNC_INDEX)}).GetTaggedValue();
431         frameWriter->PushValue(value.GetRawData());
432         virtualIndex--;
433     }
434 
435     // [call field virtual regs]
436     for (int32_t i = virtualIndex; i >= 0; i--) {
437         JSTaggedValue value = GetDeoptValue(curDepth, virtualIndex);
438         frameWriter->PushValue(value.GetRawData());
439         virtualIndex--;
440     }
441     // revise correct a0 - aN virtual regs , for example: ldobjbyname key; sta a2; update value to a2
442     //         +--------------------------+            ^
443     //         |       aN                 |            |
444     //         +--------------------------+            |
445     //         |       ...                |            |
446     //         +--------------------------+            |
447     //         |       a2(this)           |            |
448     //         +--------------------------+   revise correct vreg
449     //         |       a1(newtarget)      |            |
450     //         +--------------------------+            |
451     //         |       a0(func)           |            |
452     //         |--------------------------|            v
453     //         |       v0 - vN            |
454     //  sp --> |--------------------------|
455     int32_t vregsAndArgsNum = declaredNumArgs + callFieldNumVregs +
456         static_cast<int32_t>(method->GetNumRevervedArgs());
457     for (int32_t i = callFieldNumVregs; i < vregsAndArgsNum; i++) {
458         JSTaggedValue value = JSTaggedValue::Undefined();
459         if (HasDeoptValue(curDepth, i)) {
460             value = GetDeoptValue(curDepth, i);
461             frameWriter->ReviseValueByIndex(value.GetRawData(), i);
462         }
463     }
464     return true;
465 }
466 
Dump(JSTaggedValue callTarget,kungfu::DeoptType type,size_t depth)467 void Deoptimizier::Dump(JSTaggedValue callTarget, kungfu::DeoptType type, size_t depth)
468 {
469     if (thread_->IsPGOProfilerEnable()) {
470         JSFunction *function = JSFunction::Cast(callTarget);
471         auto profileTypeInfo = function->GetProfileTypeInfo(thread_);
472         if (profileTypeInfo.IsUndefined()) {
473             SlowRuntimeStub::NotifyInlineCache(thread_, function);
474         }
475     }
476     if (traceDeopt_) {
477         std::string checkType = DisplayItems(type);
478         LOG_TRACE(INFO) << "Check Type: " << checkType;
479         std::string data = JsStackInfo::BuildJsStackTrace(thread_, true);
480         LOG_COMPILER(INFO) << "Deoptimize" << data;
481         const uint8_t *pc = GetMethod(callTarget)->GetBytecodeArray() + pc_.at(depth);
482         BytecodeInstruction inst(pc);
483         LOG_COMPILER(INFO) << inst;
484     }
485 }
486 
DisplayItems(DeoptType type)487 std::string Deoptimizier::DisplayItems(DeoptType type)
488 {
489     const std::map<DeoptType, const char *> strMap = {
490 #define DEOPT_NAME_MAP(NAME, TYPE) {DeoptType::TYPE, #NAME},
491         GATE_META_DATA_DEOPT_REASON(DEOPT_NAME_MAP)
492 #undef DEOPT_NAME_MAP
493     };
494     if (strMap.count(type) > 0) {
495         return strMap.at(type);
496     }
497     return "DeoptType-" + std::to_string(static_cast<uint8_t>(type));
498 }
499 
500 // layout of frameWriter
501 //   |--------------------------| --------------> start(n)
502 //   |          args            |
503 //   |          this            |
504 //   |        newTarget         |
505 //   |       callTarget         |
506 //   |          vregs           |
507 //   |---------------------------
508 //   |       ASM Interpreter    |
509 //   +--------------------------+ --------------> end(n)
510 //   |        outputcounts      |          outputcounts = end(n) - start(n)
511 //   |--------------------------| --------------> start(n-1)
512 //   |          args            |
513 //   |          this            |
514 //   |        newTarget         |
515 //   |       callTarget         |
516 //   |          vregs           |
517 //   |-------------------------------------------
518 //   |       ASM Interpreter    |
519 //   +--------------------------+ --------------> end(n-1)
520 //   |        outputcounts      |           outputcounts = end(n-1) - start(n-1)
521 //   |--------------------------| --------------> start(n-1)
522 //   |       ......             |
523 //   +--------------------------+ ---------------
524 //   |        callerFp_         |               ^
525 //   |       returnAddr_        |               |
526 //   |      callFrameTop_       |          stackContext
527 //   |       inlineDepth_       |               |
528 //   |       hasException_      |               |
529 //   |      isFrameLazyDeopt_   |               v
530 //   |--------------------------| ---------------
531 
ConstructAsmInterpretFrame(JSHandle<JSTaggedValue> maybeAcc)532 JSTaggedType Deoptimizier::ConstructAsmInterpretFrame(JSHandle<JSTaggedValue> maybeAcc)
533 {
534     FrameWriter frameWriter(this);
535     // Push asm interpreter frame
536     for (int32_t curDepth = static_cast<int32_t>(inlineDepth_); curDepth >= 0; curDepth--) {
537         auto start = frameWriter.GetTop();
538         JSTaggedValue callTarget = GetDeoptValue(curDepth, static_cast<int32_t>(SpecVregIndex::FUNC_INDEX));
539         auto method = GetMethod(callTarget);
540         if (!CollectVirtualRegisters(callTarget, method, &frameWriter, curDepth)) {
541             return JSTaggedValue::Exception().GetRawData();
542         }
543         AsmInterpretedFrame *statePtr = frameWriter.ReserveAsmInterpretedFrame();
544         const uint8_t *resumePc = method->GetBytecodeArray() + pc_.at(curDepth);
545         JSTaggedValue thisObj = GetDeoptValue(curDepth, static_cast<int32_t>(SpecVregIndex::THIS_OBJECT_INDEX));
546         JSTaggedValue acc = GetDeoptValue(curDepth, static_cast<int32_t>(SpecVregIndex::ACC_INDEX));
547         if (g_isEnableCMCGC && thread_->NeedReadBarrier()) {
548             base::GCHelper::CopyCallTarget(thread_, callTarget.GetTaggedObject());
549         }
550         statePtr->function = callTarget;
551         statePtr->acc = acc;
552 
553         if (UNLIKELY(curDepth == static_cast<int32_t>(inlineDepth_) &&
554             type_ == static_cast<uint32_t>(kungfu::DeoptType::LAZYDEOPT))) {
555             ProcessLazyDeopt(maybeAcc, resumePc, statePtr);
556         }
557 
558         JSTaggedValue env = GetDeoptValue(curDepth, static_cast<int32_t>(SpecVregIndex::ENV_INDEX));
559         if (env.IsUndefined()) {
560             statePtr->env = JSFunction::Cast(callTarget.GetTaggedObject())->GetLexicalEnv(thread_);
561         } else {
562             statePtr->env = env;
563         }
564         statePtr->callSize = static_cast<uintptr_t>(GetCallSize(curDepth, resumePc));
565         statePtr->fp = 0;  // need update
566         statePtr->thisObj = thisObj;
567         statePtr->pc = resumePc;
568         // -uintptr_t skip lr
569         if (curDepth == 0) {
570             statePtr->base.prev = reinterpret_cast<JSTaggedType *>(stackContext_.callFrameTop_ - sizeof(uintptr_t));
571         } else {
572             statePtr->base.prev = 0; // need update
573         }
574 
575         statePtr->base.type = FrameType::ASM_INTERPRETER_FRAME;
576 
577         // construct stack context
578         auto end = frameWriter.GetTop();
579         auto outputCount = start - end;
580         frameWriter.PushRawValue(outputCount);
581     }
582 
583     RelocateCalleeSave();
584 
585     frameWriter.PushRawValue(stackContext_.isFrameLazyDeopt_);
586     frameWriter.PushRawValue(thread_->HasPendingException());
587     frameWriter.PushRawValue(stackContext_.callerFp_);
588     frameWriter.PushRawValue(stackContext_.returnAddr_);
589     frameWriter.PushRawValue(stackContext_.callFrameTop_);
590     frameWriter.PushRawValue(inlineDepth_);
591     return reinterpret_cast<JSTaggedType>(frameWriter.GetTop());
592 }
593 
594 // static
ResetJitHotness(JSThread * thread,JSFunction * jsFunc)595 void Deoptimizier::ResetJitHotness(JSThread *thread, JSFunction *jsFunc)
596 {
597     if (jsFunc->GetMachineCode(thread).IsMachineCodeObject()) {
598         JSTaggedValue profileTypeInfoVal = jsFunc->GetProfileTypeInfo(thread);
599         if (!profileTypeInfoVal.IsUndefined()) {
600             ProfileTypeInfo *profileTypeInfo = ProfileTypeInfo::Cast(profileTypeInfoVal.GetTaggedObject());
601             profileTypeInfo->SetJitHotnessCnt(0);
602             constexpr uint16_t thresholdStep = 4;
603             constexpr uint16_t thresholdLimit = ProfileTypeInfo::JIT_DISABLE_FLAG / thresholdStep;
604             uint16_t threshold = profileTypeInfo->GetJitHotnessThreshold();
605             threshold = threshold >= thresholdLimit ? ProfileTypeInfo::JIT_DISABLE_FLAG : threshold * thresholdStep;
606             profileTypeInfo->SetJitHotnessThreshold(threshold);
607             ProfileTypeInfoCell::Cast(jsFunc->GetRawProfileTypeInfo(thread))
608                 ->SetMachineCode(thread, JSTaggedValue::Hole());
609             Method *method = Method::Cast(jsFunc->GetMethod(thread).GetTaggedObject());
610             LOG_JIT(DEBUG) << "reset jit hotness for func: " << method->GetMethodName(thread)
611                            << ", threshold:" << threshold;
612         }
613     }
614 }
615 
616 // static
ClearCompiledCodeStatusWhenDeopt(JSThread * thread,JSFunction * func,Method * method,kungfu::DeoptType type)617 void Deoptimizier::ClearCompiledCodeStatusWhenDeopt(JSThread *thread, JSFunction *func,
618                                                     Method *method, kungfu::DeoptType type)
619 {
620     method->SetDeoptType(type);
621     if (func->GetMachineCode(thread).IsMachineCodeObject()) {
622         Jit::GetInstance()->GetJitDfx()->SetJitDeoptCount();
623     }
624     if (func->IsCompiledCode()) {
625         bool isFastCall = func->IsCompiledFastCall();  // get this flag before clear it
626         uintptr_t entry =
627             isFastCall ? thread->GetRTInterface(kungfu::RuntimeStubCSigns::ID_FastCallToAsmInterBridge)
628                        : thread->GetRTInterface(kungfu::RuntimeStubCSigns::ID_AOTCallToAsmInterBridge);
629         func->SetCodeEntry(entry);
630         method->ClearAOTStatusWhenDeopt(entry);
631         func->ClearCompiledCodeFlags();
632         ResetJitHotness(thread, func);
633         func->ClearMachineCode(thread);
634     }  // Do not change the func code entry if the method is not aot or deopt has happened already
635 }
636 
UpdateAndDumpDeoptInfo(kungfu::DeoptType type)637 void Deoptimizier::UpdateAndDumpDeoptInfo(kungfu::DeoptType type)
638 {
639     // depth records the number of layers of nested calls when deopt occurs
640     for (size_t i = 0; i <= inlineDepth_; i++) {
641         JSTaggedValue callTarget = GetDeoptValue(i, static_cast<int32_t>(SpecVregIndex::FUNC_INDEX));
642         auto func = JSFunction::Cast(callTarget.GetTaggedObject());
643         if (func->GetMachineCode(thread_).IsMachineCodeObject()) {
644             MachineCode *machineCode = MachineCode::Cast(func->GetMachineCode(thread_).GetTaggedObject());
645             if (type != kungfu::DeoptType::OSRLOOPEXIT &&
646                 machineCode->GetOSROffset() != MachineCode::INVALID_OSR_OFFSET) {
647                 machineCode->SetOsrDeoptFlag(true);
648             }
649         }
650         auto method = GetMethod(callTarget);
651         if (i == inlineDepth_) {
652             Dump(callTarget, type, i);
653         }
654         ASSERT(thread_ != nullptr);
655         uint8_t deoptThreshold = method->GetDeoptThreshold();
656         if (deoptThreshold > 0) {
657             method->SetDeoptType(type);
658             method->SetDeoptThreshold(--deoptThreshold);
659         } else {
660             ClearCompiledCodeStatusWhenDeopt(thread_, func, method, type);
661         }
662     }
663 }
664 
665 // call instructions need compute jumpSize
GetCallSize(size_t curDepth,const uint8_t * resumePc)666 int64_t Deoptimizier::GetCallSize(size_t curDepth, const uint8_t *resumePc)
667 {
668     if (inlineDepth_ > 0 && curDepth != inlineDepth_) {
669         auto op = BytecodeInstruction(resumePc).GetOpcode();
670         return InterpreterAssembly::GetCallSize(op);
671     }
672     return 0;
673 }
674 
EncodeDeoptVregIndex(int32_t index,size_t depth,size_t shift)675 int32_t Deoptimizier::EncodeDeoptVregIndex(int32_t index, size_t depth, size_t shift)
676 {
677     if (index >= 0) {
678         return (index << shift) | depth;
679     }
680     return -((-index << shift) | depth);
681 }
682 
ComputeShift(size_t depth)683 size_t Deoptimizier::ComputeShift(size_t depth)
684 {
685     size_t shift = 0;
686     if (depth != 0) {
687         shift = std::floor(std::log2(depth)) + 1;
688     }
689     return shift;
690 }
691 
DecodeVregIndex(OffsetType id,size_t shift)692 int32_t Deoptimizier::DecodeVregIndex(OffsetType id, size_t shift)
693 {
694     if (id >= 0) {
695         return id >> shift;
696     }
697     return -((-id) >> shift);
698 }
699 
DecodeDeoptDepth(OffsetType id,size_t shift)700 size_t Deoptimizier::DecodeDeoptDepth(OffsetType id, size_t shift)
701 {
702     size_t mask = (1 << shift) - 1;
703     if (id >= 0) {
704         return id & mask;
705     }
706     return (-id) & mask;
707 }
708 
709 // static
GetInlineDepth(JSThread * thread)710 size_t Deoptimizier::GetInlineDepth(JSThread *thread)
711 {
712     JSTaggedType *current = const_cast<JSTaggedType *>(thread->GetCurrentFrame());
713     FrameIterator it(current, thread);
714     for (; !it.Done(); it.Advance<GCVisitedFlag::VISITED>()) {
715         if (!it.IsOptimizedJSFunctionFrame()) {
716             continue;
717         }
718         return it.GetInlineDepth();
719     }
720     return 0;
721 }
722 
723 // static
ReplaceReturnAddrWithLazyDeoptTrampline(JSThread * thread,uintptr_t * returnAddraddress,FrameType * prevFrameTypeAddress,uintptr_t prevFrameCallSiteSp)724 void Deoptimizier::ReplaceReturnAddrWithLazyDeoptTrampline(JSThread *thread,
725                                                            uintptr_t *returnAddraddress,
726                                                            FrameType *prevFrameTypeAddress,
727                                                            uintptr_t prevFrameCallSiteSp)
728 {
729     ASSERT(returnAddraddress != nullptr);
730     uintptr_t lazyDeoptTrampoline = thread->GetRTInterface(kungfu::RuntimeStubCSigns::ID_LazyDeoptEntry);
731     uintptr_t oldPc = *returnAddraddress;
732     *returnAddraddress = lazyDeoptTrampoline;
733     ASSERT(oldPc != 0);
734     ASSERT(oldPc != lazyDeoptTrampoline);
735     thread->AddToCallsiteSpToReturnAddrTable(prevFrameCallSiteSp, oldPc);
736 
737     FrameIterator::DecodeAsLazyDeoptFrameType(prevFrameTypeAddress);
738 }
739 
740 // static
IsNeedLazyDeopt(const FrameIterator & it)741 bool Deoptimizier::IsNeedLazyDeopt(const FrameIterator &it)
742 {
743     if (!it.IsOptimizedJSFunctionFrame()) {
744         return false;
745     }
746     auto function = it.GetFunction();
747     return function.CheckIsJSFunctionBase() && !JSFunction::Cast(function)->IsCompiledCode();
748 }
749 
750 // static
PrepareForLazyDeopt(JSThread * thread)751 void Deoptimizier::PrepareForLazyDeopt(JSThread *thread)
752 {
753     JSTaggedType *current = const_cast<JSTaggedType *>(thread->GetCurrentFrame());
754     FrameIterator it(current, thread);
755     uintptr_t *prevReturnAddrAddress = nullptr;
756     FrameType *prevFrameTypeAddress;
757     uintptr_t prevFrameCallSiteSp = 0;
758     for (; !it.Done(); it.Advance<GCVisitedFlag::VISITED>()) {
759         if (IsNeedLazyDeopt(it)) {
760             ReplaceReturnAddrWithLazyDeoptTrampline(
761                 thread, prevReturnAddrAddress, prevFrameTypeAddress, prevFrameCallSiteSp);
762         }
763         prevReturnAddrAddress = it.GetReturnAddrAddress();
764         prevFrameTypeAddress = it.GetFrameTypeAddress();
765         prevFrameCallSiteSp = it.GetPrevFrameCallSiteSp();
766     }
767 }
768 
769 /**
770  * [Lazy Deoptimization Handling]
771  * This scenario specifically occurs during lazy deoptimization.
772  *
773  * Typical Trigger:
774  * When bytecode operations (LDOBJBYNAME) invoke accessors that induce
775  * lazy deoptimization of the current function's caller.
776  *
777  * Key Differences from Eager Deoptimization:
778  * Lazy deoptimization happens *after* bytecode execution completes.
779  * When return to DeoptHandlerAsm:
780  * 1. Bytecode processing remains incomplete
781  * 2. Post-processing must handle:
782  *    a. Program Counter (PC) adjustment
783  *    b. Accumulator (ACC) state overwrite
784  *    c. Handling pending exceptions
785  *
786  * Critical Constraint:
787  * Any JIT-compiled call that may trigger lazy deoptimization MUST be the final
788  * call in this bytecode.
789  * This ensures no intermediate state remains after lazy deoptimization.
790  */
ProcessLazyDeopt(JSHandle<JSTaggedValue> maybeAcc,const uint8_t * & resumePc,AsmInterpretedFrame * statePtr)791 void Deoptimizier::ProcessLazyDeopt(JSHandle<JSTaggedValue> maybeAcc, const uint8_t* &resumePc,
792                                     AsmInterpretedFrame *statePtr)
793 {
794     if (NeedOverwriteAcc(resumePc)) {
795         statePtr->acc = maybeAcc.GetTaggedValue();
796     }
797 
798     // Todo: add check constructor
799 
800     if (!thread_->HasPendingException()) {
801         EcmaOpcode curOpcode = kungfu::Bytecodes::GetOpcode(resumePc);
802         // Avoid adding the PC when a pending exception exists.
803         // Prevents ExceptionHandler from failing to identify try-catch blocks.
804         resumePc += (BytecodeInstruction::Size(curOpcode));
805     }
806 }
807 
NeedOverwriteAcc(const uint8_t * pc) const808 bool Deoptimizier::NeedOverwriteAcc(const uint8_t *pc) const
809 {
810     BytecodeInstruction inst(pc);
811     if (inst.HasFlag(BytecodeInstruction::Flags::ACC_WRITE)) {
812         return true;
813     }
814     return false;
815 }
816 }  // namespace panda::ecmascript
817