Skip to content

Commit 7f40723

Browse files
committed
fix(pkg): compat.gmp 在 MSVC ABI 上给 gmp.h 的 _MSC_VER 分支补守卫
首轮 windows CI:516 个 TU 全部编译通过,链接死在十个重复符号 —— __gmpz_get_ui / __gmpn_sub / … 各自在 gmp.h 的内联体和带 -D__GMP_FORCE_<fn> 的那个 TU 里各定义一次。 gmp.h 用一串厂商分支决定 inline 怎么写。GCC 分支给 `extern __inline__ __attribute__ ((__gnu_inline__))`,即 GNU89 语义:头里的函数体 只用于内联、永不作为独立符号发射,独立那份由 __GMP_FORCE_<fn> 的 TU 提供。而 `_MSC_VER` 那条是整串里唯一没有 `! defined (__GMP_EXTERN_INLINE)` 守卫的 (SunPro/SCO 都有),clang 打 MSVC ABI 时两个宏都定义,于是它把 GCC 的答案改写成 裸 `__inline` —— MS 语义下每个包含 gmp.h 的 TU 都发射一份外部定义。 上游没碰到,因为它没有 MSVC ABI 构建。补上邻居们已有的守卫即可,别处严格 no-op: 非 windows 不定义 _MSC_VER,真 MSVC 不定义 __GNUC__。生成器对补丁点做了"必须恰好 命中一次"的断言,上游改了这段会报错而不是静默漏打。 顺带修正一处注释:单个 gmpxx TU 的理由是"feature 表只能门控 sources、带不了 __GMP_WITHIN_GMPXX",不是 basename 撞名 —— 2026.8.3.4 起对象路径无条件嵌套, 本包自己就有三个 add.c 各自落在 obj/compat_gmp/gmp-6.3.0/{mpf,mpn/generic,mpz}/add.o。 本地(linux gcc@16.1.0 与 llvm@22.1.8)重跑 gmp / gmp-gmpxx 仍全绿 —— 该补丁在 非 windows 上不改变任何一个字节的行为。
1 parent a974951 commit 7f40723

3 files changed

Lines changed: 110 additions & 17 deletions

File tree

‎.agents/docs/2026-08-10-gmp-full-platform-no-build-system.md‎

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,12 @@ GMP 的 C++ 绑定(`mpz_class`/`mpq_class`/`mpf_class`,RAII + 运算符重载 +
131131
132132
**必须由消费端工具链编译**:这些符号的名字里带 `std::ostream`/`std::string`,即 C++ 标准库的 ABI。
133133
在钩子里用宿主 g++ 编一份 `libgmpxx.a`,在 libstdc++ 腿上能链、在每一条 libc++ 腿上都是 undefined
134-
reference。所以做成 feature:11 个 `cxx/*.cc` 合并成**一个**唯一命名的 TU
135-
(`mcpp_gmpxx.cc`)—— 一是 mcpp 按 basename 命名对象,`limits.cc → limits.o` 是个等着撞的名字
136-
(mcpp#233/#240);二是 feature 表只能门控 sources、带不了 `__GMP_WITHIN_GMPXX`。
134+
reference。所以做成 feature:11 个 `cxx/*.cc` 合并成**一个** TU(`mcpp_gmpxx.cc`)。
135+
136+
合并的理由是 **feature 表只能门控 sources、带不了 define** —— `__GMP_WITHIN_GMPXX` 必须写在文件里,
137+
所以无论如何都要一个 wrapper,一个比十一个简单。**不是**因为 basename 撞名:那是 2026.8.3.4 之前的
138+
情况,现版本对象路径无条件嵌套,本包自己就有三个 `add.c` 各自落在
139+
`obj/compat_gmp/gmp-6.3.0/{mpf,mpn/generic,mpz}/add.o`(实测)。
137140
138141
合并踩到一个真坑并已修:`gmp.h` 用"`<stdio.h>`/`<stdarg.h>` 是否已被看见"来决定要不要声明
139142
`FILE*`/`va_list` 入口(`_GMP_H_HAVE_FILE` / `_GMP_H_HAVE_VA_LIST`),`gmp-impl.h` 又拿同一个答案
@@ -142,7 +145,35 @@ reference。所以做成 feature:11 个 `cxx/*.cc` 合并成**一个**唯一命
142145
`aggregate gmp_asprintf_t has incomplete type`,而在 `<iostream>` 恰好拖进 `<cstdarg>` 的标准库上
143146
一切正常。解法是 wrapper TU 顶部先包含那两个头。
144147
145-
### 3.5 `-fPIC`
148+
### 3.5 `gmp.h` 的一处补丁:MSVC ABI 上的 `__GMP_EXTERN_INLINE`
149+
150+
除 configure 的 8 处 substitute 外,生成的 `gmp.h` 还打了**一个**补丁,由首轮 windows CI 抓出来。
151+
152+
`gmp.h` 用一串厂商分支决定 "inline" 怎么写。GCC 分支给的是
153+
`extern __inline__ __attribute__ ((__gnu_inline__))` —— GNU89 语义:**头里的函数体只用于内联,
154+
永远不作为独立符号发射**,而独立那份由带 `-D__GMP_FORCE_<fn>` 的那个 TU 提供
155+
(`mpz/get_ui.c`、`mpn/generic/sub.c` …)。整个库靠这个分工成立。
156+
157+
问题在于 `_MSC_VER` 那条分支是整串里**唯一没有** `! defined (__GMP_EXTERN_INLINE)` 守卫的
158+
(SunPro / SCO 两条都有)。而 clang 打 MSVC ABI 时**同时**定义 `__GNUC__` 与 `_MSC_VER`,于是它
159+
默默把 GCC 的答案改写成裸 `__inline`。MS 的 inline 语义下,每个包含 `gmp.h` 的 TU 都会发射一份外部
160+
定义,链接直接死在十个重复符号上:
161+
162+
```
163+
lld-link: error: duplicate symbol: __gmpz_get_ui
164+
>>> defined at gmp.h:1793 obj/.../mpz/pprime_p.o
165+
>>> defined at obj/.../mpz/get_ui.o
166+
```
167+
168+
上游从来没碰到,因为它压根没有 MSVC ABI 的构建。补丁就是给这条分支补上邻居们已经有的守卫
169+
(`#if defined (_MSC_VER) && ! defined (__GNUC__)`),在别处是严格 no-op:非 windows 目标不定义
170+
`_MSC_VER`,真 MSVC 不定义 `__GNUC__`。生成器里对补丁点做了"必须恰好命中一次"的断言,
171+
上游改动了这段会直接报错而不是静默漏打。
172+
173+
值得记一笔的是**首轮 windows CI 的 516 个 TU 全部编译通过**,只挂在链接 —— 也就是说
174+
`config.h`/`gmp.h`/内联表在 LLP64 + MSVC ABI 上本来就是对的。
175+
176+
### 3.6 `-fPIC`
146177
147178
`linux`/`macosx` 加 `-fPIC`(`cflags` + `cxxflags`)。静态库经常被链进消费者的 `.so`;而且 clang++
148179
默认 PIE 时链接本包就直接

‎pkgs/c/compat.gmp.lua‎

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,14 @@ typedef const __gmp_randstate_struct *gmp_randstate_srcptr;
931931
#endif
932932
933933
/* Microsoft's C compiler accepts __inline */
934-
#ifdef _MSC_VER
934+
/* mcpp-index (compat.gmp): `&& ! defined (__GNUC__)`, which upstream's
935+
neighbouring vendor branches already carry in spirit. clang targeting the
936+
MSVC ABI defines BOTH __GNUC__ and _MSC_VER; without the guard this line
937+
replaces the GCC branch's gnu_inline spelling with MS inline semantics, the
938+
header starts emitting an external definition of every __GMP_EXTERN_INLINE
939+
function in every TU that includes it, and the link fails with ten
940+
duplicate symbols against the -D__GMP_FORCE_<fn> copies. */
941+
#if defined (_MSC_VER) && ! defined (__GNUC__)
935942
#define __GMP_EXTERN_INLINE __inline
936943
#endif
937944
@@ -3449,11 +3456,14 @@ const struct bases mp_bases[257] =
34493456
["gmp-6.3.0/mcpp/mcpp_gmpxx.cc"] =
34503457
[[
34513458
/* compat.gmp `gmpxx` feature: GMP's C++ bindings.
3452-
ONE translation unit, not eleven source entries, for two reasons:
3453-
mcpp names objects by BASENAME in a flat obj/ dir, and `limits.cc`
3454-
-> `limits.o` collides with anything else in the build that has
3455-
the same basename (mcpp#233/#240); and the feature table gates
3456-
sources, not defines, so __GMP_WITHIN_GMPXX has to live in a file.
3459+
ONE translation unit rather than eleven source entries because the
3460+
feature table gates SOURCES, not defines: __GMP_WITHIN_GMPXX has to
3461+
live inside a file, so a wrapper is needed either way and one is
3462+
simpler than eleven. (Object-name collisions are NOT the reason --
3463+
`limits.cc` would have been one before mcpp 2026.8.3.4, but object
3464+
paths are nested unconditionally now: this package alone compiles
3465+
three different add.c into obj/compat_gmp/gmp-6.3.0/{mpf,mpn/generic,
3466+
mpz}/add.o.)
34573467
34583468
Compiled by the CONSUMER's toolchain on purpose. libgmpxx's
34593469
interface is std::ostream / std::istream / std::string, so its

‎tools/gmp/generate_descriptor.py‎

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,51 @@ def prepare(workdir):
298298
"@CFLAGS@": "",
299299
}
300300

301+
# --------------------------------------------------------------------------
302+
# One patch to gmp.h beyond configure's substitutions.
303+
#
304+
# gmp.h picks how to spell "inline" through a vendor cascade. The GCC branch
305+
# gives `extern __inline__ __attribute__ ((__gnu_inline__))` -- GNU89 inline
306+
# semantics: the body in the header is for inlining only and is NEVER emitted
307+
# as a standalone symbol, because the out-of-line copy comes from the one TU
308+
# that compiles with -D__GMP_FORCE_<fn> (mpz/get_ui.c, mpn/generic/sub.c, ...).
309+
# The whole library depends on that division.
310+
#
311+
# The `_MSC_VER` branch further down is the only one in the cascade WITHOUT a
312+
# `! defined (__GMP_EXTERN_INLINE)` guard (the SunPro and SCO branches have
313+
# one), so on a compiler that is BOTH -- clang targeting the MSVC ABI, which
314+
# is what this index uses on Windows -- it silently overwrites the GCC answer
315+
# with plain `__inline`. Under MS inline semantics that emits an external
316+
# definition in every TU that includes gmp.h, and the link dies on ten
317+
# duplicate symbols against the forced copies:
318+
#
319+
# lld-link: error: duplicate symbol: __gmpz_get_ui
320+
# >>> defined at gmp.h:1793 obj/.../mpz/pprime_p.o
321+
# >>> defined at obj/.../mpz/get_ui.o
322+
#
323+
# Upstream never hit this because it has no MSVC-ABI build at all. Adding the
324+
# same guard the neighbouring branches already carry is the minimal fix, and
325+
# it is a no-op everywhere else: non-Windows targets never define _MSC_VER,
326+
# and real MSVC never defines __GNUC__.
327+
GMP_H_PATCHES = [(
328+
"""/* Microsoft's C compiler accepts __inline */
329+
#ifdef _MSC_VER
330+
#define __GMP_EXTERN_INLINE __inline
331+
#endif
332+
""",
333+
"""/* Microsoft's C compiler accepts __inline */
334+
/* mcpp-index (compat.gmp): `&& ! defined (__GNUC__)`, which upstream's
335+
neighbouring vendor branches already carry in spirit. clang targeting the
336+
MSVC ABI defines BOTH __GNUC__ and _MSC_VER; without the guard this line
337+
replaces the GCC branch's gnu_inline spelling with MS inline semantics, the
338+
header starts emitting an external definition of every __GMP_EXTERN_INLINE
339+
function in every TU that includes it, and the link fails with ten
340+
duplicate symbols against the -D__GMP_FORCE_<fn> copies. */
341+
#if defined (_MSC_VER) && ! defined (__GNUC__)
342+
#define __GMP_EXTERN_INLINE __inline
343+
#endif
344+
""")]
345+
301346
# --------------------------------------------------------------------------
302347
# The mulfunc sources: one file compiled once per OPERATION_* macro, each
303348
# pass defining a different mpn entry point. Upstream spells this with a
@@ -345,9 +390,13 @@ def build_files():
345390
# 1. gmp.h, from the upstream template.
346391
gmp_h = read(os.path.join(SRC, "gmp-h.in"))
347392
for k, v in GMP_H_SUBS.items():
348-
assert k in gmp_h, k
393+
if k not in gmp_h:
394+
raise SystemExit("gmp-h.in no longer contains %s" % k)
349395
gmp_h = gmp_h.replace(k, v)
350-
assert "@" not in gmp_h.split("__GMP_CC")[0][-4000:] or True
396+
for old, new in GMP_H_PATCHES:
397+
if gmp_h.count(old) != 1:
398+
raise SystemExit("gmp-h.in patch target not found exactly once:\n" + old)
399+
gmp_h = gmp_h.replace(old, new)
351400
f[f"{WRAP}/gmp.h"] = gmp_h
352401

353402
# 2. config.h.
@@ -437,11 +486,14 @@ def fwd(where, header, target):
437486
# 10. The gmpxx feature's single TU.
438487
gmpxx = [
439488
"/* compat.gmp `gmpxx` feature: GMP's C++ bindings.\n"
440-
" ONE translation unit, not eleven source entries, for two reasons:\n"
441-
" mcpp names objects by BASENAME in a flat obj/ dir, and `limits.cc`\n"
442-
" -> `limits.o` collides with anything else in the build that has\n"
443-
" the same basename (mcpp#233/#240); and the feature table gates\n"
444-
" sources, not defines, so __GMP_WITHIN_GMPXX has to live in a file.\n"
489+
" ONE translation unit rather than eleven source entries because the\n"
490+
" feature table gates SOURCES, not defines: __GMP_WITHIN_GMPXX has to\n"
491+
" live inside a file, so a wrapper is needed either way and one is\n"
492+
" simpler than eleven. (Object-name collisions are NOT the reason --\n"
493+
" `limits.cc` would have been one before mcpp 2026.8.3.4, but object\n"
494+
" paths are nested unconditionally now: this package alone compiles\n"
495+
" three different add.c into obj/compat_gmp/gmp-6.3.0/{mpf,mpn/generic,\n"
496+
" mpz}/add.o.)\n"
445497
"\n"
446498
" Compiled by the CONSUMER's toolchain on purpose. libgmpxx's\n"
447499
" interface is std::ostream / std::istream / std::string, so its\n"

0 commit comments

Comments
 (0)