Skip to content

feat: add compat.xxhash, compat.libwebp and compat.reflectcpp - #214

Merged
Sunrisepeak merged 2 commits into
mainfrom
feat/xxhash-libwebp-reflectcpp
Aug 17, 2026
Merged

feat: add compat.xxhash, compat.libwebp and compat.reflectcpp#214
Sunrisepeak merged 2 commits into
mainfrom
feat/xxhash-libwebp-reflectcpp

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

三个包来自把 SpinningMomo(一个 66k 行的 Windows 桌面工程)从 vcpkg 迁到 mcpp 的过程。它们此前活在项目自己的 path 索引里,已在 Windows/MSVC 上构建通过;现在按索引规范补齐三平台、CN 镜像与行为测试后上游。

compat.xxhash 0.8.3

单 TU、单头、无 feature。两处没有编译什么,值得写进描述符:

  • xxh_x86dispatch.c 不编。 它在运行期选 AVX2/AVX512 路径,需要 per-file -mavx2,并要求每个调用点定义 XXH_X86DISPATCH。不编它,就是无需任何 flag 的 SSE2 基线。
  • 不选 header-only 的 XXH_INLINE_ALL 那会在每个做哈希的 TU 里重新展开整份实现,只有「恰好只有一个这样的 TU」时才划算 —— 而这件事包无从知道。消费者仍可自行定义该宏,与本包是否被链接无关。

测试是已知答案测试,向量取自上游 tests/sanity_test_vectors.h,连同 tests/sanity_test.c 里生成被测缓冲区的 fillTestBuffer()(上游注释:"its values must not be changed")。没有任何一个期望值是用被测代码算出来的。

长度覆盖 XXH3 切换的每个尺寸分支(0 / 1-3 / 4-8 / 9-16 / 17-128 / 129-240 / >240),外加 2048 与 4160 两个多块路径 —— 选错的 SIMD 变体或字节序滑移会在那里现形。另有:流式与一次性一致(在 3 字节处切开,既非 16 字节 stripe 也非 256 字节块)、canonical 大端序列化往返、单比特翻转必须改变摘要。

compat.libwebp 1.5.0

117 个 TU 用五条目录通配写完,而不是逐条转录文件名。通配之所以安全,理由值得写进描述符:

src/dsp/ 下每个 *_sse2.c / *_sse41.c / *_neon.c / *_mips*.c / *_msa.c 变体,都经 src/dsp/cpu.h编译器自己的 __SSE2__ / __SSE4_1__ / __aarch64__ 内建宏自我门控。不匹配当前 target 的变体编译成空 TU,运行期 CPU 探测走标量回退。

所以既不需要 per-file flag,也不会随上游版本漂移。

  • -msse4.1 有意不加:加了,产物就要求 SSE4.1 CPU。
  • src/demux / src/mux(动图与元数据容器)是上游各自独立、各带公开头文件的库,在有人需要之前不收。
  • 两个 include root 都要传播:库自身源码从归档根平铺 include("src/dsp/dsp.h""sharpyuv/sharpyuv.h"),而消费者写 <webp/decode.h> —— 那个头在 src/webp/ 下。
  • sharpyuv 在同一个 archive 里而不是单独成包:编码器的 picture_csp_enc.c 直接调它,上游 CMake 拆成独立 target 只为在别处复用,而这里没有别处。

测试断言像素而不是符号:无损必须逐字节相等,有损(quality 90)平均绝对误差 < 8,且 quality 10 的产物必须更小。理由是——一个没能自我门控的 SIMD 变体照样能链接,只会算错像素。

compat.reflectcpp 0.25.0

上游按后端各出一个伞形 TU,这里只编 core + JSON 两个。另外九个(avro / bson / capnproto / cbor / flexbuf / msgpack / toml / xml / yaml)各自 #include 一个第三方库的头文件 —— 编了就把一个零依赖包变成九依赖包。它们该以 feature 形态各带自己的 deps 单独进来,等对应的库先有描述符。

