-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[IRGen] Use the lexical decl context for VarDecls. #33306
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
Conversation
Ping. Let me know if you need further information. |
Ping. As additional context, this doesn't just happen specifically when building stdlib (and the specific case described in #28968), this happens when using any C header with a local extern variable and with LLVM built with assertions enabled. For example, this is a reproduction case from a Linux environment using a development snapshot:
|
Please commit that as a regression test. I'll try to get eyes on this. |
555109c
to
11a2c52
Compare
Thanks. I've gone ahead and added a test to this commit. I was a little unsure at first whether to add a test for this since it only occurs when LLVM is built with assertions enabled, but I've done so now anyway. |
test/IRGen/Inputs/local_extern.h
Outdated
@@ -0,0 +1 @@ | |||
static inline int _foo() { extern int i; return i; } |
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 should test the case where a function uses an extern declaration that's actually defined within the header. And the test needs to verify that we actually emit the variable.
You also need to test extern functions.
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.
Added additional cases where a local extern declaration has no prior definitions of variables and functions, as well for local externs with prior definitions of variables and functions.
Added checks for the extern declaration name in the emitted IR.
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.
Actually, these cases as-is probably aren't quite sufficient to ensure correctness...
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.
These cases should be correct now.
lib/IRGen/GenClangDecl.cpp
Outdated
@@ -80,7 +80,7 @@ void IRGenModule::emitClangDecl(const clang::Decl *decl) { | |||
// If it's a member of a file-level decl, like a C++ static member variable, | |||
// we want to add the entire file-level declaration because Clang doesn't | |||
// expect to see members directly here. | |||
for (auto *DC = D->getDeclContext();; DC = DC->getParent()) { | |||
for (auto *DC = D->getLexicalDeclContext();; DC = DC->getParent()) { |
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'm not sure this is right; your logic makes us ignore local externs, which works in your test case only because we don't actually need to emit the variable/function. We need to look for a non-local-extern declaration.
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.
Can you expand a little more about that? I'm not sure I follow 100%. In practice, there are cases where the resolution of the local extern may not happen until link time.
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.
Sure, and in those cases we aren't responsible for emitting the declaration; it's the programmer's responsibility to ensure that the symbol is defined somewhere. But if there is a definition in the header, it might still be our obligation to trigger its emission, e.g.
int get_x() {
extern const int x;
return x;
}
const int x = 5;
And you can get a similar effect with functions with inline
.
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 see the problem: as it stands here, we're not emitting any references to symbols from local externs, even when we are able to resolve them immediately. I'll look in to an alternative way of handling this.
6541bc4
to
20f1493
Compare
emitClangDecl interacts with clang and LLVM to achieve C interop. On the LLVM side, CodeGenModule::EmitGlobal asserts if the decl eventually passed to it is not a "file scoped" via VarDecl::isFileVarDecl. LLVM currently asserts on local extern variables in C headers passed to Swift when the definition exists outside that header. To fix this, we need to ensure that we are only passing Decls that do not trip the assertion but not unduly limit local extern variables when the corresponding definition exists inside that header. We can do that fairly simply by checking for isFileVarDecl just before we hand-off to clang. When the definition for the local extern variable exists inside the header, isFileVarDecl is true, and if it exists elsewhere, it is false. This matches up with the assert expectation on the LLVM side exactly. This indirectly addresses swiftlang#28968, since that contains the only part of the Swift stdlib that uses a local extern variable, but any header that is used with Swift that contains a local extern variable will cause the compiler to assert when built with assertions enabled.
20f1493
to
38dd7d8
Compare
Okay, PTAL. Unit test covers the case of calling extern functions as well as variables and functions defined prior in the header. Code change is now to filter out |
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, LGTM.
@swift-ci Please test. |
Build failed |
Build failed |
Thank you! |
@shahmishal - this didn't trigger the windows CI? |
This seems to have caused a regression on Windows that @xedin is hitting (https://ci-external.swift.org/job/swift-PR-windows/6918/console) |
Explanation: LLVM currently asserts on local extern variables in C headers passed to Swift when the definition exists outside that header. Scope: This never worked. Risk: Low. Testing: Regression test added. Reviewed-by: John McCall Original PR: swiftlang#33306 This fix also required the cherry-pick of: swiftlang#31035 and swiftlang#31272 rdar://67951491
In emitClangDecl, we spend some time ensuring that we only pass file or top-level decls to LLVM. This is done by examining the decl context (via Decl::getDeclContext) and determining whether isFileContext is true. On the LLVM side, CodeGenModule::EmitGlobal asserts if the decl eventually passed to it is not a "file scoped" via VarDecl::isFileVarDecl.
Generally, these concepts match in all cases but one. Local extern variables have a decl context which has isFileContext being true, just like global extern variables, but isFileVarDecl does not return true -- it is a local variable, after all.
Indeed, isFileVarDecl is implemented by examining the context with Decl::getLexicalDeclContext instead of Decl::getDeclContext. Thus, we should probably be using getLexicalDeclContext instead of getDeclContext in emitClangDecl, so we properly match the expectations on the LLVM side.
This indirectly addresses #28968, since that contains the only part of the Swift stdlib that uses a local extern variable, but any header that is used with Swift that contains a local extern variable will cause the compiler to assert when built with assertions enabled.
//
Note to the prospective reviewer: I'm fairly confident this is correct, but it would be great for someone more familiar with this corner of the code to double-check my reasoning and whether this change is valid.