[cfe-dev] Qualified and unqualified name lookup and IdResolver

Douglas Gregor dgregor at apple.com
Wed Dec 10 10:49:29 PST 2008


Hi Chris,

On Dec 8, 2008, at 11:44 AM, Chris Lattner wrote:

> On Dec 4, 2008, at 5:30 PM, Douglas Gregor wrote:
>> The attached patch changes how we perform both qualified and  
>> unqualified name lookup in Clang. It represents a significant  
>> departure from the approach currently implemented in Clang, but I  
>> believe it will have performance and maintainability benefits in  
>> the future.
>
> Hi Doug,
>
> I think this is a great step forward.

Thanks for the review!

>
>> This patch is neither complete nor fully-optimized. It should not  
>> degrade performance in C, because the array/hash table data  
>> structures are not built in C. However, there are more optimization  
>> opportunities:
>
> I think that this patch should only go in if you're committed to  
> doing these improvements.  I'm somewhat scared that C and C++ will  
> diverge even more.  If you're planning to do these, then I'm all for  
> the change!

Updated patch coming, which makes quite a few improvements in this  
area. However, some replies to your comments below...

>> 	- We should make RecordDecl derive from DeclContext, and replace  
>> the current storage and name lookup for fields  
>> (RecordDecl::getMember, a linear search) with lookup into the  
>> DeclContext. This will give us faster lookup for records with many  
>> members. A similar thing could be done for Objective-C interfaces/ 
>> implementations.
>
> Yep, we should migrate ObjC as well.  I think it is important that  
> we keep it and C and C++ all consistent where possible.

*That* will need to be a follow-on patch.

> +  /// LookupPtr - Pointer to a data structure used to lookup
> +  /// declarations within this context. If the context contains fewer
> +  /// than seven declarations, the number of declarations is provided
> +  /// in the 3 lowest-order bits and the upper bits are treated as a
> +  /// pointer to an array of NamedDecl pointers. If the context
> +  /// contains seven or more declarations, the upper bits are treated
> +  /// as a pointer to a DenseMap<DeclarationName, TwoNamedDecls>.
> +  llvm::PointerIntPair<void*, 3> LookupPtr;
>
> Very clever.  I never thought of using the low bits as a counter,  
> I'll have to remember that :)

I was pretty proud of that.

>
> +  lookup_result lookup(ASTContext &Context, DeclarationName Name);
> +  lookup_const_result lookup(ASTContext &Context, DeclarationName  
> Name) const;
>
> Since the impl of the second lookup function is just a  
> const_cast'ing forwarding function for the first one, it might be  
> worth putting it inline in the header.

Then we'd need to include "clang/AST/DeclarationName.h".

>
>
> +    if (NamespaceDecl *OrigNS = dyn_cast_or_null<NamespaceDecl> 
> (PrevDecl)) {
> +      // This is an extended namespace definition.
> +      // Attach this namespace decl to the chain of extended  
> namespace
> +      // definitions.
> +      NamespaceDecl *NextNS = OrigNS;
> +      while (NextNS->getNextNamespace())
> +	NextNS = NextNS->getNextNamespace();
>
> Is it necessary to walk the entire list of namespaces here?  Would  
> it be possible to keep the list built and maintained backwards?   
> Also, slightly wonky indentation.

Actually, we'll never actually go into the loop, now that PrevDecl is  
always the most recent declaration of this namespace. Good catch.

> +  // Determine whether the current context supports qualified name
> +  // lookup. If so, we may insert this declaration into the current
> +  // context.
> +  bool SupportQualifiedNameLookup = getLangOptions().CPlusPlus &&
> +    (isa<TranslationUnitDecl>(CurContext) ||
> +     isa<NamespaceDecl>(CurContext) ||
> +     isa<CXXRecordDecl>(CurContext) ||
> +     (getLangOptions().CPlusPlus0x && isa<EnumDecl>(CurContext)));
>
> can this be split out into a predicate method on CurContext?

This is gone in the upcoming patch.

>
> +  if (ScopedDecl *SD = dyn_cast<ScopedDecl>(D))
> +    CurContext->addDecl(Context, SD, SupportQualifiedNameLookup);
> +  else if (CXXFieldDecl *FD = dyn_cast<CXXFieldDecl>(D))
> +    FD->getParent()->insert(Context, FD);
> +
>   IdResolver.AddDecl(D);
> }
>
> Is there an "else" for this?  If not, it would be good to make it  
> assert and die.  If so, it would be good to add a comment.

There's no "else", so I've added a comment. Basically, if it's going  
to be in a DeclContext, it's going to be a ScopedDecl. No exceptions.  
(For reference: CXXFieldDecl is gone in the new patch, and FieldDecl  
is a ScopedDecl).

> +  } else {
> +    if (getLangOptions().CPlusPlus &&
> +	(NS & (Decl::IDNS_Ordinary | Decl::IDNS_Tag))) {
> +      // Name lookup for ordinary names and tag names in C++ requires
> +      // looking into scopes that aren't strictly lexical, and
> +      // therefore we walk through the context as well as walking
> +      // through the scopes.
>
> To reduce nesting, please turn this into } else if (...) {

Okay.

>
> +	// Check whether the IdResolver has anything in this scope.
> +	// FIXME: The isDeclScope check could be expensive. Can we do  
> better?
> +	while (I != IdResolver.end() && S->isDeclScope(*I)) {
> +	  if ((*I)->getIdentifierNamespace() & NS)
> +	    return *I;
> +	
> +	  ++I;
> +	}
>
> How about getting the decls context and using that?  Do scopes have  
> an associated context?

Some scopes have an associated "entity", which ends up being a  
DeclContext, but there's no mapping from DeclContext -> Scope (because  
Scope is a transient thing only available at parse time). The  
isDeclScope check is just a lookup in a (typically relatively small)  
set, so it shouldn't be *that* expensive. I'll leave the FIXME and  
think about it more, later :)
>
> The while loop might be more natural as a 'for' loop, to pull the + 
> +I into the loop condition.

