Ejemplo n.º 1
0
void DebugDatabase::addIRInstructions(GenerateRTL *hw) {
    Function *F = hw->getFunction();
    int instr_count = 0;
    for (Function::iterator b = F->begin(), be = F->end(); b != be; b++) {
        for (BasicBlock::iterator i = b->begin(), ie = b->end(); i != ie; i++) {

            instr_count++;

            bool isDummyDbgCall = false;
            if (isa<DbgDeclareInst>(i) || isa<DbgValueInst>(i))
                isDummyDbgCall = true;

            if (i->hasMetadata()) {
                MDNode *n = i->getMetadata("dbg");
                DILocation loc(n);
                int lineNumber = loc.getLineNumber();
                int columnNumber = loc.getColumnNumber();
                std::string filePath =
                    loc.getDirectory().str() + "/" + loc.getFilename().str();

                addIRInstruction(hw, i, instr_count, isDummyDbgCall, filePath,
                                 lineNumber, columnNumber);
            } else {
                addIRInstruction(hw, i, instr_count, isDummyDbgCall, "", 0, 0);
            }
        }
    }
}
Ejemplo n.º 2
0
bool LowerSIMDLoop::hasSIMDLoopMetadata(Loop *L) const
{
    // Note: If a loop has 0 or multiple latch blocks, it's probably not a simd_loop anyway.
    if (BasicBlock* latch = L->getLoopLatch())
        for (BasicBlock::iterator II = latch->begin(), EE = latch->end(); II!=EE; ++II)
            if (II->getMetadata(simd_loop_mdkind))
                return true;
    return false;
}
Ejemplo n.º 3
0
string getSourceFileName(Function *f){
	for(Function::iterator bb = f->begin(); bb != f->end(); bb++)
	for(BasicBlock::iterator i = bb->begin(); i != bb->end(); i++){
		if (MDNode *N = i->getMetadata("dbg")) { // this if is never executed
			DILocation Loc(N);
			string dir = Loc.getDirectory().str();
			string name = Loc.getFilename().str();
			stringstream fileName;
			fileName<<dir<<name;
			errs() << fileName.str() << "\n";
			return fileName.str();
			//return ConstantInt::get(Type::getInt32Ty(I->getContext()), Line);
		}

	}
	return "";
}
Ejemplo n.º 4
0
bool Loop::isAnnotatedParallel() const {
  MDNode *desiredLoopIdMetadata = getLoopID();

  if (!desiredLoopIdMetadata)
      return false;

  // The loop branch contains the parallel loop metadata. In order to ensure
  // that any parallel-loop-unaware optimization pass hasn't added loop-carried
  // dependencies (thus converted the loop back to a sequential loop), check
  // that all the memory instructions in the loop contain parallelism metadata
  // that point to the same unique "loop id metadata" the loop branch does.
  for (block_iterator BB = block_begin(), BE = block_end(); BB != BE; ++BB) {
    for (BasicBlock::iterator II = (*BB)->begin(), EE = (*BB)->end();
         II != EE; II++) {

      if (!II->mayReadOrWriteMemory())
        continue;

      // The memory instruction can refer to the loop identifier metadata
      // directly or indirectly through another list metadata (in case of
      // nested parallel loops). The loop identifier metadata refers to
      // itself so we can check both cases with the same routine.
      MDNode *loopIdMD =
          II->getMetadata(LLVMContext::MD_mem_parallel_loop_access);

      if (!loopIdMD)
        return false;

      bool loopIdMDFound = false;
      for (unsigned i = 0, e = loopIdMD->getNumOperands(); i < e; ++i) {
        if (loopIdMD->getOperand(i) == desiredLoopIdMetadata) {
          loopIdMDFound = true;
          break;
        }
      }

      if (!loopIdMDFound)
        return false;
    }
  }
  return true;
}
Ejemplo n.º 5
0
void InterPro::print(raw_ostream &O,  Module *M)  
{

	char pPath[100];
	for(Module::iterator F = M->begin(); F != M->end(); F ++)
	{
		if(!F->getName().startswith("test"))
		{
			continue;
		}

		O << F->getName() << ":\n";

		for(Function::iterator BB = F->begin(); BB != F->end(); ++ BB)
		{
			for(BasicBlock::iterator I = BB->begin(); I != BB->end(); I ++)
			{
				I->dump();

				if( MDNode *N = I->getMetadata("dbg") )
				{	 
					DILocation Loc(N);
					string sFileNameForInstruction = Loc.getDirectory().str() + "/" + Loc.getFilename().str();    
					realpath( sFileNameForInstruction.c_str() , pPath);
					sFileNameForInstruction = string(pPath);                        
					unsigned int uLineNoForInstruction = Loc.getLineNumber();
					O << sFileNameForInstruction << ": " << uLineNoForInstruction << ": ";
				}

				O << this->InstBeforeSetMapping[I].size() << " ";
				O << this->InstAfterSetMapping[I].size() << "\n";
			}

		}


		O << "*********************************************\n";

	}
	
}
Ejemplo n.º 6
0
/*
 * Main args are always input
 * Functions currently considered as input functions:
 * scanf
 * fscanf
 * gets
 * fgets
 * fread
 * fgetc
 * getc
 * getchar
 * recv
 * recvmsg
 * read
 * recvfrom
 * fread
 */
