Skip to content

Commit 9aaa765

Browse files
committed
fix(pkg): compat.gmp 的 MSVC 分支改用 static __inline(上一版守卫无效)
上一版按"clang 打 MSVC ABI 同时定义 __GNUC__ 与 _MSC_VER"的假设,只给 _MSC_VER 分支补了 `! defined (__GNUC__)` 守卫。CI 结果一字未变(gmp.h 行号从 1845 移到 1852, 证明新头确实生效),这反过来证明:**该编译器不定义 __GNUC__**,守卫恒真,分支照走。 真正的解法是换掉这条分支的答案:`static __inline`,即 GMP 自己给另外两个 "extern inline 会漏出全局符号"的编译器(DEC C、SCO OpenUNIX)的答案。 关键耦合是 __GMP_INLINE_PROTOTYPES:GCC 分支把它设为 1(发 extern 原型,与 gnu_inline 定义相容),MSVC 分支不设 → 默认 0 → 不发 extern 原型,static 定义才不会 和它冲突。本地第一次模拟只换 __GMP_EXTERN_INLINE 不动 __GMP_INLINE_PROTOTYPES, 立刻得到 `static declaration of '__gmpz_abs' follows non-static declaration` —— 忠实复现 MSVC 分支后实测:非 FORCE TU 里那些符号一个都不发射(全内联),FORCE TU 发射唯一的全局,六个 TU 合并链接无重复符号。 守卫保留:将来若有既 GNU 兼容又设 _MSC_VER 的编译器,仍走 gnu_inline 那条。 本地两条 linux 腿重跑 gmp / gmp-gmpxx 仍全绿(_MSC_VER 在那边根本没定义,严格 no-op)。
1 parent 7f40723 commit 9aaa765

3 files changed

Lines changed: 77 additions & 50 deletions

