1f22ef01cSRoman Divacky //===-- llvm/CodeGen/MachineModuleInfo.cpp ----------------------*- C++ -*-===// 2f22ef01cSRoman Divacky // 3f22ef01cSRoman Divacky // The LLVM Compiler Infrastructure 4f22ef01cSRoman Divacky // 5f22ef01cSRoman Divacky // This file is distributed under the University of Illinois Open Source 6f22ef01cSRoman Divacky // License. See LICENSE.TXT for details. 7f22ef01cSRoman Divacky // 8f22ef01cSRoman Divacky //===----------------------------------------------------------------------===// 9f22ef01cSRoman Divacky 10f22ef01cSRoman Divacky #include "llvm/CodeGen/MachineModuleInfo.h" 11139f7f9bSDimitry Andric #include "llvm/ADT/PointerUnion.h" 12d88c1a5aSDimitry Andric #include "llvm/ADT/PostOrderIterator.h" 137d523365SDimitry Andric #include "llvm/ADT/TinyPtrVector.h" 147d523365SDimitry Andric #include "llvm/Analysis/EHPersonalities.h" 15f22ef01cSRoman Divacky #include "llvm/Analysis/ValueTracking.h" 16f22ef01cSRoman Divacky #include "llvm/CodeGen/MachineFunction.h" 17d88c1a5aSDimitry Andric #include "llvm/CodeGen/MachineFunctionInitializer.h" 18139f7f9bSDimitry Andric #include "llvm/CodeGen/MachineFunctionPass.h" 19f22ef01cSRoman Divacky #include "llvm/CodeGen/Passes.h" 20139f7f9bSDimitry Andric #include "llvm/IR/Constants.h" 21139f7f9bSDimitry Andric #include "llvm/IR/DerivedTypes.h" 22139f7f9bSDimitry Andric #include "llvm/IR/GlobalVariable.h" 23d88c1a5aSDimitry Andric #include "llvm/IR/Instructions.h" 24139f7f9bSDimitry Andric #include "llvm/IR/Module.h" 256122f3e6SDimitry Andric #include "llvm/MC/MCObjectFileInfo.h" 26f22ef01cSRoman Divacky #include "llvm/MC/MCSymbol.h" 27f22ef01cSRoman Divacky #include "llvm/Support/Dwarf.h" 28f22ef01cSRoman Divacky #include "llvm/Support/ErrorHandling.h" 29d88c1a5aSDimitry Andric #include "llvm/Target/TargetLoweringObjectFile.h" 30d88c1a5aSDimitry Andric #include "llvm/Target/TargetMachine.h" 31f22ef01cSRoman Divacky using namespace llvm; 32f22ef01cSRoman Divacky using namespace llvm::dwarf; 33f22ef01cSRoman Divacky 343861d79fSDimitry Andric // Handle the Pass registration stuff necessary to use DataLayout's. 35d8866befSDimitry Andric INITIALIZE_PASS(MachineModuleInfo, "machinemoduleinfo", 362754fe60SDimitry Andric "Machine Module Information", false, false) 37f22ef01cSRoman Divacky char MachineModuleInfo::ID = 0; 38f22ef01cSRoman Divacky 39f22ef01cSRoman Divacky // Out of line virtual method. 40f22ef01cSRoman Divacky MachineModuleInfoImpl::~MachineModuleInfoImpl() {} 41f22ef01cSRoman Divacky 42f22ef01cSRoman Divacky namespace llvm { 437d523365SDimitry Andric class MMIAddrLabelMapCallbackPtr final : CallbackVH { 44f22ef01cSRoman Divacky MMIAddrLabelMap *Map; 45f22ef01cSRoman Divacky public: 4691bc56edSDimitry Andric MMIAddrLabelMapCallbackPtr() : Map(nullptr) {} 4791bc56edSDimitry Andric MMIAddrLabelMapCallbackPtr(Value *V) : CallbackVH(V), Map(nullptr) {} 48f22ef01cSRoman Divacky 49f22ef01cSRoman Divacky void setPtr(BasicBlock *BB) { 50f22ef01cSRoman Divacky ValueHandleBase::operator=(BB); 51f22ef01cSRoman Divacky } 52f22ef01cSRoman Divacky 53f22ef01cSRoman Divacky void setMap(MMIAddrLabelMap *map) { Map = map; } 54f22ef01cSRoman Divacky 5591bc56edSDimitry Andric void deleted() override; 5691bc56edSDimitry Andric void allUsesReplacedWith(Value *V2) override; 57f22ef01cSRoman Divacky }; 58f22ef01cSRoman Divacky 59f22ef01cSRoman Divacky class MMIAddrLabelMap { 60f22ef01cSRoman Divacky MCContext &Context; 61f22ef01cSRoman Divacky struct AddrLabelSymEntry { 62d88c1a5aSDimitry Andric /// The symbols for the label. 633dac3a9bSDimitry Andric TinyPtrVector<MCSymbol *> Symbols; 64f22ef01cSRoman Divacky 65f22ef01cSRoman Divacky Function *Fn; // The containing function of the BasicBlock. 66f22ef01cSRoman Divacky unsigned Index; // The index in BBCallbacks for the BasicBlock. 67f22ef01cSRoman Divacky }; 68f22ef01cSRoman Divacky 69f22ef01cSRoman Divacky DenseMap<AssertingVH<BasicBlock>, AddrLabelSymEntry> AddrLabelSymbols; 70f22ef01cSRoman Divacky 71d88c1a5aSDimitry Andric /// Callbacks for the BasicBlock's that we have entries for. We use this so 72d88c1a5aSDimitry Andric /// we get notified if a block is deleted or RAUWd. 73f22ef01cSRoman Divacky std::vector<MMIAddrLabelMapCallbackPtr> BBCallbacks; 74f22ef01cSRoman Divacky 75d88c1a5aSDimitry Andric /// This is a per-function list of symbols whose corresponding BasicBlock got 76d88c1a5aSDimitry Andric /// deleted. These symbols need to be emitted at some point in the file, so 77d88c1a5aSDimitry Andric /// AsmPrinter emits them after the function body. 78f22ef01cSRoman Divacky DenseMap<AssertingVH<Function>, std::vector<MCSymbol*> > 79f22ef01cSRoman Divacky DeletedAddrLabelsNeedingEmission; 80f22ef01cSRoman Divacky public: 81f22ef01cSRoman Divacky 82f22ef01cSRoman Divacky MMIAddrLabelMap(MCContext &context) : Context(context) {} 83f22ef01cSRoman Divacky ~MMIAddrLabelMap() { 84f22ef01cSRoman Divacky assert(DeletedAddrLabelsNeedingEmission.empty() && 85f22ef01cSRoman Divacky "Some labels for deleted blocks never got emitted"); 86f22ef01cSRoman Divacky } 87f22ef01cSRoman Divacky 883dac3a9bSDimitry Andric ArrayRef<MCSymbol *> getAddrLabelSymbolToEmit(BasicBlock *BB); 89f22ef01cSRoman Divacky 90f22ef01cSRoman Divacky void takeDeletedSymbolsForFunction(Function *F, 91f22ef01cSRoman Divacky std::vector<MCSymbol*> &Result); 92f22ef01cSRoman Divacky 93f22ef01cSRoman Divacky void UpdateForDeletedBlock(BasicBlock *BB); 94f22ef01cSRoman Divacky void UpdateForRAUWBlock(BasicBlock *Old, BasicBlock *New); 95f22ef01cSRoman Divacky }; 963dac3a9bSDimitry Andric } 97f22ef01cSRoman Divacky 983dac3a9bSDimitry Andric ArrayRef<MCSymbol *> MMIAddrLabelMap::getAddrLabelSymbolToEmit(BasicBlock *BB) { 99f22ef01cSRoman Divacky assert(BB->hasAddressTaken() && 100f22ef01cSRoman Divacky "Shouldn't get label for block without address taken"); 101f22ef01cSRoman Divacky AddrLabelSymEntry &Entry = AddrLabelSymbols[BB]; 102f22ef01cSRoman Divacky 103f22ef01cSRoman Divacky // If we already had an entry for this block, just return it. 1043dac3a9bSDimitry Andric if (!Entry.Symbols.empty()) { 105f22ef01cSRoman Divacky assert(BB->getParent() == Entry.Fn && "Parent changed"); 1063dac3a9bSDimitry Andric return Entry.Symbols; 107f22ef01cSRoman Divacky } 108f22ef01cSRoman Divacky 109f22ef01cSRoman Divacky // Otherwise, this is a new entry, create a new symbol for it and add an 110f22ef01cSRoman Divacky // entry to BBCallbacks so we can be notified if the BB is deleted or RAUWd. 11197bc6c73SDimitry Andric BBCallbacks.emplace_back(BB); 112f22ef01cSRoman Divacky BBCallbacks.back().setMap(this); 113f22ef01cSRoman Divacky Entry.Index = BBCallbacks.size() - 1; 114f22ef01cSRoman Divacky Entry.Fn = BB->getParent(); 1153dac3a9bSDimitry Andric Entry.Symbols.push_back(Context.createTempSymbol()); 1163dac3a9bSDimitry Andric return Entry.Symbols; 117f22ef01cSRoman Divacky } 118f22ef01cSRoman Divacky 119d88c1a5aSDimitry Andric /// If we have any deleted symbols for F, return them. 120f22ef01cSRoman Divacky void MMIAddrLabelMap:: 121f22ef01cSRoman Divacky takeDeletedSymbolsForFunction(Function *F, std::vector<MCSymbol*> &Result) { 122f22ef01cSRoman Divacky DenseMap<AssertingVH<Function>, std::vector<MCSymbol*> >::iterator I = 123f22ef01cSRoman Divacky DeletedAddrLabelsNeedingEmission.find(F); 124f22ef01cSRoman Divacky 125f22ef01cSRoman Divacky // If there are no entries for the function, just return. 126f22ef01cSRoman Divacky if (I == DeletedAddrLabelsNeedingEmission.end()) return; 127f22ef01cSRoman Divacky 128f22ef01cSRoman Divacky // Otherwise, take the list. 129f22ef01cSRoman Divacky std::swap(Result, I->second); 130f22ef01cSRoman Divacky DeletedAddrLabelsNeedingEmission.erase(I); 131f22ef01cSRoman Divacky } 132f22ef01cSRoman Divacky 133f22ef01cSRoman Divacky 134f22ef01cSRoman Divacky void MMIAddrLabelMap::UpdateForDeletedBlock(BasicBlock *BB) { 135f22ef01cSRoman Divacky // If the block got deleted, there is no need for the symbol. If the symbol 136f22ef01cSRoman Divacky // was already emitted, we can just forget about it, otherwise we need to 137f22ef01cSRoman Divacky // queue it up for later emission when the function is output. 1383dac3a9bSDimitry Andric AddrLabelSymEntry Entry = std::move(AddrLabelSymbols[BB]); 139f22ef01cSRoman Divacky AddrLabelSymbols.erase(BB); 1403dac3a9bSDimitry Andric assert(!Entry.Symbols.empty() && "Didn't have a symbol, why a callback?"); 14191bc56edSDimitry Andric BBCallbacks[Entry.Index] = nullptr; // Clear the callback. 142f22ef01cSRoman Divacky 14391bc56edSDimitry Andric assert((BB->getParent() == nullptr || BB->getParent() == Entry.Fn) && 144f22ef01cSRoman Divacky "Block/parent mismatch"); 145f22ef01cSRoman Divacky 1463dac3a9bSDimitry Andric for (MCSymbol *Sym : Entry.Symbols) { 147f22ef01cSRoman Divacky if (Sym->isDefined()) 148f22ef01cSRoman Divacky return; 149f22ef01cSRoman Divacky 150f22ef01cSRoman Divacky // If the block is not yet defined, we need to emit it at the end of the 151f22ef01cSRoman Divacky // function. Add the symbol to the DeletedAddrLabelsNeedingEmission list 152f22ef01cSRoman Divacky // for the containing Function. Since the block is being deleted, its 153f22ef01cSRoman Divacky // parent may already be removed, we have to get the function from 'Entry'. 154f22ef01cSRoman Divacky DeletedAddrLabelsNeedingEmission[Entry.Fn].push_back(Sym); 155f22ef01cSRoman Divacky } 156f22ef01cSRoman Divacky } 157f22ef01cSRoman Divacky 158f22ef01cSRoman Divacky void MMIAddrLabelMap::UpdateForRAUWBlock(BasicBlock *Old, BasicBlock *New) { 159f22ef01cSRoman Divacky // Get the entry for the RAUW'd block and remove it from our map. 1603dac3a9bSDimitry Andric AddrLabelSymEntry OldEntry = std::move(AddrLabelSymbols[Old]); 161f22ef01cSRoman Divacky AddrLabelSymbols.erase(Old); 1623dac3a9bSDimitry Andric assert(!OldEntry.Symbols.empty() && "Didn't have a symbol, why a callback?"); 163f22ef01cSRoman Divacky 164f22ef01cSRoman Divacky AddrLabelSymEntry &NewEntry = AddrLabelSymbols[New]; 165f22ef01cSRoman Divacky 166f22ef01cSRoman Divacky // If New is not address taken, just move our symbol over to it. 1673dac3a9bSDimitry Andric if (NewEntry.Symbols.empty()) { 168f22ef01cSRoman Divacky BBCallbacks[OldEntry.Index].setPtr(New); // Update the callback. 1693dac3a9bSDimitry Andric NewEntry = std::move(OldEntry); // Set New's entry. 170f22ef01cSRoman Divacky return; 171f22ef01cSRoman Divacky } 172f22ef01cSRoman Divacky 17391bc56edSDimitry Andric BBCallbacks[OldEntry.Index] = nullptr; // Update the callback. 174f22ef01cSRoman Divacky 1753dac3a9bSDimitry Andric // Otherwise, we need to add the old symbols to the new block's set. 1763dac3a9bSDimitry Andric NewEntry.Symbols.insert(NewEntry.Symbols.end(), OldEntry.Symbols.begin(), 1773dac3a9bSDimitry Andric OldEntry.Symbols.end()); 178f22ef01cSRoman Divacky } 179f22ef01cSRoman Divacky 180f22ef01cSRoman Divacky 181f22ef01cSRoman Divacky void MMIAddrLabelMapCallbackPtr::deleted() { 182f22ef01cSRoman Divacky Map->UpdateForDeletedBlock(cast<BasicBlock>(getValPtr())); 183f22ef01cSRoman Divacky } 184f22ef01cSRoman Divacky 185f22ef01cSRoman Divacky void MMIAddrLabelMapCallbackPtr::allUsesReplacedWith(Value *V2) { 186f22ef01cSRoman Divacky Map->UpdateForRAUWBlock(cast<BasicBlock>(getValPtr()), cast<BasicBlock>(V2)); 187f22ef01cSRoman Divacky } 188f22ef01cSRoman Divacky 189f22ef01cSRoman Divacky 190f22ef01cSRoman Divacky //===----------------------------------------------------------------------===// 191f22ef01cSRoman Divacky 192d88c1a5aSDimitry Andric MachineModuleInfo::MachineModuleInfo(const TargetMachine *TM) 193d88c1a5aSDimitry Andric : ImmutablePass(ID), TM(*TM), 194d88c1a5aSDimitry Andric Context(TM->getMCAsmInfo(), TM->getMCRegisterInfo(), 195d88c1a5aSDimitry Andric TM->getObjFileLowering(), nullptr, false) { 1962754fe60SDimitry Andric initializeMachineModuleInfoPass(*PassRegistry::getPassRegistry()); 197f22ef01cSRoman Divacky } 198f22ef01cSRoman Divacky 199f22ef01cSRoman Divacky MachineModuleInfo::~MachineModuleInfo() { 200f22ef01cSRoman Divacky } 201f22ef01cSRoman Divacky 202139f7f9bSDimitry Andric bool MachineModuleInfo::doInitialization(Module &M) { 203139f7f9bSDimitry Andric 20491bc56edSDimitry Andric ObjFileMMI = nullptr; 205139f7f9bSDimitry Andric CurCallSite = 0; 20639d628a0SDimitry Andric DbgInfoAvailable = UsesVAFloatArgument = UsesMorestackAddr = false; 20791bc56edSDimitry Andric AddrLabelSymbols = nullptr; 208d88c1a5aSDimitry Andric TheModule = &M; 209139f7f9bSDimitry Andric 210f22ef01cSRoman Divacky return false; 211f22ef01cSRoman Divacky } 212f22ef01cSRoman Divacky 213139f7f9bSDimitry Andric bool MachineModuleInfo::doFinalization(Module &M) { 214139f7f9bSDimitry Andric 215139f7f9bSDimitry Andric Personalities.clear(); 216139f7f9bSDimitry Andric 217f22ef01cSRoman Divacky delete AddrLabelSymbols; 21891bc56edSDimitry Andric AddrLabelSymbols = nullptr; 219139f7f9bSDimitry Andric 220139f7f9bSDimitry Andric Context.reset(); 221139f7f9bSDimitry Andric 222139f7f9bSDimitry Andric delete ObjFileMMI; 22391bc56edSDimitry Andric ObjFileMMI = nullptr; 224139f7f9bSDimitry Andric 225f22ef01cSRoman Divacky return false; 226f22ef01cSRoman Divacky } 227f22ef01cSRoman Divacky 228f22ef01cSRoman Divacky //===- Address of Block Management ----------------------------------------===// 229f22ef01cSRoman Divacky 2303dac3a9bSDimitry Andric ArrayRef<MCSymbol *> 2313dac3a9bSDimitry Andric MachineModuleInfo::getAddrLabelSymbolToEmit(const BasicBlock *BB) { 232f22ef01cSRoman Divacky // Lazily create AddrLabelSymbols. 23391bc56edSDimitry Andric if (!AddrLabelSymbols) 234f22ef01cSRoman Divacky AddrLabelSymbols = new MMIAddrLabelMap(Context); 235f22ef01cSRoman Divacky return AddrLabelSymbols->getAddrLabelSymbolToEmit(const_cast<BasicBlock*>(BB)); 236f22ef01cSRoman Divacky } 237f22ef01cSRoman Divacky 238f22ef01cSRoman Divacky void MachineModuleInfo:: 239f22ef01cSRoman Divacky takeDeletedSymbolsForFunction(const Function *F, 240f22ef01cSRoman Divacky std::vector<MCSymbol*> &Result) { 241f22ef01cSRoman Divacky // If no blocks have had their addresses taken, we're done. 24291bc56edSDimitry Andric if (!AddrLabelSymbols) return; 243f22ef01cSRoman Divacky return AddrLabelSymbols-> 244f22ef01cSRoman Divacky takeDeletedSymbolsForFunction(const_cast<Function*>(F), Result); 245f22ef01cSRoman Divacky } 246f22ef01cSRoman Divacky 247d88c1a5aSDimitry Andric /// \name Exception Handling 248d88c1a5aSDimitry Andric /// \{ 249f22ef01cSRoman Divacky 250875ed548SDimitry Andric void MachineModuleInfo::addPersonality(const Function *Personality) { 251f22ef01cSRoman Divacky for (unsigned i = 0; i < Personalities.size(); ++i) 252f22ef01cSRoman Divacky if (Personalities[i] == Personality) 253f22ef01cSRoman Divacky return; 254f22ef01cSRoman Divacky Personalities.push_back(Personality); 255f22ef01cSRoman Divacky } 256f22ef01cSRoman Divacky 257d88c1a5aSDimitry Andric /// \} 258d88c1a5aSDimitry Andric 259d88c1a5aSDimitry Andric MachineFunction &MachineModuleInfo::getMachineFunction(const Function &F) { 260d88c1a5aSDimitry Andric // Shortcut for the common case where a sequence of MachineFunctionPasses 261d88c1a5aSDimitry Andric // all query for the same Function. 262d88c1a5aSDimitry Andric if (LastRequest == &F) 263d88c1a5aSDimitry Andric return *LastResult; 264d88c1a5aSDimitry Andric 265d88c1a5aSDimitry Andric auto I = MachineFunctions.insert( 266d88c1a5aSDimitry Andric std::make_pair(&F, std::unique_ptr<MachineFunction>())); 267d88c1a5aSDimitry Andric MachineFunction *MF; 268d88c1a5aSDimitry Andric if (I.second) { 269d88c1a5aSDimitry Andric // No pre-existing machine function, create a new one. 270d88c1a5aSDimitry Andric MF = new MachineFunction(&F, TM, NextFnNum++, *this); 271d88c1a5aSDimitry Andric // Update the set entry. 272d88c1a5aSDimitry Andric I.first->second.reset(MF); 273d88c1a5aSDimitry Andric 274d88c1a5aSDimitry Andric if (MFInitializer) 275d88c1a5aSDimitry Andric if (MFInitializer->initializeMachineFunction(*MF)) 276d88c1a5aSDimitry Andric report_fatal_error("Unable to initialize machine function"); 277d88c1a5aSDimitry Andric } else { 278d88c1a5aSDimitry Andric MF = I.first->second.get(); 279f22ef01cSRoman Divacky } 280f22ef01cSRoman Divacky 281d88c1a5aSDimitry Andric LastRequest = &F; 282d88c1a5aSDimitry Andric LastResult = MF; 283d88c1a5aSDimitry Andric return *MF; 284f22ef01cSRoman Divacky } 285f22ef01cSRoman Divacky 286d88c1a5aSDimitry Andric void MachineModuleInfo::deleteMachineFunctionFor(Function &F) { 287d88c1a5aSDimitry Andric MachineFunctions.erase(&F); 288d88c1a5aSDimitry Andric LastRequest = nullptr; 289d88c1a5aSDimitry Andric LastResult = nullptr; 290f22ef01cSRoman Divacky } 291f22ef01cSRoman Divacky 292d88c1a5aSDimitry Andric namespace { 293d88c1a5aSDimitry Andric /// This pass frees the MachineFunction object associated with a Function. 294d88c1a5aSDimitry Andric class FreeMachineFunction : public FunctionPass { 295d88c1a5aSDimitry Andric public: 296d88c1a5aSDimitry Andric static char ID; 297d88c1a5aSDimitry Andric FreeMachineFunction() : FunctionPass(ID) {} 298d88c1a5aSDimitry Andric 299d88c1a5aSDimitry Andric void getAnalysisUsage(AnalysisUsage &AU) const override { 300d88c1a5aSDimitry Andric AU.addRequired<MachineModuleInfo>(); 301d88c1a5aSDimitry Andric AU.addPreserved<MachineModuleInfo>(); 302ff0cc061SDimitry Andric } 303ff0cc061SDimitry Andric 304d88c1a5aSDimitry Andric bool runOnFunction(Function &F) override { 305d88c1a5aSDimitry Andric MachineModuleInfo &MMI = getAnalysis<MachineModuleInfo>(); 306d88c1a5aSDimitry Andric MMI.deleteMachineFunctionFor(F); 307d88c1a5aSDimitry Andric return true; 308ff0cc061SDimitry Andric } 3097a7e6055SDimitry Andric 3107a7e6055SDimitry Andric StringRef getPassName() const override { 3117a7e6055SDimitry Andric return "Free MachineFunction"; 3127a7e6055SDimitry Andric } 313d88c1a5aSDimitry Andric }; 314d88c1a5aSDimitry Andric char FreeMachineFunction::ID; 315d88c1a5aSDimitry Andric } // end anonymous namespace 316ff0cc061SDimitry Andric 317d88c1a5aSDimitry Andric namespace llvm { 318d88c1a5aSDimitry Andric FunctionPass *createFreeMachineFunctionPass() { 319d88c1a5aSDimitry Andric return new FreeMachineFunction(); 320f22ef01cSRoman Divacky } 321d88c1a5aSDimitry Andric } // end namespace llvm 322f22ef01cSRoman Divacky 323d88c1a5aSDimitry Andric //===- MMI building helpers -----------------------------------------------===// 324f22ef01cSRoman Divacky 325d88c1a5aSDimitry Andric void llvm::computeUsesVAFloatArgument(const CallInst &I, 326d88c1a5aSDimitry Andric MachineModuleInfo &MMI) { 327d88c1a5aSDimitry Andric FunctionType *FT = 328d88c1a5aSDimitry Andric cast<FunctionType>(I.getCalledValue()->getType()->getContainedType(0)); 329d88c1a5aSDimitry Andric if (FT->isVarArg() && !MMI.usesVAFloatArgument()) { 330d88c1a5aSDimitry Andric for (unsigned i = 0, e = I.getNumArgOperands(); i != e; ++i) { 331d88c1a5aSDimitry Andric Type *T = I.getArgOperand(i)->getType(); 332d88c1a5aSDimitry Andric for (auto i : post_order(T)) { 333d88c1a5aSDimitry Andric if (i->isFloatingPointTy()) { 334d88c1a5aSDimitry Andric MMI.setUsesVAFloatArgument(true); 335d88c1a5aSDimitry Andric return; 336f22ef01cSRoman Divacky } 337f22ef01cSRoman Divacky } 3386122f3e6SDimitry Andric } 339f22ef01cSRoman Divacky } 340f22ef01cSRoman Divacky } 341