bool InputDep::runOnModule(Module &M) {
	//	DEBUG (errs() << "Function " << F.getName() << "\n";);
	NumInputValues = 0;
	bool inserted;
	Function* main = M.getFunction("main");
	if (main) {
		MDNode *mdn = main->begin()->begin()->getMetadata("dbg");
		for (Function::arg_iterator Arg = main->arg_begin(), aEnd =
				main->arg_end(); Arg != aEnd; Arg++) {
			inputDepValues.insert(Arg);
			NumInputValues++;
			if (mdn) {
				DILocation Loc(mdn);
				unsigned Line = Loc.getLineNumber();
				lineNo[Arg] = Line-1; //educated guess (can only get line numbers from insts, suppose main is declared one line before 1st inst
			}
		}


	}
	for (Module::iterator F = M.begin(), eM = M.end(); F != eM; ++F) {
		for (Function::iterator BB = F->begin(), e = F->end(); BB != e; ++BB) {
			for (BasicBlock::iterator I = BB->begin(), ee = BB->end(); I != ee; ++I) {
				if (CallInst *CI = dyn_cast<CallInst>(I)) {
					Function *Callee = CI->getCalledFunction();
					if (Callee) {
						Value* V;
						inserted = false;
						StringRef Name = Callee->getName();
						if (Name.equals("main")) {
							errs() << "main\n";
							V = CI->getArgOperand(1); //char* argv[]
							inputDepValues.insert(V);
							inserted = true;
							//errs() << "Input    " << *V << "\n";
						}
						if (Name.equals("__isoc99_scanf") || Name.equals(
								"scanf")) {
							for (unsigned i = 1, eee = CI->getNumArgOperands(); i
									!= eee; ++i) { // skip format string (i=1)
								V = CI->getArgOperand(i);
								if (V->getType()->isPointerTy()) {
									inputDepValues.insert(V);
									inserted = true;
									//errs() << "Input    " << *V << "\n";
								}
							}
						} else if (Name.equals("__isoc99_fscanf")
								|| Name.equals("fscanf")) {
							for (unsigned i = 2, eee = CI->getNumArgOperands(); i
									!= eee; ++i) { // skip file pointer and format string (i=1)
								V = CI->getArgOperand(i);
								if (V->getType()->isPointerTy()) {
									inputDepValues.insert(V);
									inserted = true;
									//errs() << "Input    " << *V << "\n";
								}
							}
						} else if ((Name.equals("gets") || Name.equals("fgets")
								|| Name.equals("fread"))
								|| Name.equals("getwd")
								|| Name.equals("getcwd")) {
							V = CI->getArgOperand(0); //the first argument receives the input for these functions
							if (V->getType()->isPointerTy()) {
								inputDepValues.insert(V);
								inserted = true;
								//errs() << "Input    " << *V << "\n";
							}
						} else if ((Name.equals("fgetc") || Name.equals("getc")
								|| Name.equals("getchar"))) {
							inputDepValues.insert(CI);
							inserted = true;
							//errs() << "Input    " << *CI << "\n";
						} else if (Name.equals("recv")
								|| Name.equals("recvmsg")
								|| Name.equals("read")) {
							Value* V = CI->getArgOperand(1);
							if (V->getType()->isPointerTy()) {
								inputDepValues.insert(V);
								inserted = true;
								//errs() << "Input    " << *V << "\n";
							}
						} else if (Name.equals("recvfrom")) {
							V = CI->getArgOperand(1);
							if (V->getType()->isPointerTy()) {
								inputDepValues.insert(V);
								inserted = true;
								//errs() << "Input    " << *V << "\n";
							}
							V = CI->getArgOperand(4);
							if (V->getType()->isPointerTy()) {
								inputDepValues.insert(V);
								inserted = true;
								//errs() << "Input    " << *V << "\n";
							}
						}
						if (inserted) {
							if (MDNode *mdn = I->getMetadata("dbg")) {
								NumInputValues++;
								DILocation Loc(mdn);
								unsigned Line = Loc.getLineNumber();
								lineNo[V] = Line;
							}
						}
					}
				}
			}
		}
	}
	DEBUG(printer());
	return false;
}
Ejemplo n.º 7
0
/// CloneAndPruneFunctionInto - This works exactly like CloneFunctionInto,
/// except that it does some simple constant prop and DCE on the fly.  The
/// effect of this is to copy significantly less code in cases where (for
/// example) a function call with constant arguments is inlined, and those
/// constant arguments cause a significant amount of code in the callee to be
/// dead.  Since this doesn't produce an exact copy of the input, it can't be
/// used for things like CloneFunction or CloneModule.
void llvm::CloneAndPruneFunctionInto(Function *NewFunc, const Function *OldFunc,
                                     ValueToValueMapTy &VMap,
                                     SmallVectorImpl<ReturnInst*> &Returns,
                                     const char *NameSuffix, 
                                     ClonedCodeInfo *CodeInfo,
                                     const TargetData *TD,
                                     Instruction *TheCall) {
  assert(NameSuffix && "NameSuffix cannot be null!");
  
#ifndef NDEBUG
  for (Function::const_arg_iterator II = OldFunc->arg_begin(), 
       E = OldFunc->arg_end(); II != E; ++II)
    assert(VMap.count(II) && "No mapping from source argument specified!");
#endif

  PruningFunctionCloner PFC(NewFunc, OldFunc, VMap, Returns,
                            NameSuffix, CodeInfo, TD);

  // Clone the entry block, and anything recursively reachable from it.
  std::vector<const BasicBlock*> CloneWorklist;
  CloneWorklist.push_back(&OldFunc->getEntryBlock());
  while (!CloneWorklist.empty()) {
    const BasicBlock *BB = CloneWorklist.back();
    CloneWorklist.pop_back();
    PFC.CloneBlock(BB, CloneWorklist);
  }
  
  // Loop over all of the basic blocks in the old function.  If the block was
  // reachable, we have cloned it and the old block is now in the value map:
  // insert it into the new function in the right order.  If not, ignore it.
  //
  // Defer PHI resolution until rest of function is resolved.
  SmallVector<const PHINode*, 16> PHIToResolve;
  for (Function::const_iterator BI = OldFunc->begin(), BE = OldFunc->end();
       BI != BE; ++BI) {
    BasicBlock *NewBB = cast_or_null<BasicBlock>(VMap[BI]);
    if (NewBB == 0) continue;  // Dead block.

    // Add the new block to the new function.
    NewFunc->getBasicBlockList().push_back(NewBB);
    
    // Loop over all of the instructions in the block, fixing up operand
    // references as we go.  This uses VMap to do all the hard work.
    //
    BasicBlock::iterator I = NewBB->begin();

    unsigned DbgKind = OldFunc->getContext().getMDKindID("dbg");
    MDNode *TheCallMD = NULL;
    if (TheCall && TheCall->hasMetadata()) 
      TheCallMD = TheCall->getMetadata(DbgKind);
    
    // Handle PHI nodes specially, as we have to remove references to dead
    // blocks.
    if (PHINode *PN = dyn_cast<PHINode>(I)) {
      // Skip over all PHI nodes, remembering them for later.
      BasicBlock::const_iterator OldI = BI->begin();
      for (; (PN = dyn_cast<PHINode>(I)); ++I, ++OldI) {
        if (I->hasMetadata()) {
          if (TheCallMD) {
            if (MDNode *IMD = I->getMetadata(DbgKind)) {
              MDNode *NewMD = UpdateInlinedAtInfo(IMD, TheCallMD);
              I->setMetadata(DbgKind, NewMD);
            }
          } else {
            // The cloned instruction has dbg info but the call instruction
            // does not have dbg info. Remove dbg info from cloned instruction.
            I->setMetadata(DbgKind, 0);
          }
        }
        PHIToResolve.push_back(cast<PHINode>(OldI));
      }
    }
    
    // FIXME:
    // FIXME:
    // FIXME: Unclone all this metadata stuff.
    // FIXME:
    // FIXME:
    
    // Otherwise, remap the rest of the instructions normally.
    for (; I != NewBB->end(); ++I) {
      if (I->hasMetadata()) {
        if (TheCallMD) {
          if (MDNode *IMD = I->getMetadata(DbgKind)) {
            MDNode *NewMD = UpdateInlinedAtInfo(IMD, TheCallMD);
            I->setMetadata(DbgKind, NewMD);
          }
        } else {
          // The cloned instruction has dbg info but the call instruction
          // does not have dbg info. Remove dbg info from cloned instruction.
          I->setMetadata(DbgKind, 0);
        }
      }
      RemapInstruction(I, VMap);
    }
  }
  
  // Defer PHI resolution until rest of function is resolved, PHI resolution
  // requires the CFG to be up-to-date.
  for (unsigned phino = 0, e = PHIToResolve.size(); phino != e; ) {
    const PHINode *OPN = PHIToResolve[phino];
    unsigned NumPreds = OPN->getNumIncomingValues();
    const BasicBlock *OldBB = OPN->getParent();
    BasicBlock *NewBB = cast<BasicBlock>(VMap[OldBB]);

    // Map operands for blocks that are live and remove operands for blocks
    // that are dead.
    for (; phino != PHIToResolve.size() &&
         PHIToResolve[phino]->getParent() == OldBB; ++phino) {
      OPN = PHIToResolve[phino];
      PHINode *PN = cast<PHINode>(VMap[OPN]);
      for (unsigned pred = 0, e = NumPreds; pred != e; ++pred) {
        if (BasicBlock *MappedBlock = 
            cast_or_null<BasicBlock>(VMap[PN->getIncomingBlock(pred)])) {
          Value *InVal = MapValue(PN->getIncomingValue(pred),
                                  VMap);
          assert(InVal && "Unknown input value?");
          PN->setIncomingValue(pred, InVal);
          PN->setIncomingBlock(pred, MappedBlock);
        } else {
          PN->removeIncomingValue(pred, false);
          --pred, --e;  // Revisit the next entry.
        }
      } 
    }
    
    // The loop above has removed PHI entries for those blocks that are dead
    // and has updated others.  However, if a block is live (i.e. copied over)
    // but its terminator has been changed to not go to this block, then our
    // phi nodes will have invalid entries.  Update the PHI nodes in this
    // case.
    PHINode *PN = cast<PHINode>(NewBB->begin());
    NumPreds = std::distance(pred_begin(NewBB), pred_end(NewBB));
    if (NumPreds != PN->getNumIncomingValues()) {
      assert(NumPreds < PN->getNumIncomingValues());
      // Count how many times each predecessor comes to this block.
      std::map<BasicBlock*, unsigned> PredCount;
      for (pred_iterator PI = pred_begin(NewBB), E = pred_end(NewBB);
           PI != E; ++PI)
        --PredCount[*PI];
      
      // Figure out how many entries to remove from each PHI.
      for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i)
        ++PredCount[PN->getIncomingBlock(i)];
      
      // At this point, the excess predecessor entries are positive in the
      // map.  Loop over all of the PHIs and remove excess predecessor
      // entries.
      BasicBlock::iterator I = NewBB->begin();
      for (; (PN = dyn_cast<PHINode>(I)); ++I) {
        for (std::map<BasicBlock*, unsigned>::iterator PCI =PredCount.begin(),
             E = PredCount.end(); PCI != E; ++PCI) {
          BasicBlock *Pred     = PCI->first;
          for (unsigned NumToRemove = PCI->second; NumToRemove; --NumToRemove)
            PN->removeIncomingValue(Pred, false);
        }
      }
    }
    
    // If the loops above have made these phi nodes have 0 or 1 operand,
    // replace them with undef or the input value.  We must do this for
    // correctness, because 0-operand phis are not valid.
    PN = cast<PHINode>(NewBB->begin());
    if (PN->getNumIncomingValues() == 0) {
      BasicBlock::iterator I = NewBB->begin();
      BasicBlock::const_iterator OldI = OldBB->begin();
      while ((PN = dyn_cast<PHINode>(I++))) {
        Value *NV = UndefValue::get(PN->getType());
        PN->replaceAllUsesWith(NV);
        assert(VMap[OldI] == PN && "VMap mismatch");
        VMap[OldI] = NV;
        PN->eraseFromParent();
        ++OldI;
      }
    }
    // NOTE: We cannot eliminate single entry phi nodes here, because of
    // VMap.  Single entry phi nodes can have multiple VMap entries
    // pointing at them.  Thus, deleting one would require scanning the VMap
    // to update any entries in it that would require that.  This would be
    // really slow.
  }
  
  // Now that the inlined function body has been fully constructed, go through
  // and zap unconditional fall-through branches.  This happen all the time when
  // specializing code: code specialization turns conditional branches into
  // uncond branches, and this code folds them.
  Function::iterator I = cast<BasicBlock>(VMap[&OldFunc->getEntryBlock()]);
  while (I != NewFunc->end()) {
    BranchInst *BI = dyn_cast<BranchInst>(I->getTerminator());
    if (!BI || BI->isConditional()) { ++I; continue; }
    
    // Note that we can't eliminate uncond branches if the destination has
    // single-entry PHI nodes.  Eliminating the single-entry phi nodes would
    // require scanning the VMap to update any entries that point to the phi
    // node.
    BasicBlock *Dest = BI->getSuccessor(0);
    if (!Dest->getSinglePredecessor() || isa<PHINode>(Dest->begin())) {
      ++I; continue;
    }
    
    // We know all single-entry PHI nodes in the inlined function have been
    // removed, so we just need to splice the blocks.
    BI->eraseFromParent();
    
    // Move all the instructions in the succ to the pred.
    I->getInstList().splice(I->end(), Dest->getInstList());
    
    // Make all PHI nodes that referred to Dest now refer to I as their source.
    Dest->replaceAllUsesWith(I);

    // Remove the dest block.
    Dest->eraseFromParent();
    
    // Do not increment I, iteratively merge all things this block branches to.
  }
}
  bool ClamBCTrace::runOnModule(Module &M) {
    if (!InsertTracing)
      return false;
    unsigned MDDbgKind = M.getContext().getMDKindID("dbg");
    DenseMap<MDNode*, unsigned> scopeIDs;
    unsigned scopeid = 0;
    IRBuilder<> builder(M.getContext());

    const Type *I32Ty = Type::getInt32Ty(M.getContext());
    std::vector<const Type*> args;
    args.push_back(PointerType::getUnqual(Type::getInt8Ty(M.getContext())));
    args.push_back(I32Ty);
    const FunctionType *FTy = FunctionType::get(I32Ty, args, false);
    Constant *trace_directory = M.getOrInsertFunction("trace_directory", FTy);
    Constant *trace_scope = M.getOrInsertFunction("trace_scope", FTy);
    Constant *trace_source = M.getOrInsertFunction("trace_source", FTy);
    Constant *trace_op = M.getOrInsertFunction("trace_op", FTy);
    Constant *trace_value = M.getOrInsertFunction("trace_value", FTy);
    Constant *trace_ptr = M.getOrInsertFunction("trace_ptr", FTy);
    assert (trace_scope && trace_source && trace_op && trace_value &&
            trace_directory && trace_ptr);
    if (!trace_directory->use_empty() || !trace_scope->use_empty()
        || !trace_source->use_empty() || !trace_op->use_empty() ||
        !trace_value->use_empty() || !trace_ptr->use_empty())
      ClamBCModule::stop("Tracing API can only be used by compiler!\n", &M);

    for (Module::iterator I=M.begin(),E=M.end(); I != E; ++I) {
      Function &F = *I;
      if (F.isDeclaration())
        continue;
      bool first = true;
      for (Function::iterator J=I->begin(),JE=I->end(); J != JE; ++J) {
        MDNode *Scope = 0;
        StringRef directory;
        Value *LastFile = 0;
        unsigned SrcLine = 0;
        BasicBlock::iterator BBIt = J->begin();
        while (BBIt != J->end()) {
          while (isa<AllocaInst>(BBIt) || isa<PHINode>(BBIt)) ++BBIt;
          MDNode *Dbg = BBIt->getMetadata(MDDbgKind);
          if (!Dbg) {
            ++BBIt;
            continue;
          }
          builder.SetInsertPoint(&*J, BBIt);
          Instruction *II = BBIt;
          ++BBIt;
          DILocation Loc(Dbg);
          StringRef file = Loc.getFilename();
          Value *File = builder.CreateGlobalStringPtr(file.str().c_str());
          MDNode *NewScope = Loc.getScope().getNode();

          if (NewScope != Scope) {
            Scope = NewScope;
            unsigned sid = scopeIDs[NewScope];
            if (!sid) {
              sid = ++scopeid;
              scopeIDs[NewScope] = sid;
            }
            DIScope scope(Loc.getScope());
            while (scope.isLexicalBlock()) {
              DILexicalBlock lex(scope.getNode());
              scope = lex.getContext();
            }
            Value *Scope = 0;
            if (scope.isSubprogram()) {
              DISubprogram sub(scope.getNode());
              StringRef name = sub.getDisplayName();
              if (name.empty()) name = sub.getName();
              Scope = builder.CreateGlobalStringPtr(name.str().c_str());
            } else {
              assert(scope.isCompileUnit());
              DICompileUnit unit(scope.getNode());
              Scope =
                builder.CreateGlobalStringPtr(unit.getFilename().str().c_str());
            }
            builder.CreateCall2(trace_scope, Scope,
                                ConstantInt::get(Type::getInt32Ty(M.getContext()), sid));
          }
          unsigned newLine = Loc.getLineNumber();
          if (File != LastFile || newLine != SrcLine) {
            LastFile = File;
            SrcLine = newLine;
            if (Loc.getDirectory() != directory) {
              directory = Loc.getDirectory();
              builder.CreateCall2(trace_directory,
                                  builder.CreateGlobalStringPtr(directory.str().c_str()),
                                  ConstantInt::get(Type::getInt32Ty(M.getContext()), 0));
            }
            builder.CreateCall2(trace_source, File,
                                ConstantInt::get(Type::getInt32Ty(M.getContext()), newLine));
          }
          if (first) {
            first = false;
            for (Function::arg_iterator AI=I->arg_begin(),AE=I->arg_end();
                 AI != AE; ++AI) {
              if (isa<IntegerType>(AI->getType())) {
#if 0
                Value *V = builder.CreateIntCast(AI, Type::getInt32Ty(M.getContext()), false);
                Value *ValueName = builder.CreateGlobalStringPtr(AI->getName().data());
                builder.CreateCall2(trace_value, ValueName, V);
#endif
              } else if (isa<PointerType>(AI->getType())) {
                Value *V = builder.CreatePointerCast(AI, 
                                                     PointerType::getUnqual(Type::getInt8Ty(M.getContext())));
                builder.CreateCall2(trace_ptr, V,
                                    ConstantInt::get(Type::getInt32Ty(M.getContext()),
                                                     0));
              }
            }
          }
          std::string op;
          raw_string_ostream opstr(op);
          opstr << *II;
          Value *Op = builder.CreateGlobalStringPtr(opstr.str().c_str());
          builder.CreateCall2(trace_op, Op,
                              ConstantInt::get(Type::getInt32Ty(M.getContext()),
                                               Loc.getColumnNumber()));
          //Value *ValueName = builder.CreateGlobalStringPtr(II->getName().data());
          if (isa<IntegerType>(II->getType())) {
#if 0
            builder.SetInsertPoint(&*J, BBIt);
            Value *V = builder.CreateIntCast(II, Type::getInt32Ty(M.getContext()), false);
            builder.CreateCall2(trace_value, ValueName, V);
#endif
          } else if (isa<PointerType>(II->getType())) {
            builder.SetInsertPoint(&*J, BBIt);
            Value *V = builder.CreatePointerCast(II, 
                                                 PointerType::getUnqual(Type::getInt8Ty(M.getContext())));
            builder.CreateCall2(trace_ptr, V,
                                ConstantInt::get(Type::getInt32Ty(M.getContext()),
                                                 0));
          }
        }
      }
    }
    return true;
  }