File tree

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

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -149,29 +149,38 @@ reference。所以做成 feature:11 个 `cxx/*.cc` 合并成**一个** TU(`mcpp_
149149
150150
除 configure 的 8 处 substitute 外,生成的 `gmp.h` 还打了**一个**补丁,由首轮 windows CI 抓出来。
151151
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` …)。整个库靠这个分工成立。
152+
`gmp.h` 里约十二个小函数(`mpz_get_ui`、`mpn_add`、`mpn_cmp` …)有两份定义:头里一份走
153+
`__GMP_EXTERN_INLINE`,库里一份由带 `-D__GMP_FORCE_<fn>` 的那个 TU 提供
154+
(`mpz/get_ui.c`、`mpn/generic/add.c` …;那里 gmp.h 会**手工抑制** `__GMP_EXTERN_INLINE`,
155+
所以那份一定是普通外部定义)。这套分工成立的前提是:**头里那份永远不能变成独立符号**。
156+
GCC 分支用 `extern __inline__ __attribute__ ((__gnu_inline__))` 保证了这点。
156157
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-
定义,链接直接死在十个重复符号上:
158+
`_MSC_VER` 分支给的是裸 `__inline`,MS 语义下**每个**包含 gmp.h 的 TU 都会发射一份外部定义;
159+
而且它是整串厂商分支里**唯一没有** `! defined (__GMP_EXTERN_INLINE)` 守卫的(SunPro / SCO 两条都有)。
160+
本索引 windows 用的 clang 打 MSVC ABI **定义 `_MSC_VER` 但不定义 `__GNUC__`**(实测:先按"两个都定义"
161+
的假设只补 `! defined (__GNUC__)` 守卫,CI 结果一字未变),于是落到 MSVC 那条,链接死在十个重复符号:
161162
162163
```
163164
lld-link: error: duplicate symbol: __gmpz_get_ui
164-
>>> defined at gmp.h:1793 obj/.../mpz/pprime_p.o
165+
>>> defined at gmp.h:1800 obj/.../mpz/pprime_p.o
165166
>>> defined at obj/.../mpz/get_ui.o
166167
```
167168
168-
上游从来没碰到,因为它压根没有 MSVC ABI 的构建。补丁就是给这条分支补上邻居们已经有的守卫
169-
(`#if defined (_MSC_VER) && ! defined (__GNUC__)`),在别处是严格 no-op:非 windows 目标不定义
170-
`_MSC_VER`,真 MSVC 不定义 `__GNUC__`。生成器里对补丁点做了"必须恰好命中一次"的断言,
171-
上游改动了这段会直接报错而不是静默漏打。
169+
**解法是把这条分支改成 `static __inline`** —— 正是 GMP 自己给另外两个"extern inline 会漏出全局符号"
170+
的编译器(DEC C、SCO OpenUNIX)的答案。关键耦合在于 `__GMP_INLINE_PROTOTYPES`:GCC 分支会把它设为 1
171+
(于是发 extern 原型,与 gnu_inline 定义相容),而 MSVC 分支不设 → 后面默认 0 → **不发 extern 原型**,
172+
`static` 定义才不会和它打架。第一次本地模拟只换 `__GMP_EXTERN_INLINE` 不动 `__GMP_INLINE_PROTOTYPES`,
173+
立刻得到 `static declaration of '__gmpz_abs' follows non-static declaration` —— 忠实复现 MSVC 分支
174+
(两者都换)之后本地实测:非 FORCE TU 里那些符号**一个都不发射**(全内联),FORCE TU 发射唯一的全局,
175+
六个 TU 合并链接无重复。守卫照旧补上,这样将来若有既 GNU 兼容又设 `_MSC_VER` 的编译器,仍走
176+
gnu_inline 那条。
177+
178+
生成器里对补丁点做了"必须恰好命中一次"的断言,上游改动了这段会直接报错而不是静默漏打。
172179
173180
值得记一笔的是**首轮 windows CI 的 516 个 TU 全部编译通过**,只挂在链接 —— 也就是说
174-
`config.h`/`gmp.h`/内联表在 LLP64 + MSVC ABI 上本来就是对的。
181+
`config.h`/`gmp.h`/内联表在 LLP64 + MSVC ABI 上本来就是对的。另外由于 windows 上 `__GNUC__` 未定义,
182+
`config.h` 里 `HAVE_ATTRIBUTE_*` 与 `longlong.h`/`MPN_IORD_U` 的 inline asm 在那边自动全关,
183+
走纯 C 路径 —— 正确但比 linux/macOS 慢,这是可接受的取舍。
175184
176185
### 3.6 `-fPIC`
177186

pkgs/c/compat.gmp.lua

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -931,15 +931,20 @@ typedef const __gmp_randstate_struct *gmp_randstate_srcptr;
931931
#endif
932932
933933
/* Microsoft's C compiler accepts __inline */
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__)
942-
#define __GMP_EXTERN_INLINE __inline
934+
/* mcpp-index (compat.gmp): `static __inline`, not `__inline`, and guarded.
935+
Plain `__inline` has MS inline semantics -- the header's copy of every
936+
__GMP_EXTERN_INLINE function becomes an external definition in EVERY TU
937+
that includes gmp.h, which collides with the out-of-line copy the
938+
-D__GMP_FORCE_<fn> TU emits (ten `lld-link: duplicate symbol` errors on
939+
clang targeting the MSVC ABI, which defines _MSC_VER but not __GNUC__).
940+
`static __inline` is the answer GMP already gives for the other compilers
941+
whose "extern inline" leaks a global (DEC C, SCO OpenUNIX): the header's
942+
copy stays file-local and still inlines, while the forced TU -- where
943+
gmp.h suppresses this macro by hand -- remains the single external
944+
definition. The guard mirrors the SunPro/SCO branches, so a GNU-compatible
945+
compiler that also sets _MSC_VER keeps the gnu_inline answer above. */
946+
#if defined (_MSC_VER) && ! defined (__GMP_EXTERN_INLINE)
947+
#define __GMP_EXTERN_INLINE static __inline
943948
#endif
944949
945950
/* Recent enough Sun C compilers want "inline" */

