1 /*
2 * Copyright (c) 2023 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 "ecmascript/compiler/early_elimination.h"
17
18 namespace panda::ecmascript::kungfu {
19
Initialize()20 void EarlyElimination::Initialize()
21 {
22 dependChains_.resize(circuit_->GetMaxGateId() + 1, nullptr); // 1: +1 for size
23 renames_.resize(circuit_->GetMaxGateId() + 1, Circuit::NullGate()); // 1: +1 for size
24 GateRef entry = acc_.GetDependRoot();
25 VisitDependEntry(entry);
26 }
27
GetLoopDependInfo(GateRef depend)28 DependInfoNode* EarlyElimination::GetLoopDependInfo(GateRef depend)
29 {
30 auto depIn = acc_.GetDep(depend);
31 auto dependChain = GetDependChain(depIn);
32 if (dependChain == nullptr) {
33 return nullptr;
34 }
35 auto newChain = new (chunk_) DependInfoNode(chunk_);
36 newChain->CopyFrom(dependChain);
37 ChunkSet<GateRef> visited(chunk_);
38 ChunkQueue<GateRef> workList(chunk_);
39 workList.push(depend);
40 visited.insert(acc_.GetDep(depend));
41 while (!workList.empty()) {
42 auto curDep = workList.front();
43 workList.pop();
44 if (visited.count(curDep)) {
45 continue;
46 }
47 if (!acc_.IsNotWrite(curDep)) {
48 newChain = UpdateWrite(curDep, newChain);
49 }
50 visited.insert(curDep);
51 auto depCount = acc_.GetDependCount(curDep);
52 for (size_t i = 0; i < depCount; ++i) {
53 workList.push(acc_.GetDep(curDep, i));
54 }
55 }
56 return newChain;
57 }
58
VisitDependEntry(GateRef gate)59 GateRef EarlyElimination::VisitDependEntry(GateRef gate)
60 {
61 auto empty = new (chunk_) DependInfoNode(chunk_);
62 return UpdateDependChain(gate, empty);
63 }
64
VisitGate(GateRef gate)65 GateRef EarlyElimination::VisitGate(GateRef gate)
66 {
67 auto opcode = acc_.GetOpCode(gate);
68 switch (opcode) {
69 case OpCode::LOAD_PROPERTY:
70 case OpCode::LOAD_ELEMENT:
71 case OpCode::LOAD_ARRAY_LENGTH:
72 case OpCode::LOAD_TYPED_ARRAY_LENGTH:
73 case OpCode::TYPED_ARRAY_CHECK:
74 case OpCode::OBJECT_TYPE_CHECK:
75 case OpCode::STABLE_ARRAY_CHECK:
76 case OpCode::INDEX_CHECK:
77 case OpCode::ELEMENTSKIND_CHECK:
78 case OpCode::TYPED_CALL_CHECK:
79 case OpCode::LOAD_CONST_OFFSET:
80 case OpCode::LOAD_HCLASS_FROM_CONSTPOOL:
81 case OpCode::TYPED_BINARY_OP:
82 case OpCode::TYPED_UNARY_OP:
83 case OpCode::JSINLINETARGET_TYPE_CHECK:
84 case OpCode::PROTOTYPE_CHECK:
85 case OpCode::LOAD_GETTER:
86 case OpCode::LOAD_SETTER:
87 case OpCode::ECMA_STRING_CHECK:
88 case OpCode::BUILTIN_PROTOTYPE_HCLASS_CHECK:
89 case OpCode::TYPE_OF_CHECK:
90 case OpCode::ARRAY_CONSTRUCTOR_CHECK:
91 case OpCode::FLOAT32_ARRAY_CONSTRUCTOR_CHECK:
92 case OpCode::OBJECT_CONSTRUCTOR_CHECK:
93 case OpCode::BOOLEAN_CONSTRUCTOR_CHECK:
94 case OpCode::PROTO_CHANGE_MARKER_CHECK:
95 case OpCode::MONO_LOAD_PROPERTY_ON_PROTO:
96 case OpCode::LOAD_BUILTIN_OBJECT:
97 case OpCode::LOOK_UP_HOLDER:
98 case OpCode::IS_CALLABLE_CHECK:
99 return TryEliminateGate(gate);
100 case OpCode::STATE_SPLIT:
101 if (enableFrameStateElimination_) {
102 return TryEliminateFrameState(gate);
103 }
104 break;
105 case OpCode::DEPEND_SELECTOR:
106 return TryEliminateDependSelector(gate);
107 default:
108 if (acc_.GetDependCount(gate) == 1) { // 1: depend in is 1
109 return TryEliminateOther(gate);
110 }
111 break;
112 }
113 return Circuit::NullGate();
114 }
115
TryEliminateOther(GateRef gate)116 GateRef EarlyElimination::TryEliminateOther(GateRef gate)
117 {
118 ASSERT(acc_.GetDependCount(gate) >= 1);
119 auto depIn = acc_.GetDep(gate);
120 auto dependChain = GetDependChain(depIn);
121 if (dependChain == nullptr) {
122 return Circuit::NullGate();
123 }
124
125 if (!acc_.IsNotWrite(gate)) {
126 dependChain = UpdateWrite(gate, dependChain);
127 }
128
129 return UpdateDependChain(gate, dependChain);
130 }
131
TryEliminateGate(GateRef gate)132 GateRef EarlyElimination::TryEliminateGate(GateRef gate)
133 {
134 ASSERT(acc_.GetDependCount(gate) == 1);
135 auto depIn = acc_.GetDep(gate);
136 auto dependChain = GetDependChain(depIn);
137 // dependChain is null
138 if (dependChain == nullptr) {
139 return Circuit::NullGate();
140 }
141
142 if (!acc_.IsNotWrite(gate)) {
143 dependChain = UpdateWrite(gate, dependChain);
144 return UpdateDependChain(gate, dependChain);
145 }
146
147 auto numIns = acc_.GetNumValueIn(gate);
148 for (size_t i = 0; i < numIns; ++i) {
149 auto origin = acc_.GetValueIn(gate, i);
150 auto checkd = dependChain->LookupCheckedNode(this, origin);
151 if (origin != checkd) {
152 acc_.ReplaceValueIn(gate, checkd, i);
153 }
154 }
155
156 // lookup gate, replace
157 auto preGate = dependChain->LookupNode(this, gate);
158 if (preGate != Circuit::NullGate()) {
159 return preGate;
160 }
161 // update gate, for others elimination
162 dependChain = dependChain->UpdateNode(gate);
163 return UpdateDependChain(gate, dependChain);
164 }
165
TryEliminateFrameState(GateRef gate)166 GateRef EarlyElimination::TryEliminateFrameState(GateRef gate)
167 {
168 ASSERT(acc_.GetOpCode(gate) == OpCode::STATE_SPLIT);
169 auto depIn = acc_.GetDep(gate);
170 auto dependChain = GetDependChain(depIn);
171 // dependChain is null
172 if (dependChain == nullptr) {
173 return Circuit::NullGate();
174 }
175 // lookup gate, replace
176 auto preFrame = dependChain->LookupFrameState();
177 auto curFrame = acc_.GetFrameState(gate);
178 if ((preFrame != Circuit::NullGate()) && (preFrame != curFrame) &&
179 acc_.GetFrameState(preFrame) == acc_.GetFrameState(curFrame)) {
180 acc_.UpdateAllUses(curFrame, preFrame);
181 auto frameValues = acc_.GetValueIn(curFrame, 1); // 1: frameValues
182 acc_.DeleteGate(frameValues);
183 acc_.DeleteGate(curFrame);
184 return depIn;
185 } else {
186 dependChain = dependChain->UpdateFrameState(curFrame);
187 }
188 // update gate, for others elimination
189
190 return UpdateDependChain(gate, dependChain);
191 }
192
TryEliminateDependSelector(GateRef gate)193 GateRef EarlyElimination::TryEliminateDependSelector(GateRef gate)
194 {
195 auto state = acc_.GetState(gate);
196 if (acc_.IsLoopHead(state)) {
197 auto dependChain = GetLoopDependInfo(gate);
198 if (dependChain == nullptr) {
199 return Circuit::NullGate();
200 }
201 return UpdateDependChain(gate, dependChain);
202 }
203
204 auto dependCount = acc_.GetDependCount(gate);
205 for (size_t i = 0; i < dependCount; ++i) {
206 auto depend = acc_.GetDep(gate, i);
207 auto dependChain = GetDependChain(depend);
208 if (dependChain == nullptr) {
209 return Circuit::NullGate();
210 }
211 }
212
213 // all depend done.
214 auto depend = acc_.GetDep(gate);
215 auto dependChain = GetDependChain(depend);
216 DependInfoNode* copy = new (chunk_) DependInfoNode(chunk_);
217 copy->CopyFrom(dependChain);
218 for (size_t i = 1; i < dependCount; ++i) { // 1: second in
219 auto dependIn = acc_.GetDep(gate, i);
220 auto tempChain = GetDependChain(dependIn);
221 copy->Merge(this, tempChain);
222 }
223 return UpdateDependChain(gate, copy);
224 }
225
UpdateDependChain(GateRef gate,DependInfoNode * dependChain)226 GateRef EarlyElimination::UpdateDependChain(GateRef gate, DependInfoNode* dependChain)
227 {
228 ASSERT(dependChain != nullptr);
229 auto oldDependChain = GetDependChain(gate);
230 if (dependChain->Equals(oldDependChain)) {
231 return Circuit::NullGate();
232 }
233 dependChains_[acc_.GetId(gate)] = dependChain;
234 return gate;
235 }
236
UpdateWrite(GateRef gate,DependInfoNode * dependInfo)237 DependInfoNode* EarlyElimination::UpdateWrite(GateRef gate, DependInfoNode* dependInfo)
238 {
239 if (!enableMemoryAnalysis_) {
240 return new (chunk_) DependInfoNode(chunk_);
241 }
242 auto op = acc_.GetOpCode(gate);
243 switch (op) {
244 case OpCode::STORE_PROPERTY:
245 case OpCode::STORE_PROPERTY_NO_BARRIER:
246 case OpCode::STORE_CONST_OFFSET:
247 case OpCode::STORE_ELEMENT:
248 case OpCode::STORE_MEMORY:
249 case OpCode::MIGRATE_ARRAY_WITH_KIND:
250 case OpCode::MONO_STORE_PROPERTY_LOOK_UP_PROTO:
251 case OpCode::MONO_STORE_PROPERTY:
252 return dependInfo->UpdateStoreProperty(this, gate);
253 default:
254 return new (chunk_) DependInfoNode(chunk_);
255 }
256 }
257
MayAccessOneMemory(GateRef lhs,GateRef rhs)258 bool EarlyElimination::MayAccessOneMemory(GateRef lhs, GateRef rhs)
259 {
260 auto rop = acc_.GetOpCode(rhs);
261 auto lop = acc_.GetOpCode(lhs);
262 switch (rop) {
263 case OpCode::STORE_MEMORY:
264 ASSERT(acc_.GetMemoryType(rhs) == MemoryType::ELEMENT_TYPE);
265 return acc_.GetOpCode(lhs) == OpCode::LOAD_ELEMENT;
266 case OpCode::MIGRATE_ARRAY_WITH_KIND: {
267 if (lop == OpCode::LOAD_ELEMENT) {
268 GateRef lopValueIn = acc_.GetValueIn(lhs, 0); // loadelement receiver
269 GateRef ropValueIn = acc_.GetValueIn(rhs, 0); // migrate receiver
270 return lopValueIn == ropValueIn;
271 }
272 return false;
273 }
274 case OpCode::STORE_ELEMENT: {
275 if (lop == OpCode::LOAD_ELEMENT) {
276 bool lopIsTypedArray = acc_.TypedOpIsTypedArray(lhs, TypedOpKind::TYPED_LOAD_OP);
277 bool ropIsTypedArray = acc_.TypedOpIsTypedArray(rhs, TypedOpKind::TYPED_STORE_OP);
278 return lopIsTypedArray == ropIsTypedArray;
279 }
280 return false;
281 }
282 case OpCode::STORE_PROPERTY:
283 case OpCode::STORE_PROPERTY_NO_BARRIER: {
284 if (lop == OpCode::LOAD_PROPERTY) {
285 auto loff = acc_.GetValueIn(lhs, 1);
286 auto roff = acc_.GetValueIn(rhs, 1);
287 ASSERT(acc_.GetOpCode(loff) == OpCode::CONSTANT);
288 ASSERT(acc_.GetOpCode(roff) == OpCode::CONSTANT);
289 return loff == roff;
290 } else if (lop == OpCode::PROTOTYPE_CHECK) {
291 auto lindex = acc_.GetHClassIndex(lhs);
292 auto rindex = acc_.GetHClassIndex(rhs);
293 return (lindex == 0) || (rindex == 0) || (lindex != rindex);
294 }
295 break;
296 }
297 case OpCode::STORE_CONST_OFFSET: {
298 if (lop == OpCode::LOAD_CONST_OFFSET) {
299 auto loff = acc_.GetOffset(lhs);
300 auto roff = acc_.GetOffset(rhs);
301 return loff == roff;
302 }
303 break;
304 }
305 case OpCode::LOAD_PROPERTY:
306 case OpCode::MONO_LOAD_PROPERTY_ON_PROTO:
307 if (acc_.GetGateType(lhs).Value() != acc_.GetGateType(rhs).Value()) {
308 return false;
309 }
310 break;
311 default:
312 break;
313 }
314 return false;
315 }
316
CompareOrder(GateRef lhs,GateRef rhs)317 bool EarlyElimination::CompareOrder(GateRef lhs, GateRef rhs)
318 {
319 return visitor_->GetGateOrder(lhs) < visitor_->GetGateOrder(rhs);
320 }
321
CheckReplacement(GateRef lhs,GateRef rhs)322 bool EarlyElimination::CheckReplacement(GateRef lhs, GateRef rhs)
323 {
324 if (!acc_.MetaDataEqu(lhs, rhs)) {
325 if (acc_.GetOpCode(lhs) != acc_.GetOpCode(rhs)) {
326 return false;
327 }
328 }
329
330 size_t valueCount = acc_.GetNumValueIn(lhs);
331 for (size_t i = 0; i < valueCount; i++) {
332 if (Rename(acc_.GetValueIn(lhs, i)) != Rename(acc_.GetValueIn(rhs, i))) {
333 return false;
334 }
335 }
336
337 auto opcode = acc_.GetOpCode(lhs);
338 switch (opcode) {
339 case OpCode::LOAD_ELEMENT: {
340 if (acc_.GetTypedLoadOp(lhs) != acc_.GetTypedLoadOp(rhs)) {
341 return false;
342 }
343 break;
344 }
345 case OpCode::TYPED_BINARY_OP: {
346 auto lhsOp = acc_.GetTypedBinaryOp(lhs);
347 auto rhsOp = acc_.GetTypedBinaryOp(rhs);
348 if (lhsOp != rhsOp) {
349 return false;
350 }
351 break;
352 }
353 case OpCode::TYPED_UNARY_OP: {
354 auto lhsOp = acc_.GetTypedUnAccessor(lhs).GetTypedUnOp();
355 auto rhsOp = acc_.GetTypedUnAccessor(rhs).GetTypedUnOp();
356 if (lhsOp != rhsOp) {
357 return false;
358 }
359 break;
360 }
361 case OpCode::TYPED_ARRAY_CHECK: {
362 TypedArrayMetaDataAccessor lhsAccessor = acc_.GetTypedArrayMetaDataAccessor(lhs);
363 TypedArrayMetaDataAccessor rhsAccessor = acc_.GetTypedArrayMetaDataAccessor(rhs);
364 if ((lhsAccessor.GetParamType() != rhsAccessor.GetParamType()) ||
365 (lhsAccessor.GetOnHeapMode() != rhsAccessor.GetOnHeapMode())) {
366 return false;
367 }
368 break;
369 }
370 case OpCode::TYPE_OF_CHECK: {
371 if (acc_.GetParamType(lhs) != acc_.GetParamType(rhs)) {
372 return false;
373 }
374 break;
375 }
376 case OpCode::PROTOTYPE_CHECK: {
377 if (acc_.GetHClassIndex(lhs) != acc_.GetHClassIndex(rhs)) {
378 return false;
379 }
380 break;
381 }
382 case OpCode::LOAD_CONST_OFFSET: {
383 if (acc_.GetOffset(lhs) != acc_.GetOffset(rhs)) {
384 return false;
385 }
386 if (acc_.GetMachineType(lhs) != acc_.GetMachineType(rhs)) {
387 return false;
388 }
389 if (acc_.GetMemoryAttribute(lhs).Value() != acc_.GetMemoryAttribute(rhs).Value()) {
390 return false;
391 }
392 break;
393 }
394 case OpCode::LOAD_HCLASS_FROM_CONSTPOOL: {
395 if (acc_.GetIndex(lhs) != acc_.GetIndex(rhs)) {
396 return false;
397 }
398 break;
399 }
400 case OpCode::JSINLINETARGET_TYPE_CHECK: {
401 if (acc_.GetFuncGT(lhs) != acc_.GetFuncGT(rhs)) {
402 return false;
403 }
404 break;
405 }
406 case OpCode::ARRAY_CONSTRUCTOR_CHECK:
407 case OpCode::FLOAT32_ARRAY_CONSTRUCTOR_CHECK:
408 case OpCode::OBJECT_CONSTRUCTOR_CHECK:
409 case OpCode::BOOLEAN_CONSTRUCTOR_CHECK: {
410 if (acc_.GetValueIn(lhs) != acc_.GetValueIn(rhs)) {
411 return false;
412 }
413 break;
414 }
415 case OpCode::LOAD_BUILTIN_OBJECT: {
416 if (acc_.GetIndex(lhs) != acc_.GetIndex(rhs)) {
417 return false;
418 }
419 break;
420 }
421 case OpCode::STABLE_ARRAY_CHECK: {
422 ArrayMetaDataAccessor lhsAccessor = acc_.GetArrayMetaDataAccessor(lhs);
423 ArrayMetaDataAccessor rhsAccessor = acc_.GetArrayMetaDataAccessor(rhs);
424 if (lhsAccessor.GetMode() != rhsAccessor.GetMode()) {
425 return false;
426 }
427 break;
428 }
429 default:
430 break;
431 }
432 return true;
433 }
434
CheckRenameReplacement(GateRef lhs,GateRef rhs)435 bool EarlyElimination::CheckRenameReplacement(GateRef lhs, GateRef rhs)
436 {
437 auto opcode = acc_.GetOpCode(lhs);
438 switch (opcode) {
439 case OpCode::INDEX_CHECK: {
440 auto index = acc_.GetValueIn(lhs, 1);
441 if (Rename(index) == Rename(rhs)) {
442 return true;
443 }
444 break;
445 }
446 default:
447 break;
448 }
449 return false;
450 }
451
Rename(GateRef gate)452 GateRef EarlyElimination::Rename(GateRef gate)
453 {
454 ChunkStack<GateRef> gateStack(chunk_);
455 while (true) {
456 auto op = acc_.GetOpCode(gate);
457 bool renamed = false;
458 switch (op) {
459 case OpCode::INDEX_CHECK: {
460 GateRef ans = renames_[acc_.GetId(gate)];
461 if (ans == Circuit::NullGate()) {
462 renamed = true;
463 gateStack.push(gate);
464 gate = acc_.GetValueIn(gate, 1);
465 } else {
466 gate = ans;
467 }
468 break;
469 }
470 default:
471 break;
472 }
473 if (!renamed) {
474 break;
475 }
476 }
477 while (!gateStack.empty()) {
478 auto topGate = gateStack.top();
479 gateStack.pop();
480 renames_[acc_.GetId(topGate)] = gate;
481 }
482 return gate;
483 }
484
Merge(EarlyElimination * elimination,DependInfoNode * that)485 void DependInfoNode::Merge(EarlyElimination* elimination, DependInfoNode* that)
486 {
487 auto siz = this->size_; // size of lhs-chain
488 auto lhs = this->head_;
489 auto rhs = that->head_;
490 ChunkStack<GateRef> gateStack(chunk_);
491 while (lhs != rhs) {
492 if (lhs == nullptr || rhs == nullptr) {
493 siz = 0;
494 lhs = nullptr;
495 break;
496 } else if (lhs->gate == rhs->gate) {
497 gateStack.push(lhs->gate);
498 siz--;
499 lhs = lhs->next;
500 rhs = rhs->next;
501 } else if (elimination->CompareOrder(lhs->gate, rhs->gate)) {
502 rhs = rhs->next;
503 } else {
504 siz--;
505 lhs = lhs->next;
506 }
507 }
508 // lhs : common suffix of lhs-chain and rhs-chain
509 this->head_ = lhs;
510 this->size_ = siz;
511 while (!gateStack.empty()) {
512 Node* node = chunk_->New<Node>(gateStack.top(), head_);
513 gateStack.pop();
514 this->size_++;
515 this->head_ = node;
516 }
517 if (this->frameState_ != that->frameState_) {
518 this->frameState_ = Circuit::NullGate();
519 }
520 }
521
Equals(DependInfoNode * that)522 bool DependInfoNode::Equals(DependInfoNode* that)
523 {
524 if (that == nullptr) {
525 return false;
526 }
527 if (size_ != that->size_ || frameState_ != that->frameState_) {
528 return false;
529 }
530 auto lhs = this->head_;
531 auto rhs = that->head_;
532 while (lhs != rhs) {
533 if (lhs->gate != rhs->gate) {
534 return false;
535 }
536 lhs = lhs->next;
537 rhs = rhs->next;
538 }
539 return true;
540 }
541
LookupFrameState() const542 GateRef DependInfoNode::LookupFrameState() const
543 {
544 return frameState_;
545 }
546
LookupCheckedNode(EarlyElimination * elimination,GateRef gate)547 GateRef DependInfoNode::LookupCheckedNode(EarlyElimination* elimination, GateRef gate)
548 {
549 for (Node* node = head_; node != nullptr; node = node->next) {
550 if (elimination->CheckRenameReplacement(node->gate, gate)) {
551 return node->gate;
552 }
553 }
554 return gate;
555 }
556
GetGates(std::vector<GateRef> & gates) const557 void DependInfoNode::GetGates(std::vector<GateRef>& gates) const
558 {
559 ChunkStack<GateRef> st(chunk_);
560 for (Node* node = head_; node != nullptr; node = node->next) {
561 st.push(node->gate);
562 }
563 while (!st.empty()) {
564 gates.emplace_back(st.top());
565 st.pop();
566 }
567 }
568
LookupNode(EarlyElimination * elimination,GateRef gate)569 GateRef DependInfoNode::LookupNode(EarlyElimination* elimination, GateRef gate)
570 {
571 for (Node* node = head_; node != nullptr; node = node->next) {
572 if (elimination->CheckReplacement(node->gate, gate)) {
573 return node->gate;
574 }
575 }
576 return Circuit::NullGate();
577 }
578
UpdateNode(GateRef gate)579 DependInfoNode* DependInfoNode::UpdateNode(GateRef gate)
580 {
581 // assign node->next to head
582 Node* node = chunk_->New<Node>(gate, head_);
583 DependInfoNode* that = new (chunk_) DependInfoNode(chunk_);
584 // assign head to node
585 that->head_ = node;
586 that->size_ = size_ + 1;
587 that->frameState_ = frameState_;
588 return that;
589 }
590
UpdateFrameState(GateRef framestate)591 DependInfoNode* DependInfoNode::UpdateFrameState(GateRef framestate)
592 {
593 // assign node->next to head
594 DependInfoNode* that = new (chunk_) DependInfoNode(chunk_);
595 // assign head to node
596 that->head_ = head_;
597 that->size_ = size_;
598 that->frameState_ = framestate;
599 return that;
600 }
601
UpdateStoreProperty(EarlyElimination * elimination,GateRef gate)602 DependInfoNode* DependInfoNode::UpdateStoreProperty(EarlyElimination* elimination, GateRef gate)
603 {
604 DependInfoNode* that = new (chunk_) DependInfoNode(chunk_);
605 ChunkStack<GateRef> gateStack(chunk_);
606 for (Node* node = head_; node != nullptr; node = node->next) {
607 if (!elimination->MayAccessOneMemory(node->gate, gate)) {
608 gateStack.push(node->gate);
609 }
610 }
611 while (!gateStack.empty()) {
612 that = that->UpdateNode(gateStack.top());
613 gateStack.pop();
614 }
615 return that;
616 }
617 } // namespace panda::ecmascript::kungfu
618