1 /*
2 * Copyright (c) 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 #include "deoptimizer.h"
16 #include "ecmascript/compiler/assembler/assembler.h"
17 #include "ecmascript/compiler/gate_meta_data.h"
18 #include "ecmascript/dfx/stackinfo/js_stackinfo.h"
19 #include "ecmascript/frames.h"
20 #include "ecmascript/interpreter/interpreter.h"
21 #include "ecmascript/js_thread.h"
22 #include "ecmascript/stubs/runtime_stubs-inl.h"
23
24 namespace panda::ecmascript {
25 class FrameWriter {
26 public:
FrameWriter(Deoptimizier * deoptimizier)27 explicit FrameWriter(Deoptimizier *deoptimizier) : thread_(deoptimizier->GetThread())
28 {
29 JSTaggedType *prevSp = const_cast<JSTaggedType *>(thread_->GetCurrentSPFrame());
30 start_ = top_ = EcmaInterpreter::GetInterpreterFrameEnd(thread_, prevSp);
31 }
32
PushValue(JSTaggedType value)33 void PushValue(JSTaggedType value)
34 {
35 *(--top_) = value;
36 }
37
PushRawValue(uintptr_t value)38 void PushRawValue(uintptr_t value)
39 {
40 *(--top_) = value;
41 }
42
Reserve(size_t size)43 bool Reserve(size_t size)
44 {
45 return !thread_->DoStackOverflowCheck(top_ - size);
46 }
47
ReserveAsmInterpretedFrame()48 AsmInterpretedFrame *ReserveAsmInterpretedFrame()
49 {
50 auto frame = AsmInterpretedFrame::GetFrameFromSp(top_);
51 top_ = reinterpret_cast<JSTaggedType *>(frame);
52 return frame;
53 }
54
GetStart() const55 JSTaggedType *GetStart() const
56 {
57 return start_;
58 }
59
GetTop() const60 JSTaggedType *GetTop() const
61 {
62 return top_;
63 }
64
65 private:
66 JSThread *thread_ {nullptr};
67 JSTaggedType *start_;
68 JSTaggedType *top_;
69 };
70
CollectVregs(const std::vector<kungfu::ARKDeopt> & deoptBundle)71 void Deoptimizier::CollectVregs(const std::vector<kungfu::ARKDeopt>& deoptBundle)
72 {
73 deoptVregs_.clear();
74 for (size_t i = 0; i < deoptBundle.size(); i++) {
75 kungfu::ARKDeopt deopt = deoptBundle.at(i);
76 JSTaggedType v;
77 kungfu::OffsetType id = deopt.Id;
78 if (std::holds_alternative<kungfu::DwarfRegAndOffsetType>(deopt.value)) {
79 ASSERT(deopt.kind == kungfu::LocationTy::Kind::INDIRECT);
80 auto value = std::get<kungfu::DwarfRegAndOffsetType>(deopt.value);
81 kungfu::DwarfRegType dwarfReg = value.first;
82 kungfu::OffsetType offset = value.second;
83 ASSERT (dwarfReg == GCStackMapRegisters::FP || dwarfReg == GCStackMapRegisters::SP);
84 uintptr_t addr;
85 if (dwarfReg == GCStackMapRegisters::SP) {
86 addr = context_.callsiteSp + offset;
87 } else {
88 addr = context_.callsiteFp + offset;
89 }
90 v = *(reinterpret_cast<JSTaggedType *>(addr));
91 } else if (std::holds_alternative<kungfu::LargeInt>(deopt.value)) {
92 ASSERT(deopt.kind == kungfu::LocationTy::Kind::CONSTANTNDEX);
93 v = JSTaggedType(static_cast<int64_t>(std::get<kungfu::LargeInt>(deopt.value)));
94 } else {
95 ASSERT(std::holds_alternative<kungfu::OffsetType>(deopt.value));
96 ASSERT(deopt.kind == kungfu::LocationTy::Kind::CONSTANT);
97 v = JSTaggedType(static_cast<int64_t>(std::get<kungfu::OffsetType>(deopt.value)));
98 }
99 if (id != static_cast<kungfu::OffsetType>(SpecVregIndex::PC_INDEX)) {
100 if (id == static_cast<kungfu::OffsetType>(SpecVregIndex::ENV_INDEX)) {
101 env_ = JSTaggedValue(v);
102 } else {
103 deoptVregs_[id] = JSTaggedValue(v);
104 }
105 } else {
106 pc_ = static_cast<uint32_t>(v);
107 }
108 }
109 }
110
111 // when AOT trigger deopt, frame layout as the following
112 // * OptimizedJSFunctionFrame layout description as the following:
113 // +--------------------------+ ---------------
114 // | ...... | ^
115 // | ...... | callerFunction
116 // | ...... | |
117 // |--------------------------| |
118 // | args | v
119 // +--------------------------+ ---------------
120 // | returnAddr | ^
121 // |--------------------------| |
122 // | callsiteFp | |
123 // |--------------------------| OptimizedJSFunction FrameType:OPTIMIZED_JS_FUNCTION_FRAME
124 // | frameType | |
125 // |--------------------------| |
126 // | call-target | |
127 // |--------------------------| |
128 // | lexEnv | |
129 // |--------------------------| |
130 // | ........... | v
131 // +--------------------------+ ---------------
132 // | returnAddr | ^
133 // |--------------------------| |
134 // | callsiteFp | |
135 // |--------------------------| __llvm_deoptimize FrameType:OPTIMIZED_FRAME
136 // | frameType | |
137 // |--------------------------| |
138 // | No CalleeSave | |
139 // | Registers | v
140 // +--------------------------+ ---------------
141 // | returnAddr | ^
142 // |--------------------------| |
143 // | callsiteFp | |
144 // |--------------------------| DeoptHandlerAsm FrameType:ASM_BRIDGE_FRAME
145 // | frameType | |
146 // |--------------------------| |
147 // | glue | |
148 // |--------------------------| |
149 // | CalleeSave Registers | v
150 // +--------------------------+ ---------------
151 // | ......... | ^
152 // | ......... | CallRuntime FrameType:LEAVE_FRAME
153 // | ......... | |
154 // | ......... | v
155 // |--------------------------| ---------------
156
CollectDeoptBundleVec(std::vector<kungfu::ARKDeopt> & deoptBundle)157 void Deoptimizier::CollectDeoptBundleVec(std::vector<kungfu::ARKDeopt>& deoptBundle)
158 {
159 JSTaggedType *lastLeave = const_cast<JSTaggedType *>(thread_->GetLastLeaveFrame());
160 FrameIterator it(lastLeave, thread_);
161 // note: last deopt bridge frame is generated by DeoptHandlerAsm, callee Regs is grow from this frame
162 for (; !it.Done() && deoptBundle.empty(); it.Advance<GCVisitedFlag::VISITED>()) {
163 FrameType type = it.GetFrameType();
164 switch (type) {
165 case FrameType::OPTIMIZED_JS_FUNCTION_FRAME: {
166 auto frame = it.GetFrame<OptimizedJSFunctionFrame>();
167 frame->GetDeoptBundleInfo(it, deoptBundle);
168 kungfu::CalleeRegAndOffsetVec calleeRegInfo;
169 frame->GetFuncCalleeRegAndOffset(it, calleeRegInfo);
170 context_.calleeRegAndOffset = calleeRegInfo;
171 context_.callsiteSp = it.GetCallSiteSp();
172 context_.callsiteFp = reinterpret_cast<uintptr_t>(it.GetSp());
173 auto preFrameSp = frame->ComputePrevFrameSp(it);
174 frameArgc_ = frame->GetArgc(preFrameSp);
175 frameArgvs_ = frame->GetArgv(preFrameSp);
176 stackContext_.callFrameTop_ = it.GetPrevFrameCallSiteSp();
177 stackContext_.returnAddr_ = frame->GetReturnAddr();
178 stackContext_.callerFp_ = reinterpret_cast<uintptr_t>(frame->GetPrevFrameFp());
179 break;
180 }
181 case FrameType::ASM_BRIDGE_FRAME: {
182 auto sp = reinterpret_cast<uintptr_t*>(it.GetSp());
183 static constexpr size_t TYPE_GLUE_SLOT = 2; // 2: skip type & glue
184 sp -= TYPE_GLUE_SLOT;
185 calleeRegAddr_ = sp - numCalleeRegs_;
186 break;
187 }
188 case FrameType::OPTIMIZED_FRAME:
189 case FrameType::LEAVE_FRAME:
190 break;
191 default: {
192 LOG_FULL(FATAL) << "frame type error!";
193 UNREACHABLE();
194 }
195 }
196 }
197 ASSERT(!it.Done());
198 }
199
GetMethod(JSTaggedValue & target)200 Method* Deoptimizier::GetMethod(JSTaggedValue &target)
201 {
202 ECMAObject *callTarget = reinterpret_cast<ECMAObject*>(target.GetTaggedObject());
203 ASSERT(callTarget != nullptr);
204 Method *method = callTarget->GetCallTarget();
205 return method;
206 }
207
RelocateCalleeSave()208 void Deoptimizier::RelocateCalleeSave()
209 {
210 kungfu::CalleeReg callreg;
211 for (auto &it: context_.calleeRegAndOffset) {
212 auto reg = it.first;
213 auto offset = it.second;
214 uintptr_t value = *(reinterpret_cast<uintptr_t *>(context_.callsiteFp + offset));
215 int order = callreg.FindCallRegOrder(reg);
216 calleeRegAddr_[order] = value;
217 }
218 }
219
CollectVirtualRegisters(Method * method,FrameWriter * frameWriter)220 bool Deoptimizier::CollectVirtualRegisters(Method* method, FrameWriter *frameWriter)
221 {
222 int32_t actualNumArgs = static_cast<int32_t>(frameArgc_) - NUM_MANDATORY_JSFUNC_ARGS;
223 bool haveExtra = method->HaveExtraWithCallField();
224 int32_t declaredNumArgs = static_cast<int32_t>(method->GetNumArgsWithCallField());
225 int32_t callFieldNumVregs = static_cast<int32_t>(method->GetNumVregsWithCallField());
226 // layout of frame:
227 // [maybe argc] [actual args] [reserved args] [call field virtual regs]
228
229 // [maybe argc]
230 if (declaredNumArgs != actualNumArgs && haveExtra) {
231 auto value = JSTaggedValue(actualNumArgs);
232 frameWriter->PushValue(value.GetRawData());
233 }
234 int32_t reservedCount = std::max(actualNumArgs, declaredNumArgs);
235 int32_t virtualIndex = reservedCount + callFieldNumVregs +
236 static_cast<int32_t>(method->GetNumRevervedArgs()) - 1;
237 if (!frameWriter->Reserve(static_cast<size_t>(virtualIndex))) {
238 return false;
239 }
240 // [actual args]
241 if (declaredNumArgs > actualNumArgs) {
242 for (int32_t i = 0; i < declaredNumArgs - actualNumArgs; i++) {
243 frameWriter->PushValue(JSTaggedValue::Undefined().GetRawData());
244 virtualIndex--;
245 }
246 }
247 for (int32_t i = actualNumArgs - 1; i >= 0; i--) {
248 JSTaggedValue value = JSTaggedValue::Undefined();
249 // deopt value
250 if (HasDeoptValue(virtualIndex)) {
251 value = deoptVregs_.at(static_cast<kungfu::OffsetType>(virtualIndex));
252 } else {
253 value = GetActualFrameArgs(i);
254 }
255 frameWriter->PushValue(value.GetRawData());
256 virtualIndex--;
257 }
258
259 // [reserved args]
260 if (method->HaveThisWithCallField()) {
261 JSTaggedValue value = GetFrameArgv(kungfu::CommonArgIdx::THIS_OBJECT);
262 frameWriter->PushValue(value.GetRawData());
263 virtualIndex--;
264 }
265 if (method->HaveNewTargetWithCallField()) {
266 JSTaggedValue value = GetFrameArgv(kungfu::CommonArgIdx::NEW_TARGET);
267 frameWriter->PushValue(value.GetRawData());
268 virtualIndex--;
269 }
270 if (method->HaveFuncWithCallField()) {
271 JSTaggedValue value = GetFrameArgv(kungfu::CommonArgIdx::FUNC);
272 frameWriter->PushValue(value.GetRawData());
273 virtualIndex--;
274 }
275
276 // [call field virtual regs]
277 for (int32_t i = virtualIndex; i >= 0; i--) {
278 JSTaggedValue value = GetDeoptValue(virtualIndex);
279 frameWriter->PushValue(value.GetRawData());
280 virtualIndex--;
281 }
282 return true;
283 }
284
Dump(Method * method,kungfu::DeoptType type)285 void Deoptimizier::Dump(Method* method, kungfu::DeoptType type)
286 {
287 if (traceDeopt_) {
288 std::string checkType = DisplayItems(type);
289 LOG_COMPILER(INFO) << "Check Type: " << checkType;
290 std::string data = JsStackInfo::BuildMethodTrace(method, pc_);
291 LOG_COMPILER(INFO) << "Deoptimize" << data;
292 const uint8_t *pc = method->GetBytecodeArray() + pc_;
293 BytecodeInstruction inst(pc);
294 LOG_COMPILER(INFO) << inst;
295 }
296 }
297
DisplayItems(kungfu::DeoptType type)298 std::string Deoptimizier::DisplayItems(kungfu::DeoptType type)
299 {
300 switch (type) {
301 case kungfu::DeoptType::NOTINT:
302 return "NOT INT";
303 case kungfu::DeoptType::NOTDOUBLE:
304 return "NOT DOUBLE";
305 case kungfu::DeoptType::NOTNUMBER:
306 return "NOT NUMBER";
307 case kungfu::DeoptType::NOTBOOL:
308 return "NOT BOOL";
309 case kungfu::DeoptType::NOTARRAY:
310 return "NOT ARRAY";
311 case kungfu::DeoptType::NOTSARRAY:
312 return "NOT SARRAY";
313 case kungfu::DeoptType::NOTF32ARRAY:
314 return "NOT F32ARRAY";
315 case kungfu::DeoptType::WRONGHCLASS:
316 return "WRONG HCLASS";
317 case kungfu::DeoptType::NOTNEWOBJ:
318 return "NOT NEWOBJ TYPE";
319 case kungfu::DeoptType::NOTARRAYIDX:
320 return "NOT ARRAY IDX";
321 case kungfu::DeoptType::NOTF32ARRAYIDX:
322 return "NOT F32 ARRAY IDX";
323 case kungfu::DeoptType::NOTINCOV:
324 return "NOT INC OVERFLOW";
325 case kungfu::DeoptType::NOTDECOV:
326 return "NOT DEC OVERFLOW";
327 case kungfu::DeoptType::NOTNEGOV:
328 return "NOT NEG OVERFLOW";
329 case kungfu::DeoptType::NOTCALLTGT:
330 return "NOT CALL TARGET";
331 default: {
332 return "NOT CHECK";
333 }
334 }
335 }
336
ConstructAsmInterpretFrame(kungfu::DeoptType type)337 JSTaggedType Deoptimizier::ConstructAsmInterpretFrame(kungfu::DeoptType type)
338 {
339 JSTaggedValue callTarget = GetFrameArgv(kungfu::CommonArgIdx::FUNC);
340 auto method = GetMethod(callTarget);
341 Dump(method, type);
342 ASSERT(thread_ != nullptr);
343 uint8_t deoptThreshold = method->GetDeoptThreshold();
344 if (deoptThreshold > 0) {
345 method->SetDeoptType(type);
346 method->SetDeoptThreshold(--deoptThreshold);
347 } else {
348 method->SetAotCodeBit(false);
349 method->SetDeoptType(kungfu::DeoptType::NOTCHECK);
350 method->SetCodeEntryOrLiteral(reinterpret_cast<uintptr_t>(nullptr));
351 }
352
353 FrameWriter frameWriter(this);
354 // Push asm interpreter frame
355 if (!CollectVirtualRegisters(method, &frameWriter)) {
356 return JSTaggedValue::Exception().GetRawData();
357 }
358 AsmInterpretedFrame *statePtr = frameWriter.ReserveAsmInterpretedFrame();
359 const uint8_t *resumePc = method->GetBytecodeArray() + pc_;
360
361 JSTaggedValue thisObj = GetFrameArgv(kungfu::CommonArgIdx::THIS_OBJECT);
362 auto acc = GetDeoptValue(static_cast<int32_t>(SpecVregIndex::ACC_INDEX));
363 statePtr->function = callTarget;
364 statePtr->acc = acc;
365 statePtr->env = env_;
366 statePtr->callSize = 0;
367 statePtr->fp = 0; // need update
368 statePtr->thisObj = thisObj;
369 statePtr->pc = resumePc;
370 // -uintptr_t skip lr
371 statePtr->base.prev = reinterpret_cast<JSTaggedType *>(
372 stackContext_.callFrameTop_ - sizeof(uintptr_t));
373 statePtr->base.type = FrameType::ASM_INTERPRETER_FRAME;
374
375 // construct stack context
376 auto start = frameWriter.GetStart();
377 auto end = frameWriter.GetTop();
378 auto outputCount = start - end;
379
380 RelocateCalleeSave();
381
382 frameWriter.PushRawValue(stackContext_.callerFp_);
383 frameWriter.PushRawValue(stackContext_.returnAddr_);
384 frameWriter.PushRawValue(stackContext_.callFrameTop_);
385 frameWriter.PushRawValue(outputCount);
386 return reinterpret_cast<JSTaggedType>(frameWriter.GetTop());
387 }
388 } // namespace panda::ecmascript