10b57cec5SDimitry Andric //===-- llvm/CodeGen/GlobalISel/Legalizer.cpp -----------------------------===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric // 90b57cec5SDimitry Andric /// \file This file implements the LegalizerHelper class to legalize individual 100b57cec5SDimitry Andric /// instructions and the LegalizePass wrapper pass for the primary 110b57cec5SDimitry Andric /// legalization. 120b57cec5SDimitry Andric // 130b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 140b57cec5SDimitry Andric 150b57cec5SDimitry Andric #include "llvm/CodeGen/GlobalISel/Legalizer.h" 160b57cec5SDimitry Andric #include "llvm/ADT/PostOrderIterator.h" 170b57cec5SDimitry Andric #include "llvm/ADT/SetVector.h" 180b57cec5SDimitry Andric #include "llvm/CodeGen/GlobalISel/CSEInfo.h" 190b57cec5SDimitry Andric #include "llvm/CodeGen/GlobalISel/CSEMIRBuilder.h" 200b57cec5SDimitry Andric #include "llvm/CodeGen/GlobalISel/GISelChangeObserver.h" 210b57cec5SDimitry Andric #include "llvm/CodeGen/GlobalISel/GISelWorkList.h" 220b57cec5SDimitry Andric #include "llvm/CodeGen/GlobalISel/LegalizationArtifactCombiner.h" 230b57cec5SDimitry Andric #include "llvm/CodeGen/GlobalISel/LegalizerHelper.h" 240b57cec5SDimitry Andric #include "llvm/CodeGen/GlobalISel/Utils.h" 250b57cec5SDimitry Andric #include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h" 260b57cec5SDimitry Andric #include "llvm/CodeGen/MachineRegisterInfo.h" 270b57cec5SDimitry Andric #include "llvm/CodeGen/TargetPassConfig.h" 280b57cec5SDimitry Andric #include "llvm/CodeGen/TargetSubtargetInfo.h" 29*480093f4SDimitry Andric #include "llvm/InitializePasses.h" 300b57cec5SDimitry Andric #include "llvm/Support/Debug.h" 310b57cec5SDimitry Andric #include "llvm/Target/TargetMachine.h" 320b57cec5SDimitry Andric 330b57cec5SDimitry Andric #include <iterator> 340b57cec5SDimitry Andric 350b57cec5SDimitry Andric #define DEBUG_TYPE "legalizer" 360b57cec5SDimitry Andric 370b57cec5SDimitry Andric using namespace llvm; 380b57cec5SDimitry Andric 390b57cec5SDimitry Andric static cl::opt<bool> 400b57cec5SDimitry Andric EnableCSEInLegalizer("enable-cse-in-legalizer", 410b57cec5SDimitry Andric cl::desc("Should enable CSE in Legalizer"), 420b57cec5SDimitry Andric cl::Optional, cl::init(false)); 430b57cec5SDimitry Andric 440b57cec5SDimitry Andric char Legalizer::ID = 0; 450b57cec5SDimitry Andric INITIALIZE_PASS_BEGIN(Legalizer, DEBUG_TYPE, 460b57cec5SDimitry Andric "Legalize the Machine IR a function's Machine IR", false, 470b57cec5SDimitry Andric false) 480b57cec5SDimitry Andric INITIALIZE_PASS_DEPENDENCY(TargetPassConfig) 490b57cec5SDimitry Andric INITIALIZE_PASS_DEPENDENCY(GISelCSEAnalysisWrapperPass) 500b57cec5SDimitry Andric INITIALIZE_PASS_END(Legalizer, DEBUG_TYPE, 510b57cec5SDimitry Andric "Legalize the Machine IR a function's Machine IR", false, 520b57cec5SDimitry Andric false) 530b57cec5SDimitry Andric 540b57cec5SDimitry Andric Legalizer::Legalizer() : MachineFunctionPass(ID) { } 550b57cec5SDimitry Andric 560b57cec5SDimitry Andric void Legalizer::getAnalysisUsage(AnalysisUsage &AU) const { 570b57cec5SDimitry Andric AU.addRequired<TargetPassConfig>(); 580b57cec5SDimitry Andric AU.addRequired<GISelCSEAnalysisWrapperPass>(); 590b57cec5SDimitry Andric AU.addPreserved<GISelCSEAnalysisWrapperPass>(); 600b57cec5SDimitry Andric getSelectionDAGFallbackAnalysisUsage(AU); 610b57cec5SDimitry Andric MachineFunctionPass::getAnalysisUsage(AU); 620b57cec5SDimitry Andric } 630b57cec5SDimitry Andric 640b57cec5SDimitry Andric void Legalizer::init(MachineFunction &MF) { 650b57cec5SDimitry Andric } 660b57cec5SDimitry Andric 670b57cec5SDimitry Andric static bool isArtifact(const MachineInstr &MI) { 680b57cec5SDimitry Andric switch (MI.getOpcode()) { 690b57cec5SDimitry Andric default: 700b57cec5SDimitry Andric return false; 710b57cec5SDimitry Andric case TargetOpcode::G_TRUNC: 720b57cec5SDimitry Andric case TargetOpcode::G_ZEXT: 730b57cec5SDimitry Andric case TargetOpcode::G_ANYEXT: 740b57cec5SDimitry Andric case TargetOpcode::G_SEXT: 750b57cec5SDimitry Andric case TargetOpcode::G_MERGE_VALUES: 760b57cec5SDimitry Andric case TargetOpcode::G_UNMERGE_VALUES: 770b57cec5SDimitry Andric case TargetOpcode::G_CONCAT_VECTORS: 780b57cec5SDimitry Andric case TargetOpcode::G_BUILD_VECTOR: 790b57cec5SDimitry Andric case TargetOpcode::G_EXTRACT: 800b57cec5SDimitry Andric return true; 810b57cec5SDimitry Andric } 820b57cec5SDimitry Andric } 830b57cec5SDimitry Andric using InstListTy = GISelWorkList<256>; 840b57cec5SDimitry Andric using ArtifactListTy = GISelWorkList<128>; 850b57cec5SDimitry Andric 860b57cec5SDimitry Andric namespace { 870b57cec5SDimitry Andric class LegalizerWorkListManager : public GISelChangeObserver { 880b57cec5SDimitry Andric InstListTy &InstList; 890b57cec5SDimitry Andric ArtifactListTy &ArtifactList; 900b57cec5SDimitry Andric #ifndef NDEBUG 910b57cec5SDimitry Andric SmallVector<MachineInstr *, 4> NewMIs; 920b57cec5SDimitry Andric #endif 930b57cec5SDimitry Andric 940b57cec5SDimitry Andric public: 950b57cec5SDimitry Andric LegalizerWorkListManager(InstListTy &Insts, ArtifactListTy &Arts) 960b57cec5SDimitry Andric : InstList(Insts), ArtifactList(Arts) {} 970b57cec5SDimitry Andric 980b57cec5SDimitry Andric void createdOrChangedInstr(MachineInstr &MI) { 990b57cec5SDimitry Andric // Only legalize pre-isel generic instructions. 1000b57cec5SDimitry Andric // Legalization process could generate Target specific pseudo 1010b57cec5SDimitry Andric // instructions with generic types. Don't record them 1020b57cec5SDimitry Andric if (isPreISelGenericOpcode(MI.getOpcode())) { 1030b57cec5SDimitry Andric if (isArtifact(MI)) 1040b57cec5SDimitry Andric ArtifactList.insert(&MI); 1050b57cec5SDimitry Andric else 1060b57cec5SDimitry Andric InstList.insert(&MI); 1070b57cec5SDimitry Andric } 1080b57cec5SDimitry Andric } 1090b57cec5SDimitry Andric 1100b57cec5SDimitry Andric void createdInstr(MachineInstr &MI) override { 1110b57cec5SDimitry Andric LLVM_DEBUG(dbgs() << ".. .. New MI: " << MI); 1120b57cec5SDimitry Andric LLVM_DEBUG(NewMIs.push_back(&MI)); 1130b57cec5SDimitry Andric createdOrChangedInstr(MI); 1140b57cec5SDimitry Andric } 1150b57cec5SDimitry Andric 1160b57cec5SDimitry Andric void printNewInstrs() { 1170b57cec5SDimitry Andric LLVM_DEBUG({ 1180b57cec5SDimitry Andric for (const auto *MI : NewMIs) 1190b57cec5SDimitry Andric dbgs() << ".. .. New MI: " << *MI; 1200b57cec5SDimitry Andric NewMIs.clear(); 1210b57cec5SDimitry Andric }); 1220b57cec5SDimitry Andric } 1230b57cec5SDimitry Andric 1240b57cec5SDimitry Andric void erasingInstr(MachineInstr &MI) override { 1250b57cec5SDimitry Andric LLVM_DEBUG(dbgs() << ".. .. Erasing: " << MI); 1260b57cec5SDimitry Andric InstList.remove(&MI); 1270b57cec5SDimitry Andric ArtifactList.remove(&MI); 1280b57cec5SDimitry Andric } 1290b57cec5SDimitry Andric 1300b57cec5SDimitry Andric void changingInstr(MachineInstr &MI) override { 1310b57cec5SDimitry Andric LLVM_DEBUG(dbgs() << ".. .. Changing MI: " << MI); 1320b57cec5SDimitry Andric } 1330b57cec5SDimitry Andric 1340b57cec5SDimitry Andric void changedInstr(MachineInstr &MI) override { 1350b57cec5SDimitry Andric // When insts change, we want to revisit them to legalize them again. 1360b57cec5SDimitry Andric // We'll consider them the same as created. 1370b57cec5SDimitry Andric LLVM_DEBUG(dbgs() << ".. .. Changed MI: " << MI); 1380b57cec5SDimitry Andric createdOrChangedInstr(MI); 1390b57cec5SDimitry Andric } 1400b57cec5SDimitry Andric }; 1410b57cec5SDimitry Andric } // namespace 1420b57cec5SDimitry Andric 143*480093f4SDimitry Andric Legalizer::MFResult 144*480093f4SDimitry Andric Legalizer::legalizeMachineFunction(MachineFunction &MF, const LegalizerInfo &LI, 145*480093f4SDimitry Andric ArrayRef<GISelChangeObserver *> AuxObservers, 146*480093f4SDimitry Andric MachineIRBuilder &MIRBuilder) { 1470b57cec5SDimitry Andric MachineRegisterInfo &MRI = MF.getRegInfo(); 1480b57cec5SDimitry Andric 149*480093f4SDimitry Andric // Populate worklists. 1500b57cec5SDimitry Andric InstListTy InstList; 1510b57cec5SDimitry Andric ArtifactListTy ArtifactList; 1520b57cec5SDimitry Andric ReversePostOrderTraversal<MachineFunction *> RPOT(&MF); 1530b57cec5SDimitry Andric // Perform legalization bottom up so we can DCE as we legalize. 1540b57cec5SDimitry Andric // Traverse BB in RPOT and within each basic block, add insts top down, 1550b57cec5SDimitry Andric // so when we pop_back_val in the legalization process, we traverse bottom-up. 1560b57cec5SDimitry Andric for (auto *MBB : RPOT) { 1570b57cec5SDimitry Andric if (MBB->empty()) 1580b57cec5SDimitry Andric continue; 1590b57cec5SDimitry Andric for (MachineInstr &MI : *MBB) { 1600b57cec5SDimitry Andric // Only legalize pre-isel generic instructions: others don't have types 1610b57cec5SDimitry Andric // and are assumed to be legal. 1620b57cec5SDimitry Andric if (!isPreISelGenericOpcode(MI.getOpcode())) 1630b57cec5SDimitry Andric continue; 1640b57cec5SDimitry Andric if (isArtifact(MI)) 1650b57cec5SDimitry Andric ArtifactList.deferred_insert(&MI); 1660b57cec5SDimitry Andric else 1670b57cec5SDimitry Andric InstList.deferred_insert(&MI); 1680b57cec5SDimitry Andric } 1690b57cec5SDimitry Andric } 1700b57cec5SDimitry Andric ArtifactList.finalize(); 1710b57cec5SDimitry Andric InstList.finalize(); 1720b57cec5SDimitry Andric 173*480093f4SDimitry Andric // This observer keeps the worklists updated. 1740b57cec5SDimitry Andric LegalizerWorkListManager WorkListObserver(InstList, ArtifactList); 175*480093f4SDimitry Andric // We want both WorkListObserver as well as all the auxiliary observers (e.g. 176*480093f4SDimitry Andric // CSEInfo) to observe all changes. Use the wrapper observer. 1770b57cec5SDimitry Andric GISelObserverWrapper WrapperObserver(&WorkListObserver); 178*480093f4SDimitry Andric for (GISelChangeObserver *Observer : AuxObservers) 179*480093f4SDimitry Andric WrapperObserver.addObserver(Observer); 180*480093f4SDimitry Andric 1810b57cec5SDimitry Andric // Now install the observer as the delegate to MF. 1820b57cec5SDimitry Andric // This will keep all the observers notified about new insertions/deletions. 1830b57cec5SDimitry Andric RAIIDelegateInstaller DelInstall(MF, &WrapperObserver); 184*480093f4SDimitry Andric LegalizerHelper Helper(MF, LI, WrapperObserver, MIRBuilder); 185*480093f4SDimitry Andric LegalizationArtifactCombiner ArtCombiner(MIRBuilder, MRI, LI); 1860b57cec5SDimitry Andric auto RemoveDeadInstFromLists = [&WrapperObserver](MachineInstr *DeadMI) { 1870b57cec5SDimitry Andric WrapperObserver.erasingInstr(*DeadMI); 1880b57cec5SDimitry Andric }; 1890b57cec5SDimitry Andric bool Changed = false; 1908bcb0991SDimitry Andric SmallVector<MachineInstr *, 128> RetryList; 1910b57cec5SDimitry Andric do { 192*480093f4SDimitry Andric LLVM_DEBUG(dbgs() << "=== New Iteration ===\n"); 1938bcb0991SDimitry Andric assert(RetryList.empty() && "Expected no instructions in RetryList"); 1948bcb0991SDimitry Andric unsigned NumArtifacts = ArtifactList.size(); 1950b57cec5SDimitry Andric while (!InstList.empty()) { 1960b57cec5SDimitry Andric MachineInstr &MI = *InstList.pop_back_val(); 197*480093f4SDimitry Andric assert(isPreISelGenericOpcode(MI.getOpcode()) && 198*480093f4SDimitry Andric "Expecting generic opcode"); 1990b57cec5SDimitry Andric if (isTriviallyDead(MI, MRI)) { 2000b57cec5SDimitry Andric LLVM_DEBUG(dbgs() << MI << "Is dead; erasing.\n"); 2010b57cec5SDimitry Andric MI.eraseFromParentAndMarkDBGValuesForRemoval(); 2020b57cec5SDimitry Andric continue; 2030b57cec5SDimitry Andric } 2040b57cec5SDimitry Andric 2050b57cec5SDimitry Andric // Do the legalization for this instruction. 2060b57cec5SDimitry Andric auto Res = Helper.legalizeInstrStep(MI); 2070b57cec5SDimitry Andric // Error out if we couldn't legalize this instruction. We may want to 2080b57cec5SDimitry Andric // fall back to DAG ISel instead in the future. 2090b57cec5SDimitry Andric if (Res == LegalizerHelper::UnableToLegalize) { 2108bcb0991SDimitry Andric // Move illegal artifacts to RetryList instead of aborting because 2118bcb0991SDimitry Andric // legalizing InstList may generate artifacts that allow 2128bcb0991SDimitry Andric // ArtifactCombiner to combine away them. 2138bcb0991SDimitry Andric if (isArtifact(MI)) { 214*480093f4SDimitry Andric LLVM_DEBUG(dbgs() << ".. Not legalized, moving to artifacts retry\n"); 215*480093f4SDimitry Andric assert(NumArtifacts == 0 && 216*480093f4SDimitry Andric "Artifacts are only expected in instruction list starting the " 217*480093f4SDimitry Andric "second iteration, but each iteration starting second must " 218*480093f4SDimitry Andric "start with an empty artifacts list"); 219*480093f4SDimitry Andric (void)NumArtifacts; 2208bcb0991SDimitry Andric RetryList.push_back(&MI); 2218bcb0991SDimitry Andric continue; 2228bcb0991SDimitry Andric } 223*480093f4SDimitry Andric Helper.MIRBuilder.stopObservingChanges(); 224*480093f4SDimitry Andric return {Changed, &MI}; 2250b57cec5SDimitry Andric } 2260b57cec5SDimitry Andric WorkListObserver.printNewInstrs(); 2270b57cec5SDimitry Andric Changed |= Res == LegalizerHelper::Legalized; 2280b57cec5SDimitry Andric } 2298bcb0991SDimitry Andric // Try to combine the instructions in RetryList again if there 2308bcb0991SDimitry Andric // are new artifacts. If not, stop legalizing. 2318bcb0991SDimitry Andric if (!RetryList.empty()) { 232*480093f4SDimitry Andric if (!ArtifactList.empty()) { 2338bcb0991SDimitry Andric while (!RetryList.empty()) 2348bcb0991SDimitry Andric ArtifactList.insert(RetryList.pop_back_val()); 2358bcb0991SDimitry Andric } else { 236*480093f4SDimitry Andric LLVM_DEBUG(dbgs() << "No new artifacts created, not retrying!\n"); 237*480093f4SDimitry Andric Helper.MIRBuilder.stopObservingChanges(); 238*480093f4SDimitry Andric return {Changed, RetryList.front()}; 2398bcb0991SDimitry Andric } 2408bcb0991SDimitry Andric } 2410b57cec5SDimitry Andric while (!ArtifactList.empty()) { 2420b57cec5SDimitry Andric MachineInstr &MI = *ArtifactList.pop_back_val(); 243*480093f4SDimitry Andric assert(isPreISelGenericOpcode(MI.getOpcode()) && 244*480093f4SDimitry Andric "Expecting generic opcode"); 2450b57cec5SDimitry Andric if (isTriviallyDead(MI, MRI)) { 2460b57cec5SDimitry Andric LLVM_DEBUG(dbgs() << MI << "Is dead\n"); 2470b57cec5SDimitry Andric RemoveDeadInstFromLists(&MI); 2480b57cec5SDimitry Andric MI.eraseFromParentAndMarkDBGValuesForRemoval(); 2490b57cec5SDimitry Andric continue; 2500b57cec5SDimitry Andric } 2510b57cec5SDimitry Andric SmallVector<MachineInstr *, 4> DeadInstructions; 252*480093f4SDimitry Andric LLVM_DEBUG(dbgs() << "Trying to combine: " << MI); 2530b57cec5SDimitry Andric if (ArtCombiner.tryCombineInstruction(MI, DeadInstructions, 2540b57cec5SDimitry Andric WrapperObserver)) { 2550b57cec5SDimitry Andric WorkListObserver.printNewInstrs(); 2560b57cec5SDimitry Andric for (auto *DeadMI : DeadInstructions) { 2570b57cec5SDimitry Andric LLVM_DEBUG(dbgs() << *DeadMI << "Is dead\n"); 2580b57cec5SDimitry Andric RemoveDeadInstFromLists(DeadMI); 2590b57cec5SDimitry Andric DeadMI->eraseFromParentAndMarkDBGValuesForRemoval(); 2600b57cec5SDimitry Andric } 2610b57cec5SDimitry Andric Changed = true; 2620b57cec5SDimitry Andric continue; 2630b57cec5SDimitry Andric } 2640b57cec5SDimitry Andric // If this was not an artifact (that could be combined away), this might 2650b57cec5SDimitry Andric // need special handling. Add it to InstList, so when it's processed 2660b57cec5SDimitry Andric // there, it has to be legal or specially handled. 267*480093f4SDimitry Andric else { 268*480093f4SDimitry Andric LLVM_DEBUG(dbgs() << ".. Not combined, moving to instructions list\n"); 2690b57cec5SDimitry Andric InstList.insert(&MI); 2700b57cec5SDimitry Andric } 271*480093f4SDimitry Andric } 2720b57cec5SDimitry Andric } while (!InstList.empty()); 2730b57cec5SDimitry Andric 274*480093f4SDimitry Andric return {Changed, /*FailedOn*/ nullptr}; 275*480093f4SDimitry Andric } 276*480093f4SDimitry Andric 277*480093f4SDimitry Andric bool Legalizer::runOnMachineFunction(MachineFunction &MF) { 278*480093f4SDimitry Andric // If the ISel pipeline failed, do not bother running that pass. 279*480093f4SDimitry Andric if (MF.getProperties().hasProperty( 280*480093f4SDimitry Andric MachineFunctionProperties::Property::FailedISel)) 281*480093f4SDimitry Andric return false; 282*480093f4SDimitry Andric LLVM_DEBUG(dbgs() << "Legalize Machine IR for: " << MF.getName() << '\n'); 283*480093f4SDimitry Andric init(MF); 284*480093f4SDimitry Andric const TargetPassConfig &TPC = getAnalysis<TargetPassConfig>(); 285*480093f4SDimitry Andric GISelCSEAnalysisWrapper &Wrapper = 286*480093f4SDimitry Andric getAnalysis<GISelCSEAnalysisWrapperPass>().getCSEWrapper(); 287*480093f4SDimitry Andric MachineOptimizationRemarkEmitter MORE(MF, /*MBFI=*/nullptr); 288*480093f4SDimitry Andric 289*480093f4SDimitry Andric const size_t NumBlocks = MF.size(); 290*480093f4SDimitry Andric 291*480093f4SDimitry Andric std::unique_ptr<MachineIRBuilder> MIRBuilder; 292*480093f4SDimitry Andric GISelCSEInfo *CSEInfo = nullptr; 293*480093f4SDimitry Andric bool EnableCSE = EnableCSEInLegalizer.getNumOccurrences() 294*480093f4SDimitry Andric ? EnableCSEInLegalizer 295*480093f4SDimitry Andric : TPC.isGISelCSEEnabled(); 296*480093f4SDimitry Andric if (EnableCSE) { 297*480093f4SDimitry Andric MIRBuilder = std::make_unique<CSEMIRBuilder>(); 298*480093f4SDimitry Andric CSEInfo = &Wrapper.get(TPC.getCSEConfig()); 299*480093f4SDimitry Andric MIRBuilder->setCSEInfo(CSEInfo); 300*480093f4SDimitry Andric } else 301*480093f4SDimitry Andric MIRBuilder = std::make_unique<MachineIRBuilder>(); 302*480093f4SDimitry Andric 303*480093f4SDimitry Andric SmallVector<GISelChangeObserver *, 1> AuxObservers; 304*480093f4SDimitry Andric if (EnableCSE && CSEInfo) { 305*480093f4SDimitry Andric // We want CSEInfo in addition to WorkListObserver to observe all changes. 306*480093f4SDimitry Andric AuxObservers.push_back(CSEInfo); 307*480093f4SDimitry Andric } 308*480093f4SDimitry Andric 309*480093f4SDimitry Andric const LegalizerInfo &LI = *MF.getSubtarget().getLegalizerInfo(); 310*480093f4SDimitry Andric MFResult Result = legalizeMachineFunction(MF, LI, AuxObservers, *MIRBuilder); 311*480093f4SDimitry Andric 312*480093f4SDimitry Andric if (Result.FailedOn) { 313*480093f4SDimitry Andric reportGISelFailure(MF, TPC, MORE, "gisel-legalize", 314*480093f4SDimitry Andric "unable to legalize instruction", *Result.FailedOn); 315*480093f4SDimitry Andric return false; 316*480093f4SDimitry Andric } 3170b57cec5SDimitry Andric // For now don't support if new blocks are inserted - we would need to fix the 3180b57cec5SDimitry Andric // outer loop for that. 3190b57cec5SDimitry Andric if (MF.size() != NumBlocks) { 3200b57cec5SDimitry Andric MachineOptimizationRemarkMissed R("gisel-legalize", "GISelFailure", 3210b57cec5SDimitry Andric MF.getFunction().getSubprogram(), 3220b57cec5SDimitry Andric /*MBB=*/nullptr); 3230b57cec5SDimitry Andric R << "inserting blocks is not supported yet"; 3240b57cec5SDimitry Andric reportGISelFailure(MF, TPC, MORE, R); 3250b57cec5SDimitry Andric return false; 3260b57cec5SDimitry Andric } 327*480093f4SDimitry Andric return Result.Changed; 3280b57cec5SDimitry Andric } 329