代码结构与构建
本章介绍 upb 的目录布局、port 移植/优化层(所有性能宏的集中地)、生成代码如何接入快核心,以及 Bazel / CMake / amalgamation 三种构建形态。port 层是后续每一章都会反复引用的基础,值得先读熟。
1. 目录布局
upb 的目录即分层(详见 第 2 章)。按代码量排序的主要目录:
| 目录 | 角色 | 量级 |
|---|---|---|
reflection/ | 反射层:DefPool、各种 *_def、descriptor | 最大(含生成的 descriptor 代码) |
wire/ | 二进制编解码 + decode_fast/ fasttable | ~7.5k 行 |
message/ | 消息表示、访问器、array、map | ~6.6k 行 |
json/ | JSON 编解码 | ~2.5k 行 |
mini_table/ | upb_MiniTable 运行时布局表 | ~2.5k 行 |
mini_descriptor/ | 从紧凑串重建 MiniTable | ~2.2k 行 |
mem/ | arena / alloc 内存模型 | ~1.6k 行 |
hash/ | 开放寻址哈希表 | ~1.4k 行 |
util/ | 基于反射的高层工具 | ~1.3k 行 |
text/ | 文本序列化 / debug_string | ~1k 行 |
base/ | status、常量、string_view | ~0.5k 行 |
port/ | 纯预处理器移植/优化层 | ~0.5k 行 |
lex/ | 数字 / unicode 转换 | ~0.4k 行 |
一个普遍约定:每个目录里 internal/ 子目录放内部头文件——真正的结构体定义、内联热路径,通常带 UPB_PRIVATE 命名保护(见下文)。公共头只暴露 API 声明。
2. port 层:所有优化宏的集中地
port/def.inc 是理解 upb 性能的”词典”。它不是普通头文件(扩展名 .inc),而是被每个翻译单元在最后包含、再由 port/undef.inc 在头文件结尾全部 #undef,以免污染用户命名空间(约定见 port/def.inc:8-28,清理见 undef.inc:8)。
2.1 能力探测
所有优化都建立在”编译器真的支持”之上,先有探测宏再有应用:
UPB_HAS_ATTRIBUTE(def.inc:48-52)UPB_HAS_BUILTIN(def.inc:78-82)UPB_HAS_FEATURE(def.inc:90-94)UPB_GNUC_MIN(def.inc:38-43)
2.2 内联控制
| 宏 | 展开 | 用途 |
|---|---|---|
UPB_INLINE | 尽量内联(def.inc:140-147) | 普通内联 |
UPB_FORCEINLINE | __inline__ __attribute__((always_inline)) static(def.inc:281) | 强制内联热路径 |
UPB_NOINLINE | __attribute__((noinline))(def.inc:282) | 把冷/回落代码踢出热函数 |
把冷路径标 UPB_NOINLINE(例如解码器的回落函数)是一种反复出现的手法:热函数体积小、留在 i-cache 里,罕见分支被分到别处。
2.3 分支与不可达提示
UPB_LIKELY/UPB_UNLIKELY=__builtin_expect(...,1/0)(def.inc:265-269)。UPB_UNPREDICTABLE=__builtin_expect_with_probability(...,0.5)(def.inc:272-277),用在二分查找里故意压制分支预测(mini_table/internal/message.h:168)。UPB_ASSUME——release 下展开为if(!expr) __builtin_unreachable()(def.inc:321-335),让优化器把不变量传播下去(例如解码偏移掩码、消息 size 已对齐);debug 下退化为assert。UPB_ASSERT——release 下完全编译掉,但仍对表达式做类型检查(def.inc:348-354)。
2.4 对齐
UPB_ALIGN_UP / DOWN / MALLOC(def.inc:209-211),UPB_ALIGN_OF(C23/C11/GNU/MSVC 级联,def.inc:213-230)。UPB_MALLOC_ALIGN 普通为 8,HWASAN 下为 16(def.inc:179、:182)。arena 的对齐分配(第 4 章)就建立在这些之上。
2.5 命名保护:UPB_PRIVATE 与 UPB_ONLYBITS
UPB_PRIVATE(x)把名字改写成x_dont_copy_me__upb_internal_use_only(def.inc:423),让”公开”结构体成员无法被随意使用。UPB_ONLYBITS(x)默认等于UPB_PRIVATE(x),但定义了UPB_ALLOW_PRIVATE_ACCESS__FOR_BITS_ONLY时变回原名(def.inc:425-429)——让生成代码/位布局代码够得着成员,普通用户够不着。两者贯穿 MiniTable 结构(mini_table/internal/message.h:72-83)。
2.6 fasttable / musttail —— 核心速度开关
这是 upb 最关键的一组宏(详见 第 7 章):
UPB_MUSTTAIL=__attribute__((musttail))(有就用,否则空,def.inc:433-437)——保证尾调用,让解析链编译成一连串寄存器传参的尾跳转。UPB_PRESERVE_MOST/UPB_PRESERVE_NONE(def.inc:439-449)——自定义调用约定,把解析参数留在寄存器里。UPB_FASTTABLE_SUPPORTED——要求 x86-64 或 aarch64-LE 且 支持preserve_none且 支持musttail(def.inc:474-479)。UPB_FASTTABLE——只有显式-DUPB_ENABLE_FASTTABLE(不支持就报错)或UPB_TRY_ENABLE_FASTTABLE(尽力而为)时才为 1,否则为 0(def.inc:484-496)。UPB_FASTTABLE_INIT(...)/UPB_FASTTABLE_MASK(mask)——fasttable 关闭时展开成空/-1,让生成的 MiniTable 优雅降级(def.inc:500-506)。MiniTable 里的fasttable[]柔性数组也只在UPB_FASTTABLE时才存在(mini_table/internal/message.h:89-93)。
2.7 其他值得一提
UPB_SIZE(s32,s64)——按指针宽度选值(def.inc:107-111),生成代码里的偏移到处用它,让同一份源码在 32/64 位都正确。UPB_LINKARR_*——用链接器数组替代 main 之前的注册(def.inc:547-650),支撑 README 的 “no global state”,分 ELF / Mach-O / MSVC 三套实现。UPB_ATOMIC(T)——映射到 C11_Atomic(T)/ MSVCvolatile T/ 无线程构建的裸T(def.inc:413-417),arena 融合的原子操作建立在此(第 4 章)。
3. 生成代码如何接入
每个生成文件都包含同一个入口头 generated_code_support.h。它和 port 层做了一个刻意的两步舞(generated_code_support.h:11-21):先包含 port/def.inc,若 UPB_FASTTABLE 打开就定义 UPB_INCLUDE_FAST_DECODE,再包含 port/undef.inc——这样后面才能按需把快速解码器拉进来(:44-46),而不至于让两个 .inc 互相递归包含。然后它 export 出生成代码需要的全部快核心头:base/upcast、message 访问器/array/map、mini_descriptor/decode、所有 mini_table 头、wire decode/encode(:23-47)。注意它一个反射头都不拉,再次印证快核心/反射的切分。
生成代码的接入方式:
.upb_minitable.c:包含generated_code_support.h和对应.upb_minitable.h,发射静态的upb_MiniTableField数组和upb_MiniTable结构,用到UPB_SIZE、UPB_PRIVATE、UPB_FASTTABLE_MASK、UPB_FASTTABLE_INIT(descriptor.upb_minitable.c:9-39)。fasttable 条目直接引用形如upb_DecodeFast_Message_Repeated_Tag1Byte的解析函数(:36-37,以及UPB_FASTTABLE_INIT块 :74-83)。.upb.h:包含generated_code_support.h+ minitable 头,定义不透明的消息结构({ upb_Message base; })和内联的类型化访问器(descriptor.upb.h:12-23)。
4. 构建系统
4.1 Bazel(主构建)
顶层 BUILD 定义各层库以及 codegen 支持库(如 generated_code_support,BUILD:109)。fasttable 是一个 bool_flag(BUILD:46-50),限定 64 位(BUILD:59-79);打开时通过 copts 注入 -DUPB_ENABLE_FASTTABLE(CPPOPTS 在 bazel/copts.bzl:47、COPTS 在 :55),并条件链接 decode_fast:field_parsers(BUILD:129-136)。各语言的 proto 规则在 bazel/:upb_proto_library.bzl、upb_c_proto_library.bzl、upb_minitable_proto_library.bzl、upb_proto_reflection_library.bzl(bazel/upb_proto_library.bzl:13-37)。
4.2 CMake 支持
CMake 支持是从 Bazel 生成的。cmake/ 下的 staleness_test 宏校验签入的生成文件(如 reflection/cmake/ 下的 .upb.h / .upb_minitable.c)与生成器产物一致,带 --fix 模式(cmake/build_defs.bzl:11-40)。
4.3 Amalgamation(单文件合并)
amalgamation 把所有源码合并成一对 .h + .c。bazel/private/oss/amalgamate.py 遍历 #include "..." 行并把每个文件内联一次(amalgamate.py:37-60)。upb_amalgamation Bazel 规则收集 srcs(丢弃 .hpp)后运行合并器(amalgamation.bzl:38-60)。顶层 BUILD 提供 :amalgamation、:php_amalgamation、:ruby_amalgamation(BUILD:200、:255、:310)——这就是 PHP / Ruby 把 upb 作为单文件随扩展分发的方式。
5. 本章小结
| 要点 | 出处 |
|---|---|
| def.inc 内部约定 + 全部 undef | port/def.inc:8-28、undef.inc:8 |
UPB_FORCEINLINE / UPB_NOINLINE | def.inc:281-282 |
UPB_LIKELY/UNLIKELY / UPB_UNPREDICTABLE | def.inc:265-277 |
UPB_ASSUME / UPB_ASSERT | def.inc:321-354 |
UPB_PRIVATE / UPB_ONLYBITS | def.inc:423-429 |
UPB_MUSTTAIL / UPB_PRESERVE_NONE | def.inc:433-449 |
UPB_FASTTABLE_SUPPORTED / UPB_FASTTABLE | def.inc:474-496 |
UPB_FASTTABLE_INIT / MASK 降级 | def.inc:500-506 |
UPB_SIZE / UPB_LINKARR / UPB_ATOMIC | def.inc:107-111、:547-650、:413-417 |
generated_code_support.h 两步舞 + 只导出快核心 | generated_code_support.h:11-47 |
生成的 .upb_minitable.c 用 FASTTABLE 宏 | descriptor.upb_minitable.c:9-39 |
| fasttable bool_flag + 64 位限定 + copts | BUILD:46-79、copts.bzl:47-54 |
| amalgamation 合并器 | amalgamate.py:37-60 |