1 /*
2  * Copyright (c) 2021 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/bytecode_circuit_builder.h"
17 #include "ecmascript/compiler/circuit.h"
18 #include "ecmascript/compiler/debug_info.h"
19 #include "ecmascript/compiler/ecma_opcode_des.h"
20 #include "ecmascript/compiler/gate_accessor.h"
21 #include "ecmascript/platform/map.h"
22 
23 namespace panda::ecmascript::kungfu {
Circuit(NativeAreaAllocator* allocator, DebugInfo* debugInfo, const char* funcName, bool isArch64, panda::ecmascript::FrameType type)24 Circuit::Circuit(NativeAreaAllocator* allocator, DebugInfo* debugInfo, const char* funcName,
25                  bool isArch64, panda::ecmascript::FrameType type)
26     : circuitSize_(0),
27       gateCount_(0),
28       time_(1),
29       frameType_(type),
30       isArch64_(isArch64),
31       chunk_(allocator),
32       root_(Circuit::NullGate()),
33       metaBuilder_(chunk()),
34       gateToDInfo_(chunk()),
35       debugInfo_(debugInfo)
36 #ifndef NDEBUG
37       , allGates_(chunk())
38 #endif
39 {
40     if (funcName != nullptr && debugInfo_->IsEnable()) {
41         debugInfo_->AddFuncDebugInfo(funcName);
42     }
43     space_ = panda::ecmascript::PageMap(CIRCUIT_SPACE, PAGE_PROT_READWRITE).GetMem();
44     InitRoot();
45 }
46 
~Circuit()47 Circuit::~Circuit()
48 {
49     panda::ecmascript::PageUnmap(MemMap(space_, CIRCUIT_SPACE));
50     debugInfo_ = nullptr;
51     space_ = nullptr;
52 }
53 
InitRoot()54 void Circuit::InitRoot()
55 {
56     root_ = NewGate(metaBuilder_.CircuitRoot(), MachineType::NOVALUE, {}, GateType::Empty());
57     NewGate(metaBuilder_.StateEntry(), MachineType::NOVALUE, { root_ }, GateType::Empty());
58     NewGate(metaBuilder_.DependEntry(), MachineType::NOVALUE, { root_ }, GateType::Empty());
59     NewGate(metaBuilder_.ReturnList(), MachineType::NOVALUE, { root_ }, GateType::Empty());
60     NewGate(metaBuilder_.ArgList(), MachineType::NOVALUE, { root_ }, GateType::Empty());
61 }
62 
AllocateSpace(size_t gateSize)63 uint8_t *Circuit::AllocateSpace(size_t gateSize)
64 {
65     circuitSize_ += gateSize;
66     if (circuitSize_ > CIRCUIT_SPACE) {
67         return nullptr;  // abort compilation
68     }
69     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
70     return GetDataPtr(circuitSize_ - gateSize);
71 }
72 
AllocateGateSpace(size_t numIns)73 Gate *Circuit::AllocateGateSpace(size_t numIns)
74 {
75     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
76     return reinterpret_cast<Gate *>(AllocateSpace(Gate::GetGateSize(numIns)) + Gate::GetOutListSize(numIns));
77 }
78 
AddComment(GateRef g, std::string &&str)79 bool Circuit::AddComment(GateRef g, std::string &&str)
80 {
81     if (debugInfo_ == nullptr) {
82         return false;
83     }
84     if (!debugInfo_->IsEnable()) {
85         return false;
86     }
87     auto it = gateToDInfo_.find(g);
88     if (it == gateToDInfo_.end()) {
89         size_t index = debugInfo_->AddComment(std::move(str));
90         gateToDInfo_[g] = index;
91     } else {
92         debugInfo_->AppendComment(it->second, std::move(str));
93     }
94     return true;
95 }
96 
GetComment(GateRef gate) const97 std::string_view Circuit::GetComment(GateRef gate) const
98 {
99     if (debugInfo_ == nullptr || !debugInfo_->IsEnable()) {
100         return "";
101     }
102     size_t index;
103     if (!GetDebugInfo(gate, index)) {
104         return "";
105     }
106     return debugInfo_->GetComment(index);
107 }
108 
GetDebugInfo(GateRef g, size_t &index) const109 bool Circuit::GetDebugInfo(GateRef g, size_t &index) const
110 {
111     auto it = gateToDInfo_.find(g);
112     if (it != gateToDInfo_.end()) {
113         index = it->second;
114         return true;
115     } else {
116         return false;
117     }
118 }
119 
120 // NOLINTNEXTLINE(modernize-avoid-c-arrays)
NewGate(const GateMetaData *meta, MachineType machineType, size_t numIns, const GateRef inList[], GateType type, const char* comment)121 GateRef Circuit::NewGate(const GateMetaData *meta, MachineType machineType, size_t numIns,
122                          const GateRef inList[], GateType type, const char* comment)
123 {
124 #ifndef NDEBUG
125     if (numIns != meta->GetNumIns()) {
126         LOG_COMPILER(FATAL) << "Invalid input list!"
127                             << " op=" << meta->GetOpCode()
128                             << " expected_num_in=" << meta->GetNumIns() << " actual_num_in=" << numIns;
129         UNREACHABLE();
130     }
131 #endif
132     std::vector<Gate *> inPtrList(numIns);
133     auto gateSpace = AllocateGateSpace(numIns);
134     for (size_t idx = 0; idx < numIns; idx++) {
135         // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
136         inPtrList[idx] = (inList[idx] == Circuit::NullGate()) ? nullptr : LoadGatePtr(inList[idx]);
137     }
138     auto newGate = new (gateSpace) Gate(meta, gateCount_++, inPtrList.data(), machineType, type);
139 #ifndef NDEBUG
140     allGates_.push_back(GetGateRef(newGate));
141 #endif
142     GateRef result = GetGateRef(newGate);
143     if (comment != nullptr) {
144         AddComment(result, std::string(comment));
145     }
146 #ifndef NDEBUG
147     if (UNLIKELY(debugInfo_ != nullptr && !currentComment_.empty())) {
148         AddComment(result, std::string(currentComment_));
149     }
150 #endif
151     return result;
152 }
153 
NewGate(const GateMetaData *meta, const std::vector<GateRef> &inList, const char* comment)154 GateRef Circuit::NewGate(const GateMetaData *meta, const std::vector<GateRef> &inList, const char* comment)
155 {
156     return NewGate(meta, MachineType::NOVALUE, inList.size(), inList.data(), GateType::Empty(), comment);
157 }
158 
NewGate(const GateMetaData *meta, MachineType machineType, const std::initializer_list<GateRef>& args, GateType type, const char* comment)159 GateRef Circuit::NewGate(const GateMetaData *meta, MachineType machineType,
160     const std::initializer_list<GateRef>& args, GateType type, const char* comment)
161 {
162     return NewGate(meta, machineType, args.size(), args.begin(), type, comment);
163 }
164 
NewGate(const GateMetaData *meta, MachineType machineType, const std::vector<GateRef>& inList, GateType type, const char* comment)165 GateRef Circuit::NewGate(const GateMetaData *meta, MachineType machineType,
166     const std::vector<GateRef>& inList, GateType type, const char* comment)
167 {
168     return NewGate(meta, machineType, inList.size(), inList.data(), type, comment);
169 }
170 
NewGate(const GateMetaData *meta, MachineType machineType, GateType type, const char* comment)171 GateRef Circuit::NewGate(const GateMetaData *meta, MachineType machineType, GateType type, const char* comment)
172 {
173     return NewGate(meta, machineType, {}, type, comment);
174 }
175 
PrintAllGates() const176 void Circuit::PrintAllGates() const
177 {
178     std::vector<GateRef> gateList;
179     GetAllGates(gateList);
180     for (const auto &gate : gateList) {
181         LoadGatePtrConst(gate)->Print();
182     }
183 }
184 
PrintAllGatesWithBytecode() const185 void Circuit::PrintAllGatesWithBytecode() const
186 {
187     std::vector<GateRef> gateList;
188     GetAllGates(gateList);
189     for (const auto &gate : gateList) {
190         LoadGatePtrConst(gate)->PrintWithBytecode(GetComment(gate));
191     }
192 }
193 
GetAllGates(std::vector<GateRef>& gateList) const194 void Circuit::GetAllGates(std::vector<GateRef>& gateList) const
195 {
196     gateList.clear();
197     for (size_t out = 0; out < circuitSize_;
198         out += Gate::GetGateSize(reinterpret_cast<const Out *>(LoadGatePtrConst(GateRef(out)))->GetIndex() + 1)) {
199         auto gatePtr = reinterpret_cast<const Out *>(LoadGatePtrConst(GateRef(out)))->GetGateConst();
200         if (!gatePtr->GetMetaData()->IsNop()) {
201             gateList.push_back(GetGateRef(gatePtr));
202         }
203     }
204 }
205 
GetGateRef(const Gate *gate) const206 GateRef Circuit::GetGateRef(const Gate *gate) const
207 {
208     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
209     return static_cast<GateRef>(reinterpret_cast<const uint8_t *>(gate) - GetDataPtrConst(0));
210 }
211 
LoadGatePtr(GateRef shift)212 Gate *Circuit::LoadGatePtr(GateRef shift)
213 {
214     ASSERT(shift != Circuit::NullGate());
215     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
216     return reinterpret_cast<Gate *>(GetDataPtr(shift));
217 }
218 
LoadGatePtrConst(GateRef shift) const219 const Gate *Circuit::LoadGatePtrConst(GateRef shift) const
220 {
221     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
222     return reinterpret_cast<const Gate *>(GetDataPtrConst(shift));
223 }
224 
AdvanceTime() const225 void Circuit::AdvanceTime() const
226 {
227     auto &curTime = const_cast<TimeStamp &>(time_);
228     curTime++;
229     if (curTime == 0) {
230         curTime = 1;
231         ResetAllGateTimeStamps();
232     }
233 }
234 
ResetAllGateTimeStamps() const235 void Circuit::ResetAllGateTimeStamps() const
236 {
237     std::vector<GateRef> gateList;
238     GetAllGates(gateList);
239     for (auto &gate : gateList) {
240         const_cast<Gate *>(LoadGatePtrConst(gate))->SetMark(MarkCode::NO_MARK, 0);
241     }
242 }
243 
GetTime() const244 TimeStamp Circuit::GetTime() const
245 {
246     return time_;
247 }
248 
GetMark(GateRef gate) const249 MarkCode Circuit::GetMark(GateRef gate) const
250 {
251     return LoadGatePtrConst(gate)->GetMark(GetTime());
252 }
253 
SetMark(GateRef gate, MarkCode mark) const254 void Circuit::SetMark(GateRef gate, MarkCode mark) const
255 {
256     const_cast<Gate *>(LoadGatePtrConst(gate))->SetMark(mark, GetTime());
257 }
258 
Verify(GateRef gate, const std::string& methodName) const259 void Circuit::Verify(GateRef gate, const std::string& methodName) const
260 {
261     LoadGatePtrConst(gate)->Verify(IsArch64(), methodName);
262 }
263 
NullGate()264 GateRef Circuit::NullGate()
265 {
266     return Gate::InvalidGateRef;
267 }
268 
IsLoopHead(GateRef gate) const269 bool Circuit::IsLoopHead(GateRef gate) const
270 {
271     if (gate != NullGate()) {
272         const Gate *curGate = LoadGatePtrConst(gate);
273         return curGate->GetMetaData()->IsLoopHead();
274     }
275     return false;
276 }
277 
IsControlCase(GateRef gate) const278 bool Circuit::IsControlCase(GateRef gate) const
279 {
280     if (gate != NullGate()) {
281         const Gate *curGate = LoadGatePtrConst(gate);
282         return curGate->GetMetaData()->IsControlCase();
283     }
284     return false;
285 }
286 
IsValueSelector(GateRef gate) const287 bool Circuit::IsValueSelector(GateRef gate) const
288 {
289     if (gate != NullGate()) {
290         const Gate *curGate = LoadGatePtrConst(gate);
291         return curGate->GetOpCode() == OpCode::VALUE_SELECTOR;
292     }
293     return false;
294 }
295 
IsSelector(GateRef gate) const296 bool Circuit::IsSelector(GateRef gate) const
297 {
298     if (gate != NullGate()) {
299         const Gate *curGate = LoadGatePtrConst(gate);
300         OpCode op = curGate->GetOpCode();
301         return (op == OpCode::VALUE_SELECTOR) || (op == OpCode::DEPEND_SELECTOR);
302     }
303     return false;
304 }
305 
GetIn(GateRef gate, size_t idx) const306 GateRef Circuit::GetIn(GateRef gate, size_t idx) const
307 {
308     ASSERT(idx < LoadGatePtrConst(gate)->GetNumIns());
309     if (IsInGateNull(gate, idx)) {
310         return NullGate();
311     }
312     const Gate *curGate = LoadGatePtrConst(gate);
313     return GetGateRef(curGate->GetInGateConst(idx));
314 }
315 
IsInGateNull(GateRef gate, size_t idx) const316 bool Circuit::IsInGateNull(GateRef gate, size_t idx) const
317 {
318     const Gate *curGate = LoadGatePtrConst(gate);
319     return curGate->GetInConst(idx)->IsGateNull();
320 }
321 
IsFirstOutNull(GateRef gate) const322 bool Circuit::IsFirstOutNull(GateRef gate) const
323 {
324     const Gate *curGate = LoadGatePtrConst(gate);
325     return curGate->IsFirstOutNull();
326 }
327 
GetOutVector(GateRef gate) const328 std::vector<GateRef> Circuit::GetOutVector(GateRef gate) const
329 {
330     std::vector<GateRef> result;
331     const Gate *curGate = LoadGatePtrConst(gate);
332     if (!curGate->IsFirstOutNull()) {
333         const Out *curOut = curGate->GetFirstOutConst();
334         result.push_back(GetGateRef(curOut->GetGateConst()));
335         while (!curOut->IsNextOutNull()) {
336             curOut = curOut->GetNextOutConst();
337             result.push_back(GetGateRef(curOut->GetGateConst()));
338         }
339     }
340     return result;
341 }
342 
NewIn(GateRef gate, size_t idx, GateRef in)343 void Circuit::NewIn(GateRef gate, size_t idx, GateRef in)
344 {
345 #ifndef NDEBUG
346     ASSERT(idx < LoadGatePtrConst(gate)->GetNumIns());
347     ASSERT(Circuit::IsInGateNull(gate, idx));
348 #endif
349     LoadGatePtr(gate)->NewIn(idx, LoadGatePtr(in));
350 }
351 
ModifyIn(GateRef gate, size_t idx, GateRef in)352 void Circuit::ModifyIn(GateRef gate, size_t idx, GateRef in)
353 {
354 #ifndef NDEBUG
355     ASSERT(idx < LoadGatePtrConst(gate)->GetNumIns());
356     ASSERT(!Circuit::IsInGateNull(gate, idx) || (GetOpCode(gate) == OpCode::SAVE_REGISTER));
357 #endif
358     LoadGatePtr(gate)->ModifyIn(idx, LoadGatePtr(in));
359 }
360 
DeleteIn(GateRef gate, size_t idx)361 void Circuit::DeleteIn(GateRef gate, size_t idx)
362 {
363     ASSERT(idx < LoadGatePtrConst(gate)->GetNumIns());
364     ASSERT(!Circuit::IsInGateNull(gate, idx));
365     LoadGatePtr(gate)->DeleteIn(idx);
366 }
367 
DeleteGate(GateRef gate)368 void Circuit::DeleteGate(GateRef gate)
369 {
370     // constant in constant cache, dont delete it.
371     if (GetOpCode(gate) != OpCode::CONSTANT) {
372         LoadGatePtr(gate)->DeleteGate();
373         LoadGatePtr(gate)->SetMetaData(Nop());
374     }
375 }
376 
DecreaseIn(GateRef gate, size_t idx)377 void Circuit::DecreaseIn(GateRef gate, size_t idx)
378 {
379     auto numIns = LoadGatePtrConst(gate)->GetNumIns();
380     ASSERT(numIns > 0);
381     for (size_t i = idx; i < numIns - 1; i++) {
382         ModifyIn(gate, i, GetIn(gate, i + 1));
383     }
384     DeleteIn(gate, numIns - 1);
385     GateMetaData *meta = const_cast<GateMetaData *>(
386             LoadGatePtr(gate)->GetMetaData());
387     if (meta->GetKind() == GateMetaData::Kind::MUTABLE_WITH_SIZE) {
388         meta->DecreaseIn(idx);
389     } else {
390         meta = metaBuilder_.NewGateMetaData(meta);
391         meta->DecreaseIn(idx);
392         LoadGatePtr(gate)->SetMetaData(meta);
393     }
394 }
395 
SetGateType(GateRef gate, GateType type)396 void Circuit::SetGateType(GateRef gate, GateType type)
397 {
398     LoadGatePtr(gate)->SetGateType(type);
399 }
400 
SetMachineType(GateRef gate, MachineType machineType)401 void Circuit::SetMachineType(GateRef gate, MachineType machineType)
402 {
403     LoadGatePtr(gate)->SetMachineType(machineType);
404 }
405 
GetGateType(GateRef gate) const406 GateType Circuit::GetGateType(GateRef gate) const
407 {
408     return LoadGatePtrConst(gate)->GetGateType();
409 }
410 
GetMachineType(GateRef gate) const411 MachineType Circuit::GetMachineType(GateRef gate) const
412 {
413     return LoadGatePtrConst(gate)->GetMachineType();
414 }
415 
GetOpCode(GateRef gate) const416 OpCode Circuit::GetOpCode(GateRef gate) const
417 {
418     return LoadGatePtrConst(gate)->GetOpCode();
419 }
420 
GetId(GateRef gate) const421 GateId Circuit::GetId(GateRef gate) const
422 {
423     return LoadGatePtrConst(gate)->GetId();
424 }
425 
426 #ifndef NDEBUG
ScopedComment(std::string &&str, std::string_view *comment)427 Circuit::ScopedComment::ScopedComment(std::string &&str, std::string_view *comment)
428     : old_(*comment), comment_(comment)
429 {
430     if (comment->empty()) {
431         str_ = std::move(str);
432     } else {
433         str_ = std::string{*comment} + " " + std::move(str);
434     }
435     *comment_ = {str_};
436 }
437 
VisitGateBegin(GateRef visitedGate)438 Circuit::ScopedComment Circuit::VisitGateBegin(GateRef visitedGate)
439 {
440     return ScopedComment("old " + std::to_string(GetId(visitedGate)), &currentComment_);
441 }
442 
CommentBegin(std::string &&str)443 Circuit::ScopedComment Circuit::CommentBegin(std::string &&str)
444 {
445     return ScopedComment(std::move(str), &currentComment_);
446 }
447 #endif
448 
Print(GateRef gate) const449 void Circuit::Print(GateRef gate) const
450 {
451     LoadGatePtrConst(gate)->Print();
452 }
453 
GetCircuitDataSize() const454 size_t Circuit::GetCircuitDataSize() const
455 {
456     return circuitSize_;
457 }
458 
GetSpaceDataStartPtrConst() const459 const void *Circuit::GetSpaceDataStartPtrConst() const
460 {
461     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
462     return GetDataPtrConst(0);
463 }
464 
GetSpaceDataEndPtrConst() const465 const void *Circuit::GetSpaceDataEndPtrConst() const
466 {
467     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
468     return GetDataPtrConst(circuitSize_);
469 }
470 
GetDataPtrConst(size_t offset) const471 const uint8_t *Circuit::GetDataPtrConst(size_t offset) const
472 {
473     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
474     return static_cast<uint8_t *>(space_) + offset;
475 }
476 
GetDataPtr(size_t offset)477 uint8_t *Circuit::GetDataPtr(size_t offset)
478 {
479     // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
480     return static_cast<uint8_t *>(space_) + offset;
481 }
482 
GetFrameType() const483 panda::ecmascript::FrameType Circuit::GetFrameType() const
484 {
485     return frameType_;
486 }
487 
SetFrameType(panda::ecmascript::FrameType type)488 void Circuit::SetFrameType(panda::ecmascript::FrameType type)
489 {
490     frameType_ = type;
491 }
492 
GetConstantGate(MachineType machineType, uint64_t value, GateType type)493 GateRef Circuit::GetConstantGate(MachineType machineType, uint64_t value,
494                                  GateType type)
495 {
496     auto search = constantCache_.find({machineType, value, type});
497     if (search != constantCache_.end()) {
498         return search->second;
499     }
500     auto gate = NewGate(metaBuilder_.Constant(value), machineType, type);
501     constantCache_[{machineType, value, type}] = gate;
502     return gate;
503 }
504 
GetConstantGateWithoutCache(MachineType machineType, uint64_t value, GateType type)505 GateRef Circuit::GetConstantGateWithoutCache(MachineType machineType, uint64_t value, GateType type)
506 {
507     auto gate = NewGate(metaBuilder_.Constant(value), machineType, type);
508     return gate;
509 }
510 
ClearConstantCache(MachineType machineType, uint64_t value, GateType type)511 void Circuit::ClearConstantCache(MachineType machineType, uint64_t value, GateType type)
512 {
513     auto search = constantCache_.find({machineType, value, type});
514     if (search != constantCache_.end()) {
515         constantCache_.erase(search);
516     }
517 }
518 
GetConstantStringGate(MachineType machineType, std::string_view str, GateType type)519 GateRef Circuit::GetConstantStringGate(MachineType machineType, std::string_view str,
520                                        GateType type)
521 {
522     auto gate = NewGate(metaBuilder_.ConstString(str), machineType, type);
523     return gate;
524 }
525 
GetInitialEnvGate(GateRef depend, GateRef jsFunc)526 GateRef Circuit::GetInitialEnvGate(GateRef depend, GateRef jsFunc)
527 {
528     auto search = initialEnvCache_.find(jsFunc);
529     if (search != initialEnvCache_.end()) {
530         return initialEnvCache_.at(jsFunc);
531     }
532     auto gate = NewGate(GetEnv(), MachineType::I64, {depend, jsFunc}, GateType::AnyType());
533     initialEnvCache_[jsFunc] = gate;
534     return gate;
535 }
536 
NewArg(MachineType machineType, size_t index, GateType type, GateRef argRoot)537 GateRef Circuit::NewArg(MachineType machineType, size_t index,
538                         GateType type, GateRef argRoot)
539 {
540     return NewGate(metaBuilder_.Arg(index), machineType, { argRoot }, type);
541 }
542 
GetGateCount() const543 size_t Circuit::GetGateCount() const
544 {
545     return gateCount_;
546 }
547 
GetStateRoot() const548 GateRef Circuit::GetStateRoot() const
549 {
550     const GateAccessor acc(const_cast<Circuit*>(this));
551     return acc.GetStateRoot();
552 }
553 
GetDependRoot() const554 GateRef Circuit::GetDependRoot() const
555 {
556     const GateAccessor acc(const_cast<Circuit*>(this));
557     return acc.GetDependRoot();
558 }
559 
GetArgRoot() const560 GateRef Circuit::GetArgRoot() const
561 {
562     const GateAccessor acc(const_cast<Circuit*>(this));
563     return acc.GetArgRoot();
564 }
565 
GetReturnRoot() const566 GateRef Circuit::GetReturnRoot() const
567 {
568     const GateAccessor acc(const_cast<Circuit*>(this));
569     return acc.GetReturnRoot();
570 }
571 }  // namespace panda::ecmascript::kungfu
572