Skip to content

[bug] Windows driver-style 链接不走 rspfile:大型源码包(ffmpeg/opencv,数千对象)链接命令行溢出 CreateProcess 32KiB 上限 #247

Description

@Sunrisepeak

现象

Windows 上构建大型源码包(ffmpeg 8.1.2,2283 个 TU / .o)时,编译全部通过,但最终链接步骤直接失败:

ninja: fatal: CreateProcess: The parameter is incorrect.

失败的正是那条 driver-style 链接命令(clang-MSVC 工具链,clang++.exe 作为链接驱动):

clang++.exe obj\ffmpegwin.o obj\...\libswscale\yuv2rgb.o obj\yuv_2_rgb.asm.o obj\scale_avx2.asm.o ... (2283+ 个对象内联)

根因

src/build/ninja_backend.cppm 的链接/归档/动态库规则按 separateLinker 分两支(L564–598):

  • separateLinker == true(MSVC dialect,link.exe/lib.exe):走 response file(@$out.rsp + rspfile_content = $in)——已正确规避命令行长度限制。
  • separateLinker == false(driver-style,g++/clang++ 既是编译器又是链接驱动):
    rule cxx_link
      command = $cxx $in -o $out $ldflags $unit_ldflags      # $in = 全部对象,内联
    rule cxx_archive
      command = $ar rcs $out $in                              # 同样内联
    rule cxx_shared
      command = $cxx -shared $in -o $out ...
    
    没有 rspfile

Windows 的 mcpp 托管工具链是 clang-MSVC-ABI(x86_64-pc-windows-msvc),clang++ 是 driver,故 dialect 判定为 driver-style(separateLinker=false.archiveCmd = "$ar rcs $out $in",dialect.cppm L86),命中 else 分支。而 Windows 上无论驱动是不是 cl.exe,ninja 都经 CreateProcess 拉起,受 32 KiB(historically 8191)命令行上限约束。2283 个对象路径内联 → 直接溢出 → CreateProcess: The parameter is incorrect

Linux/macOS 因 ARG_MAX(~256 KiB–2 MiB)容得下同样的对象列表,所以同一份 multi-platform compat.ffmpeg 描述符在 linux + macosx 两条腿全绿,唯独 windows 挂在链接这一步。

影响

挡死 Windows 上一切大型全源码构建包——不止 ffmpeg,opencv(compat.opencv,同样数千 TU)必然撞同一堵墙。这是 mcpp 生态"全平台"目标在 Windows 的最后一道系统性障碍;编译侧(clang-MSVC 吃下 2283 个 ffmpeg C 源 + 158 个 win64 NASM)已验证通过,只差链接命令行走 rspfile。

建议修复

driver-style 分支在 Windows 上也走 response file(clang++ 与 GNU/llvm-ar 均支持 @file 语法)。else 分支改为:

} else if constexpr (mcpp::platform::is_windows) {
    // Driver-style toolchains (clang++/gcc) still spawn through CreateProcess
    // on Windows (32 KiB cmdline limit); large object lists overflow. Both
    // clang and GNU/llvm-ar accept @rspfile, so route $in through one.
    append("rule cxx_link\n");
    append("  command = $cxx @$out.rsp -o $out $ldflags $unit_ldflags\n");
    append("  rspfile = $out.rsp\n");
    append("  rspfile_content = $in\n");
    append("  description = LINK $out\n\n");

    append("rule cxx_archive\n");
    append("  command = $ar rcs $out @$out.rsp\n");
    append("  rspfile = $out.rsp\n");
    append("  rspfile_content = $in\n");
    append("  description = AR $out\n\n");

    append("rule cxx_shared\n");
    append("  command = $cxx -shared @$out.rsp -o $out $ldflags $soname_flag $unit_ldflags\n");
    append("  rspfile = $out.rsp\n");
    append("  rspfile_content = $in\n");
    append("  description = SHARED $out\n\n");
} else {
    // ...现有 POSIX driver-style 三条规则不变...
}

(POSIX 分支保持内联即可,ARG_MAX 足够;只在 is_windows 上加 rspfile,避免影响 linux/macOS 现有行为。归档命令这里硬编码了 $ar rcs,与 dialect.cppm L86 的 .archiveCmd 一致;若想更稳妥可让 dialect 暴露一个 archiveRspCmd。)

复现

任意 >~500 个 TU 的源码包 kind=lib,在 windows-latest 上 mcpp test。手边可复现的最小对象:multi-platform compat.ffmpeg(mcpp-index 临时 spike 分支)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions