Clang modules builtin resolution falls back to missing symbol on Windows UCRT due to lazy deserialization
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## 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:
1. 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).
2. To conform to standard C/C++, UCRT's `` declares `_hypotf` and defines `hypotf` as an inline wrapper function:
```cpp
__inline float hypotf(float _X, float _Y) { return _hypotf(_X, _Y); }
```
3. 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).
4. **Textual (non-modular) builds**: The header `` 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`).
5. **Modular builds**: The header `` 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)
```cpp
// 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)
```cpp
// math.h
#ifndef MOCK_MATH_H
#define MOCK_MATH_H
#include "corecrt_math.h"
#endif
```
### 3. `__math/hypot.h` (libc++ mock)
```cpp
// __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)
```cpp
// cmath
#ifndef MOCK_CMATH
#define MOCK_CMATH
#include "math.h"
#include "__math/hypot.h"
#endif
```
### 5. `main.cc` (User source)
```cpp
#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:
```bash
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)**:
```ll
; 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:
```bash
# 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:
```bash
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)**:
```ll
; 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)
Contributor guide
Research direction
Start with the listed minimal reproducer files and compare the textual and modular LLVM IR outputs. Trace CodeGen’s builtin resolution through the AST lookup table and lazy PCM deserialization for hypotf. Done means the modular build resolves the UCRT wrapper and emits _hypotf rather than a direct hypotf reference.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100