Agreed.

>
>
> +	// If there is an entity associated with this scope, it's a
> +	// DeclContext. We might need to perform qualified lookup into
> +	// it.
> +	DeclContext *Ctx = static_cast<DeclContext *>(S->getEntity());
> +	while (Ctx && Ctx->isFunctionOrMethod())
> +	  Ctx = Ctx->getParent();
>
> Can you have more than one function/method context?  If no, please  
> convert the while to if.

IIUC,  we could have a block context inside of a function context.

>
> +	while (Ctx && (Ctx->isNamespace() || Ctx->isCXXRecord())) {
> +	  // Look for declarations of this name in this scope.
> +	  for (DeclContext::lookup_const_result I = Ctx->lookup(Context,  
> Name);
> +	       I.first != I.second; ++I.first) {
> +	    // FIXME: Cache this result in the IdResolver
> +	    if ((*I.first)->getIdentifierNamespace() & NS)
> +	      return  *I.first;
> +	  }
> +
> +	  Ctx = Ctx->getParent();
> +	}
>
> I think that this would be better to split out as a (potentially  
> virtual?) method on the context.  This would make it easier to  
> extend with different sorts of contexts.

Okay.

>  For example, could 'enableLazyBuiltinCreation' be handled as a  
> "lookup" on the global (or maybe a new 'builtin') scope instead of  
> as a special case hack?

Well, maybe. It depends somewhat on whether we want to permit  
qualified lookup to find these names ('::__builtin_foo') or not. One  
could imagine that they live in an imaginary scope outside of the  
translation unit, so you can never actually find them via unqualified  
lookup.

>
>
> +      // Scan up the scope chain looking for a decl that matches this
> +      // identifier
> +      // that is in the appropriate namespace.  This search should
> +      // not take long, as
> +      // shadowing of names is uncommon, and deep shadowing is
> +      // extremely uncommon.
> +      for (; I != IdResolver.end(); ++I)
> +	if ((*I)->getIdentifierNamespace() & NS)
> +	  return *I;
>
> Please wrap the comment better, and avoid evaluating .end() every  
> time through the loop.

Okay.

>
>
>           if (OldDecl == PrevDecl) {
>             // Remove the name binding for the previous
> +            // declaration.
> +	    //	    if (cast<ScopedDecl>(PrevDecl)->getLexicalDeclContext()  
> == CurContext) {
> +	    if (S->isDeclScope(PrevDecl)) {
>
> commented out code.

Oops.

>
>
> +	  // Add this declaration
> +	  if (getLangOptions().CPlusPlus)
> +	    CurContext->addDecl(Context, NewFD, false);
>
> +	  // In C++, check default arguments now that we have merged decls.
> +	  if (getLangOptions().CPlusPlus)
> +	    CheckCXXDefaultArguments(NewFD);
>
> should these be merged into one "if"?

Sure.

>
>
>   const RecordType *BaseRecord = Base->getType()->getAsRecordType();
> +  DeclContext::lookup_const_result Lookup
> +    = cast<CXXRecordType>(BaseRecord)->getDecl()->lookup(Context,  
> OpName);
>
> This will go away when C and C++ use the same lookup for struct  
> fields, right?

Yes. The upcoming patch will do this.

> +DeclContext::lookup_result
> +DeclContext::lookup(ASTContext &Context, DeclarationName Name) {
> ..
> +    }
> +  } else {
>
> How about "return Result" at the end of the "if".  This lets you  
> unnest the 'else' clause.

Okay.

> +DeclContext::lookup_iterator
> +DeclContext::insert(ASTContext &Context, NamedDecl *D) {
> +  DeclContext *PrimaryContext = getPrimaryContext(Context);
> +  if (PrimaryContext != this)
> +    return PrimaryContext->insert(Context, D);
> +
> +  unsigned Size = LookupPtr.getInt();
> +  if (Size < LookupIsMap) {
>
> There are various places where you compare against LookupIsMap in  
> different ways (some using != some using <), how about introducing a  
> private isLookupMap() method or something like that?  In this case,  
> the fact that LookupIsMap is the largest value really doesn't have  
> anything to do with the algorithm.  If you don't like the predicate  
> method, please change it to use !=.

A predicate is fine.

> Another option would be to pull the array/map code into its own  
> generic class (out of the DeclContext class), which might be the  
> real best solution.

What it does is far too specific to end up in its own generic class.

> *ahhh*, this is because there are tabs in the file!  Please zap  
> them! :)

Will do.

>
> Finally, when the dust settles on name lookup, can you please add an  
> overview to the Internals doc? :)  The issues are nuanced enough  
> that a high level overview would be very useful.

Yeah, I will. I want to make sure I've gotten it right for the truly  
ugly cases (using declarations and such) before investing too much  
time in documentation.

	- Doug



More information about the cfe-dev mailing list