[clang] [clang][CodeGen] Trigger AST lookup for libfunction builtins (PR #209997)

Takuto Ikuta via cfe-commits cfe-commits at lists.llvm.org
Thu Jul 16 02:28:37 PDT 2026


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

>From 177b1752aecc06dc06249db89a498a7a159a1cf3 Mon Sep 17 00:00:00 2001
From: Takuto Ikuta <tikuta at google.com>
Date: Thu, 16 Jul 2026 16:37:25 +0900
Subject: [PATCH] [clang][CodeGen] Trigger AST lookup for libfunction builtins

Unlike normal function calls where Sema performs name lookup during parsing
and triggers lazy deserialization of AST declarations from Clang Modules
(PCMs), built-in library function calls (e.g., __builtin_hypotf) bypass Sema
name lookup for the target C function name (hypotf). Consequently, Clang
Modules does not lazily deserialize module-defined declarations (such as
MSVC UCRT's inline hypotf wrapper in corecrt_math.h) from PCM files. CodeGen
then falls back to emitting direct external references to non-existent
symbols like hypotf, causing unresolved symbol linker errors.

This change triggers an explicit AST name lookup on TranslationUnitDecl for
the target library function name in CodeGenModule::getBuiltinLibFunction.
When a corresponding C-linkage FunctionDecl (isExternC) is deserialized from
modules, CodeGen uses its address (GetAddrOfFunction) to properly emit its
inline definition and dependent DLL import symbols (e.g. _hypotf).

Added regression test in clang/test/Modules/builtin-libfunction-lookup.cpp.
---
 clang/lib/CodeGen/CGBuiltin.cpp               | 20 +++++
 .../Modules/builtin-libfunction-lookup.cpp    | 79 +++++++++++++++++++
 2 files changed, 99 insertions(+)
 create mode 100644 clang/test/Modules/builtin-libfunction-lookup.cpp

diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 37846bbb0b5ec..6c4742b3853f2 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -247,6 +247,26 @@ llvm::Constant *CodeGenModule::getBuiltinLibFunction(const FunctionDecl *FD,
       Name = Context.BuiltinInfo.getName(BuiltinID).substr(10);
   }
 
+  // Look up the corresponding standard library C function in the AST. Unlike
+  // normal function calls where Sema performs name lookup and lazily
+  // deserializes declarations from Clang Modules (PCMs), built-in library
+  // function calls (e.g. __builtin_hypotf) bypass Sema name lookup for the C
+  // function name ("hypotf"). Explicitly looking up the name in
+  // TranslationUnitDecl triggers ASTReader to lazily deserialize any
+  // module-defined declaration (such as MSVC UCRT's inline hypotf wrapper). If
+  // a definition is found, use GetAddrOfFunction to properly emit its
+  // definition and dependencies.
+  DeclarationName DecName = &Context.Idents.get(Name);
+  DeclContext::lookup_result Decls =
+      Context.getTranslationUnitDecl()->lookup(DecName);
+  for (NamedDecl *ND : Decls) {
+    if (auto *TargetFD = dyn_cast<FunctionDecl>(ND)) {
+      if (TargetFD->isExternC() && TargetFD->hasBody()) {
+        return GetAddrOfFunction(TargetFD);
+      }
+    }
+  }
+
   llvm::FunctionType *Ty =
     cast<llvm::FunctionType>(getTypes().ConvertType(FD->getType()));
 
diff --git a/clang/test/Modules/builtin-libfunction-lookup.cpp b/clang/test/Modules/builtin-libfunction-lookup.cpp
new file mode 100644
index 0000000000000..b764d7274a8d7
--- /dev/null
+++ b/clang/test/Modules/builtin-libfunction-lookup.cpp
@@ -0,0 +1,79 @@
+// Test that calling built-in library functions like __builtin_hypotf under Clang Modules
+// triggers AST name lookup for the target C function (hypotf). This ensures that
+// module-defined inline wrappers (e.g. MSVC UCRT's hypotf wrapper calling _hypotf)
+// are lazily deserialized from PCMs rather than emitting external non-existent function calls.
+//
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: cd %t
+//
+// RUN: %clang_cc1 -xc++ -emit-module -fmodules -fmodule-name=ucrt -fmodule-map-file=ucrt.modulemap -triple x86_64-pc-windows-msvc -fms-extensions -I. ucrt.modulemap -o ucrt.pcm
+// RUN: %clang_cc1 -xc++ -emit-module -fmodules -fmodule-name=std -fmodule-map-file=std.modulemap -fmodule-file=ucrt=ucrt.pcm -triple x86_64-pc-windows-msvc -fms-extensions -I. std.modulemap -o std.pcm
+// RUN: %clang_cc1 -xc++ -emit-llvm -fmodules -fmodule-map-file=std.modulemap -fmodule-map-file=ucrt.modulemap -fmodule-file=std=std.pcm -fmodule-file=ucrt=ucrt.pcm -triple x86_64-pc-windows-msvc -fms-extensions -I. main.cc -o - | FileCheck %s
+
+//--- corecrt_math.h
+#ifndef MOCK_CORECRT_MATH_H
+#define MOCK_CORECRT_MATH_H
+extern "C" {
+__declspec(dllimport) float __cdecl _hypotf(float x, float y);
+inline float __cdecl hypotf(float x, float y) {
+  return _hypotf(x, y);
+}
+}
+#endif
+
+//--- math.h
+#ifndef MOCK_MATH_H
+#define MOCK_MATH_H
+#include "corecrt_math.h"
+#endif
+
+//--- __math/hypot.h
+#ifndef MOCK_MATH_HYPOT_H
+#define MOCK_MATH_HYPOT_H
+inline float hypot(float x, float y) {
+  return __builtin_hypotf(x, y);
+}
+#endif
+
+//--- cmath
+#ifndef MOCK_CMATH
+#define MOCK_CMATH
+#include "math.h"
+#include "__math/hypot.h"
+#endif
+
+//--- std.modulemap
+module std {
+  module cmath {
+    header "cmath"
+    export *
+  }
+  module math_hypot {
+    header "__math/hypot.h"
+    export *
+  }
+}
+
+//--- ucrt.modulemap
+module ucrt {
+  module math {
+    header "math.h"
+    export *
+  }
+  module corecrt_math {
+    header "corecrt_math.h"
+    export *
+  }
+}
+
+//--- main.cc
+#include "cmath"
+
+float test_call(float x, float y) {
+  return hypot(x, y);
+}
+
+// CHECK: define linkonce_odr dso_local float @hypotf(float noundef %{{.*}}, float noundef %{{.*}})
+// CHECK: call float @_hypotf(float noundef %{{.*}}, float noundef %{{.*}})
+// CHECK: declare dllimport float @_hypotf(float noundef, float noundef)



More information about the cfe-commits mailing list