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