yyjson 的取舍反过来。 rfl/json/*.hpp__has_include(<yyjson.h>) 探测:探得到就用外部的,探不到就回落到自带的 include/rfl/thirdparty/yyjson.h。只暴露 include/ 就保住了内置副本,本包因此依赖 compat.yyjson,也就不可能和它在版本上打架。想用外部 yyjson 的消费者只要自己加上那个依赖,其 include root 就会赢下探测 —— 但两份副本必须在 yyjson_api_inline 上达成一致,所以这不是默认。

include/rfl/thirdparty 之所以是第二个 include root,只因 src/yyjson.c 是平铺 include "yyjson.h" 的。

测试断言到解析出的值:结构体 → JSON → 结构体往返(含 std::optional 与嵌套 vector)、SnakeCaseToCamelCase 改名处理器、类型不匹配必须返回错误而不是抛异常、to_generic/from_generic(RPC 层在方法级才知道具体类型时走的那条路)、以及 to_schema。少编 src/yyjson.c链接失败,include root 写错则会在运行期选错 yyjson —— 两种都被这组断言拦住。

验证

$ mcpp test -p xxhash        # linux / gcc@16.1.0 — 1 passed
$ mcpp test -p libwebp       # 1 passed
$ mcpp test -p reflectcpp    # 1 passed

lint 本地全过:loadfile / check_mirror_urls / check_package_name / check_platform_version_parity / mcpp xpkg parse

CN 镜像已建:gitcode mcpp-res/{xxhash,reflectcpp,libwebp},三个都按 docs/cn-mirror.md 的标准流程建仓 + 发 release + 上传资产,并验证过与 GLOBAL 字节一致(sha256 相同,HTTP 200)。

README / README.zh-CN 的 Reference examples 表已同步。

三个包来自把 SpinningMomo(一个 66k 行的 Windows 桌面工程,Sunrisepeak/SpinningMomo#2)
从 vcpkg 迁到 mcpp 的过程。它们此前活在项目自己的 path 索引里,已在 Windows/MSVC 上
构建通过,现在按索引规范补齐三平台、CN 镜像与行为测试后上游。

## compat.xxhash 0.8.3

单 TU、单头、无 feature。两处「没有编译什么」值得写进描述符:

- `xxh_x86dispatch.c` 不编。它在运行期选 AVX2/AVX512 路径,需要 per-file `-mavx2`
  并要求**每个调用点**定义 `XXH_X86DISPATCH`。不编它就是无需任何 flag 的 SSE2 基线。
- 不选 header-only 的 `XXH_INLINE_ALL`。那会在每个做哈希的 TU 里重新展开整份实现,
  只有「恰好只有一个这样的 TU」时才划算 —— 包无从知道这件事。消费者仍可自行定义该宏。

测试是**已知答案**测试,取自上游 `tests/sanity_test_vectors.h`,连同
`tests/sanity_test.c` 里生成被测缓冲区的 `fillTestBuffer()`。没有任何一个期望值是
用被测代码算出来的。长度覆盖 XXH3 的每个分支(0 / 1-3 / 4-8 / 9-16 / 17-128 /
129-240 / >240)外加 2048、4160 两个多块路径 —— 一个选错的 SIMD 变体或字节序滑移会
在那里现形。另有流式与一次性一致(在 3 字节这种既非 16 字节 stripe 也非 256 字节块
的位置切开)、canonical 大端序列化往返、单比特翻转必须改变摘要。

## compat.libwebp 1.5.0

117 个 TU 用**五条目录通配**写完,而不是逐条转录文件名。通配之所以安全,理由值得
写下来:`src/dsp/` 下每个 `*_sse2.c` / `*_sse41.c` / `*_neon.c` / `*_mips*.c` /
`*_msa.c` 变体都经 `src/dsp/cpu.h` 依**编译器自己的** `__SSE2__` / `__SSE4_1__` /
`__aarch64__` 内建宏自我门控 —— 不匹配当前 target 的变体编译成空 TU,运行期探测走
标量回退。既不需要 per-file flag,也不会随版本漂移。

`-msse4.1` 有意不加:加了产物就要求 SSE4.1 CPU。`src/demux` / `src/mux`(动图与
元数据容器)是上游各自独立、各带公开头文件的库,在有人需要之前不收。

两个 include root 都要传播:库自身的源码从归档根平铺 include(`"src/dsp/dsp.h"`、
`"sharpyuv/sharpyuv.h"`),而消费者写 `<webp/decode.h>` —— 那个头在 `src/webp/` 下。

测试断言**像素**而不是符号:无损必须逐字节相等,有损(quality 90)平均绝对误差
< 8,且 quality 10 的产物必须更小 —— 一个没能自我门控的 SIMD 变体照样能链接,
只会算错像素。

## compat.reflectcpp 0.25.0

上游按**后端**各出一个伞形 TU,这里只编 core + JSON 两个。另外九个(avro / bson /
capnproto / cbor / flexbuf / msgpack / toml / xml / yaml)各自 `#include` 一个第三方
库的头文件,编了就把一个零依赖包变成九依赖包;它们该以 feature 形态各带 `deps`
单独进来。

yyjson 的取舍反过来:`rfl/json/*.hpp` 用 `__has_include(<yyjson.h>)` 探测,探不到就
回落到自带的 `include/rfl/thirdparty/yyjson.h`。只暴露 `include/` 就保住了内置副本,
本包因此**不**依赖 compat.yyjson,也就不可能和它在版本上打架。想用外部 yyjson 的
消费者只要自己加上那个依赖,其 include root 就会赢下 `__has_include` 探测 —— 但两份
副本必须在 `yyjson_api_inline` 上达成一致,所以这不是默认。
`include/rfl/thirdparty` 之所以是第二个 include root,只因 `src/yyjson.c` 平铺
include `"yyjson.h"`。

测试断言到解析出的**值**:结构体 -> JSON -> 结构体往返(含 optional 与嵌套 vector)、
`SnakeCaseToCamelCase` 改名处理器、类型不匹配必须返回错误而不是抛异常、
`to_generic`/`from_generic`(RPC 层在方法级才知道具体类型时走的那条路)、以及
`to_schema`。少编 `src/yyjson.c` 会**链接**失败,include root 写错则会在运行期
选错 yyjson —— 两种都被这组断言拦住。

## 验证

    mcpp test -p xxhash / -p libwebp / -p reflectcpp     # linux gcc@16.1.0,各 1 passed

lint 本地全过(loadfile / check_mirror_urls / check_package_name /
check_platform_version_parity / mcpp xpkg parse)。

CN 镜像已建:gitcode `mcpp-res/{xxhash,reflectcpp,libwebp}`,三个都验证过与 GLOBAL
字节一致(sha256 相同,HTTP 200)。
Windows 腿(llvm)红了,而且是个有价值的红:

  always_inline function '_mm_shuffle_epi8' requires target feature 'ssse3',
  but would be inlined into function 'VP8L32bToPlanar_SSE41' that is compiled
  without support for 'ssse3'

libwebp 的 SSE4.1 门是

  #if (defined(__SSE4_1__) || defined(WEBP_MSC_SSE41)) && \
      (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_SSE41))

而 `WEBP_MSC_SSE41` **只**看 `_MSC_VER`。每个 MSVC ABI 编译器都定义它 —— clang 也
定义 —— 但只有 cl.exe 允许不带 target flag 使用任意 intrinsic。所以「变体自门控」
那句话对 MSVC 成立,对 targeting-MSVC 的 clang 不成立。原描述符(连同它的注释)是
在 cl.exe 上验证的,这一条是它没覆盖到的。

上游 CMake 的解法是 **per-file** `-msse4.1`,而描述符没有 per-file flag 字段。整包
加 `-msse4.1` 不是等价替代:clang 会在**基线** TU 里也发 SSE4.1,绕过 libwebp 自己
的 `VP8GetCPUInfo` 运行期分发 —— 那是在老 CPU 上 SIGILL,而不是回退。

所以改用上游同一套机制的另一半:定义 `HAVE_CONFIG_H` 并生成 `src/webp/config.h`。
这会把 cpu.h 里每个 `(!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_x))` 门从「默认开、
可否决」翻成「默认关、需允许」,于是这份生成的头就是白名单:只写 SSE2 与 NEON
(两者分别是 x86-64 与 aarch64 的**基线**,不需要任何 flag),不写 SSE4.1。

上游本来就为这种配置准备好了退路:`dec_sse41.c` 等在 `#else` 分支里是
`WEBP_DSP_INIT_STUB(VP8DspInitSSE41)`,而调用点 `if (VP8GetCPUInfo(kSSE4_1))
VP8DspInitSSE41();` 由 `WEBP_HAVE_SSE41` 门控 —— 库照样链接、照样分发,只是低一档。

顺带用 `__GNUC__`/`__clang__` 声明三个 `HAVE_BUILTIN_BSWAP*`:HAVE_CONFIG_H 一开,
它们也从「默认有」变成「需声明」,不补就退回可移植实现。
Sunrisepeak added a commit to Sunrisepeak/SpinningMomo that referenced this pull request Aug 17, 2026
llvm 腿的探针 10/10 全绿,full-build 只剩一处,而且不在项目代码里:

  libwebp/src/dsp/common_sse41.h:77: error: always_inline function
  '_mm_shuffle_epi8' requires target feature 'ssse3', but would be inlined into
  function 'VP8PlanarTo24b_SSE41' that is compiled without support for 'ssse3'

libwebp 的 SSE4.1 门只看 `_MSC_VER`(`WEBP_MSC_SSE41`)。每个 MSVC ABI 编译器都
定义它,clang 也定义,但只有 cl.exe 允许不带 target flag 用任意 intrinsic —— 原描述符
是在 cl.exe 上验证的,这条它没覆盖到。

修法与上游 PR mcpplibs/mcpp-index#214 完全一致:`HAVE_CONFIG_H` + 生成
`src/webp/config.h` 白名单,只开 SSE2 与 NEON(两者分别是 x86-64 / aarch64 的基线,
不需要任何 flag)。上游为这种配置备好了退路 —— `dec_sse41.c` 等落到
`WEBP_DSP_INIT_STUB`,调用点由 `WEBP_HAVE_SSE41` 门控一并消失。

不整包加 `-msse4.1` 的理由:clang 会在**基线** TU 里也发 SSE4.1,绕过 libwebp 自己的
运行期 CPU 分发 —— 老 CPU 上是 SIGILL 而不是回退。上游 CMake 用 per-file flag 解决,
而描述符没有这个字段。

本文件现在是 compat.libwebp 的逐字副本(只改 namespace),#214 合入后即删除。
顺带把原来 117 行硬编码 `libwebp-1.5.0/` 前缀的源列表换成五条目录通配。
@Sunrisepeak
Sunrisepeak merged commit 2bceccd into main Aug 17, 2026
11 checks passed
@Sunrisepeak
Sunrisepeak deleted the feat/xxhash-libwebp-reflectcpp branch August 17, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant