Description
When compiling C++ code for Windows MSVC targets under Clang Modules (-fmodules), calls to standard library math builtins like __builtin_hypotf fail to link because the compiler generates references to the non-existent symbol hypotf instead of the UCRT-imported symbol _hypotf.
Why it fails:
- Microsoft's Windows C Runtime (UCRT) does not export
hypotf or _hypotl directly. UCRT only exports _hypotf (as __imp__hypotf in the dynamic import library).
- To conform to standard C/C++, UCRT's
<corecrt_math.h> declares _hypotf and defines hypotf as an inline wrapper function:
__inline float hypotf(float _X, float _Y) { return _hypotf(_X, _Y); }
- During code generation, Clang's CodeGen engine resolves
__builtin_hypotf by searching the AST lookup table for a standard library declaration named hypotf (with C linkage).
- Textual (non-modular) builds: The header
<math.h> is textually parsed, eagerly loading the hypotf inline wrapper declaration. CodeGen binds to it, inlines it, and successfully generates a call to _hypotf (__imp__hypotf).
- Modular builds: The header
<math.h> is parsed as a module, storing its declarations inside a precompiled module file (.pcm). Under Clang Modules, declarations in PCM files are lazily deserialized only when looked up by name. Because the builtin call references __builtin_hypotf, hypotf is never looked up and is not deserialized. CodeGen fails to find the declaration and falls back to emitting a direct external reference to hypotf (which fails link).
Minimal Reproduction Steps
Create a directory containing the following files:
1. corecrt_math.h (UCRT mock)
// corecrt_math.h
#ifndef MOCK_CORECRT_MATH_H
#define MOCK_CORECRT_MATH_H
extern "C" {
#ifdef _DLL
__declspec(dllimport) float __cdecl _hypotf(float x, float y);
#else
float __cdecl _hypotf(float x, float y);
#endif
inline float __cdecl hypotf(float x, float y) {
return _hypotf(x, y);
}
}
#endif
2. math.h (UCRT mock)
// math.h
#ifndef MOCK_MATH_H
#define MOCK_MATH_H
#include "corecrt_math.h"
#endif
3. __math/hypot.h (libc++ mock)
// __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
4. cmath (libc++ mock)
// cmath
#ifndef MOCK_CMATH
#define MOCK_CMATH
#include "math.h"
#include "__math/hypot.h"
#endif
5. main.cc (User source)
#include "cmath"
float test_call(float x, float y) {
return hypot(x, y);
}
6. std.modulemap
module std {
module cmath {
header "cmath"
export *
}
module math_hypot {
header "__math/hypot.h"
export *
}
}
7. ucrt.modulemap
module ucrt {
module math {
header "math.h"
export *
}
module corecrt_math {
header "corecrt_math.h"
export *
}
}
Compilation & IR Comparison
Case A: Textual Compilation (No Modules)
Compile textually to emit LLVM IR:
clang-cl.exe /c main.cc -Xclang -emit-llvm -D_DLL -target x86_64-pc-windows-msvc19.34.0 -I. /Fotextual.ll
Generated IR Output (working):
; The compiler resolves and inlines UCRT's wrapper, generating correct dllimport:
declare dllimport float @_hypotf(float noundef, float noundef)
Case B: Modular Compilation
First, compile the module PCM files directly:
# 1. Compile UCRT module
clang-cl.exe -c -x c++-module ucrt.modulemap -Xclang -emit-module -fmodules -fmodule-name=ucrt -fmodule-map-file=ucrt.modulemap -D_DLL -target x86_64-pc-windows-msvc19.34.0 -I. -o ucrt.pcm
# 2. Compile libc++ std module
clang-cl.exe -c -x c++-module std.modulemap -Xclang -emit-module -fmodules -fmodule-name=std -fmodule-map-file=std.modulemap -fmodule-map-file=ucrt.modulemap -fmodule-file=ucrt=ucrt.pcm -D_DLL -target x86_64-pc-windows-msvc19.34.0 -I. -o std.pcm
Next, compile main.cc using the PCMs:
clang-cl.exe -c main.cc -emit-llvm -S -fmodules -fmodule-map-file=std.modulemap -fmodule-map-file=ucrt.modulemap -fmodule-file=std=std.pcm -fmodule-file=ucrt=ucrt.pcm -D_DLL -target x86_64-pc-windows-msvc19.34.0 -I. -o repro_modular.ll
Generated IR Output (broken):
; The compiler fails to deserialize the UCRT declaration, falling back to:
declare dso_local float @hypotf(float noundef, float noundef)
This is derived from Chromium build issue in https://ci.chromium.org/ui/p/chromium/builders/ci/Win%20x64%20Builder%20%28dbg%29/190643/overview (tracked internally as http://b/525295309)
Description
When compiling C++ code for Windows MSVC targets under Clang Modules (
-fmodules), calls to standard library math builtins like__builtin_hypotffail to link because the compiler generates references to the non-existent symbolhypotfinstead of the UCRT-imported symbol_hypotf.Why it fails:
hypotfor_hypotldirectly. UCRT only exports_hypotf(as__imp__hypotfin the dynamic import library).<corecrt_math.h>declares_hypotfand defineshypotfas an inline wrapper function:__builtin_hypotfby searching the AST lookup table for a standard library declaration namedhypotf(with C linkage).<math.h>is textually parsed, eagerly loading thehypotfinline wrapper declaration. CodeGen binds to it, inlines it, and successfully generates a call to_hypotf(__imp__hypotf).<math.h>is parsed as a module, storing its declarations inside a precompiled module file (.pcm). Under Clang Modules, declarations in PCM files are lazily deserialized only when looked up by name. Because the builtin call references__builtin_hypotf,hypotfis never looked up and is not deserialized. CodeGen fails to find the declaration and falls back to emitting a direct external reference tohypotf(which fails link).Minimal Reproduction Steps
Create a directory containing the following files:
1.
corecrt_math.h(UCRT mock)2.
math.h(UCRT mock)3.
__math/hypot.h(libc++ mock)4.
cmath(libc++ mock)5.
main.cc(User source)6.
std.modulemap7.
ucrt.modulemapCompilation & IR Comparison
Case A: Textual Compilation (No Modules)
Compile textually to emit LLVM IR:
Generated IR Output (working):
Case B: Modular Compilation
First, compile the module PCM files directly:
Next, compile
main.ccusing the PCMs:Generated IR Output (broken):
This is derived from Chromium build issue in https://ci.chromium.org/ui/p/chromium/builders/ci/Win%20x64%20Builder%20%28dbg%29/190643/overview (tracked internally as http://b/525295309)