Fix #2338: error with mixed internal/external qualifiers in function declaration#3468
Fix #2338: error with mixed internal/external qualifiers in function declaration#3468
Conversation
…re mixed in the same function declaration
36fc668 to
6f3c883
Compare
nurmukhametov
left a comment
There was a problem hiding this comment.
Nit picking comment about the commit message is below.
I generally prefer that the first line of the commit message should be as short as possible (ideally less than 50 characters), i.e., this is kind of the title of the commit message. Then starting from the 3rd line it may contain whatever text wrapped to something like 72 or 80.
I would do something like this for this commit:
Error for misused internal/external qualifiers
Report during function declaration construction the error about mixed
internal/external qualifiers. Fix issue #2338| if (isInternal && isExternal) { | ||
| const char *internalQualifier = isInline ? "inline" : "static"; | ||
| const char *externalQualifier = isExternCorSYCL ? "extern" : "export"; | ||
| Error(pos, "Function qualifier '%s' incompatible with '%s'.", externalQualifier, internalQualifier); |
There was a problem hiding this comment.
Let's just report the warning here. The initial request was about a warning, and I think it is better not to break users' builds on this occasion.
There was a problem hiding this comment.
IMHO this is a bug like declaring a variable signed unsigned (which does not work in C/C++ nor in ISPC). Moreover, it is similar to static extern "C" which is already reported as an error in ISPC (see test5).
I do not see any use-case where it would be legit to mix such specifiers on the same function.
I am not strongly against reporting a warning though.
Please note export and extern "C" take precedence btw.
There was a problem hiding this comment.
IMHO this is a bug like declaring a variable
signed unsigned(which does not work in C/C++ nor in ISPC). Moreover, it is similar tostatic extern "C"which is already reported as an error in ISPC (seetest5). I do not see any use-case where it would be legit to mix such specifiers on the same function. I am not strongly against reporting a warning though.
signed and unsigned contradict each other. Whereas we have inline that has a slightly unclear meaning (especially in different languages). It looks like C, C++ and ISPC all have different meanings for this function qualifier. According to docs, inline in ISPC is only about the requirement to always inline the function on its call sites. I don't see that it mentions anything about internal linkage of this function.
It is kind of surprise for me because I slightly doubt that it happens always, and I expected that inline implies internal linkage (similar to C). Knowing that, the original issue seems invalid to me, or it is actually about something different, e.g., inline keyword doesn't force inlining for some functions.
See an example here where inline and export mixed together, and they both work as expected: https://godbolt.org/z/EnqGvPP8n
There was a problem hiding this comment.
The issue with exported functions in ISPC is that they generate two versions of the function:
- An externally callable version (for use by the application).
- An internal version (for use within ISPC code).
When you annotate an exported function with inline, the attribute applies only to the internal version, not the externally visible one. This can be misleading, as users often assume the exported function exists only in one form (the external one).
A similar nuance exists with the unmasked keyword: ISPC emits a warning like
"unmasked qualifier is redundant for exported functions" - which is partially correct, since the qualifier has no effect on the external version, but still valid for the internal version.
From my experience, many users are unaware that export creates two versions of the function and often believe it only creates the external one.
So in ISPC, inline is simply a hint to the optimizer, instructing it to try inlining the function. While technically valid, using inline on exported or extern functions may cause confusion. It would be helpful to emit a warning in this case to clarify what’s actually happening. Something like: "'inline' on an 'export' function applies only to the internal ISPC version; the exported version is always emitted and may not be inlined."
On the other hand, using static with either export or extern is incorrect. static specifies internal linkage, which contradicts the external visibility intent of export and extern. In such cases, ISPC should emit a error.
There was a problem hiding this comment.
Thank you for the thorough explanation!
I am now convinced to use a warning with inline export (and inline extern "[...]").
By the way, regarding inline in C and C++, they do not exactly behave the same way (AFAIR static inline does and I think it should also be similar to static inline in ISPC or even just inline since AFAIK ISPC functions are implicitly static, hence the export and extern "C" keyword). In C, inline without extern is identical to static inline (i.e. internal linkage by default). In C++, inline without extern is not (i.e. external linkage by default). The way they handle external linkage of inlined function is also slightly different (so AFAIK C++ implementations should uses weak symbols while C implementations may not).
Technically, ISPC should have the same issue than C and C++ when an inline function is defined in a header and this function cannot be inlined (too big? recursive function?). The inlined function will be replicated in each TU causing link issues unless it is considered to be the exact same function (i.e. identical definition). I personally expect ISPC to behave like C in this case but I did not test it so far. I actually thought functions to be implicitly static, but it looks like this is not true (using static impact the result in Godbolt). I often forget there is two versions generated when extern is used in ISPC.
I guess we should emit an error in this case too (the resulting code will not link anyway -- see on Godbolt):
__attribute__((external_only))
inline export uniform int foo() { return 42; }
uniform int boo() { return foo(); }Surprisingly, It looks like inlining is independent of whether there is an internal non-exported version of the function (see on Godbolt:
inline extern "C" uniform int foo() { return 42; }
uniform int boo() { return foo(); }Even more interestingly, the generated code for this one looks wrong (see on Godbolt):
inline extern uniform int foo() { return 42; }
uniform int boo() { return foo(); }This is supposed to be actually a valid code? This is typically the tricky case mentioned above where foo could be replicated in multiple TU (possibly with weak symbols). Here, it looks like ISPC ignore the extern so it assume extern inline is identical to inline, and also to static inline (i.e. internal linkage). Please note that foo can be put in a separate header file (i.e. isph) shared by two TU (i.e. two ispc files). Actually, the second ispc file could even do that (causing no error nor any warning so far -- see on Godbolt):
inline extern uniform int foo();
uniform int boo() { return foo(); }The two last use-cases are a bit confusing to me, especially the last one.
There was a problem hiding this comment.
... since AFAIK ISPC functions are implicitly
static...
No, they are not. It should be legit to have two ISPC translation units like these:
// tu1.ispc
int foo();
int boo() { return foo(); }
// tu2.ispc
int foo() { return 42; }I actually thought functions to be implicitly
static, but it looks like this is not true (usingstaticimpact the result in Godbolt).
Yes, they are not implicitly static.
I guess we should emit an error in this case too (the resulting code will not link anyway -- see on Godbolt):
__attribute__((external_only)) inline export uniform int foo() { return 42; } uniform int boo() { return foo(); }
This code is incorrect even without inline and the linker reports error about it.
Surprisingly, It looks like inlining is independent of whether there is an internal non-exported version of the function (see on Godbolt:
inline extern "C" uniform int foo() { return 42; } uniform int boo() { return foo(); }Even more interestingly, the generated code for this one looks wrong (see on Godbolt):
inline extern uniform int foo() { return 42; } uniform int boo() { return foo(); }This is supposed to be actually a valid code?
I don't know. I am also not sure that these qualifiers were intended to be used with function definitions at all. I guess their primary goal was to be used with function declarations: extern for ISPC functions from a different ISPC TU, extern "C" for C functions from a different C TU.
There was a problem hiding this comment.
Even more interestingly, the generated code for this one looks wrong (see on Godbolt):
inline extern uniform int foo() { return 42; }
uniform int boo() { return foo(); }
The extern semantic in ISPC is not defined well. There is some description of it in the table at the end of https://ispc.github.io/ispc.html#interoperability-overview section which partially justifies the behavior. Note that using extern "C" keeps foo function alive.
https://godbolt.org/z/vfj6P69Tv
Description
This PR fixes #2338 . An errors is reported when internal and external qualifiers are mixed in the same function declaration (not just a warning since it make no sense and the generated code only consider only one of the two keyword which is pretty bug-prone).
Related Issue
Checklist
clang-format(e.g.,clang-format -i src/ispc.cpp)