https://github.com/atetubou updated 
https://github.com/llvm/llvm-project/pull/204792

>From b9a8d6ae820d957c7f7a984256ff25b5ff75b00b Mon Sep 17 00:00:00 2001
From: Takuto Ikuta <[email protected]>
Date: Fri, 19 Jun 2026 16:40:36 +0900
Subject: [PATCH] [Clang][CodeGen] Lookup standard library declarations for
 builtins to inherit dllimport

When compiling C++ code that calls standard library math functions (e.g.
`std::hypot(float, float)`), standard library headers (such as libc++
`__math/hypot.h`) typically implement these via compiler builtins:
  inline float hypot(float __x, float __y) _NOEXCEPT {
    return __builtin_hypotf(__x, __y);
  }

In Windows Universal CRT (`corecrt_math.h`), the real standard C library
function is declared with DLL import attributes and inline wrappers:
  _Check_return_ _ACRTIMP float __cdecl _hypotf(_In_ float _X, _In_ float _Y);
  _Check_return_ __inline float __CRTDECL hypotf(_In_ float _X, _In_ float _Y) {
    return _hypotf(_X, _Y);
  }
(where `_ACRTIMP` expands to `__declspec(dllimport)` under /MD).

During CodeGen, `CodeGenModule::getBuiltinLibFunction` derives the library
symbol name (`"hypotf"`) and emits an IR declaration.

Previously, `getBuiltinLibFunction` created this IR declaration directly
from the builtin's `FunctionDecl`. Consequently, attributes declared on the
real standard library function (specifically `dllimport`) or inline wrapper
semantics were not attached to the IR symbol.

On Windows MSVC targets (/MD with Clang Modules), this caused a subtle
symbol-level non-determinism (generating raw `hypotf` instead of `__imp_hypotf`
or `_hypotf`) between Linux cross-compilation and Windows native compilation.
This is believed to be triggered by the interplay of two mechanisms:

1. Clang Modules Lazy Deserialization:
   In non-module builds, textual `#include <math.h>` eagerly parses the
   library declaration into the top-level AST lookup table. In Clang
   Modules (`-fmodules`), declarations in PCM cache files are lazily
   deserialized by `ASTReader` only when looked up by name. Because the
   builtin call referenced `"__builtin_hypotf"`, `"hypotf"` was not eagerly
   deserialized.

2. Filesystem Enumeration Ordering (ext4 vs NTFS):
   When scanning headers to build PCM module caches, directory enumeration
   (`readdir`) order differs between Linux (ext4 hash order) and Windows
   (NTFS alphabetical order). This difference is understood to affect PCM
   submodule layout and lazy AST deserialization chains. On Linux, static
   initialization chains in imported submodules appear to deserialize and emit
   `@hypotf` prior to the builtin call; CodeGen subsequently reused the
   existing symbol (with `dllimport`). On Windows, the ordering deferred this
   deserialization, leaving IR empty when `getBuiltinLibFunction` was called.

This patch guarantees deterministic behavior across platforms by explicitly
looking up the real library function declaration in the AST translation unit
during `getBuiltinLibFunction` and using its `GlobalDecl` so attributes and
linkage specifications are always correctly inherited.

When matching lookup candidates, we use
`hasSameFunctionTypeIgnoringExceptionSpec` rather than exact type equality.
Compiler builtins are implicitly declared `noexcept` (e.g.
`float (float, float) noexcept`), whereas standard C library headers
often declare the function without `noexcept`. In C++17 and later, exception
specifications are part of `FunctionType`. Ignoring exception specifications
ensures we correctly match the system header declaration without dropping
proper compiler builtin `NoThrow` semantics.

Adds a regression test verifying that calling `__builtin_hypotf` when
`hypotf` is declared with `dllimport` in a module correctly produces
`declare dllimport float @hypotf`.
---
 clang/lib/CodeGen/CGBuiltin.cpp               | 18 ++++++++
 .../test/Modules/builtin-hypotf-dllimport.cpp | 42 +++++++++++++++++++
 2 files changed, 60 insertions(+)
 create mode 100644 clang/test/Modules/builtin-hypotf-dllimport.cpp

diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 509ab4245d99a..031381e592130 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -247,6 +247,24 @@ llvm::Constant *CodeGenModule::getBuiltinLibFunction(const 
FunctionDecl *FD,
       Name = Context.BuiltinInfo.getName(BuiltinID).substr(10);
   }
 
+  // If the library function has already been declared in the AST (e.g. from an
+  // imported header or module), look it up and use its GlobalDecl so that
+  // attributes (such as dllimport) are correctly attached. Builtins are often
+  // declared noexcept while standard C library headers may lack noexcept, so
+  // we ignore exception specifications when comparing function types.
+  if (const DeclContext *TU = Context.getTranslationUnitDecl()) {
+    IdentifierInfo &II = Context.Idents.get(Name);
+    for (const auto *Result : TU->lookup(&II)) {
+      if (const auto *LibFD = dyn_cast<FunctionDecl>(Result)) {
+        if (Context.hasSameFunctionTypeIgnoringExceptionSpec(LibFD->getType(),
+                                                             FD->getType())) {
+          D = GlobalDecl(LibFD);
+          break;
+        }
+      }
+    }
+  }
+
   llvm::FunctionType *Ty =
     cast<llvm::FunctionType>(getTypes().ConvertType(FD->getType()));
 
diff --git a/clang/test/Modules/builtin-hypotf-dllimport.cpp 
b/clang/test/Modules/builtin-hypotf-dllimport.cpp
new file mode 100644
index 0000000000000..cc97c8b1d989e
--- /dev/null
+++ b/clang/test/Modules/builtin-hypotf-dllimport.cpp
@@ -0,0 +1,42 @@
+// Test that calling a builtin library function (e.g. __builtin_hypotf) 
correctly
+// looks up the real standard library declaration from an imported module or 
header
+// and attaches attributes such as dllimport, even if the builtin is implicitly
+// declared noexcept while the C standard library declaration lacks noexcept.
+// Also test that calling the builtin without prior declaration in the AST
+// continues to work, falling back to an external symbol without dllimport.
+
+// RUN: rm -rf %t && mkdir %t
+// RUN: split-file %s %t
+
+// 1. Clang Modules enabled: calling __builtin_hypotf attaches dllimport from 
imported math.h
+// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fdeclspec -fmodules 
-fimplicit-module-maps -fmodules-cache-path=%t/cache -I%t -emit-llvm 
%t/main.cpp -o - | FileCheck %s --check-prefix=CHECK-DLLIMPORT
+
+// 2. Non-modules (textual include): calling __builtin_hypotf attaches 
dllimport from math.h
+// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fdeclspec -I%t -emit-llvm 
%t/main.cpp -o - | FileCheck %s --check-prefix=CHECK-DLLIMPORT
+
+// 3. No math.h included: calling __builtin_hypotf directly works and emits 
external symbol without dllimport
+// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fdeclspec -emit-llvm 
%t/direct_builtin.cpp -o - | FileCheck %s --check-prefix=CHECK-NO-DLLIMPORT
+
+// CHECK-DLLIMPORT: declare dllimport float @hypotf(float noundef, float 
noundef)
+
+// CHECK-NO-DLLIMPORT: declare dso_local float @hypotf(float noundef, float 
noundef)
+
+//--- math.h
+extern "C" __declspec(dllimport) float hypotf(float x, float y);
+
+//--- module.modulemap
+module Math {
+  header "math.h"
+}
+
+//--- main.cpp
+#include "math.h"
+
+extern "C" float test_func(float x, float y) {
+  return __builtin_hypotf(x, y);
+}
+
+//--- direct_builtin.cpp
+extern "C" float test_func(float x, float y) {
+  return __builtin_hypotf(x, y);
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to