162c7b5bfSEvan Cheng //===-- MLxExpansionPass.cpp - Expand MLx instrs to avoid hazards ----------=// 262c7b5bfSEvan Cheng // 362c7b5bfSEvan Cheng // The LLVM Compiler Infrastructure 462c7b5bfSEvan Cheng // 562c7b5bfSEvan Cheng // This file is distributed under the University of Illinois Open Source 662c7b5bfSEvan Cheng // License. See LICENSE.TXT for details. 762c7b5bfSEvan Cheng // 862c7b5bfSEvan Cheng //===----------------------------------------------------------------------===// 962c7b5bfSEvan Cheng // 1062c7b5bfSEvan Cheng // Expand VFP / NEON floating point MLA / MLS instructions (each to a pair of 1162c7b5bfSEvan Cheng // multiple and add / sub instructions) when special VMLx hazards are detected. 1262c7b5bfSEvan Cheng // 1362c7b5bfSEvan Cheng //===----------------------------------------------------------------------===// 1462c7b5bfSEvan Cheng 1562c7b5bfSEvan Cheng #define DEBUG_TYPE "mlx-expansion" 1662c7b5bfSEvan Cheng #include "ARM.h" 1762c7b5bfSEvan Cheng #include "ARMBaseInstrInfo.h" 180858c3aaSBob Wilson #include "ARMSubtarget.h" 1962c7b5bfSEvan Cheng #include "llvm/CodeGen/MachineInstr.h" 2062c7b5bfSEvan Cheng #include "llvm/CodeGen/MachineInstrBuilder.h" 2162c7b5bfSEvan Cheng #include "llvm/CodeGen/MachineFunctionPass.h" 2262c7b5bfSEvan Cheng #include "llvm/CodeGen/MachineRegisterInfo.h" 2362c7b5bfSEvan Cheng #include "llvm/Target/TargetRegisterInfo.h" 240858c3aaSBob Wilson #include "llvm/ADT/SmallPtrSet.h" 2562c7b5bfSEvan Cheng #include "llvm/ADT/Statistic.h" 2662c7b5bfSEvan Cheng #include "llvm/Support/CommandLine.h" 2762c7b5bfSEvan Cheng #include "llvm/Support/Debug.h" 2862c7b5bfSEvan Cheng #include "llvm/Support/raw_ostream.h" 2962c7b5bfSEvan Cheng using namespace llvm; 3062c7b5bfSEvan Cheng 3162c7b5bfSEvan Cheng static cl::opt<bool> 3262c7b5bfSEvan Cheng ForceExapnd("expand-all-fp-mlx", cl::init(false), cl::Hidden); 3362c7b5bfSEvan Cheng static cl::opt<unsigned> 3462c7b5bfSEvan Cheng ExpandLimit("expand-limit", cl::init(~0U), cl::Hidden); 3562c7b5bfSEvan Cheng 3662c7b5bfSEvan Cheng STATISTIC(NumExpand, "Number of fp MLA / MLS instructions expanded"); 3762c7b5bfSEvan Cheng 3862c7b5bfSEvan Cheng namespace { 3962c7b5bfSEvan Cheng struct MLxExpansion : public MachineFunctionPass { 4062c7b5bfSEvan Cheng static char ID; 4162c7b5bfSEvan Cheng MLxExpansion() : MachineFunctionPass(ID) {} 4262c7b5bfSEvan Cheng 4362c7b5bfSEvan Cheng virtual bool runOnMachineFunction(MachineFunction &Fn); 4462c7b5bfSEvan Cheng 4562c7b5bfSEvan Cheng virtual const char *getPassName() const { 4662c7b5bfSEvan Cheng return "ARM MLA / MLS expansion pass"; 4762c7b5bfSEvan Cheng } 4862c7b5bfSEvan Cheng 4962c7b5bfSEvan Cheng private: 5062c7b5bfSEvan Cheng const ARMBaseInstrInfo *TII; 5162c7b5bfSEvan Cheng const TargetRegisterInfo *TRI; 5262c7b5bfSEvan Cheng MachineRegisterInfo *MRI; 5362c7b5bfSEvan Cheng 540858c3aaSBob Wilson bool isA9; 5562c7b5bfSEvan Cheng unsigned MIIdx; 5662c7b5bfSEvan Cheng MachineInstr* LastMIs[4]; 570858c3aaSBob Wilson SmallPtrSet<MachineInstr*, 4> IgnoreStall; 5862c7b5bfSEvan Cheng 5962c7b5bfSEvan Cheng void clearStack(); 6062c7b5bfSEvan Cheng void pushStack(MachineInstr *MI); 6162c7b5bfSEvan Cheng MachineInstr *getAccDefMI(MachineInstr *MI) const; 6262c7b5bfSEvan Cheng unsigned getDefReg(MachineInstr *MI) const; 6362c7b5bfSEvan Cheng bool hasRAWHazard(unsigned Reg, MachineInstr *MI) const; 640858c3aaSBob Wilson bool FindMLxHazard(MachineInstr *MI); 6562c7b5bfSEvan Cheng void ExpandFPMLxInstruction(MachineBasicBlock &MBB, MachineInstr *MI, 6662c7b5bfSEvan Cheng unsigned MulOpc, unsigned AddSubOpc, 6762c7b5bfSEvan Cheng bool NegAcc, bool HasLane); 6862c7b5bfSEvan Cheng bool ExpandFPMLxInstructions(MachineBasicBlock &MBB); 6962c7b5bfSEvan Cheng }; 7062c7b5bfSEvan Cheng char MLxExpansion::ID = 0; 7162c7b5bfSEvan Cheng } 7262c7b5bfSEvan Cheng 7362c7b5bfSEvan Cheng void MLxExpansion::clearStack() { 7462c7b5bfSEvan Cheng std::fill(LastMIs, LastMIs + 4, (MachineInstr*)0); 7562c7b5bfSEvan Cheng MIIdx = 0; 7662c7b5bfSEvan Cheng } 7762c7b5bfSEvan Cheng 7862c7b5bfSEvan Cheng void MLxExpansion::pushStack(MachineInstr *MI) { 7962c7b5bfSEvan Cheng LastMIs[MIIdx] = MI; 8062c7b5bfSEvan Cheng if (++MIIdx == 4) 8162c7b5bfSEvan Cheng MIIdx = 0; 8262c7b5bfSEvan Cheng } 8362c7b5bfSEvan Cheng 8462c7b5bfSEvan Cheng MachineInstr *MLxExpansion::getAccDefMI(MachineInstr *MI) const { 8562c7b5bfSEvan Cheng // Look past COPY and INSERT_SUBREG instructions to find the 8662c7b5bfSEvan Cheng // real definition MI. This is important for _sfp instructions. 8762c7b5bfSEvan Cheng unsigned Reg = MI->getOperand(1).getReg(); 8862c7b5bfSEvan Cheng if (TargetRegisterInfo::isPhysicalRegister(Reg)) 8962c7b5bfSEvan Cheng return 0; 9062c7b5bfSEvan Cheng 9162c7b5bfSEvan Cheng MachineBasicBlock *MBB = MI->getParent(); 9262c7b5bfSEvan Cheng MachineInstr *DefMI = MRI->getVRegDef(Reg); 9362c7b5bfSEvan Cheng while (true) { 9462c7b5bfSEvan Cheng if (DefMI->getParent() != MBB) 9562c7b5bfSEvan Cheng break; 9662c7b5bfSEvan Cheng if (DefMI->isCopyLike()) { 9762c7b5bfSEvan Cheng Reg = DefMI->getOperand(1).getReg(); 9862c7b5bfSEvan Cheng if (TargetRegisterInfo::isVirtualRegister(Reg)) { 9962c7b5bfSEvan Cheng DefMI = MRI->getVRegDef(Reg); 10062c7b5bfSEvan Cheng continue; 10162c7b5bfSEvan Cheng } 10262c7b5bfSEvan Cheng } else if (DefMI->isInsertSubreg()) { 10362c7b5bfSEvan Cheng Reg = DefMI->getOperand(2).getReg(); 10462c7b5bfSEvan Cheng if (TargetRegisterInfo::isVirtualRegister(Reg)) { 10562c7b5bfSEvan Cheng DefMI = MRI->getVRegDef(Reg); 10662c7b5bfSEvan Cheng continue; 10762c7b5bfSEvan Cheng } 10862c7b5bfSEvan Cheng } 10962c7b5bfSEvan Cheng break; 11062c7b5bfSEvan Cheng } 11162c7b5bfSEvan Cheng return DefMI; 11262c7b5bfSEvan Cheng } 11362c7b5bfSEvan Cheng 11462c7b5bfSEvan Cheng unsigned MLxExpansion::getDefReg(MachineInstr *MI) const { 11562c7b5bfSEvan Cheng unsigned Reg = MI->getOperand(0).getReg(); 11662c7b5bfSEvan Cheng if (TargetRegisterInfo::isPhysicalRegister(Reg) || 11762c7b5bfSEvan Cheng !MRI->hasOneNonDBGUse(Reg)) 11862c7b5bfSEvan Cheng return Reg; 11962c7b5bfSEvan Cheng 12062c7b5bfSEvan Cheng MachineBasicBlock *MBB = MI->getParent(); 12162c7b5bfSEvan Cheng MachineInstr *UseMI = &*MRI->use_nodbg_begin(Reg); 12262c7b5bfSEvan Cheng if (UseMI->getParent() != MBB) 12362c7b5bfSEvan Cheng return Reg; 12462c7b5bfSEvan Cheng 12562c7b5bfSEvan Cheng while (UseMI->isCopy() || UseMI->isInsertSubreg()) { 12662c7b5bfSEvan Cheng Reg = UseMI->getOperand(0).getReg(); 12762c7b5bfSEvan Cheng if (TargetRegisterInfo::isPhysicalRegister(Reg) || 12862c7b5bfSEvan Cheng !MRI->hasOneNonDBGUse(Reg)) 12962c7b5bfSEvan Cheng return Reg; 13062c7b5bfSEvan Cheng UseMI = &*MRI->use_nodbg_begin(Reg); 13162c7b5bfSEvan Cheng if (UseMI->getParent() != MBB) 13262c7b5bfSEvan Cheng return Reg; 13362c7b5bfSEvan Cheng } 13462c7b5bfSEvan Cheng 13562c7b5bfSEvan Cheng return Reg; 13662c7b5bfSEvan Cheng } 13762c7b5bfSEvan Cheng 13862c7b5bfSEvan Cheng bool MLxExpansion::hasRAWHazard(unsigned Reg, MachineInstr *MI) const { 13962c7b5bfSEvan Cheng // FIXME: Detect integer instructions properly. 1406cc775f9SEvan Cheng const MCInstrDesc &MCID = MI->getDesc(); 1416cc775f9SEvan Cheng unsigned Domain = MCID.TSFlags & ARMII::DomainMask; 142*7f8e563aSEvan Cheng if (MI->mayStore()) 14304ad35b5SEvan Cheng return false; 1446cc775f9SEvan Cheng unsigned Opcode = MCID.getOpcode(); 14504ad35b5SEvan Cheng if (Opcode == ARM::VMOVRS || Opcode == ARM::VMOVRRD) 14662c7b5bfSEvan Cheng return false; 14704ad35b5SEvan Cheng if ((Domain & ARMII::DomainVFP) || (Domain & ARMII::DomainNEON)) 14862c7b5bfSEvan Cheng return MI->readsRegister(Reg, TRI); 14962c7b5bfSEvan Cheng return false; 15062c7b5bfSEvan Cheng } 15162c7b5bfSEvan Cheng 15262c7b5bfSEvan Cheng 1530858c3aaSBob Wilson bool MLxExpansion::FindMLxHazard(MachineInstr *MI) { 15462c7b5bfSEvan Cheng if (NumExpand >= ExpandLimit) 15562c7b5bfSEvan Cheng return false; 15662c7b5bfSEvan Cheng 15762c7b5bfSEvan Cheng if (ForceExapnd) 15862c7b5bfSEvan Cheng return true; 15962c7b5bfSEvan Cheng 16062c7b5bfSEvan Cheng MachineInstr *DefMI = getAccDefMI(MI); 1610858c3aaSBob Wilson if (TII->isFpMLxInstruction(DefMI->getOpcode())) { 16262c7b5bfSEvan Cheng // r0 = vmla 16362c7b5bfSEvan Cheng // r3 = vmla r0, r1, r2 16462c7b5bfSEvan Cheng // takes 16 - 17 cycles 16562c7b5bfSEvan Cheng // 16662c7b5bfSEvan Cheng // r0 = vmla 16762c7b5bfSEvan Cheng // r4 = vmul r1, r2 16862c7b5bfSEvan Cheng // r3 = vadd r0, r4 16962c7b5bfSEvan Cheng // takes about 14 - 15 cycles even with vmul stalling for 4 cycles. 1700858c3aaSBob Wilson IgnoreStall.insert(DefMI); 17162c7b5bfSEvan Cheng return true; 1720858c3aaSBob Wilson } 1730858c3aaSBob Wilson 1740858c3aaSBob Wilson if (IgnoreStall.count(MI)) 1750858c3aaSBob Wilson return false; 17662c7b5bfSEvan Cheng 17762c7b5bfSEvan Cheng // If a VMLA.F is followed by an VADD.F or VMUL.F with no RAW hazard, the 17862c7b5bfSEvan Cheng // VADD.F or VMUL.F will stall 4 cycles before issue. The 4 cycle stall 17962c7b5bfSEvan Cheng // preserves the in-order retirement of the instructions. 18062c7b5bfSEvan Cheng // Look at the next few instructions, if *most* of them can cause hazards, 18162c7b5bfSEvan Cheng // then the scheduler can't *fix* this, we'd better break up the VMLA. 1820858c3aaSBob Wilson unsigned Limit1 = isA9 ? 1 : 4; 1830858c3aaSBob Wilson unsigned Limit2 = isA9 ? 1 : 4; 18462c7b5bfSEvan Cheng for (unsigned i = 1; i <= 4; ++i) { 18562c7b5bfSEvan Cheng int Idx = ((int)MIIdx - i + 4) % 4; 18662c7b5bfSEvan Cheng MachineInstr *NextMI = LastMIs[Idx]; 18762c7b5bfSEvan Cheng if (!NextMI) 18862c7b5bfSEvan Cheng continue; 18962c7b5bfSEvan Cheng 1900858c3aaSBob Wilson if (TII->canCauseFpMLxStall(NextMI->getOpcode())) { 1910858c3aaSBob Wilson if (i <= Limit1) 19262c7b5bfSEvan Cheng return true; 1930858c3aaSBob Wilson } 19462c7b5bfSEvan Cheng 19562c7b5bfSEvan Cheng // Look for VMLx RAW hazard. 1960858c3aaSBob Wilson if (i <= Limit2 && hasRAWHazard(getDefReg(MI), NextMI)) 19762c7b5bfSEvan Cheng return true; 19862c7b5bfSEvan Cheng } 19962c7b5bfSEvan Cheng 20062c7b5bfSEvan Cheng return false; 20162c7b5bfSEvan Cheng } 20262c7b5bfSEvan Cheng 20362c7b5bfSEvan Cheng /// ExpandFPMLxInstructions - Expand a MLA / MLS instruction into a pair 20462c7b5bfSEvan Cheng /// of MUL + ADD / SUB instructions. 20562c7b5bfSEvan Cheng void 20662c7b5bfSEvan Cheng MLxExpansion::ExpandFPMLxInstruction(MachineBasicBlock &MBB, MachineInstr *MI, 20762c7b5bfSEvan Cheng unsigned MulOpc, unsigned AddSubOpc, 20862c7b5bfSEvan Cheng bool NegAcc, bool HasLane) { 20962c7b5bfSEvan Cheng unsigned DstReg = MI->getOperand(0).getReg(); 21062c7b5bfSEvan Cheng bool DstDead = MI->getOperand(0).isDead(); 21162c7b5bfSEvan Cheng unsigned AccReg = MI->getOperand(1).getReg(); 21262c7b5bfSEvan Cheng unsigned Src1Reg = MI->getOperand(2).getReg(); 21362c7b5bfSEvan Cheng unsigned Src2Reg = MI->getOperand(3).getReg(); 21462c7b5bfSEvan Cheng bool Src1Kill = MI->getOperand(2).isKill(); 21562c7b5bfSEvan Cheng bool Src2Kill = MI->getOperand(3).isKill(); 21662c7b5bfSEvan Cheng unsigned LaneImm = HasLane ? MI->getOperand(4).getImm() : 0; 21762c7b5bfSEvan Cheng unsigned NextOp = HasLane ? 5 : 4; 21862c7b5bfSEvan Cheng ARMCC::CondCodes Pred = (ARMCC::CondCodes)MI->getOperand(NextOp).getImm(); 21962c7b5bfSEvan Cheng unsigned PredReg = MI->getOperand(++NextOp).getReg(); 22062c7b5bfSEvan Cheng 2216cc775f9SEvan Cheng const MCInstrDesc &MCID1 = TII->get(MulOpc); 2226cc775f9SEvan Cheng const MCInstrDesc &MCID2 = TII->get(AddSubOpc); 2236cc775f9SEvan Cheng unsigned TmpReg = MRI->createVirtualRegister(TII->getRegClass(MCID1, 0, TRI)); 22462c7b5bfSEvan Cheng 2256cc775f9SEvan Cheng MachineInstrBuilder MIB = BuildMI(MBB, *MI, MI->getDebugLoc(), MCID1, TmpReg) 22662c7b5bfSEvan Cheng .addReg(Src1Reg, getKillRegState(Src1Kill)) 22762c7b5bfSEvan Cheng .addReg(Src2Reg, getKillRegState(Src2Kill)); 22862c7b5bfSEvan Cheng if (HasLane) 22962c7b5bfSEvan Cheng MIB.addImm(LaneImm); 23062c7b5bfSEvan Cheng MIB.addImm(Pred).addReg(PredReg); 23162c7b5bfSEvan Cheng 2326cc775f9SEvan Cheng MIB = BuildMI(MBB, *MI, MI->getDebugLoc(), MCID2) 23362c7b5bfSEvan Cheng .addReg(DstReg, getDefRegState(true) | getDeadRegState(DstDead)); 23462c7b5bfSEvan Cheng 23562c7b5bfSEvan Cheng if (NegAcc) { 23662c7b5bfSEvan Cheng bool AccKill = MRI->hasOneNonDBGUse(AccReg); 23762c7b5bfSEvan Cheng MIB.addReg(TmpReg, getKillRegState(true)) 23862c7b5bfSEvan Cheng .addReg(AccReg, getKillRegState(AccKill)); 23962c7b5bfSEvan Cheng } else { 24062c7b5bfSEvan Cheng MIB.addReg(AccReg).addReg(TmpReg, getKillRegState(true)); 24162c7b5bfSEvan Cheng } 24262c7b5bfSEvan Cheng MIB.addImm(Pred).addReg(PredReg); 24362c7b5bfSEvan Cheng 24462c7b5bfSEvan Cheng DEBUG({ 24562c7b5bfSEvan Cheng dbgs() << "Expanding: " << *MI; 24662c7b5bfSEvan Cheng dbgs() << " to:\n"; 24762c7b5bfSEvan Cheng MachineBasicBlock::iterator MII = MI; 24862c7b5bfSEvan Cheng MII = llvm::prior(MII); 24962c7b5bfSEvan Cheng MachineInstr &MI2 = *MII; 25062c7b5bfSEvan Cheng MII = llvm::prior(MII); 25162c7b5bfSEvan Cheng MachineInstr &MI1 = *MII; 25262c7b5bfSEvan Cheng dbgs() << " " << MI1; 25362c7b5bfSEvan Cheng dbgs() << " " << MI2; 25462c7b5bfSEvan Cheng }); 25562c7b5bfSEvan Cheng 25662c7b5bfSEvan Cheng MI->eraseFromParent(); 25762c7b5bfSEvan Cheng ++NumExpand; 25862c7b5bfSEvan Cheng } 25962c7b5bfSEvan Cheng 26062c7b5bfSEvan Cheng bool MLxExpansion::ExpandFPMLxInstructions(MachineBasicBlock &MBB) { 26162c7b5bfSEvan Cheng bool Changed = false; 26262c7b5bfSEvan Cheng 26362c7b5bfSEvan Cheng clearStack(); 2640858c3aaSBob Wilson IgnoreStall.clear(); 26562c7b5bfSEvan Cheng 26662c7b5bfSEvan Cheng unsigned Skip = 0; 26762c7b5bfSEvan Cheng MachineBasicBlock::reverse_iterator MII = MBB.rbegin(), E = MBB.rend(); 26862c7b5bfSEvan Cheng while (MII != E) { 26962c7b5bfSEvan Cheng MachineInstr *MI = &*MII; 27062c7b5bfSEvan Cheng 27162c7b5bfSEvan Cheng if (MI->isLabel() || MI->isImplicitDef() || MI->isCopy()) { 27262c7b5bfSEvan Cheng ++MII; 27362c7b5bfSEvan Cheng continue; 27462c7b5bfSEvan Cheng } 27562c7b5bfSEvan Cheng 2766cc775f9SEvan Cheng const MCInstrDesc &MCID = MI->getDesc(); 277*7f8e563aSEvan Cheng if (MI->isBarrier()) { 27862c7b5bfSEvan Cheng clearStack(); 27962c7b5bfSEvan Cheng Skip = 0; 28062c7b5bfSEvan Cheng ++MII; 28162c7b5bfSEvan Cheng continue; 28262c7b5bfSEvan Cheng } 28362c7b5bfSEvan Cheng 2846cc775f9SEvan Cheng unsigned Domain = MCID.TSFlags & ARMII::DomainMask; 28562c7b5bfSEvan Cheng if (Domain == ARMII::DomainGeneral) { 28662c7b5bfSEvan Cheng if (++Skip == 2) 28762c7b5bfSEvan Cheng // Assume dual issues of non-VFP / NEON instructions. 28862c7b5bfSEvan Cheng pushStack(0); 28962c7b5bfSEvan Cheng } else { 29062c7b5bfSEvan Cheng Skip = 0; 29162c7b5bfSEvan Cheng 29262c7b5bfSEvan Cheng unsigned MulOpc, AddSubOpc; 29362c7b5bfSEvan Cheng bool NegAcc, HasLane; 2946cc775f9SEvan Cheng if (!TII->isFpMLxInstruction(MCID.getOpcode(), 29562c7b5bfSEvan Cheng MulOpc, AddSubOpc, NegAcc, HasLane) || 29662c7b5bfSEvan Cheng !FindMLxHazard(MI)) 29762c7b5bfSEvan Cheng pushStack(MI); 29862c7b5bfSEvan Cheng else { 29962c7b5bfSEvan Cheng ExpandFPMLxInstruction(MBB, MI, MulOpc, AddSubOpc, NegAcc, HasLane); 30062c7b5bfSEvan Cheng E = MBB.rend(); // May have changed if MI was the 1st instruction. 30162c7b5bfSEvan Cheng Changed = true; 30262c7b5bfSEvan Cheng continue; 30362c7b5bfSEvan Cheng } 30462c7b5bfSEvan Cheng } 30562c7b5bfSEvan Cheng 30662c7b5bfSEvan Cheng ++MII; 30762c7b5bfSEvan Cheng } 30862c7b5bfSEvan Cheng 30962c7b5bfSEvan Cheng return Changed; 31062c7b5bfSEvan Cheng } 31162c7b5bfSEvan Cheng 31262c7b5bfSEvan Cheng bool MLxExpansion::runOnMachineFunction(MachineFunction &Fn) { 31362c7b5bfSEvan Cheng TII = static_cast<const ARMBaseInstrInfo*>(Fn.getTarget().getInstrInfo()); 31462c7b5bfSEvan Cheng TRI = Fn.getTarget().getRegisterInfo(); 31562c7b5bfSEvan Cheng MRI = &Fn.getRegInfo(); 3160858c3aaSBob Wilson const ARMSubtarget *STI = &Fn.getTarget().getSubtarget<ARMSubtarget>(); 3170858c3aaSBob Wilson isA9 = STI->isCortexA9(); 31862c7b5bfSEvan Cheng 31962c7b5bfSEvan Cheng bool Modified = false; 32062c7b5bfSEvan Cheng for (MachineFunction::iterator MFI = Fn.begin(), E = Fn.end(); MFI != E; 32162c7b5bfSEvan Cheng ++MFI) { 32262c7b5bfSEvan Cheng MachineBasicBlock &MBB = *MFI; 32362c7b5bfSEvan Cheng Modified |= ExpandFPMLxInstructions(MBB); 32462c7b5bfSEvan Cheng } 32562c7b5bfSEvan Cheng 32662c7b5bfSEvan Cheng return Modified; 32762c7b5bfSEvan Cheng } 32862c7b5bfSEvan Cheng 32962c7b5bfSEvan Cheng FunctionPass *llvm::createMLxExpansionPass() { 33062c7b5bfSEvan Cheng return new MLxExpansion(); 33162c7b5bfSEvan Cheng } 332