Skip to content

[clang][Sema] Declare builtins used in #pragma intrinsic #138205

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

Merged
merged 3 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,11 @@ class Sema final : public SemaBase {
/// Set of no-builtin functions listed by \#pragma function.
llvm::SmallSetVector<StringRef, 4> MSFunctionNoBuiltins;

/// Map of BuiltinIDs to source locations that have #pragma intrinsic calls
/// that refer to them.
llvm::DenseMap<unsigned, llvm::SmallSetVector<SourceLocation, 4>>
Copy link
Collaborator

Choose a reason for hiding this comment

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

DenseMap of SmallVector values is not an efficient data structure choice, because the DenseMap is always at least 25% empty, and each SmallVector is likely to contain exactly 1 element.

I think we need to serialize these pragmas through the AST. Is it possible to do this by creating an implicit declaration of the builtin at the point of the pragma? You would test for this by making your system header into a module or PCH file in another test case.

Copy link
Member Author

@sarnex sarnex May 16, 2025

Choose a reason for hiding this comment

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

Sorry, I intended to write a comment explaining this commit but I got distracted and you reviewed it too fast :)

I tried creating the declaration just by calling LazilyCreateBuiltin in the pragma handler, but that caused problems when there is a declaration in the header because then we have two real declarations so we get a ambiguous call error. The reason that doesn't happen today with no changes is because we only call LazilyCreateBuiltin when the lookup fails, as in when there is no header providing a declaration and the symbol is totally unknown.

Let me try your idea, thanks for the feedback.

Copy link
Member Author

@sarnex sarnex May 19, 2025

Choose a reason for hiding this comment

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

I tried compiling the system header into a PCH and dumping the AST with llvm-bcanalyzer and I didn't see any implicit declarations, not sure if I was checking for the right thing.

Either way, I tried another solution where we declare the builtin if it wasn't already declared. Fixes the ambiguous call errors I saw when trying to universally declare it and fixes the original issue. Please take a look when you have a sec, thanks.

PragmaIntrinsicBuiltinIDMap;

/// AddAlignmentAttributesForRecord - Adds any needed alignment attributes to
/// a the record decl, to handle '\#pragma pack' and '\#pragma options align'.
void AddAlignmentAttributesForRecord(RecordDecl *RD);
Expand Down Expand Up @@ -4345,6 +4350,11 @@ class Sema final : public SemaBase {
/// contain non-field names.
Scope *getNonFieldDeclScope(Scope *S);

// Determine if the given builtin usage at the given source location
// was previously specified in a #pragma intrinsic.
bool isBuiltinSpecifiedInPragmaIntrinsic(unsigned BuiltinID,
SourceLocation UsageLoc) const;

FunctionDecl *CreateBuiltin(IdentifierInfo *II, QualType Type, unsigned ID,
SourceLocation Loc);

Expand Down
13 changes: 10 additions & 3 deletions clang/lib/Parse/ParsePragma.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,13 @@ struct PragmaMSRuntimeChecksHandler : public EmptyPragmaHandler {
};

struct PragmaMSIntrinsicHandler : public PragmaHandler {
PragmaMSIntrinsicHandler() : PragmaHandler("intrinsic") {}
PragmaMSIntrinsicHandler(Sema &Actions)
: PragmaHandler("intrinsic"), Actions(Actions) {}
void HandlePragma(Preprocessor &PP, PragmaIntroducer Introducer,
Token &FirstToken) override;

private:
Sema &Actions;
};

// "\#pragma fenv_access (on)".
Expand Down Expand Up @@ -517,7 +521,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<PragmaMSIntrinsicHandler>(Actions);
PP.AddPragmaHandler(MSIntrinsic.get());
MSFenvAccess = std::make_unique<PragmaMSFenvAccessHandler>();
PP.AddPragmaHandler(MSFenvAccess.get());
Expand Down Expand Up @@ -3803,7 +3807,10 @@ void PragmaMSIntrinsicHandler::HandlePragma(Preprocessor &PP,
if (!II->getBuiltinID())
PP.Diag(Tok.getLocation(), diag::warn_pragma_intrinsic_builtin)
<< II << SuggestIntrinH;

/// Store the location at which the builtin was used in a #pragma intrinsic
/// so we don't emit a missing header warning later.
Actions.PragmaIntrinsicBuiltinIDMap[II->getBuiltinID()].insert(
Tok.getLocation());
PP.Lex(Tok);
if (Tok.isNot(tok::comma))
break;
Expand Down
25 changes: 21 additions & 4 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2299,6 +2299,21 @@ static StringRef getHeaderName(Builtin::Context &BuiltinInfo, unsigned ID,
llvm_unreachable("unhandled error kind");
}

bool Sema::isBuiltinSpecifiedInPragmaIntrinsic(unsigned BuiltinID,
SourceLocation UsageLoc) const {
assert(Context.BuiltinInfo(BuiltinID) && "Invalid builtin id");
assert(UsageLoc.isValid() && "Invalid source location");
auto It = PragmaIntrinsicBuiltinIDMap.find(BuiltinID);
if (It == PragmaIntrinsicBuiltinIDMap.end())
return false;
for (const SourceLocation &PragmaIntrinLoc : It->second) {
if (Context.getSourceManager().isBeforeInTranslationUnit(PragmaIntrinLoc,
UsageLoc))
return true;
}
return false;
}

FunctionDecl *Sema::CreateBuiltin(IdentifierInfo *II, QualType Type,
unsigned ID, SourceLocation Loc) {
DeclContext *Parent = Context.getTranslationUnitDecl();
Expand Down Expand Up @@ -2370,15 +2385,17 @@ NamedDecl *Sema::LazilyCreateBuiltin(IdentifierInfo *II, unsigned ID,

// Generally, we emit a warning that the declaration requires the
// appropriate header.
Diag(Loc, diag::warn_implicit_decl_requires_sysheader)
<< getHeaderName(Context.BuiltinInfo, ID, Error)
<< Context.BuiltinInfo.getName(ID);
if (!isBuiltinSpecifiedInPragmaIntrinsic(ID, Loc))
Diag(Loc, diag::warn_implicit_decl_requires_sysheader)
<< getHeaderName(Context.BuiltinInfo, ID, Error)
<< Context.BuiltinInfo.getName(ID);
return nullptr;
}

if (!ForRedeclaration &&
(Context.BuiltinInfo.isPredefinedLibFunction(ID) ||
Context.BuiltinInfo.isHeaderDependentFunction(ID))) {
Context.BuiltinInfo.isHeaderDependentFunction(ID)) &&
!isBuiltinSpecifiedInPragmaIntrinsic(ID, Loc)) {
Diag(Loc, LangOpts.C99 ? diag::ext_implicit_lib_function_decl_c99
: diag::ext_implicit_lib_function_decl)
<< Context.BuiltinInfo.getName(ID) << R;
Expand Down
9 changes: 9 additions & 0 deletions clang/test/Sema/Inputs/builtin-system-header.h
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions clang/test/Sema/builtin-pragma-intrinsic.c
Original file line number Diff line number Diff line change
@@ -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
Loading