-
Notifications
You must be signed in to change notification settings - Fork 790
[SYCL] Update kernel name diagnostics logic #3069
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
[SYCL] Update kernel name diagnostics logic #3069
Conversation
2b69fb0
to
55efe70
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM for test changes
But you should also fix llvm-test-suite test (SYCL :: Functor/kernel_functor.cpp):
http://icl-jenkins.sc.intel.com:8080/job/SYCL_CI/job/intel/job/Win/job/Test_Suite/630/console
if (KernelNameIsMissing) { | ||
|
||
while (!DeclCtx->isTranslationUnit()) { | ||
auto *NSDecl = dyn_cast_or_null<NamespaceDecl>(DeclCtx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can just be a dyn_cast, otherwise the condition in the 'while' is UB :)
I addressed SYCL :: Functor/kernel_functor.cpp in this commit: @vladimirlaz I don't see any errors shown in the log for the 2 CUDA failing tests. I just see the RUN command and "Skipping test" in the log. Do you have any idea what's going on? |
} else { | ||
return; | ||
} | ||
if (isa<CXXMethodDecl>(DeclCtx) && !Tag->isCompleteDefinition()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and line 3065 seems to change the logic quite a bit here. Can you explain what is happening here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The if block at L3076 is to handle the cases like:
int main()
h.single_task<class fake_kernel2>([]() {
auto l = [](auto f) { f(); };
});
The order of DeclContexts for fake_kernel2 is
clang::Decl::CXXMethod
clang::Decl::CXXRecord
clang::Decl::Function
clang::Decl::TranslationUnit
So I just check if the DeclCtx is clang::Decl::CXXMethod and "class fake_kernel2" does not have complete definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need the context check here? Won't the warning for implicit decl be generated even without this check?
@@ -3047,7 +3062,7 @@ class SYCLKernelNameTypeVisitor | |||
IsInvalid = true; | |||
return; | |||
} | |||
if (Tag->isCompleteDefinition()) { | |||
if (isa<FunctionDecl>(DeclCtx) && Tag->isCompleteDefinition()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The if at L3065 is to handle cases like the following:
void test() {
class InvalidKernelName1 {};
q.submit([&](handler &h) {
h.single_task<InvalidKernelName1>([]() {});
});
The order of decl context for InvalidKernelName1 is
clang::Decl::Function
clang::Decl::TranslationUnit
And class InvalidKernelName1 {};
is completely defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I'm more asking for a situation where that isa is required. It seems that both your examples are positive things here, but what cases are you trying to exclude from these 'if's?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to exclude cases like nested named structs and classes inside structs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I see... You're limiting the check to only function scope. I think it might be better to instead check if the scope is file context or something, and throw the error if it isn't. @erichkeane thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I unfortunately don't know the visitor here well enough to know exactly what is going on here, but this part of the change "feels" wrong, but I obviously cannot specify what makes me think that. It just generally seems like the visitor isn't doing what it is supposed to if all this is necessary, and these two 'if' statements are particularly constrained for situations that are not sufficiently thought out.
I'm hoping that @Fznamznon can analyze the patch/problem and make sure this is the right approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to have more tests and diagnostics.
@@ -124,4 +108,4 @@ int main() { | |||
q.submit([&](cl::sycl::handler &h) { h.single_task([] {}); }); | |||
|
|||
return 0; | |||
} | |||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore it.
return; | ||
} | ||
|
||
const bool UnnamedTypeUsed = Tag->getName().empty(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be outside the while loop right?
} else { | ||
return; | ||
} | ||
if (isa<CXXMethodDecl>(DeclCtx) && !Tag->isCompleteDefinition()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need the context check here? Won't the warning for implicit decl be generated even without this check?
@@ -3047,7 +3062,7 @@ class SYCLKernelNameTypeVisitor | |||
IsInvalid = true; | |||
return; | |||
} | |||
if (Tag->isCompleteDefinition()) { | |||
if (isa<FunctionDecl>(DeclCtx) && Tag->isCompleteDefinition()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I see... You're limiting the check to only function scope. I think it might be better to instead check if the scope is file context or something, and throw the error if it isn't. @erichkeane thoughts?
|
||
while (!DeclCtx->isTranslationUnit()) { | ||
auto *NSDecl = dyn_cast_or_null<NamespaceDecl>(DeclCtx); | ||
if (NSDecl && NSDecl->isStdNamespace()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Recursive check will now throw an error for std::test::U
, which previously passed. I think the error is right behavior though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right that std::test::U
should fail, however the loop makes me concerned that the visitor isn't doing what it is supposed to. Why is it not doing a 'visit' of the namespace and catching it that way? I would expect each of the decl-contexts to be visited, not need to be looped here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a TypeVisitor
and ConstTemplateArgumentVisitor
, it can visit only types and template arguments, we have to inherit it from some "declaration context visitor" to do so, if this thing exists at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, huh... I figured we would use something like the text-dumpers use, which visit each node along the way individually. They seem to better fit the integration-header/kernel name checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean things like TextNodeDumper
? i.e. this one https://clang.llvm.org/doxygen/classclang_1_1TextNodeDumper.html .
The original idea was to make our visitor similar to this one. That is why we used TypeVisitor
and ConstTemplateArgumentVisitor
as bases, as well as TextNodeDumper
does. We could take a look on how it handles namespaces and other declaration contexts, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the context.
We definitely need to prevent std::something::SomethingElse, the fact that we aren't is likely a regression (as I seem to remember it worked before the refactor? Unfortunate there were no tests).
This patch does NOT seem to do anything with the integration header though, which would need to be taught how to forward-declare these globally visible names. That said, I don't think this necessary part of this is actually possible with integration-headers (see my other comment).
At the moment, I'd suggest a separate patch to fix the std::something::something_else case (as well as the something::<anon_ns>::another::type case), and we wait for the nested-struct version until we can get @mkinsner and the SYCL spec clarifications of what we have to do.
if we have to support this nested struct case, I think we have to figure out an alternative to integration headers for runtime support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as I seem to remember it worked before the refactor? Unfortunate there were no tests)
Refactor did not break anything. std::something::SomethingElse was not diagnosed before.
Same with anonymous NS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. That diffs from my memory(though it is historically not particularly accurate, so I can buy I mis-remembered), but still needs fixing. The rule to disallow std::string has the same motivation/conclusion as disallowing std::ranges::views::view_base
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can look at the history. I can fix it, just clarifying that it was something not previously supported and something I encountered while working on the original issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you :) I could swear it was caught at SOME point (as I was sure I encountered it at one point...), but it doesn't mean it survived until the refactor in question I guess.
|
@@ -3047,7 +3062,7 @@ class SYCLKernelNameTypeVisitor | |||
IsInvalid = true; | |||
return; | |||
} | |||
if (Tag->isCompleteDefinition()) { | |||
if (isa<FunctionDecl>(DeclCtx) && Tag->isCompleteDefinition()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, the original purpose of this PR was to fix cases when error was reported for nested name specifiers, i.e.
struct A {
struct B {
};
};
q.submit([&](handler &h) { h.single_task<A::B>([]() {});
Turns out it is a valid case and we diagnosed that.
The spec rule which we are trying to satisfy here is "kernel name should be globally-visible".
I believe I don't know exactly what it means, but I asked Sri to clarify it before preparing a patch. My gut feeling tells that class name defined inside function scope is not globally-visible, so when we discussed this patch we decided to diagnose if kernel name was defined in function/method scope. Seems to me if my gut tells us right thing, we need to throw "kernel name is not globally-visible" error if we encounter a function/method scope during our decl context loop. I'm not really sure, why we need to check on complete definition here at all. And why we are checking context on line 3076
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct in a class inside a function scope is not globally visible. So when the 'visitor' finds a function-type/decl, I would assume it would reject it.
Globally visible names appear only in namespaces, record types, or enums I believe (though I don't believe you can create a record-type inside an enum in any way, unless there is an incredible trick I'm missing).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe I don't know exactly what it means, but I asked Sri to clarify it before preparing a patch.
I looked at "5.2 Naming of kernels" in the SYCL2020-provisional-spec and based on my understanding of rule#2,3,4 made this change.
So as this patch went on, I actually question the original motivation here. My current conclusion is that this is actually a SYCL-standard bug (or at least, we have to vastly change how our integration headers work). At the very least, this is an incomplete patch without the integration header work to make the motivating example usable. The original justification is:
Which DOES pass the globally-visible rule. However, the purpose of the globally-visible rule is to allow us to forward declare the types in the integration-header so that we can pass information to the host compilation. You'll note however, that we actually CANNOT forward declare 'B' here without defining 'A', which we obviously cannot do. @mkinsner Is this something you can help @srividya-sundaram with? I think we want this as a defect against the SYCL spec, as I don't think we can properly implement this rule. |
I believe that an unreleased version of the SYCL spec has already fixed this. I can't describe the fix on GitHub until the spec goes public, but offline we should confirm that you consider the fix sufficient to cover this case. |
Based on offline discussion with @mkinsner, we've confirmed that nested name types are no longer an issue in the upcoming SYCL 2020 release. Closing this review. |
This patch makes the following changes:
It does the above by recursively checking if the declaration contexts in the parent-chain of the kernel name are valid or not.
PS: The standards quote (in C++20 as [class.access]p5), clarifies that visibility isn't controlled by access specifiers (public/private/etc).
Before this patch, the compiler would throw an error if any of the InvalidKernelName* was used as kernel name :
But based on standards quote, access specifiers do not matter and we should allow nested named types.