Summary
clang 22 gives a builtin function (__builtin_expect) its implicit declaration at the first name lookup and attaches that declaration to whichever module unit is being compiled. When the first lookup happens inside the purview of a module partition, the declaration is owned by the named module. A sibling partition (or the primary interface) that imports that partition and a module whose global module fragment (GMF) also used the builtin then sees two unmerged declarations and clang reports:
error: call to '__builtin_expect' is ambiguous
note: candidate function (the GMF use, e.g. a third-party header)
note: candidate function (the partition's purview use)
Per [basic.lookup] / [module.unit] a builtin is not a declaration of the program at all, and clang's own module model attaches implicit declarations to the global module — so this is a clang defect, not a usage error. The reproducer below has no platform header in it; it fails on Linux x86_64 with the xim:llvm@22.1.8 payload mcpp installs.
Nothing in mcpp is wrong here and nothing in mcpp can fix it. Filing so the engine's known-issues list names it, and so the next project that hits "ambiguous __builtin_expect" on macOS finds the mechanism and the two workarounds instead of re-deriving them.
Where it bit
xrgui (C++23 modules, ~150 module units, heavy use of partitions), macOS arm64 leg, clang 22.1.8 ([toolchain] macos = "llvm@22.1.8"), Xcode 16.4 SDK.
Apple's <assert.h> expands assert(e) to (__builtin_expect(!(e), 0) ? __assert_rtn(...) : (void)0). That is the only thing macOS-specific about it: glibc's and MSVC's assert() name no builtin, so the same tree compiles on Linux and Windows. The three "first uses" clang pointed at:
| candidate |
first lookup happened in |
owned by |
src/gui/core/infrastructure/elem_ptr.ixx:54 (assert(...)) |
purview of partition mo_yanxi.gui.infrastructure:elem_ptr |
the named module |
magic_enum/magic_enum.hpp:237 (MAGIC_ENUM_ASSERT) |
GMF of the magic_enum module (#include before export module) |
global module |
plf_hive.h:1842 (assert(...)) |
GMF of a module wrapping the header |
global module |
The failing unit was elem_async_task.ixx — partition :async_task of the same module, which imports :elem_ptr and magic_enum and asserts itself.
Minimal reproducer (4 files, no SDK header)
// h.h — stands in for any third-party header that asserts in an inline function
inline bool h_use(bool b) { return __builtin_expect(b, 0); }
// a.cppm — first lookup in the GMF (like magic_enum.cppm / a plf_hive wrapper)
module;
#include "h.h"
export module a;
export bool fa(bool b) { return h_use(b); }
// p1.cppm — first lookup in a partition's purview (like elem_ptr.ixx)
export module m:p1;
export bool p1(bool b) { return __builtin_expect(b, 0); }
// p2.cppm — sibling partition importing both, using the builtin itself (like elem_async_task.ixx)
export module m:p2;
export import :p1;
import a;
export bool p2(bool b) { return __builtin_expect(p1(b) && fa(b), 0); }
clang++ -std=c++23 --precompile a.cppm -o a.pcm
clang++ -std=c++23 --precompile p1.cppm -o m-p1.pcm
clang++ -std=c++23 -fmodule-file=m:p1=m-p1.pcm -fmodule-file=a=a.pcm --precompile p2.cppm -o m-p2.pcm
p2.cppm:4:33: error: call to '__builtin_expect' is ambiguous
4 | export bool p2(bool b) { return __builtin_expect(p1(b) && fa(b), 0); }
| ^~~~~~~~~~~~~~~~
h.h:1:36: note: candidate function
1 | inline bool h_use(bool b) { return __builtin_expect(b, 0); }
| ^
p1.cppm:2:33: note: candidate function
2 | export bool p1(bool b) { return __builtin_expect(b, 0); }
| ^
1 error generated.
clang version 22.1.8 (ca7933e47d3a3451d81e72ac174dcb5aa28b59d1), Linux x86_64, the xim:llvm@22.1.8 payload.
What does and does not trigger it (all measured, same compiler)
| importer |
imports |
result |
partition m:p2 |
:p1 (purview-first) + a (GMF-first) |
ambiguous |
primary interface m |
:p1 + a |
ambiguous |
partition m:p3 |
:p1 only |
ok |
ordinary module m2 |
ordinary module m1 (purview-first) + a |
ok — a declaration owned by another named module is not visible to the importer, so only one candidate remains |
partition m:p2 |
:p1 rewritten so its first lookup is in its own GMF (#include "h.h" before export module) + a |
ok |
-fno-builtin |
— |
not a lever: a module-file configuration mismatch, and it would have to be global |
So the two ingredients are: (1) a partition whose purview performs the first lookup of the builtin, and (2) an importer within the same module that also reaches a global-module-owned declaration of the same builtin. Partitions see each other's module-owned declarations; ordinary modules do not, which is why plain module graphs never show it.
Workarounds
- Shadow the one header that names the builtin (what xrgui does):
mcpp/darwin/assert.h does #include_next <assert.h> and redefines assert without __builtin_expect (same __assert_rtn, same message, same NDEBUG behaviour; only the branch hint is lost). Put on the package's own include path for macOS only — [target.'cfg(os = "macos")'.build] include_dirs + private_include_dirs, so consumers are untouched. One 20-line file, deleted when clang is fixed.
- Make the first lookup happen in the GMF of every partition (the last table row): a GMF header with an inline function that calls
__builtin_expect. Works, but it is one hack per module unit instead of one per tree.
Not workarounds: replacing assert with a project macro (every partition and every dependency module), or giving up partitions.
Upstream
No matching llvm-project issue found; the closest is the same class of defect for __builtin_va_list under C++20 + PCH (llvm/llvm-project#61931, "reference to '__builtin_va_list' is ambiguous", two identical candidates). An LLVM issue with the reproducer above is the actual fix path; this mcpp issue is the pointer for the ecosystem.
Ask for mcpp
Nothing in the engine. A line in the C++20 modules known-issues documentation under clang 22: "a builtin first looked up in a partition's purview is owned by the module; importing that partition beside a module whose GMF used the same builtin is ambiguous — first-use it in the GMF, or shadow the header that names it (macOS assert)."
Summary
clang 22 gives a builtin function (
__builtin_expect) its implicit declaration at the first name lookup and attaches that declaration to whichever module unit is being compiled. When the first lookup happens inside the purview of a module partition, the declaration is owned by the named module. A sibling partition (or the primary interface) that imports that partition and a module whose global module fragment (GMF) also used the builtin then sees two unmerged declarations and clang reports:Per [basic.lookup] / [module.unit] a builtin is not a declaration of the program at all, and clang's own module model attaches implicit declarations to the global module — so this is a clang defect, not a usage error. The reproducer below has no platform header in it; it fails on Linux x86_64 with the
xim:llvm@22.1.8payload mcpp installs.Nothing in mcpp is wrong here and nothing in mcpp can fix it. Filing so the engine's known-issues list names it, and so the next project that hits "ambiguous
__builtin_expect" on macOS finds the mechanism and the two workarounds instead of re-deriving them.Where it bit
xrgui (C++23 modules, ~150 module units, heavy use of partitions), macOS arm64 leg, clang 22.1.8 (
[toolchain] macos = "llvm@22.1.8"), Xcode 16.4 SDK.Apple's
<assert.h>expandsassert(e)to(__builtin_expect(!(e), 0) ? __assert_rtn(...) : (void)0). That is the only thing macOS-specific about it: glibc's and MSVC'sassert()name no builtin, so the same tree compiles on Linux and Windows. The three "first uses" clang pointed at:src/gui/core/infrastructure/elem_ptr.ixx:54(assert(...))mo_yanxi.gui.infrastructure:elem_ptrmagic_enum/magic_enum.hpp:237(MAGIC_ENUM_ASSERT)magic_enummodule (#includebeforeexport module)plf_hive.h:1842(assert(...))The failing unit was
elem_async_task.ixx— partition:async_taskof the same module, which imports:elem_ptrandmagic_enumand asserts itself.Minimal reproducer (4 files, no SDK header)
clang version 22.1.8 (ca7933e47d3a3451d81e72ac174dcb5aa28b59d1), Linux x86_64, thexim:llvm@22.1.8payload.What does and does not trigger it (all measured, same compiler)
m:p2:p1(purview-first) +a(GMF-first)m:p1+am:p3:p1onlym2m1(purview-first) +am:p2:p1rewritten so its first lookup is in its own GMF (#include "h.h"beforeexport module) +a-fno-builtinSo the two ingredients are: (1) a partition whose purview performs the first lookup of the builtin, and (2) an importer within the same module that also reaches a global-module-owned declaration of the same builtin. Partitions see each other's module-owned declarations; ordinary modules do not, which is why plain module graphs never show it.
Workarounds
mcpp/darwin/assert.hdoes#include_next <assert.h>and redefinesassertwithout__builtin_expect(same__assert_rtn, same message, sameNDEBUGbehaviour; only the branch hint is lost). Put on the package's own include path for macOS only —[target.'cfg(os = "macos")'.build] include_dirs+private_include_dirs, so consumers are untouched. One 20-line file, deleted when clang is fixed.__builtin_expect. Works, but it is one hack per module unit instead of one per tree.Not workarounds: replacing
assertwith a project macro (every partition and every dependency module), or giving up partitions.Upstream
No matching llvm-project issue found; the closest is the same class of defect for
__builtin_va_listunder C++20 + PCH (llvm/llvm-project#61931, "reference to '__builtin_va_list' is ambiguous", two identical candidates). An LLVM issue with the reproducer above is the actual fix path; this mcpp issue is the pointer for the ecosystem.Ask for mcpp
Nothing in the engine. A line in the C++20 modules known-issues documentation under clang 22: "a builtin first looked up in a partition's purview is owned by the module; importing that partition beside a module whose GMF used the same builtin is
ambiguous— first-use it in the GMF, or shadow the header that names it (macOSassert)."