tools/gmp/generate_descriptor.py

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -301,45 +301,58 @@ def prepare(workdir):
301301
# --------------------------------------------------------------------------
302302
# One patch to gmp.h beyond configure's substitutions.
303303
#
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.
304+
# gmp.h picks how to spell "inline" through a vendor cascade. About a dozen
305+
# small functions (mpz_get_ui, mpn_add, mpn_cmp, ...) are then defined in the
306+
# header under __GMP_EXTERN_INLINE, AND out of line by the one TU that
307+
# compiles with -D__GMP_FORCE_<fn> (mpz/get_ui.c, mpn/generic/add.c, ...).
308+
# That only works if the header's copy never becomes a standalone symbol.
309+
# The GCC branch guarantees it with
310+
# `extern __inline__ __attribute__ ((__gnu_inline__))`.
310311
#
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:
312+
# The `_MSC_VER` branch answers plain `__inline`, whose MS semantics DO emit
313+
# an external definition in every TU that includes the header -- and it is the
314+
# only branch in the cascade without a `! defined (__GMP_EXTERN_INLINE)` guard
315+
# (SunPro and SCO both have one).
316+
#
317+
# This index compiles Windows with clang targeting the MSVC ABI, and that
318+
# compiler defines _MSC_VER but NOT __GNUC__ (measured: guarding the branch on
319+
# `! defined (__GNUC__)` changed nothing), so it lands on the MSVC answer and
320+
# the link dies on ten duplicate symbols:
318321
#
319322
# lld-link: error: duplicate symbol: __gmpz_get_ui
320-
# >>> defined at gmp.h:1793 obj/.../mpz/pprime_p.o
323+
# >>> defined at gmp.h:1800 obj/.../mpz/pprime_p.o
321324
# >>> defined at obj/.../mpz/get_ui.o
322325
#
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__.
326+
# Fix: `static __inline`, which is what GMP itself answers for the other two
327+
# compilers whose "extern inline" leaks a global (DEC C and SCO OpenUNIX).
328+
# Non-forced TUs get a file-local copy that still inlines; the forced TU is
329+
# unaffected because gmp.h suppresses __GMP_EXTERN_INLINE there by hand
330+
# (`#if ! defined (__GMP_FORCE_<fn>)`), so the external definition still comes
331+
# from exactly one object. The added guard keeps a future GNU-compatible
332+
# compiler that also sets _MSC_VER on the gnu_inline answer.
333+
#
334+
# Upstream never hit any of this: it has no MSVC-ABI build at all.
327335
GMP_H_PATCHES = [(
328336
"""/* Microsoft's C compiler accepts __inline */
329337
#ifdef _MSC_VER
330338
#define __GMP_EXTERN_INLINE __inline
331339
#endif
332340
""",
333341
"""/* 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
342+
/* mcpp-index (compat.gmp): `static __inline`, not `__inline`, and guarded.
343+
Plain `__inline` has MS inline semantics -- the header's copy of every
344+
__GMP_EXTERN_INLINE function becomes an external definition in EVERY TU
345+
that includes gmp.h, which collides with the out-of-line copy the
346+
-D__GMP_FORCE_<fn> TU emits (ten `lld-link: duplicate symbol` errors on
347+
clang targeting the MSVC ABI, which defines _MSC_VER but not __GNUC__).
348+
`static __inline` is the answer GMP already gives for the other compilers
349+
whose "extern inline" leaks a global (DEC C, SCO OpenUNIX): the header's
350+
copy stays file-local and still inlines, while the forced TU -- where
351+
gmp.h suppresses this macro by hand -- remains the single external
352+
definition. The guard mirrors the SunPro/SCO branches, so a GNU-compatible
353+
compiler that also sets _MSC_VER keeps the gnu_inline answer above. */
354+
#if defined (_MSC_VER) && ! defined (__GMP_EXTERN_INLINE)
355+
#define __GMP_EXTERN_INLINE static __inline
343356
#endif
344357
""")]
345358

0 commit comments

Comments
 (0)