[clang][Sema] Fix and reapply 'Declare builtins used in #pragma intrinsic #138205' (#142019)

I had to revert https://github.com/llvm/llvm-project/pull/138205 in
https://github.com/llvm/llvm-project/pull/141994 because it broke the
Chrome build.

The problem came down to the following:

```c++
unsigned __int64 _umul128(unsigned __int64, unsigned __int64,
                          unsigned __int64 *);

namespace {}
#pragma intrinsic(_umul128)

 void foo() {
   unsigned __int64 carry;
   unsigned __int64 low = _umul128(0, 0, &carry);
 }
```

When processing the `#pragma intrinsic` line, we do a name lookup to see
if the builtin was previously declared. In this case the lookup fails
because the current namespace of the parser and sema is the above
namespace scope. The processing of the pragma happens as part of the
namespace close parsing. This is usually fine because most pragmas don't
care about scopes. However, that's not true for this and other MS
pragmas.

To fix this, we change the `#pragma intrinsic` processing to be the same
as other MS pragmas such as "optimize". Those are processed like a
declaration, and because of that we have the correct current scope, so
the lookup succeeds.

I added a test case that locks down the Chrome fix, as well as manually
tested the Chrome build and confirmed it passed.
This commit is contained in:
Nick Sarnie 2025-06-03 10:25:20 -04:00 committed by GitHub
parent e29eb6637d
commit d9df710454
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 123 additions and 58 deletions

View File

@ -7074,6 +7074,10 @@ private:
bool HandlePragmaMSOptimize(StringRef PragmaName,
SourceLocation PragmaLocation);
// #pragma intrinsic("foo")
bool HandlePragmaMSIntrinsic(StringRef PragmaName,
SourceLocation PragmaLocation);
/// Handle the annotation token produced for
/// #pragma align...
void HandlePragmaAlign();

View File

@ -300,12 +300,6 @@ struct PragmaMSRuntimeChecksHandler : public EmptyPragmaHandler {
PragmaMSRuntimeChecksHandler() : EmptyPragmaHandler("runtime_checks") {}
};
struct PragmaMSIntrinsicHandler : public PragmaHandler {
PragmaMSIntrinsicHandler() : PragmaHandler("intrinsic") {}
void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
Token &FirstToken) override;
};
// "\#pragma fenv_access (on)".
struct PragmaMSFenvAccessHandler : public PragmaHandler {
PragmaMSFenvAccessHandler() : PragmaHandler("fenv_access") {}
@ -517,7 +511,7 @@ void Parser::initializePragmaHandlers() {
PP.AddPragmaHandler(MSOptimize.get());
MSRuntimeChecks = std::make_unique<PragmaMSRuntimeChecksHandler>();
PP.AddPragmaHandler(MSRuntimeChecks.get());
MSIntrinsic = std::make_unique<PragmaMSIntrinsicHandler>();
MSIntrinsic = std::make_unique<PragmaMSPragma>("intrinsic");
PP.AddPragmaHandler(MSIntrinsic.get());
MSFenvAccess = std::make_unique<PragmaMSFenvAccessHandler>();
PP.AddPragmaHandler(MSFenvAccess.get());
@ -1046,7 +1040,8 @@ void Parser::HandlePragmaMSPragma() {
.Case("strict_gs_check", &Parser::HandlePragmaMSStrictGuardStackCheck)
.Case("function", &Parser::HandlePragmaMSFunction)
.Case("alloc_text", &Parser::HandlePragmaMSAllocText)
.Case("optimize", &Parser::HandlePragmaMSOptimize);
.Case("optimize", &Parser::HandlePragmaMSOptimize)
.Case("intrinsic", &Parser::HandlePragmaMSIntrinsic);
if (!(this->*Handler)(PragmaName, PragmaLocation)) {
// Pragma handling failed, and has been diagnosed. Slurp up the tokens
@ -3762,56 +3757,6 @@ void PragmaUnrollHintHandler::HandlePragma(Preprocessor &PP,
/*DisableMacroExpansion=*/false, /*IsReinject=*/false);
}
/// Handle the Microsoft \#pragma intrinsic extension.
///
/// The syntax is:
/// \code
/// #pragma intrinsic(memset)
/// #pragma intrinsic(strlen, memcpy)
/// \endcode
///
/// Pragma intrisic tells the compiler to use a builtin version of the
/// function. Clang does it anyway, so the pragma doesn't really do anything.
/// Anyway, we emit a warning if the function specified in \#pragma intrinsic
/// isn't an intrinsic in clang and suggest to include intrin.h.
void PragmaMSIntrinsicHandler::HandlePragma(Preprocessor &PP,
PragmaIntroducer Introducer,
Token &Tok) {
PP.Lex(Tok);
if (Tok.isNot(tok::l_paren)) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_lparen)
<< "intrinsic";
return;
}
PP.Lex(Tok);
bool SuggestIntrinH = !PP.isMacroDefined("__INTRIN_H");
while (Tok.is(tok::identifier)) {
IdentifierInfo *II = Tok.getIdentifierInfo();
if (!II->getBuiltinID())
PP.Diag(Tok.getLocation(), diag::warn_pragma_intrinsic_builtin)
<< II << SuggestIntrinH;
PP.Lex(Tok);
if (Tok.isNot(tok::comma))
break;
PP.Lex(Tok);
}
if (Tok.isNot(tok::r_paren)) {
PP.Diag(Tok.getLocation(), diag::warn_pragma_expected_rparen)
<< "intrinsic";
return;
}
PP.Lex(Tok);
if (Tok.isNot(tok::eod))
PP.Diag(Tok.getLocation(), diag::warn_pragma_extra_tokens_at_eol)
<< "intrinsic";
}
bool Parser::HandlePragmaMSFunction(StringRef PragmaName,
SourceLocation PragmaLocation) {
Token FirstTok = Tok;
@ -3907,6 +3852,56 @@ bool Parser::HandlePragmaMSOptimize(StringRef PragmaName,
return true;
}
/// Handle the Microsoft \#pragma intrinsic extension.
///
/// The syntax is:
/// \code
/// #pragma intrinsic(memset)
/// #pragma intrinsic(strlen, memcpy)
/// \endcode
///
/// Pragma intrisic tells the compiler to use a builtin version of the
/// function. Clang does it anyway, so the pragma doesn't really do anything.
/// Anyway, we emit a warning if the function specified in \#pragma intrinsic
/// isn't an intrinsic in clang and suggest to include intrin.h, as well as
/// declare the builtin if it has not been declared.
bool Parser::HandlePragmaMSIntrinsic(StringRef PragmaName,
SourceLocation PragmaLocation) {
if (ExpectAndConsume(tok::l_paren, diag::warn_pragma_expected_lparen,
PragmaName))
return false;
bool SuggestIntrinH = !PP.isMacroDefined("__INTRIN_H");
while (Tok.is(tok::identifier)) {
IdentifierInfo *II = Tok.getIdentifierInfo();
if (!II->getBuiltinID())
PP.Diag(Tok.getLocation(), diag::warn_pragma_intrinsic_builtin)
<< II << SuggestIntrinH;
// If the builtin hasn't already been declared, declare it now.
DeclarationNameInfo NameInfo(II, Tok.getLocation());
LookupResult Previous(Actions, NameInfo, Sema::LookupOrdinaryName,
RedeclarationKind::NotForRedeclaration);
Actions.LookupName(Previous, Actions.getCurScope(),
/*CreateBuiltins*/ false);
if (Previous.empty())
Actions.LazilyCreateBuiltin(II, II->getBuiltinID(), Actions.getCurScope(),
/*ForRedeclaration*/ true, Tok.getLocation());
PP.Lex(Tok);
if (Tok.isNot(tok::comma))
break;
PP.Lex(Tok);
}
if (ExpectAndConsume(tok::r_paren, diag::warn_pragma_expected_rparen,
PragmaName))
return false;
if (ExpectAndConsume(tok::eof, diag::warn_pragma_extra_tokens_at_eol,
PragmaName))
return false;
return true;
}
void PragmaForceCUDAHostDeviceHandler::HandlePragma(
Preprocessor &PP, PragmaIntroducer Introducer, Token &Tok) {
Token FirstTok = Tok;

View File

@ -0,0 +1,9 @@
#ifdef USE_PRAGMA_BEFORE
#pragma intrinsic(_InterlockedOr64)
#endif
#define MACRO(x,y) _InterlockedOr64(x,y);
#ifdef USE_PRAGMA_AFTER
#pragma intrinsic(_InterlockedOr64)
#endif

View File

@ -0,0 +1,32 @@
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility -fsyntax-only -verify -DOUTSIDE %s
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility -fsyntax-only -verify -DINSIDE %s
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility -fsyntax-only -verify -DNESTED %s
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility -fsyntax-only -verify -DOUTSIDE -DEXTERN %s
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility -fsyntax-only -verify -DINSIDE -DEXTERN %s
// RUN: %clang_cc1 -triple x86_64-windows-msvc -fms-compatibility -fsyntax-only -verify -DNESTED -DEXTERN %s
// expected-no-diagnostics
#ifdef EXTERN
extern "C"
#endif
unsigned __int64 _umul128(unsigned __int64, unsigned __int64,
unsigned __int64 *);
namespace {
#ifdef INSIDE
#pragma intrinsic(_umul128)
#endif
#ifdef NESTED
namespace {
#pragma intrinsic(_umul128)
}
#endif
}
#ifdef OUTSIDE
#pragma intrinsic(_umul128)
#endif
void foo() {
unsigned __int64 carry;
unsigned __int64 low = _umul128(0, 0, &carry);
}

View File

@ -0,0 +1,25 @@
// RUN: %clang_cc1 -fms-extensions -fsyntax-only -verify -triple arm64-windows -isystem %S/Inputs %s -DUSE_PRAGMA_BEFORE
// RUN: %clang_cc1 -fms-extensions -fsyntax-only -verify -triple arm64-windows -isystem %S/Inputs %s -DUSE_PRAGMA_AFTER
// RUN: %clang_cc1 -fms-extensions -fsyntax-only -verify -triple arm64-windows -isystem %S/Inputs %s -DUSE_PRAGMA_AFTER_USE
// RUN: %clang_cc1 -fms-extensions -fsyntax-only -verify -triple arm64-windows -isystem %S/Inputs %s -DUSE_PRAGMA_SAME_FILE
// RUN: %clang_cc1 -fms-extensions -fsyntax-only -verify -triple arm64-windows -isystem %S/Inputs %s
#if defined(USE_PRAGMA_BEFORE) || defined(USE_PRAGMA_AFTER) || defined(USE_PRAGMA_SAME_FILE)
// expected-no-diagnostics
#else
// expected-error@+10 {{call to undeclared library function '_InterlockedOr64'}}
// expected-note@+9 {{include the header <intrin.h> or explicitly provide a declaration for '_InterlockedOr64'}}
#endif
#include <builtin-system-header.h>
#ifdef USE_PRAGMA_SAME_FILE
#pragma intrinsic(_InterlockedOr64)
#endif
void foo() {
MACRO(0,0);
}
#ifdef USE_PRAGMA_AFTER_USE
#pragma intrinsic(_InterlockedOr64)
#endif