Skip to content

Fix intrinsic lookup with namespaces #7599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

llvm-beanz
Copy link
Collaborator

This change fixes issues with intrinsic lookup caused by not correctly respecting the using declaration(s) that impact unqualified lookups.

This probably isn't a perfect solution because I'm sure there's some nuance of unqualified lookups in C++ that I'm not handling, but this does respect scoped using directives and allows us to get things working.

Additionally this change disables emitting some "declared here" notes when the source location referred to is invalid.

Fixes #7495

This change fixes issues with intrinsic lookup caused by not correctly
respecting the using declaration(s) that impact unqualified lookups.

This probably isn't a perfect solution because I'm sure there's some
nuance of unqualified lookups in C++ that I'm not handling, but this
does respect scoped using directives and allows us to get things
working.

Additionally this change disables emitting some "declared here" notes
when the source location referred to is invalid.

Fixes microsoft#7495
Copy link
Contributor

github-actions bot commented Jun 30, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.


{
int sortKey = 1;
MaybeReorderThread(sortKey, 1); // expected-error{{use of undeclared identifier 'MaybeReorderThread'; did you mean 'MaybeReorderThread'?}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the did you mean 'MaybeReorderThread' part say did you mean 'dx::MaybeReorderThread' instead? Is there any way to get the message to say that?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is something wrong with the way we're generating the ASTs for these types, and I'm not entirely sure how to fix it. I can carve out some time to look at it, but if it proves to be too time consuming I'd like to suggest tracking that as a follow up?

Does that sound reasonable to you?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that sounds reasonable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a follow-up change ready that makes this less misleading by removing the correction text. That falls out naturally from fixing how SemaHLSL constructs ASTs to properly put namespaced functions under the namespace instead of the translation unit declaration.

If you're curious to see that change it is here.


// If we have a scope chain, walk it to get using declarations.
if (S) {
UnqualUsingDirectiveSet UDirs;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems unfortunate to have to look for all using directives for each call lookup. Is there any way to make that better? Shouldn't there be a better way to recognize the namespaces available in the current lookup scope based on using statements?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so I guess that it copies the UnqualUsingDirectiveSet implementation from SemaLookup.cpp.

I see we are duplicating this because the ExternalSemaSource mechanism seems to only support unqualified name lookups, as opposed to being hooked in under CppNamespaceLookup/LookupDirect (I think).

Do you think it would be a better approach to hook into the existing C++ lookup process differently?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to use the C++ lookup process would require us to pre-populate all the impacted APIs in the AST and now allow them to be lazily added (at least not without additional complicated changes). That might not have too significant of a performance impact because we don't have many intrinsics that are in namespaces today.

One drawbacks here is that the candidate selection and overload resolution are pretty tightly coupled in the codebase, so we'd really be opting in a subset of functions to the C++ overload resolution behavior. We do this today for subscript operators, which is fine because HLSL and C++ overload resolution for functions that take a single argument are almost indistinguishable in most cases. Extending this across a wider array of functions could be more problematic in the long run. It is probably fine for the current set of functions, but would be an odd edge case we could trip over.

I think these concerns make me think it is probably better to teach the HLSL-specific code to handle unqualified lookups. WDYT @tex3d?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I was thinking of some way of hooking into the same code path that already collects the needed using namespaces as for other lookups, and exposing that namespace set to our lookup somehow. Like by moving our lookup to CppNamespaceLookup instead of under LookupName. It seems like the ExternalSource mechanism was only designed to deal with unqualified C names, as opposed to things that reside in C++ namespaces.

I wasn't thinking of adding the prototypes in advance so that the normal lookups find those.

However, that does bring up another issue. We currently pre-populate dx and vk namespaces using AddIntrinsicFunctionsToNamespace, then we go through the usual path to construct a template/specialization in the usual way in AddOverloadedCallCandidates for those functions later. This pre-population path was originally added for vk::RawBuffer* intrinsics, and has specific limitations for the template types it recognizes from the intrinsic table as a result (see CreateTemplateTypeParmDeclsForIntrinsicFunction). Shouldn't this path be unnecessary with this change?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand what you're saying here. Sema::LookupName calls CppLookupName, which calls CppNamespaceLookup. The external sema source gets called in all cases where LookupName fails, so it should always be invoked for both C and C++ names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So... looking at this more. Part of the problem is that we're using the ExternalSemaSource wrong.

DXC added a new interface to the ExternalSemaSource to populate overload candidates, but we should instead probably have handled it all in lookupUnqualified, which is really the right place to be able to perform a lazy lookup. We really only depend on lookupUnqualified for a few simple cases for builtin types, but it could do so much more.

I'm not really sure it is worth fixing this all in DXC.

// Find the first namespace or translation-unit scope.
Scope *Initial = S;
while (S && !isNamespaceOrTranslationUnitScope(S))
S = S->getParent();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think we need to visit each (non-transparent) scope on the way up to add the available namespaces, like:

for (DeclContext *UCtx = Ctx; UCtx; UCtx = UCtx->getParent()) {
if (UCtx->isTransparentContext())
continue;
UDirs.visit(UCtx, UCtx);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see now that visitScopeChain should add these, and this just gets the InnermostFileScope for that call.

I think it would have been clearer to declare a new Scope *InnermostFileScope = S;, which would be changed by this loop, instead of changing the original passed-in S.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also a little confused. The code I pointed to in SemaLookup.cpp is followed by the equivalent code to this. So why does the code in SemaLookup.cpp explicitly visit each context up the parent chain before finding InnermostFileScope calling visitScopeChain?

It appears necessary, since InnermostFileScope will stop at the first global/namespace parent scope, while the prior loop will catch all scopes that could have using statements that impact the current lookup.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... so that's interesting. This is needed for a C++-ism that I hadn't really thought about.

The visitScopeChain call hits the contexts up the scope chain, but it doesn't guarantee that it is walking up the declaration contexts for the enclosing scope. The commit that adds that code is here, its test case demonstrates the issue:

namespace Other {
  void other_foo();
}

namespace M2 {
  using namespace Other;

  namespace MInner {
    class Bar { 
      void bar();
    };
  }
}

void M2::MInner::Bar::bar() {
  other_foo();
}

The issue is that the scope chain for M2::MInner::Bar::bar() doesn't actually include the using namespace Other declaration, you have to walk the declaration contexts for the enclosing decl.

Because namespaces aren't sealed, we could encounter this, so I'll add a declaration context walk to my PR as well.

llvm-beanz and others added 8 commits July 3, 2025 13:33
This catches using declarations that aren't present in the scope chain
but are in the declaration chain.

'beanz/cbieneman/fix-intrinsic-namespaces'.
'beanz/cbieneman/fix-intrinsic-namespaces'.
'beanz/cbieneman/fix-intrinsic-namespaces'.
commits, and can be fast-forwarded.

diverged,
'beanz/cbieneman/fix-intrinsic-namespaces'.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: New
Development

Successfully merging this pull request may close these issues.

[Shader Execution Reordering] using namespace dx; not working for MaybeReorderThread
2 participants