Skip to content

Clang modules builtin resolution falls back to missing symbol on Windows UCRT due to lazy deserialization #209989

Description

@atetubou

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 <corecrt_math.h> declares _hypotf and defines hypotf as an inline wrapper function:
    __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 <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).
  5. 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)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions